codemeld 2.1.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.
- package/README.md +514 -0
- package/bin/cli.js +2 -0
- package/dist/ai/agent.d.ts +124 -0
- package/dist/ai/agent.d.ts.map +1 -0
- package/dist/ai/agent.js +289 -0
- package/dist/ai/agent.js.map +1 -0
- package/dist/ai/index.d.ts +10 -0
- package/dist/ai/index.d.ts.map +1 -0
- package/dist/ai/index.js +10 -0
- package/dist/ai/index.js.map +1 -0
- package/dist/ai/prompts.d.ts +35 -0
- package/dist/ai/prompts.d.ts.map +1 -0
- package/dist/ai/prompts.js +166 -0
- package/dist/ai/prompts.js.map +1 -0
- package/dist/ai/refinement-loop.d.ts +29 -0
- package/dist/ai/refinement-loop.d.ts.map +1 -0
- package/dist/ai/refinement-loop.js +180 -0
- package/dist/ai/refinement-loop.js.map +1 -0
- package/dist/ai/tools.d.ts +17 -0
- package/dist/ai/tools.d.ts.map +1 -0
- package/dist/ai/tools.js +353 -0
- package/dist/ai/tools.js.map +1 -0
- package/dist/ai/visual-compare.d.ts +43 -0
- package/dist/ai/visual-compare.d.ts.map +1 -0
- package/dist/ai/visual-compare.js +176 -0
- package/dist/ai/visual-compare.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +179 -0
- package/dist/cli.js.map +1 -0
- package/dist/converter.d.ts +10 -0
- package/dist/converter.d.ts.map +1 -0
- package/dist/converter.js +836 -0
- package/dist/converter.js.map +1 -0
- package/dist/deconverter.d.ts +19 -0
- package/dist/deconverter.d.ts.map +1 -0
- package/dist/deconverter.js +188 -0
- package/dist/deconverter.js.map +1 -0
- package/dist/frameworks/angular-adapter.d.ts +27 -0
- package/dist/frameworks/angular-adapter.d.ts.map +1 -0
- package/dist/frameworks/angular-adapter.js +617 -0
- package/dist/frameworks/angular-adapter.js.map +1 -0
- package/dist/frameworks/index.d.ts +10 -0
- package/dist/frameworks/index.d.ts.map +1 -0
- package/dist/frameworks/index.js +21 -0
- package/dist/frameworks/index.js.map +1 -0
- package/dist/frameworks/nextjs-adapter.d.ts +22 -0
- package/dist/frameworks/nextjs-adapter.d.ts.map +1 -0
- package/dist/frameworks/nextjs-adapter.js +392 -0
- package/dist/frameworks/nextjs-adapter.js.map +1 -0
- package/dist/frameworks/react-adapter.d.ts +21 -0
- package/dist/frameworks/react-adapter.d.ts.map +1 -0
- package/dist/frameworks/react-adapter.js +71 -0
- package/dist/frameworks/react-adapter.js.map +1 -0
- package/dist/frameworks/svelte-adapter.d.ts +27 -0
- package/dist/frameworks/svelte-adapter.d.ts.map +1 -0
- package/dist/frameworks/svelte-adapter.js +519 -0
- package/dist/frameworks/svelte-adapter.js.map +1 -0
- package/dist/frameworks/types.d.ts +78 -0
- package/dist/frameworks/types.d.ts.map +1 -0
- package/dist/frameworks/types.js +2 -0
- package/dist/frameworks/types.js.map +1 -0
- package/dist/frameworks/vue-adapter.d.ts +34 -0
- package/dist/frameworks/vue-adapter.d.ts.map +1 -0
- package/dist/frameworks/vue-adapter.js +632 -0
- package/dist/frameworks/vue-adapter.js.map +1 -0
- package/dist/generators/accessibility-generator.d.ts +43 -0
- package/dist/generators/accessibility-generator.d.ts.map +1 -0
- package/dist/generators/accessibility-generator.js +507 -0
- package/dist/generators/accessibility-generator.js.map +1 -0
- package/dist/generators/asset-handler.d.ts +14 -0
- package/dist/generators/asset-handler.d.ts.map +1 -0
- package/dist/generators/asset-handler.js +79 -0
- package/dist/generators/asset-handler.js.map +1 -0
- package/dist/generators/build-verifier.d.ts +8 -0
- package/dist/generators/build-verifier.d.ts.map +1 -0
- package/dist/generators/build-verifier.js +64 -0
- package/dist/generators/build-verifier.js.map +1 -0
- package/dist/generators/component-extractor.d.ts +25 -0
- package/dist/generators/component-extractor.d.ts.map +1 -0
- package/dist/generators/component-extractor.js +146 -0
- package/dist/generators/component-extractor.js.map +1 -0
- package/dist/generators/component-generator.d.ts +12 -0
- package/dist/generators/component-generator.d.ts.map +1 -0
- package/dist/generators/component-generator.js +724 -0
- package/dist/generators/component-generator.js.map +1 -0
- package/dist/generators/deploy-generator.d.ts +9 -0
- package/dist/generators/deploy-generator.d.ts.map +1 -0
- package/dist/generators/deploy-generator.js +409 -0
- package/dist/generators/deploy-generator.js.map +1 -0
- package/dist/generators/error-boundary.d.ts +5 -0
- package/dist/generators/error-boundary.d.ts.map +1 -0
- package/dist/generators/error-boundary.js +59 -0
- package/dist/generators/error-boundary.js.map +1 -0
- package/dist/generators/form-generator.d.ts +42 -0
- package/dist/generators/form-generator.d.ts.map +1 -0
- package/dist/generators/form-generator.js +662 -0
- package/dist/generators/form-generator.js.map +1 -0
- package/dist/generators/hooks-generator.d.ts +40 -0
- package/dist/generators/hooks-generator.d.ts.map +1 -0
- package/dist/generators/hooks-generator.js +297 -0
- package/dist/generators/hooks-generator.js.map +1 -0
- package/dist/generators/html-generator.d.ts +27 -0
- package/dist/generators/html-generator.d.ts.map +1 -0
- package/dist/generators/html-generator.js +772 -0
- package/dist/generators/html-generator.js.map +1 -0
- package/dist/generators/jquery-converter.d.ts +41 -0
- package/dist/generators/jquery-converter.d.ts.map +1 -0
- package/dist/generators/jquery-converter.js +594 -0
- package/dist/generators/jquery-converter.js.map +1 -0
- package/dist/generators/pattern-implementer.d.ts +26 -0
- package/dist/generators/pattern-implementer.d.ts.map +1 -0
- package/dist/generators/pattern-implementer.js +336 -0
- package/dist/generators/pattern-implementer.js.map +1 -0
- package/dist/generators/performance-generator.d.ts +51 -0
- package/dist/generators/performance-generator.d.ts.map +1 -0
- package/dist/generators/performance-generator.js +428 -0
- package/dist/generators/performance-generator.js.map +1 -0
- package/dist/generators/router-generator.d.ts +21 -0
- package/dist/generators/router-generator.d.ts.map +1 -0
- package/dist/generators/router-generator.js +178 -0
- package/dist/generators/router-generator.js.map +1 -0
- package/dist/generators/scaffolder.d.ts +28 -0
- package/dist/generators/scaffolder.d.ts.map +1 -0
- package/dist/generators/scaffolder.js +266 -0
- package/dist/generators/scaffolder.js.map +1 -0
- package/dist/generators/seo-generator.d.ts +29 -0
- package/dist/generators/seo-generator.d.ts.map +1 -0
- package/dist/generators/seo-generator.js +223 -0
- package/dist/generators/seo-generator.js.map +1 -0
- package/dist/generators/test-generator.d.ts +19 -0
- package/dist/generators/test-generator.d.ts.map +1 -0
- package/dist/generators/test-generator.js +398 -0
- package/dist/generators/test-generator.js.map +1 -0
- package/dist/generators/type-generator.d.ts +33 -0
- package/dist/generators/type-generator.d.ts.map +1 -0
- package/dist/generators/type-generator.js +663 -0
- package/dist/generators/type-generator.js.map +1 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/parsers/css-processor.d.ts +23 -0
- package/dist/parsers/css-processor.d.ts.map +1 -0
- package/dist/parsers/css-processor.js +129 -0
- package/dist/parsers/css-processor.js.map +1 -0
- package/dist/parsers/framework-parser.d.ts +48 -0
- package/dist/parsers/framework-parser.d.ts.map +1 -0
- package/dist/parsers/framework-parser.js +770 -0
- package/dist/parsers/framework-parser.js.map +1 -0
- package/dist/parsers/html-parser.d.ts +12 -0
- package/dist/parsers/html-parser.d.ts.map +1 -0
- package/dist/parsers/html-parser.js +444 -0
- package/dist/parsers/html-parser.js.map +1 -0
- package/dist/parsers/js-analyzer.d.ts +199 -0
- package/dist/parsers/js-analyzer.d.ts.map +1 -0
- package/dist/parsers/js-analyzer.js +680 -0
- package/dist/parsers/js-analyzer.js.map +1 -0
- package/dist/parsers/js-resolver.d.ts +8 -0
- package/dist/parsers/js-resolver.d.ts.map +1 -0
- package/dist/parsers/js-resolver.js +45 -0
- package/dist/parsers/js-resolver.js.map +1 -0
- package/dist/parsers/tailwind-detector.d.ts +23 -0
- package/dist/parsers/tailwind-detector.d.ts.map +1 -0
- package/dist/parsers/tailwind-detector.js +104 -0
- package/dist/parsers/tailwind-detector.js.map +1 -0
- package/dist/tests/advanced-features.test.d.ts +2 -0
- package/dist/tests/advanced-features.test.d.ts.map +1 -0
- package/dist/tests/advanced-features.test.js +235 -0
- package/dist/tests/advanced-features.test.js.map +1 -0
- package/dist/tests/css-modules.test.d.ts +2 -0
- package/dist/tests/css-modules.test.d.ts.map +1 -0
- package/dist/tests/css-modules.test.js +61 -0
- package/dist/tests/css-modules.test.js.map +1 -0
- package/dist/tests/css-processor.test.d.ts +2 -0
- package/dist/tests/css-processor.test.d.ts.map +1 -0
- package/dist/tests/css-processor.test.js +48 -0
- package/dist/tests/css-processor.test.js.map +1 -0
- package/dist/tests/html-parser.test.d.ts +2 -0
- package/dist/tests/html-parser.test.d.ts.map +1 -0
- package/dist/tests/html-parser.test.js +78 -0
- package/dist/tests/html-parser.test.js.map +1 -0
- package/dist/tests/integration.test.d.ts +2 -0
- package/dist/tests/integration.test.d.ts.map +1 -0
- package/dist/tests/integration.test.js +65 -0
- package/dist/tests/integration.test.js.map +1 -0
- package/dist/tests/js-analyzer.test.d.ts +2 -0
- package/dist/tests/js-analyzer.test.d.ts.map +1 -0
- package/dist/tests/js-analyzer.test.js +58 -0
- package/dist/tests/js-analyzer.test.js.map +1 -0
- package/dist/tests/naming.test.d.ts +2 -0
- package/dist/tests/naming.test.d.ts.map +1 -0
- package/dist/tests/naming.test.js +43 -0
- package/dist/tests/naming.test.js.map +1 -0
- package/dist/tests/router-generator.test.d.ts +2 -0
- package/dist/tests/router-generator.test.d.ts.map +1 -0
- package/dist/tests/router-generator.test.js +60 -0
- package/dist/tests/router-generator.test.js.map +1 -0
- package/dist/tui/chat.d.ts +13 -0
- package/dist/tui/chat.d.ts.map +1 -0
- package/dist/tui/chat.js +499 -0
- package/dist/tui/chat.js.map +1 -0
- package/dist/tui/design-guide.d.ts +41 -0
- package/dist/tui/design-guide.d.ts.map +1 -0
- package/dist/tui/design-guide.js +184 -0
- package/dist/tui/design-guide.js.map +1 -0
- package/dist/tui/input.d.ts +30 -0
- package/dist/tui/input.d.ts.map +1 -0
- package/dist/tui/input.js +239 -0
- package/dist/tui/input.js.map +1 -0
- package/dist/tui/renderer.d.ts +48 -0
- package/dist/tui/renderer.d.ts.map +1 -0
- package/dist/tui/renderer.js +212 -0
- package/dist/tui/renderer.js.map +1 -0
- package/dist/tui/tools.d.ts +14 -0
- package/dist/tui/tools.d.ts.map +1 -0
- package/dist/tui/tools.js +1370 -0
- package/dist/tui/tools.js.map +1 -0
- package/dist/types.d.ts +93 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/config.d.ts +20 -0
- package/dist/utils/config.d.ts.map +1 -0
- package/dist/utils/config.js +33 -0
- package/dist/utils/config.js.map +1 -0
- package/dist/utils/formatter.d.ts +5 -0
- package/dist/utils/formatter.d.ts.map +1 -0
- package/dist/utils/formatter.js +68 -0
- package/dist/utils/formatter.js.map +1 -0
- package/dist/utils/logger.d.ts +8 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +19 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/naming.d.ts +17 -0
- package/dist/utils/naming.d.ts.map +1 -0
- package/dist/utils/naming.js +48 -0
- package/dist/utils/naming.js.map +1 -0
- package/dist/utils/report.d.ts +56 -0
- package/dist/utils/report.d.ts.map +1 -0
- package/dist/utils/report.js +339 -0
- package/dist/utils/report.js.map +1 -0
- package/package.json +61 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Accessibility enhancement generator for converted components.
|
|
3
|
+
* Uses regex-based HTML manipulation — no DOM parser dependency required.
|
|
4
|
+
*/
|
|
5
|
+
export interface A11yIssue {
|
|
6
|
+
type: 'error' | 'warning' | 'info';
|
|
7
|
+
rule: string;
|
|
8
|
+
message: string;
|
|
9
|
+
element?: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Applies all automated accessibility enhancements to the given HTML string.
|
|
13
|
+
* Calls addAriaLabels and improveSemanticHTML, then applies additional
|
|
14
|
+
* role attributes, aria-expanded for toggle buttons, and aria-hidden for
|
|
15
|
+
* decorative images.
|
|
16
|
+
*/
|
|
17
|
+
export declare function enhanceAccessibility(html: string): string;
|
|
18
|
+
/**
|
|
19
|
+
* Detects icon-only buttons and image-only links, adding appropriate
|
|
20
|
+
* aria-label attributes. Also adds aria-current="page" to active nav links.
|
|
21
|
+
*/
|
|
22
|
+
export declare function addAriaLabels(html: string): string;
|
|
23
|
+
/**
|
|
24
|
+
* Converts common div-with-class patterns to semantic HTML5 elements.
|
|
25
|
+
* Only converts top-level landmark elements (not deeply nested ones).
|
|
26
|
+
*/
|
|
27
|
+
export declare function improveSemanticHTML(html: string): string;
|
|
28
|
+
/**
|
|
29
|
+
* Returns a React component string for a "Skip to main content" link.
|
|
30
|
+
* The link is visually hidden until focused, following best practices.
|
|
31
|
+
*/
|
|
32
|
+
export declare function addSkipNavigation(): string;
|
|
33
|
+
/**
|
|
34
|
+
* Returns the source for a useFocusTrap React hook that traps keyboard
|
|
35
|
+
* focus within a container element (e.g. a modal/dialog) while active.
|
|
36
|
+
*/
|
|
37
|
+
export declare function generateFocusTrapHook(): string;
|
|
38
|
+
/**
|
|
39
|
+
* Scans the given HTML for common accessibility issues and returns an
|
|
40
|
+
* array of A11yIssue objects describing each finding.
|
|
41
|
+
*/
|
|
42
|
+
export declare function generateA11yReport(html: string): A11yIssue[];
|
|
43
|
+
//# sourceMappingURL=accessibility-generator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"accessibility-generator.d.ts","sourceRoot":"","sources":["../../src/generators/accessibility-generator.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,OAAO,GAAG,SAAS,GAAG,MAAM,CAAC;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAMD;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAmBzD;AAMD;;;GAGG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAuClD;AAMD;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CA6BxD;AAMD;;;GAGG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CA+C1C;AAMD;;;GAGG;AACH,wBAAgB,qBAAqB,IAAI,MAAM,CAgF9C;AAMD;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,EAAE,CAiL5D"}
|
|
@@ -0,0 +1,507 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Accessibility enhancement generator for converted components.
|
|
3
|
+
* Uses regex-based HTML manipulation — no DOM parser dependency required.
|
|
4
|
+
*/
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
// Master function
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
/**
|
|
9
|
+
* Applies all automated accessibility enhancements to the given HTML string.
|
|
10
|
+
* Calls addAriaLabels and improveSemanticHTML, then applies additional
|
|
11
|
+
* role attributes, aria-expanded for toggle buttons, and aria-hidden for
|
|
12
|
+
* decorative images.
|
|
13
|
+
*/
|
|
14
|
+
export function enhanceAccessibility(html) {
|
|
15
|
+
let result = html;
|
|
16
|
+
// Semantic landmark conversion first (changes tag names that later rules rely on)
|
|
17
|
+
result = improveSemanticHTML(result);
|
|
18
|
+
// ARIA labels for interactive elements
|
|
19
|
+
result = addAriaLabels(result);
|
|
20
|
+
// Add role attributes to common patterns
|
|
21
|
+
result = addRoleAttributes(result);
|
|
22
|
+
// Add aria-expanded to toggle/collapse buttons
|
|
23
|
+
result = addAriaExpanded(result);
|
|
24
|
+
// Mark decorative images with aria-hidden
|
|
25
|
+
result = markDecorativeImages(result);
|
|
26
|
+
return result;
|
|
27
|
+
}
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
// ARIA labels
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
/**
|
|
32
|
+
* Detects icon-only buttons and image-only links, adding appropriate
|
|
33
|
+
* aria-label attributes. Also adds aria-current="page" to active nav links.
|
|
34
|
+
*/
|
|
35
|
+
export function addAriaLabels(html) {
|
|
36
|
+
let result = html;
|
|
37
|
+
// Icon-only buttons: <button> containing only <svg> or <i> with no visible text
|
|
38
|
+
result = result.replace(/<button([^>]*?)>\s*(<(?:svg|i)\b[^]*?<\/(?:svg|i)>)\s*<\/button>/gi, (_match, attrs, inner) => {
|
|
39
|
+
if (/aria-label/i.test(attrs))
|
|
40
|
+
return _match; // already labelled
|
|
41
|
+
const label = inferIconLabel(inner);
|
|
42
|
+
return `<button${attrs} aria-label="${label}">${inner}</button>`;
|
|
43
|
+
});
|
|
44
|
+
// Links containing only an <img> (no visible text)
|
|
45
|
+
result = result.replace(/<a([^>]*?)>\s*(<img\b[^>]*?>)\s*<\/a>/gi, (_match, attrs, imgTag) => {
|
|
46
|
+
if (/aria-label/i.test(attrs))
|
|
47
|
+
return _match;
|
|
48
|
+
const alt = imgTag.match(/alt=["']([^"']*?)["']/i)?.[1] ?? 'Link';
|
|
49
|
+
return `<a${attrs} aria-label="${alt}">${imgTag}</a>`;
|
|
50
|
+
});
|
|
51
|
+
// Active nav links: links with class "active" or "current" inside <nav>
|
|
52
|
+
result = result.replace(/(<nav\b[^>]*>)([\s\S]*?)(<\/nav>)/gi, (_match, open, inner, close) => {
|
|
53
|
+
const updated = inner.replace(/<a([^>]*?class\s*=\s*"[^"]*?\b(?:active|current)\b[^"]*?"[^>]*?)>/gi, (_m, aAttrs) => {
|
|
54
|
+
if (/aria-current/i.test(aAttrs))
|
|
55
|
+
return _m;
|
|
56
|
+
return `<a${aAttrs} aria-current="page">`;
|
|
57
|
+
});
|
|
58
|
+
return `${open}${updated}${close}`;
|
|
59
|
+
});
|
|
60
|
+
return result;
|
|
61
|
+
}
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
// Semantic HTML improvements
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
/**
|
|
66
|
+
* Converts common div-with-class patterns to semantic HTML5 elements.
|
|
67
|
+
* Only converts top-level landmark elements (not deeply nested ones).
|
|
68
|
+
*/
|
|
69
|
+
export function improveSemanticHTML(html) {
|
|
70
|
+
const mappings = [
|
|
71
|
+
[/\bnav(?:bar|igation)?\b/i, 'nav'],
|
|
72
|
+
[/\bheader\b/i, 'header'],
|
|
73
|
+
[/\bfooter\b/i, 'footer'],
|
|
74
|
+
[/\b(?:main|content)\b/i, 'main'],
|
|
75
|
+
[/\b(?:sidebar|aside)\b/i, 'aside'],
|
|
76
|
+
];
|
|
77
|
+
let result = html;
|
|
78
|
+
for (const [classPattern, tag] of mappings) {
|
|
79
|
+
// Match <div class="..."> where the class contains the landmark keyword.
|
|
80
|
+
// We use a replacer so we can rebuild the tag pair correctly.
|
|
81
|
+
result = result.replace(new RegExp(`<div(\\s[^>]*?class\\s*=\\s*"[^"]*?${classPattern.source}[^"]*?"[^>]*?)>([\\s\\S]*?)<\\/div>`, 'gi'), (_match, attrs, inner) => {
|
|
82
|
+
// Strip the class attribute value of the landmark keyword to keep it clean,
|
|
83
|
+
// but preserve the rest of the attributes.
|
|
84
|
+
const cleanedAttrs = cleanClassAttr(attrs, classPattern);
|
|
85
|
+
return `<${tag}${cleanedAttrs}>${inner}</${tag}>`;
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
return result;
|
|
89
|
+
}
|
|
90
|
+
// ---------------------------------------------------------------------------
|
|
91
|
+
// Skip navigation component
|
|
92
|
+
// ---------------------------------------------------------------------------
|
|
93
|
+
/**
|
|
94
|
+
* Returns a React component string for a "Skip to main content" link.
|
|
95
|
+
* The link is visually hidden until focused, following best practices.
|
|
96
|
+
*/
|
|
97
|
+
export function addSkipNavigation() {
|
|
98
|
+
return `import React from 'react';
|
|
99
|
+
|
|
100
|
+
const skipNavStyle: React.CSSProperties = {
|
|
101
|
+
position: 'absolute',
|
|
102
|
+
left: '-9999px',
|
|
103
|
+
top: 'auto',
|
|
104
|
+
width: '1px',
|
|
105
|
+
height: '1px',
|
|
106
|
+
overflow: 'hidden',
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const skipNavFocusStyle: React.CSSProperties = {
|
|
110
|
+
position: 'fixed',
|
|
111
|
+
top: '10px',
|
|
112
|
+
left: '10px',
|
|
113
|
+
width: 'auto',
|
|
114
|
+
height: 'auto',
|
|
115
|
+
padding: '16px 24px',
|
|
116
|
+
backgroundColor: '#fff',
|
|
117
|
+
color: '#1a1a1a',
|
|
118
|
+
fontSize: '16px',
|
|
119
|
+
fontWeight: 600,
|
|
120
|
+
zIndex: 100000,
|
|
121
|
+
border: '2px solid #1a1a1a',
|
|
122
|
+
borderRadius: '4px',
|
|
123
|
+
textDecoration: 'none',
|
|
124
|
+
boxShadow: '0 2px 8px rgba(0,0,0,0.2)',
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
export const SkipNavigation: React.FC = () => {
|
|
128
|
+
const [focused, setFocused] = React.useState(false);
|
|
129
|
+
|
|
130
|
+
return (
|
|
131
|
+
<a
|
|
132
|
+
href="#main-content"
|
|
133
|
+
style={focused ? skipNavFocusStyle : skipNavStyle}
|
|
134
|
+
onFocus={() => setFocused(true)}
|
|
135
|
+
onBlur={() => setFocused(false)}
|
|
136
|
+
>
|
|
137
|
+
Skip to main content
|
|
138
|
+
</a>
|
|
139
|
+
);
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
export default SkipNavigation;
|
|
143
|
+
`;
|
|
144
|
+
}
|
|
145
|
+
// ---------------------------------------------------------------------------
|
|
146
|
+
// Focus trap hook
|
|
147
|
+
// ---------------------------------------------------------------------------
|
|
148
|
+
/**
|
|
149
|
+
* Returns the source for a useFocusTrap React hook that traps keyboard
|
|
150
|
+
* focus within a container element (e.g. a modal/dialog) while active.
|
|
151
|
+
*/
|
|
152
|
+
export function generateFocusTrapHook() {
|
|
153
|
+
return `import { useEffect, useRef, useCallback } from 'react';
|
|
154
|
+
|
|
155
|
+
const FOCUSABLE_SELECTOR = [
|
|
156
|
+
'a[href]',
|
|
157
|
+
'button:not([disabled])',
|
|
158
|
+
'input:not([disabled])',
|
|
159
|
+
'select:not([disabled])',
|
|
160
|
+
'textarea:not([disabled])',
|
|
161
|
+
'[tabindex]:not([tabindex="-1"])',
|
|
162
|
+
].join(', ');
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Traps focus within the referenced element while \`active\` is true.
|
|
166
|
+
* Pressing Tab / Shift+Tab cycles through focusable children without
|
|
167
|
+
* leaving the container. Focus is restored to the previously-focused
|
|
168
|
+
* element when the trap is deactivated.
|
|
169
|
+
*/
|
|
170
|
+
export function useFocusTrap<T extends HTMLElement = HTMLDivElement>(active: boolean) {
|
|
171
|
+
const containerRef = useRef<T>(null);
|
|
172
|
+
const previousFocusRef = useRef<HTMLElement | null>(null);
|
|
173
|
+
|
|
174
|
+
const getFocusableElements = useCallback((): HTMLElement[] => {
|
|
175
|
+
if (!containerRef.current) return [];
|
|
176
|
+
return Array.from(containerRef.current.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR));
|
|
177
|
+
}, []);
|
|
178
|
+
|
|
179
|
+
useEffect(() => {
|
|
180
|
+
if (!active) return;
|
|
181
|
+
|
|
182
|
+
// Remember where focus was before we trapped it
|
|
183
|
+
previousFocusRef.current = document.activeElement as HTMLElement;
|
|
184
|
+
|
|
185
|
+
// Move focus into the container
|
|
186
|
+
const focusable = getFocusableElements();
|
|
187
|
+
if (focusable.length > 0) {
|
|
188
|
+
focusable[0].focus();
|
|
189
|
+
} else {
|
|
190
|
+
containerRef.current?.focus();
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const handleKeyDown = (e: KeyboardEvent) => {
|
|
194
|
+
if (e.key !== 'Tab') return;
|
|
195
|
+
|
|
196
|
+
const elements = getFocusableElements();
|
|
197
|
+
if (elements.length === 0) {
|
|
198
|
+
e.preventDefault();
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const first = elements[0];
|
|
203
|
+
const last = elements[elements.length - 1];
|
|
204
|
+
|
|
205
|
+
if (e.shiftKey) {
|
|
206
|
+
if (document.activeElement === first) {
|
|
207
|
+
e.preventDefault();
|
|
208
|
+
last.focus();
|
|
209
|
+
}
|
|
210
|
+
} else {
|
|
211
|
+
if (document.activeElement === last) {
|
|
212
|
+
e.preventDefault();
|
|
213
|
+
first.focus();
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
document.addEventListener('keydown', handleKeyDown);
|
|
219
|
+
|
|
220
|
+
return () => {
|
|
221
|
+
document.removeEventListener('keydown', handleKeyDown);
|
|
222
|
+
// Restore focus when trap is deactivated
|
|
223
|
+
previousFocusRef.current?.focus();
|
|
224
|
+
};
|
|
225
|
+
}, [active, getFocusableElements]);
|
|
226
|
+
|
|
227
|
+
return containerRef;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export default useFocusTrap;
|
|
231
|
+
`;
|
|
232
|
+
}
|
|
233
|
+
// ---------------------------------------------------------------------------
|
|
234
|
+
// Accessibility report
|
|
235
|
+
// ---------------------------------------------------------------------------
|
|
236
|
+
/**
|
|
237
|
+
* Scans the given HTML for common accessibility issues and returns an
|
|
238
|
+
* array of A11yIssue objects describing each finding.
|
|
239
|
+
*/
|
|
240
|
+
export function generateA11yReport(html) {
|
|
241
|
+
const issues = [];
|
|
242
|
+
// 1. Images without alt text
|
|
243
|
+
const imgTags = html.match(/<img\b[^>]*?>/gi) ?? [];
|
|
244
|
+
for (const img of imgTags) {
|
|
245
|
+
if (!/\balt\s*=/i.test(img)) {
|
|
246
|
+
issues.push({
|
|
247
|
+
type: 'error',
|
|
248
|
+
rule: 'img-alt',
|
|
249
|
+
message: 'Image is missing an alt attribute. Provide descriptive alt text or alt="" for decorative images.',
|
|
250
|
+
element: truncate(img),
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
// Check for suspicious alt text
|
|
255
|
+
const alt = img.match(/alt=["']([^"']*?)["']/i)?.[1] ?? '';
|
|
256
|
+
if (/^(image|photo|picture|img|untitled)$/i.test(alt.trim())) {
|
|
257
|
+
issues.push({
|
|
258
|
+
type: 'warning',
|
|
259
|
+
rule: 'img-alt-quality',
|
|
260
|
+
message: `Image alt text "${alt}" is not descriptive. Use meaningful alternative text.`,
|
|
261
|
+
element: truncate(img),
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
// 2. Form inputs without associated labels
|
|
267
|
+
const inputs = html.match(/<input\b[^>]*?>/gi) ?? [];
|
|
268
|
+
for (const input of inputs) {
|
|
269
|
+
if (/type\s*=\s*["'](?:hidden|submit|button|reset|image)["']/i.test(input))
|
|
270
|
+
continue;
|
|
271
|
+
const id = input.match(/id\s*=\s*["']([^"']+)["']/i)?.[1];
|
|
272
|
+
const hasAriaLabel = /aria-label\s*=/i.test(input) || /aria-labelledby\s*=/i.test(input);
|
|
273
|
+
const hasTitle = /\btitle\s*=/i.test(input);
|
|
274
|
+
const hasAssociatedLabel = id ? new RegExp(`<label[^>]*?\\bfor\\s*=\\s*["']${escapeRegex(id)}["']`, 'i').test(html) : false;
|
|
275
|
+
if (!hasAriaLabel && !hasAssociatedLabel && !hasTitle) {
|
|
276
|
+
issues.push({
|
|
277
|
+
type: 'error',
|
|
278
|
+
rule: 'input-label',
|
|
279
|
+
message: 'Form input is missing an associated label. Add a <label for="...">, aria-label, or aria-labelledby.',
|
|
280
|
+
element: truncate(input),
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
// 3. Heading hierarchy
|
|
285
|
+
const headings = [...html.matchAll(/<h([1-6])\b[^>]*?>/gi)];
|
|
286
|
+
let lastLevel = 0;
|
|
287
|
+
for (const h of headings) {
|
|
288
|
+
const level = parseInt(h[1], 10);
|
|
289
|
+
if (lastLevel > 0 && level > lastLevel + 1) {
|
|
290
|
+
issues.push({
|
|
291
|
+
type: 'warning',
|
|
292
|
+
rule: 'heading-order',
|
|
293
|
+
message: `Heading level skipped: h${lastLevel} is followed by h${level}. Headings should not skip levels.`,
|
|
294
|
+
element: truncate(h[0]),
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
lastLevel = level;
|
|
298
|
+
}
|
|
299
|
+
if (headings.length > 0) {
|
|
300
|
+
const firstLevel = parseInt(headings[0][1], 10);
|
|
301
|
+
if (firstLevel !== 1) {
|
|
302
|
+
issues.push({
|
|
303
|
+
type: 'warning',
|
|
304
|
+
rule: 'heading-first',
|
|
305
|
+
message: `First heading is h${firstLevel}. The page should start with an h1.`,
|
|
306
|
+
element: truncate(headings[0][0]),
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
// 4. Missing lang attribute on <html>
|
|
311
|
+
const htmlTag = html.match(/<html\b[^>]*?>/i);
|
|
312
|
+
if (htmlTag && !/\blang\s*=/i.test(htmlTag[0])) {
|
|
313
|
+
issues.push({
|
|
314
|
+
type: 'error',
|
|
315
|
+
rule: 'html-lang',
|
|
316
|
+
message: 'The <html> element is missing a lang attribute. Add lang="en" (or the appropriate language).',
|
|
317
|
+
element: truncate(htmlTag[0]),
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
// 5. Missing document title
|
|
321
|
+
if (/<head\b/i.test(html) && !/<title\b[^>]*?>[^<]+<\/title>/i.test(html)) {
|
|
322
|
+
issues.push({
|
|
323
|
+
type: 'error',
|
|
324
|
+
rule: 'document-title',
|
|
325
|
+
message: 'The document is missing a <title> element.',
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
// 6. Links with no discernible text
|
|
329
|
+
const links = [...html.matchAll(/<a\b([^>]*?)>([\s\S]*?)<\/a>/gi)];
|
|
330
|
+
for (const link of links) {
|
|
331
|
+
const attrs = link[1];
|
|
332
|
+
const content = link[2].replace(/<[^>]*>/g, '').trim();
|
|
333
|
+
if (!content && !/aria-label/i.test(attrs) && !/aria-labelledby/i.test(attrs)) {
|
|
334
|
+
issues.push({
|
|
335
|
+
type: 'error',
|
|
336
|
+
rule: 'link-name',
|
|
337
|
+
message: 'Link has no discernible text. Add visible text, aria-label, or aria-labelledby.',
|
|
338
|
+
element: truncate(link[0]),
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
// 7. Buttons with no discernible text
|
|
343
|
+
const buttons = [...html.matchAll(/<button\b([^>]*?)>([\s\S]*?)<\/button>/gi)];
|
|
344
|
+
for (const btn of buttons) {
|
|
345
|
+
const attrs = btn[1];
|
|
346
|
+
const content = btn[2].replace(/<[^>]*>/g, '').trim();
|
|
347
|
+
if (!content && !/aria-label/i.test(attrs) && !/aria-labelledby/i.test(attrs)) {
|
|
348
|
+
issues.push({
|
|
349
|
+
type: 'warning',
|
|
350
|
+
rule: 'button-name',
|
|
351
|
+
message: 'Button has no discernible text. Add visible text, aria-label, or aria-labelledby.',
|
|
352
|
+
element: truncate(btn[0]),
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
// 8. Inline event handlers (onclick etc.) — suggest moving to JS
|
|
357
|
+
const inlineHandlers = html.match(/\bon\w+\s*=\s*["'][^"']*["']/gi) ?? [];
|
|
358
|
+
if (inlineHandlers.length > 0) {
|
|
359
|
+
issues.push({
|
|
360
|
+
type: 'info',
|
|
361
|
+
rule: 'no-inline-handlers',
|
|
362
|
+
message: `Found ${inlineHandlers.length} inline event handler(s). Consider moving these to JavaScript for better accessibility and maintainability.`,
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
// 9. Possible low-contrast patterns (color/background-color in inline styles)
|
|
366
|
+
const inlineStyles = [...html.matchAll(/style\s*=\s*["']([^"']+)["']/gi)];
|
|
367
|
+
for (const s of inlineStyles) {
|
|
368
|
+
const style = s[1];
|
|
369
|
+
if (/color\s*:/i.test(style)) {
|
|
370
|
+
const colors = [...style.matchAll(/(?:^|;)\s*(?:background-)?color\s*:\s*([^;]+)/gi)];
|
|
371
|
+
for (const c of colors) {
|
|
372
|
+
const val = c[1].trim().toLowerCase();
|
|
373
|
+
if (isLikelyLowContrast(val)) {
|
|
374
|
+
issues.push({
|
|
375
|
+
type: 'warning',
|
|
376
|
+
rule: 'color-contrast',
|
|
377
|
+
message: `Possible low contrast color "${val}" found in inline style. Verify sufficient contrast ratio (4.5:1 for normal text).`,
|
|
378
|
+
element: truncate(s[0]),
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
// 10. Auto-playing media
|
|
385
|
+
if (/<(?:video|audio)\b[^>]*?\bautoplay\b/i.test(html)) {
|
|
386
|
+
issues.push({
|
|
387
|
+
type: 'warning',
|
|
388
|
+
rule: 'no-autoplay',
|
|
389
|
+
message: 'Auto-playing media detected. Provide controls and avoid auto-playing audio to improve accessibility.',
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
// 11. Missing table headers
|
|
393
|
+
const tables = [...html.matchAll(/<table\b[^>]*?>[\s\S]*?<\/table>/gi)];
|
|
394
|
+
for (const table of tables) {
|
|
395
|
+
if (!/<th\b/i.test(table[0])) {
|
|
396
|
+
issues.push({
|
|
397
|
+
type: 'warning',
|
|
398
|
+
rule: 'table-header',
|
|
399
|
+
message: 'Table is missing <th> header elements. Use <th> for column/row headers.',
|
|
400
|
+
element: truncate(table[0], 80),
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
return issues;
|
|
405
|
+
}
|
|
406
|
+
// ---------------------------------------------------------------------------
|
|
407
|
+
// Internal helpers
|
|
408
|
+
// ---------------------------------------------------------------------------
|
|
409
|
+
/** Add role attributes to common patterns that are not covered by semantic tags. */
|
|
410
|
+
function addRoleAttributes(html) {
|
|
411
|
+
let result = html;
|
|
412
|
+
// Search input: add role="search" to parent form or wrapper
|
|
413
|
+
result = result.replace(/<form([^>]*?)>\s*([\s\S]*?<input[^>]*?type\s*=\s*["']search["'][^>]*?>[\s\S]*?)<\/form>/gi, (_match, attrs, inner) => {
|
|
414
|
+
if (/\brole\s*=/i.test(attrs))
|
|
415
|
+
return _match;
|
|
416
|
+
return `<form${attrs} role="search">${inner}</form>`;
|
|
417
|
+
});
|
|
418
|
+
// Alert/notification divs
|
|
419
|
+
result = result.replace(/<div([^>]*?class\s*=\s*"[^"]*?\b(?:alert|notification|toast)\b[^"]*?"[^>]*?)>/gi, (_match, attrs) => {
|
|
420
|
+
if (/\brole\s*=/i.test(attrs))
|
|
421
|
+
return _match;
|
|
422
|
+
return `<div${attrs} role="alert">`;
|
|
423
|
+
});
|
|
424
|
+
// Tab lists
|
|
425
|
+
result = result.replace(/<(?:ul|div)([^>]*?class\s*=\s*"[^"]*?\btab(?:s|list|-list)\b[^"]*?"[^>]*?)>/gi, (_match, attrs) => {
|
|
426
|
+
if (/\brole\s*=/i.test(attrs))
|
|
427
|
+
return _match;
|
|
428
|
+
return `<div${attrs} role="tablist">`;
|
|
429
|
+
});
|
|
430
|
+
return result;
|
|
431
|
+
}
|
|
432
|
+
/** Add aria-expanded to toggle/collapse buttons. */
|
|
433
|
+
function addAriaExpanded(html) {
|
|
434
|
+
return html.replace(/<button([^>]*?class\s*=\s*"[^"]*?\b(?:toggle|collapse|expand|hamburger|menu-toggle|dropdown-toggle)\b[^"]*?"[^>]*?)>/gi, (_match, attrs) => {
|
|
435
|
+
if (/aria-expanded/i.test(attrs))
|
|
436
|
+
return _match;
|
|
437
|
+
return `<button${attrs} aria-expanded="false">`;
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
/** Mark decorative images (empty alt, or role="presentation") with aria-hidden. */
|
|
441
|
+
function markDecorativeImages(html) {
|
|
442
|
+
return html.replace(/<img([^>]*?)\balt\s*=\s*["']\s*["']([^>]*?)>/gi, (_match, before, after) => {
|
|
443
|
+
if (/aria-hidden/i.test(before + after))
|
|
444
|
+
return _match;
|
|
445
|
+
return `<img${before}alt=""${after} aria-hidden="true">`;
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
/** Try to infer a label from an icon element's class name or SVG content. */
|
|
449
|
+
function inferIconLabel(iconHtml) {
|
|
450
|
+
// Check for common icon class names: fa-search, icon-close, bi-arrow-left, etc.
|
|
451
|
+
const classMatch = iconHtml.match(/class\s*=\s*["'][^"']*?\b(?:fa|icon|bi|ri|material)-([a-z-]+)\b/i);
|
|
452
|
+
if (classMatch) {
|
|
453
|
+
return classMatch[1].replace(/-/g, ' ');
|
|
454
|
+
}
|
|
455
|
+
// Check for a <title> inside SVG
|
|
456
|
+
const svgTitle = iconHtml.match(/<title>([^<]+)<\/title>/i);
|
|
457
|
+
if (svgTitle) {
|
|
458
|
+
return svgTitle[1].trim();
|
|
459
|
+
}
|
|
460
|
+
// Check for aria-label already on the SVG
|
|
461
|
+
const svgLabel = iconHtml.match(/aria-label\s*=\s*["']([^"']+)["']/i);
|
|
462
|
+
if (svgLabel) {
|
|
463
|
+
return svgLabel[1];
|
|
464
|
+
}
|
|
465
|
+
return 'icon button';
|
|
466
|
+
}
|
|
467
|
+
/** Remove the matched landmark class name from a class attribute, cleaning up empty class attrs. */
|
|
468
|
+
function cleanClassAttr(attrs, pattern) {
|
|
469
|
+
return attrs.replace(/class\s*=\s*"([^"]*?)"/i, (_m, classes) => {
|
|
470
|
+
const cleaned = classes
|
|
471
|
+
.split(/\s+/)
|
|
472
|
+
.filter((c) => !pattern.test(c))
|
|
473
|
+
.join(' ')
|
|
474
|
+
.trim();
|
|
475
|
+
return cleaned ? `class="${cleaned}"` : '';
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
/** Truncate a string to a maximum length for readable reports. */
|
|
479
|
+
function truncate(str, max = 120) {
|
|
480
|
+
if (str.length <= max)
|
|
481
|
+
return str;
|
|
482
|
+
return str.slice(0, max) + '...';
|
|
483
|
+
}
|
|
484
|
+
/** Escape special regex characters. */
|
|
485
|
+
function escapeRegex(str) {
|
|
486
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
487
|
+
}
|
|
488
|
+
/** Heuristic check for likely low-contrast colors (very light greys, etc.). */
|
|
489
|
+
function isLikelyLowContrast(color) {
|
|
490
|
+
// Check for light greys that are commonly low contrast on white
|
|
491
|
+
const lightGrey = /^#(?:c[0-9a-f]{5}|d[0-9a-f]{5}|e[0-9a-f]{5}|f[0-9a-f]{5})$/i;
|
|
492
|
+
if (lightGrey.test(color))
|
|
493
|
+
return true;
|
|
494
|
+
// Check named colors that tend to be low contrast
|
|
495
|
+
const lowContrastNames = ['lightgray', 'lightgrey', 'silver', 'gainsboro', 'whitesmoke'];
|
|
496
|
+
if (lowContrastNames.includes(color))
|
|
497
|
+
return true;
|
|
498
|
+
// rgb values with all channels above 180
|
|
499
|
+
const rgbMatch = color.match(/rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/i);
|
|
500
|
+
if (rgbMatch) {
|
|
501
|
+
const [, r, g, b] = rgbMatch.map(Number);
|
|
502
|
+
if (r > 180 && g > 180 && b > 180)
|
|
503
|
+
return true;
|
|
504
|
+
}
|
|
505
|
+
return false;
|
|
506
|
+
}
|
|
507
|
+
//# sourceMappingURL=accessibility-generator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"accessibility-generator.js","sourceRoot":"","sources":["../../src/generators/accessibility-generator.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { AssetFile } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Discover all asset files in the input directory.
|
|
4
|
+
*/
|
|
5
|
+
export declare function discoverAssets(inputDir: string): Promise<AssetFile[]>;
|
|
6
|
+
/**
|
|
7
|
+
* Copy all discovered assets to the output project directory.
|
|
8
|
+
*/
|
|
9
|
+
export declare function copyAssets(assets: AssetFile[], outputDir: string): Promise<string[]>;
|
|
10
|
+
/**
|
|
11
|
+
* Check if a path points to a file (vs directory).
|
|
12
|
+
*/
|
|
13
|
+
export declare function isFile(path: string): Promise<boolean>;
|
|
14
|
+
//# sourceMappingURL=asset-handler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"asset-handler.d.ts","sourceRoot":"","sources":["../../src/generators/asset-handler.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAQ7C;;GAEG;AACH,wBAAsB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC,CAI3E;AAyCD;;GAEG;AACH,wBAAsB,UAAU,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAW1F;AAED;;GAEG;AACH,wBAAsB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAO3D"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { readdir, copyFile, mkdir, stat } from 'fs/promises';
|
|
2
|
+
import { join, extname, relative, dirname } from 'path';
|
|
3
|
+
const IMAGE_EXTENSIONS = new Set(['.png', '.jpg', '.jpeg', '.gif', '.svg', '.webp', '.ico', '.bmp', '.avif']);
|
|
4
|
+
const FONT_EXTENSIONS = new Set(['.woff', '.woff2', '.ttf', '.otf', '.eot']);
|
|
5
|
+
const SKIP_EXTENSIONS = new Set(['.html', '.htm', '.css', '.js', '.ts', '.jsx', '.tsx', '.map']);
|
|
6
|
+
/** Files that should go to public/ root for direct serving */
|
|
7
|
+
const PUBLIC_ROOT_FILES = new Set(['favicon.ico', 'favicon.png', 'favicon.svg', 'apple-touch-icon.png', 'manifest.json', 'site.webmanifest', 'robots.txt', 'sitemap.xml', 'sw.js', 'service-worker.js']);
|
|
8
|
+
/**
|
|
9
|
+
* Discover all asset files in the input directory.
|
|
10
|
+
*/
|
|
11
|
+
export async function discoverAssets(inputDir) {
|
|
12
|
+
const assets = [];
|
|
13
|
+
await walkDir(inputDir, inputDir, assets);
|
|
14
|
+
return assets;
|
|
15
|
+
}
|
|
16
|
+
async function walkDir(dir, rootDir, assets) {
|
|
17
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
18
|
+
for (const entry of entries) {
|
|
19
|
+
const fullPath = join(dir, entry.name);
|
|
20
|
+
if (entry.isDirectory()) {
|
|
21
|
+
// Skip common non-asset directories
|
|
22
|
+
if (['node_modules', '.git', 'dist', 'build'].includes(entry.name))
|
|
23
|
+
continue;
|
|
24
|
+
await walkDir(fullPath, rootDir, assets);
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
const ext = extname(entry.name).toLowerCase();
|
|
28
|
+
if (SKIP_EXTENSIONS.has(ext))
|
|
29
|
+
continue;
|
|
30
|
+
const relativePath = relative(rootDir, fullPath);
|
|
31
|
+
let type = 'other';
|
|
32
|
+
let destPath;
|
|
33
|
+
if (PUBLIC_ROOT_FILES.has(entry.name.toLowerCase())) {
|
|
34
|
+
// Favicon, manifest, robots.txt, service worker → public root
|
|
35
|
+
destPath = join('public', entry.name);
|
|
36
|
+
}
|
|
37
|
+
else if (IMAGE_EXTENSIONS.has(ext)) {
|
|
38
|
+
type = 'image';
|
|
39
|
+
destPath = join('src', 'assets', relativePath);
|
|
40
|
+
}
|
|
41
|
+
else if (FONT_EXTENSIONS.has(ext)) {
|
|
42
|
+
type = 'font';
|
|
43
|
+
destPath = join('public', 'fonts', relativePath);
|
|
44
|
+
}
|
|
45
|
+
else if (entry.name === 'manifest.json' || entry.name === 'site.webmanifest') {
|
|
46
|
+
destPath = join('public', entry.name);
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
destPath = join('public', relativePath);
|
|
50
|
+
}
|
|
51
|
+
assets.push({ sourcePath: fullPath, destPath, type });
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Copy all discovered assets to the output project directory.
|
|
56
|
+
*/
|
|
57
|
+
export async function copyAssets(assets, outputDir) {
|
|
58
|
+
const copied = [];
|
|
59
|
+
for (const asset of assets) {
|
|
60
|
+
const destPath = join(outputDir, asset.destPath);
|
|
61
|
+
await mkdir(dirname(destPath), { recursive: true });
|
|
62
|
+
await copyFile(asset.sourcePath, destPath);
|
|
63
|
+
copied.push(asset.destPath);
|
|
64
|
+
}
|
|
65
|
+
return copied;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Check if a path points to a file (vs directory).
|
|
69
|
+
*/
|
|
70
|
+
export async function isFile(path) {
|
|
71
|
+
try {
|
|
72
|
+
const s = await stat(path);
|
|
73
|
+
return s.isFile();
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=asset-handler.js.map
|