@caweb/a11y-webpack-plugin 2.0.2 → 2.0.4

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,58 @@ 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 } );
97
-
98
78
  // 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
- };
79
+ // if( compiler?.options?.devServer?.hot ){
80
+ // // we create a blank file for the hot update to compile on our page.
81
+ // // this is required for the hot-update to work.
82
+ // fs.writeFileSync(
83
+ // path.join(staticDir.directory, `a11y.update.js`),
84
+ // `` // required for hot-update to compile on our page, blank script for now
85
+ // );
86
+
87
+ // // we add the entry to the dependency factory during compilation
88
+ // compiler.hooks.compilation.tap(
89
+ // pluginName,
90
+ // (compilation, { normalModuleFactory }) => {
91
+ // compilation.dependencyFactories.set(
92
+ // EntryDependency,
93
+ // normalModuleFactory
94
+ // );
95
+
96
+ // }
97
+ // );
98
+
99
+ // // we add the entry before the compilation ends
100
+ // compiler.hooks.make.tapAsync(
101
+ // pluginName,
102
+ // (compilation, callback) => {
103
+ // const { entry, options, context } = {
104
+ // entry: path.join( staticDir.directory, 'a11y.update.js'),
105
+ // options: {
106
+ // name: 'a11y.update'
107
+ // },
108
+ // context: 'a11y'
109
+ // };
110
+
111
+ // const dep = new EntryDependency(entry);
112
+ // dep.loc = {
113
+ // name: options.name
114
+ // };
135
115
 
136
- compilation.addEntry(
137
- context,
138
- dep,
139
- options,
140
- err => {
141
- callback(err);
142
- });
143
- });
144
- }
116
+ // compilation.addEntry(
117
+ // context,
118
+ // dep,
119
+ // options,
120
+ // err => {
121
+ // callback(err);
122
+ // });
123
+ // });
124
+ // }
145
125
 
146
126
  compiler.hooks.thisCompilation.tap(pluginName, (compilation) => {
147
127
  // We can audit the html files now that the compilation is done.
@@ -150,29 +130,23 @@ class A11yPlugin {
150
130
  pluginName,
151
131
  (stats, callback) => {
152
132
 
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,
133
+ console.log('<i> \x1b[32m[webpack-dev-middleware] Running IBM Accessibility scan...\x1b[0m');
134
+ /**
135
+ * We run the accessibility checker
136
+ *
137
+ * we scan the output.publicPath if its set and not set to 'auto'
138
+ * otherwise we scan the output.path
139
+ */
140
+ let scanDir = compiler.options.output.publicPath && 'auto' !== compiler.options.output.publicPath ?
141
+ compiler.options.output.publicPath :
142
+ compiler.options.output.path;
143
+
144
+ this.a11yCheck(
145
+ scanDir,
162
146
  this.config
163
147
  );
164
148
 
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()) }`);
149
+ 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
150
 
177
151
  });
178
152
  })
@@ -182,6 +156,7 @@ class A11yPlugin {
182
156
  /**
183
157
  * Run accessibility checks
184
158
  *
159
+ * @param {string} target The target URL or directory to scan.
185
160
  * @param {Object} options
186
161
  * @param {boolean} options.debug True if debug mode is enabled.
187
162
  * @param {boolean} options.ruleArchive Specify the rule archive.
@@ -194,7 +169,7 @@ class A11yPlugin {
194
169
  * @param {boolean} options.outputFolder Where the scan results should be saved.
195
170
  * @param {boolean} options.outputFilenameTimestamp Should the timestamp be included in the filename of the reports?
196
171
  */
197
- a11yCheck(url, {
172
+ a11yCheck( target, {
198
173
  debug,
199
174
  ruleArchive,
200
175
  policies,
@@ -207,39 +182,38 @@ class A11yPlugin {
207
182
  outputFilenameTimestamp
208
183
  }){
209
184
 
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 );
185
+ // outputFolder should not be absolute
186
+ outputFolder = path.isAbsolute(outputFolder) ? path.join( process.cwd(), outputFolder ) : outputFolder;
215
187
 
188
+ // accessibility-checker CLI arguments
216
189
  let acheckerArgs = [
217
190
  '--ruleArchive',
218
191
  ruleArchive,
219
192
  '--policies',
220
- Array.isArray(policies) ? policies.filter(e => e).join(',') : policies,
193
+ Array.isArray(policies) ? policies.filter(Boolean).join(',') : policies,
221
194
  '--failLevels',
222
- Array.isArray(failLevels) ? failLevels.filter(e => e).join(',') : failLevels,
195
+ Array.isArray(failLevels) ? failLevels.filter(Boolean).join(',') : failLevels,
223
196
  '--reportLevels',
224
- Array.isArray(reportLevels) ? reportLevels.filter(e => e).join(',') : reportLevels,
197
+ Array.isArray(reportLevels) ? reportLevels.filter(Boolean).join(',') : reportLevels,
225
198
  '--outputFolder',
226
199
  outputFolder,
227
200
  '--outputFormat',
228
201
  outputFormat,
229
202
  '---outputFilenameTimestamp',
230
203
  outputFilenameTimestamp,
231
- url
204
+ target
232
205
  ];
233
206
 
234
- let isValid = fs.existsSync( url ) || 'localhost' === new URL(url).hostname || isUrl( url );
235
- let isDirectory = fs.existsSync( url ) && fs.statSync(url).isDirectory();
207
+ // checks for the target validity
208
+ let isValid = fs.existsSync( target ) || 'localhost' === new URL(target).hostname || isUrl( target );
209
+ // let isDirectory = fs.existsSync( target ) && fs.statSync(target).isDirectory();
236
210
 
211
+ // if the target is valid, we run the accessibility checker
237
212
  if( isValid ){
238
213
 
239
- let outputDir = path.resolve('.', outputFolder );
240
-
214
+ // we run the accessibility-checker CLI
241
215
  let {stderr, stdout} = spawn.sync(
242
- path.resolve(currentPath, '..', '..', 'accessibility-checker', 'bin', 'achecker.js'),
216
+ path.resolve(require.resolve('accessibility-checker'), '..', '..' , 'bin', 'achecker.js'),
243
217
  acheckerArgs,
244
218
  {
245
219
  stdio: 'pipe'
@@ -255,53 +229,39 @@ class A11yPlugin {
255
229
 
256
230
  // we iterate thru the output directory in reverse order,
257
231
  // 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 => {
232
+ fs.readdirSync(outputFolder, {recursive: true}).reverse().forEach( file => {
233
+ let filePath = path.join(outputFolder, file);
234
+
259
235
  // process each json file file
260
- if( fs.statSync(path.join(outputDir, file)).isFile() ){
236
+ if( fs.statSync(filePath).isFile() ){
261
237
  if ( file.startsWith('summary_') ){
262
238
  // remove the summary files
263
- fs.rmSync( path.join(outputDir, file) )
239
+ fs.rmSync( filePath )
264
240
  return;
265
241
  }
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
242
 
273
- newName = newName.replace(/^\\/, '');
243
+ // if the fle ends with .json or .html
244
+ if( file.endsWith('.json') || file.endsWith('.html') ){
245
+ let newName = file.replace( target.replace(':', '_') + '\\', '').replace('.html.html', '.html');
274
246
 
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
247
+ // we rename the json/html file to remove the target from the filename
281
248
  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$/, '') } );
249
+ path.join(outputFolder, file),
250
+ path.join(outputFolder, newName)
251
+ );
252
+
253
+ // we add the file to the audit index
254
+ if( file.endsWith('.html') && ! auditIndex.includes(newName) ){
255
+ auditIndex.push( newName );
256
+ }
297
257
  }
298
258
 
299
- }else if ( fs.statSync(path.join(outputDir, file)).isDirectory() ){
259
+ }else if ( fs.statSync(path.join(outputFolder, file)).isDirectory() ){
300
260
  // process each directory
301
261
  // delete any empty directories.
302
- if( 0 === fs.readdirSync(path.join(outputDir, file)).length ){
262
+ if( 0 === fs.readdirSync(path.join(outputFolder, file)).length ){
303
263
  // remove the directory
304
- fs.rmSync(path.join(outputDir, file), {recursive: true});
264
+ fs.rmSync(path.join(outputFolder, file), {recursive: true});
305
265
  }
306
266
  }
307
267
 
@@ -312,14 +272,13 @@ class A11yPlugin {
312
272
 
313
273
  // we generate the .
314
274
  if( 'a11y' === process.argv[2] ){
315
- // console.log( reportedFile )
275
+ console.log( 'Accessibility Report Generated File: ', path.join(outputFolder, `${outputFilename}.html`) )
316
276
  }else{
317
277
  return true;
318
- // return reportedFile;
319
278
  }
320
279
  }
321
280
  }else{
322
- console.log( `${url} is not a valid url.` )
281
+ console.log( `${target} is not a valid url or directory.` )
323
282
  }
324
283
 
325
284
  } // end of a11yCheck
@@ -327,4 +286,4 @@ class A11yPlugin {
327
286
  } // end of class
328
287
 
329
288
 
330
- export default A11yPlugin;
289
+ export default CAWebA11yWebpackPlugin;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@caweb/a11y-webpack-plugin",
3
- "version": "2.0.2",
3
+ "version": "2.0.4",
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,13 @@
33
35
  "webpack"
34
36
  ],
35
37
  "dependencies": {
36
- "accessibility-checker": "^4.0.9",
38
+ "@caweb/webpack": "^1.6.3",
39
+ "accessibility-checker": "^4.0.10",
37
40
  "check-valid-url": "^0.1.0"
38
41
  },
39
42
  "devDependencies": {
40
- "webpack": "^5.102.1",
43
+ "@caweb/html-webpack-plugin": "^2.0.2",
44
+ "webpack": "^5.104.1",
41
45
  "webpack-cli": "^6.0.1"
42
- },
43
- "peerDependencies": {
44
- "@caweb/template": "^1.0.6"
45
46
  }
46
47
  }
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