wcag-scanner 1.0.0 → 1.2.0

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.
@@ -0,0 +1,2 @@
1
+ #!!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,183 @@
1
+ #!!/usr/bin/env node
2
+ "use strict";
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ const commander_1 = require("commander");
8
+ const __1 = require("..");
9
+ const fs_1 = __importDefault(require("fs"));
10
+ const path_1 = __importDefault(require("path"));
11
+ const http_1 = __importDefault(require("http"));
12
+ const https_1 = __importDefault(require("https"));
13
+ const url_1 = require("url");
14
+ // Try to load package.json for version information
15
+ let version = '0.1.0';
16
+ try {
17
+ const packagePath = path_1.default.join(__dirname, '../../package.json');
18
+ const packageJson = JSON.parse(fs_1.default.readFileSync(packagePath, 'utf8'));
19
+ version = packageJson.version;
20
+ }
21
+ catch (e) {
22
+ // Ignore package.json load error
23
+ }
24
+ commander_1.program
25
+ .name('wcag-scanner')
26
+ .description('Scan HTML files and websites for WCAG accessibility violations')
27
+ .version(version);
28
+ // Command to scan HTML file
29
+ commander_1.program
30
+ .command('scan <filePath>')
31
+ .description('Scan a local HTML file for accessibility violations')
32
+ .option('-l, --level <level>', 'WCAG level (A, AA, AAA)', 'AA')
33
+ .option('-f, --format <format>', 'Output format (json, console, html)', 'console')
34
+ .option('-o, --output <file>', 'Save results to file')
35
+ .option('-v, --verbose', 'Show verbose output')
36
+ .option('-r, --rules <rules>', 'Comma-separated list of rules to check')
37
+ .action(async (filePath, options) => {
38
+ try {
39
+ console.log(`Scanning file: ${filePath}`);
40
+ const scannerOptions = {
41
+ level: options.level,
42
+ verbose: options.verbose || false,
43
+ rules: options.rules ? options.rules.split(',') : undefined
44
+ };
45
+ const results = await (0, __1.scanFile)(filePath, scannerOptions);
46
+ // Generate report
47
+ const report = (0, __1.formatReport)(results, options.format, scannerOptions);
48
+ // Output to console if not html format
49
+ if (options.format !== 'html') {
50
+ console.log(report);
51
+ }
52
+ // Save to file if specified
53
+ if (options.output) {
54
+ console.log(`Saving report to: ${options.output}`);
55
+ (0, __1.saveReport)(report, options.output);
56
+ }
57
+ // Exit with appropriate code based on violations
58
+ process.exit(results.violations.length > 0 ? 1 : 0);
59
+ }
60
+ catch (error) {
61
+ console.error('Error scanning file:', error);
62
+ process.exit(1);
63
+ }
64
+ });
65
+ // Command to scan URL
66
+ commander_1.program
67
+ .command('url <url>')
68
+ .description('Scan a website URL for accessibility issues')
69
+ .option('-l, --level <level>', 'WCAG level (A, AA, AAA)', 'AA')
70
+ .option('-f, --format <format>', 'Output format (json, console, html)', 'console')
71
+ .option('-o, --output <file>', 'Save results to file')
72
+ .option('-v, --verbose', 'Show verbose output')
73
+ .option('-r, --rules <rules>', 'Comma-separated list of rules to check')
74
+ .action(async (urlString, options) => {
75
+ try {
76
+ // Add protocol if missing
77
+ if (!urlString.startsWith('http://') && !urlString.startsWith('https://')) {
78
+ urlString = 'https://' + urlString;
79
+ console.log(`Added protocol to URL: ${urlString}`);
80
+ }
81
+ console.log(`Scanning URL: ${urlString}`);
82
+ // Fetch the URL content
83
+ const html = await fetchUrl(urlString);
84
+ const scannerOptions = {
85
+ level: options.level,
86
+ verbose: options.verbose || false,
87
+ baseUrl: urlString,
88
+ rules: options.rules ? options.rules.split(',') : undefined
89
+ };
90
+ const results = await (0, __1.scanHtml)(html, scannerOptions);
91
+ // Generate report
92
+ const report = (0, __1.formatReport)(results, options.format, scannerOptions);
93
+ // Output to console if not html format
94
+ if (options.format !== 'html') {
95
+ console.log(report);
96
+ }
97
+ // Save to file if specified
98
+ if (options.output) {
99
+ console.log(`Saving report to: ${options.output}`);
100
+ (0, __1.saveReport)(report, options.output);
101
+ }
102
+ // Exit with appropriate code based on violations
103
+ process.exit(results.violations.length > 0 ? 1 : 0);
104
+ }
105
+ catch (error) {
106
+ console.error('Error scanning URL:', error);
107
+ process.exit(1);
108
+ }
109
+ });
110
+ // Command to scan HTML from stdin
111
+ commander_1.program
112
+ .command('stdin')
113
+ .description('Scan HTML input from stdin')
114
+ .option('-l, --level <level>', 'WCAG level (A, AA, AAA)', 'AA')
115
+ .option('-f, --format <format>', 'Output format (json, console, html)', 'console')
116
+ .option('-o, --output <file>', 'Save results to file')
117
+ .option('-v, --verbose', 'Show verbose output')
118
+ .option('-r, --rules <rules>', 'Comma-separated list of rules to check')
119
+ .action(async (options) => {
120
+ try {
121
+ console.log('Reading HTML from stdin...');
122
+ let html = '';
123
+ process.stdin.on('data', (chunk) => {
124
+ html += chunk;
125
+ });
126
+ process.stdin.on('end', async () => {
127
+ console.log(`Received ${html.length} bytes of HTML`);
128
+ const scannerOptions = {
129
+ level: options.level,
130
+ verbose: options.verbose || false,
131
+ rules: options.rules ? options.rules.split(',') : undefined
132
+ };
133
+ const results = await (0, __1.scanHtml)(html, scannerOptions);
134
+ // Generate report
135
+ const report = (0, __1.formatReport)(results, options.format, scannerOptions);
136
+ // Output to console if not html format
137
+ if (options.format !== 'html') {
138
+ console.log(report);
139
+ }
140
+ // Save to file if specified
141
+ if (options.output) {
142
+ console.log(`Saving report to: ${options.output}`);
143
+ (0, __1.saveReport)(report, options.output);
144
+ }
145
+ // Exit with appropriate code based on violations
146
+ process.exit(results.violations.length > 0 ? 1 : 0);
147
+ });
148
+ }
149
+ catch (error) {
150
+ console.error('Error scanning HTML:', error);
151
+ process.exit(1);
152
+ }
153
+ });
154
+ // Helper function to fetch URL content
155
+ async function fetchUrl(urlString) {
156
+ return new Promise((resolve, reject) => {
157
+ const url = new url_1.URL(urlString);
158
+ const client = url.protocol === 'https:' ? https_1.default : http_1.default;
159
+ const req = client.get(urlString, (res) => {
160
+ if (res.statusCode !== 200) {
161
+ reject(new Error(`Request failed with status code ${res.statusCode}`));
162
+ return;
163
+ }
164
+ let data = '';
165
+ res.on('data', (chunk) => {
166
+ data += chunk;
167
+ });
168
+ res.on('end', () => {
169
+ resolve(data);
170
+ });
171
+ });
172
+ req.on('error', (err) => {
173
+ reject(err);
174
+ });
175
+ req.end();
176
+ });
177
+ }
178
+ // If no arguments provided, show help
179
+ if (process.argv.length <= 2) {
180
+ commander_1.program.help();
181
+ }
182
+ // Parse command line arguments
183
+ commander_1.program.parse(process.argv);
package/dist/index.d.ts CHANGED
@@ -1,18 +1,56 @@
1
- import { WCAGScanner } from "./scanner";
2
- import { ScannerOptions, ScanResults } from "./types";
1
+ import { WCAGScanner } from './scanner';
2
+ import { ScannerOptions, ScanResults } from './types';
3
+ import { ReporterFormat } from './reporters';
4
+ import middleware from './middleware';
3
5
  /**
4
6
  * Scan HTML string for WCAG violations
5
7
  * @param html HTML content to scan
6
8
  * @param options Scanner options
7
- * @return Promise<ScanResults> Scan results
9
+ * @returns Promise<ScanResults> Scan results
8
10
  */
9
- export declare function scanHTML(html: string, options?: ScannerOptions): Promise<ScanResults>;
11
+ export declare function scanHtml(html: string, options?: ScannerOptions): Promise<ScanResults>;
10
12
  /**
11
13
  * Scan HTML file for WCAG violations
12
14
  * @param filePath Path to HTML file
13
15
  * @param options Scanner options
14
- * @return Promise<ScanResults> Scan results
16
+ * @returns Promise<ScanResults> Scan results
15
17
  */
16
18
  export declare function scanFile(filePath: string, options?: ScannerOptions): Promise<ScanResults>;
19
+ /**
20
+ * Scan a URL for WCAG violations
21
+ * @param urlString URL to scan
22
+ * @param options Scanner options
23
+ * @returns Promise<ScanResults> Scan results
24
+ */
25
+ export declare function scanUrl(urlString: string, options?: ScannerOptions): Promise<ScanResults>;
26
+ /**
27
+ * Generate a report from scan results
28
+ * @param results Scan results
29
+ * @param format Report format
30
+ * @param options Scanner options
31
+ * @returns Report string
32
+ */
33
+ export declare function formatReport(results: ScanResults, format?: ReporterFormat, options?: ScannerOptions): string;
34
+ /**
35
+ * Save report to a file
36
+ * @param report Report string
37
+ * @param filePath Output file path
38
+ */
39
+ export declare function saveReport(report: string, filePath: string): void;
17
40
  export { WCAGScanner };
18
- export * from "./types";
41
+ export * from './types';
42
+ export { ReporterFormat };
43
+ export { middleware };
44
+ declare const _default: {
45
+ scanHtml: typeof scanHtml;
46
+ scanFile: typeof scanFile;
47
+ scanUrl: typeof scanUrl;
48
+ formatReport: typeof formatReport;
49
+ saveReport: typeof saveReport;
50
+ middleware: {
51
+ express: {
52
+ createMiddleware: typeof import("./middleware/express").createMiddleware;
53
+ };
54
+ };
55
+ };
56
+ export default _default;
package/dist/index.js CHANGED
@@ -17,20 +17,29 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
17
17
  return (mod && mod.__esModule) ? mod : { "default": mod };
18
18
  };
19
19
  Object.defineProperty(exports, "__esModule", { value: true });
20
- exports.WCAGScanner = void 0;
21
- exports.scanHTML = scanHTML;
20
+ exports.middleware = exports.WCAGScanner = void 0;
21
+ exports.scanHtml = scanHtml;
22
22
  exports.scanFile = scanFile;
23
+ exports.scanUrl = scanUrl;
24
+ exports.formatReport = formatReport;
25
+ exports.saveReport = saveReport;
23
26
  const scanner_1 = require("./scanner");
24
27
  Object.defineProperty(exports, "WCAGScanner", { enumerable: true, get: function () { return scanner_1.WCAGScanner; } });
28
+ const reporters_1 = require("./reporters");
29
+ const middleware_1 = __importDefault(require("./middleware"));
30
+ exports.middleware = middleware_1.default;
25
31
  const fs_1 = __importDefault(require("fs"));
26
32
  const path_1 = __importDefault(require("path"));
33
+ const url_1 = require("url");
34
+ const http_1 = __importDefault(require("http"));
35
+ const https_1 = __importDefault(require("https"));
27
36
  /**
28
37
  * Scan HTML string for WCAG violations
29
38
  * @param html HTML content to scan
30
39
  * @param options Scanner options
31
- * @return Promise<ScanResults> Scan results
40
+ * @returns Promise<ScanResults> Scan results
32
41
  */
33
- async function scanHTML(html, options = {}) {
42
+ async function scanHtml(html, options = {}) {
34
43
  const scanner = new scanner_1.WCAGScanner(options);
35
44
  await scanner.loadHTML(html);
36
45
  return scanner.scan();
@@ -39,7 +48,7 @@ async function scanHTML(html, options = {}) {
39
48
  * Scan HTML file for WCAG violations
40
49
  * @param filePath Path to HTML file
41
50
  * @param options Scanner options
42
- * @return Promise<ScanResults> Scan results
51
+ * @returns Promise<ScanResults> Scan results
43
52
  */
44
53
  async function scanFile(filePath, options = {}) {
45
54
  const html = fs_1.default.readFileSync(path_1.default.resolve(filePath), 'utf8');
@@ -48,4 +57,77 @@ async function scanFile(filePath, options = {}) {
48
57
  await scanner.loadHTML(html, baseUrl);
49
58
  return scanner.scan();
50
59
  }
60
+ /**
61
+ * Scan a URL for WCAG violations
62
+ * @param urlString URL to scan
63
+ * @param options Scanner options
64
+ * @returns Promise<ScanResults> Scan results
65
+ */
66
+ async function scanUrl(urlString, options = {}) {
67
+ // Add protocol if missing
68
+ if (!urlString.startsWith('http://') && !urlString.startsWith('https://')) {
69
+ urlString = 'https://' + urlString;
70
+ }
71
+ const html = await fetchUrl(urlString);
72
+ const scannerOptions = {
73
+ ...options,
74
+ baseUrl: urlString
75
+ };
76
+ return scanHtml(html, scannerOptions);
77
+ }
78
+ /**
79
+ * Generate a report from scan results
80
+ * @param results Scan results
81
+ * @param format Report format
82
+ * @param options Scanner options
83
+ * @returns Report string
84
+ */
85
+ function formatReport(results, format = 'json', options = {}) {
86
+ return (0, reporters_1.generateReport)(results, format, options);
87
+ }
88
+ /**
89
+ * Save report to a file
90
+ * @param report Report string
91
+ * @param filePath Output file path
92
+ */
93
+ function saveReport(report, filePath) {
94
+ fs_1.default.writeFileSync(filePath, report);
95
+ }
96
+ /**
97
+ * Helper function to fetch URL content
98
+ * @param urlString URL to fetch
99
+ * @returns Promise<string> HTML content
100
+ */
101
+ async function fetchUrl(urlString) {
102
+ return new Promise((resolve, reject) => {
103
+ const url = new url_1.URL(urlString);
104
+ const client = url.protocol === 'https:' ? https_1.default : http_1.default;
105
+ const req = client.get(urlString, (res) => {
106
+ if (res.statusCode !== 200) {
107
+ reject(new Error(`Request failed with status code ${res.statusCode}`));
108
+ return;
109
+ }
110
+ let data = '';
111
+ res.on('data', (chunk) => {
112
+ data += chunk;
113
+ });
114
+ res.on('end', () => {
115
+ resolve(data);
116
+ });
117
+ });
118
+ req.on('error', (err) => {
119
+ reject(err);
120
+ });
121
+ req.end();
122
+ });
123
+ }
51
124
  __exportStar(require("./types"), exports);
125
+ // Default export with all main functions
126
+ exports.default = {
127
+ scanHtml,
128
+ scanFile,
129
+ scanUrl,
130
+ formatReport,
131
+ saveReport,
132
+ middleware: middleware_1.default
133
+ };
@@ -0,0 +1,25 @@
1
+ import { Request, Response, NextFunction } from 'express';
2
+ import { ScannerOptions, ScanResults } from '../index';
3
+ /**
4
+ * Options for the Express middleware
5
+ */
6
+ export interface ExpressMiddlewareOptions extends ScannerOptions {
7
+ /** Enable/disable the middleware */
8
+ enabled?: boolean;
9
+ /** Custom header name for violation count */
10
+ headerName?: string;
11
+ /** Add inline report to HTML responses */
12
+ inlineReport?: boolean;
13
+ /** Callback function to handle violations */
14
+ onViolation?: (results: ScanResults, req: Request, res: Response) => void;
15
+ }
16
+ /**
17
+ * Create Express middleware for scanning HTML responses for accessibility issues
18
+ * @param options Middleware options
19
+ * @returns Express middleware function
20
+ */
21
+ export declare function createMiddleware(options?: ExpressMiddlewareOptions): (req: Request, res: Response, next: NextFunction) => Promise<void>;
22
+ declare const _default: {
23
+ createMiddleware: typeof createMiddleware;
24
+ };
25
+ export default _default;
@@ -0,0 +1,209 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createMiddleware = createMiddleware;
4
+ const index_1 = require("../index");
5
+ /**
6
+ * Create Express middleware for scanning HTML responses for accessibility issues
7
+ * @param options Middleware options
8
+ * @returns Express middleware function
9
+ */
10
+ function createMiddleware(options = {}) {
11
+ const defaultOptions = {
12
+ enabled: process.env.NODE_ENV !== 'production', // Disable in production by default
13
+ level: 'AA',
14
+ headerName: 'X-WCAG-Violations',
15
+ inlineReport: false,
16
+ ...options
17
+ };
18
+ return async function wcagScannerMiddleware(req, res, next) {
19
+ // Skip if disabled or non-HTML request
20
+ if (!defaultOptions.enabled || !shouldProcessRequest(req)) {
21
+ return next();
22
+ }
23
+ // Store original send method
24
+ const originalSend = res.send;
25
+ // Override send method to intercept HTML responses
26
+ res.send = function (body) {
27
+ // Only process HTML responses
28
+ if (typeof body === 'string' && isHtmlResponse(res)) {
29
+ try {
30
+ // Create scanner
31
+ const scanner = new index_1.WCAGScanner(defaultOptions);
32
+ // Run scan asynchronously (we can't make res.send async)
33
+ scanner.loadHTML(body).then(() => {
34
+ return scanner.scan();
35
+ }).then((results) => {
36
+ // Add violation count header
37
+ res.setHeader(defaultOptions.headerName || 'X-WCAG-Violations', results.violations.length.toString());
38
+ // Call violation handler if provided
39
+ if (defaultOptions.onViolation && results.violations.length > 0) {
40
+ defaultOptions.onViolation(results, req, res);
41
+ }
42
+ // Add inline report if enabled
43
+ if (defaultOptions.inlineReport && results.violations.length > 0) {
44
+ body = insertInlineReport(body, results);
45
+ }
46
+ // Send modified response
47
+ originalSend.call(res, body);
48
+ }).catch((error) => {
49
+ console.error('Error in WCAG scanner middleware:', error);
50
+ // Send original response if there's an error
51
+ originalSend.call(res, body);
52
+ });
53
+ // Return a dummy response to prevent Express from sending twice
54
+ return res;
55
+ }
56
+ catch (error) {
57
+ console.error('Error in WCAG scanner middleware:', error);
58
+ }
59
+ }
60
+ // Call original send for non-HTML responses
61
+ return originalSend.call(this, body);
62
+ };
63
+ next();
64
+ };
65
+ }
66
+ /**
67
+ * Determine if the request should be processed
68
+ * @param req Express request object
69
+ * @returns True if request should be processed
70
+ */
71
+ function shouldProcessRequest(req) {
72
+ // Skip non-GET requests
73
+ if (req.method !== 'GET') {
74
+ return false;
75
+ }
76
+ // Skip API routes
77
+ if (req.path.startsWith('/api/')) {
78
+ return false;
79
+ }
80
+ // Skip if client doesn't want HTML
81
+ const accept = req.headers.accept || '';
82
+ if (!accept.includes('html') && !accept.includes('*/*')) {
83
+ return false;
84
+ }
85
+ return true;
86
+ }
87
+ /**
88
+ * Check if response is HTML
89
+ * @param res Express response object
90
+ * @returns True if response is HTML
91
+ */
92
+ function isHtmlResponse(res) {
93
+ const contentType = res.get('Content-Type') || '';
94
+ return contentType.includes('html');
95
+ }
96
+ /**
97
+ * Insert inline accessibility report into HTML
98
+ * @param html Original HTML
99
+ * @param results Scan results
100
+ * @returns HTML with inline report
101
+ */
102
+ function insertInlineReport(html, results) {
103
+ // Skip if no violations
104
+ if (results.violations.length === 0) {
105
+ return html;
106
+ }
107
+ // Create simple inline report
108
+ const report = `
109
+ <div id="wcag-scanner-report" style="
110
+ position: fixed;
111
+ bottom: 20px;
112
+ right: 20px;
113
+ z-index: 9999;
114
+ background: white;
115
+ border: 2px solid #e53935;
116
+ border-radius: 8px;
117
+ padding: 20px;
118
+ max-width: 400px;
119
+ max-height: 80vh;
120
+ overflow: auto;
121
+ box-shadow: 0 0 10px rgba(0,0,0,0.2);
122
+ font-family: sans-serif;
123
+ color: #333;
124
+ ">
125
+ <div style="
126
+ display: flex;
127
+ justify-content: space-between;
128
+ align-items: center;
129
+ margin-bottom: 15px;
130
+ border-bottom: 1px solid #eee;
131
+ padding-bottom: 10px;
132
+ ">
133
+ <h3 style="margin: 0; color: #e53935;">
134
+ ${results.violations.length} Accessibility Issues Found
135
+ </h3>
136
+ <button onclick="document.getElementById('wcag-scanner-report').style.display='none'" style="
137
+ background: #f5f5f5;
138
+ border: none;
139
+ border-radius: 4px;
140
+ padding: 5px 10px;
141
+ cursor: pointer;
142
+ ">
143
+ Close
144
+ </button>
145
+ </div>
146
+
147
+ <ul style="
148
+ list-style-type: none;
149
+ padding: 0;
150
+ margin: 0;
151
+ ">
152
+ ${results.violations.slice(0, 5).map(violation => `
153
+ <li style="
154
+ margin-bottom: 15px;
155
+ padding-bottom: 15px;
156
+ border-bottom: 1px solid #eee;
157
+ ">
158
+ <div style="
159
+ font-weight: bold;
160
+ margin-bottom: 5px;
161
+ ">
162
+ ${violation.description}
163
+ </div>
164
+ ${violation.help ? `
165
+ <div style="
166
+ font-size: 14px;
167
+ margin-bottom: 8px;
168
+ ">
169
+ ${violation.help}
170
+ </div>
171
+ ` : ''}
172
+ ${violation.element ? `
173
+ <div style="
174
+ font-size: 13px;
175
+ color: #666;
176
+ ">
177
+ ${violation.element.tagName}
178
+ ${violation.element.id ? `#${violation.element.id}` : ''}
179
+ </div>
180
+ ` : ''}
181
+ </li>
182
+ `).join('')}
183
+ ${results.violations.length > 5 ? `
184
+ <li style="text-align: center; font-size: 14px; color: #666;">
185
+ +${results.violations.length - 5} more issues
186
+ </li>
187
+ ` : ''}
188
+ </ul>
189
+ <div style="
190
+ margin-top: 15px;
191
+ font-size: 12px;
192
+ text-align: right;
193
+ color: #666;
194
+ ">
195
+ Generated by WCAG Scanner
196
+ </div>
197
+ </div>
198
+ `;
199
+ // Insert before </body> if it exists, otherwise append to the end
200
+ if (html.includes('</body>')) {
201
+ return html.replace('</body>', `${report}</body>`);
202
+ }
203
+ else {
204
+ return html + report;
205
+ }
206
+ }
207
+ exports.default = {
208
+ createMiddleware
209
+ };
@@ -0,0 +1,8 @@
1
+ import express from './express';
2
+ export { express };
3
+ declare const _default: {
4
+ express: {
5
+ createMiddleware: typeof import("./express").createMiddleware;
6
+ };
7
+ };
8
+ export default _default;
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.express = void 0;
7
+ const express_1 = __importDefault(require("./express"));
8
+ exports.express = express_1.default;
9
+ exports.default = {
10
+ express: express_1.default
11
+ };
@@ -0,0 +1,12 @@
1
+ import { ScanResults, ScannerOptions } from "../types";
2
+ /**
3
+ * Format scan results for console output
4
+ * @param results Scanner results object
5
+ * @param options Options used for the scan
6
+ * @returns Formatted console output string
7
+ */
8
+ export declare function format(results: ScanResults, options?: ScannerOptions): string;
9
+ declare const _default: {
10
+ format: typeof format;
11
+ };
12
+ export default _default;