@obra-studio/figma-console-mcp 1.32.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/LICENSE +21 -0
- package/README.md +879 -0
- package/dist/apps/design-system-dashboard/scoring/accessibility.d.ts +14 -0
- package/dist/apps/design-system-dashboard/scoring/accessibility.d.ts.map +1 -0
- package/dist/apps/design-system-dashboard/scoring/accessibility.js +278 -0
- package/dist/apps/design-system-dashboard/scoring/accessibility.js.map +1 -0
- package/dist/apps/design-system-dashboard/scoring/component-metadata.d.ts +29 -0
- package/dist/apps/design-system-dashboard/scoring/component-metadata.d.ts.map +1 -0
- package/dist/apps/design-system-dashboard/scoring/component-metadata.js +358 -0
- package/dist/apps/design-system-dashboard/scoring/component-metadata.js.map +1 -0
- package/dist/apps/design-system-dashboard/scoring/consistency.d.ts +14 -0
- package/dist/apps/design-system-dashboard/scoring/consistency.d.ts.map +1 -0
- package/dist/apps/design-system-dashboard/scoring/consistency.js +342 -0
- package/dist/apps/design-system-dashboard/scoring/consistency.js.map +1 -0
- package/dist/apps/design-system-dashboard/scoring/coverage.d.ts +14 -0
- package/dist/apps/design-system-dashboard/scoring/coverage.d.ts.map +1 -0
- package/dist/apps/design-system-dashboard/scoring/coverage.js +231 -0
- package/dist/apps/design-system-dashboard/scoring/coverage.js.map +1 -0
- package/dist/apps/design-system-dashboard/scoring/engine.d.ts +27 -0
- package/dist/apps/design-system-dashboard/scoring/engine.d.ts.map +1 -0
- package/dist/apps/design-system-dashboard/scoring/engine.js +93 -0
- package/dist/apps/design-system-dashboard/scoring/engine.js.map +1 -0
- package/dist/apps/design-system-dashboard/scoring/naming-semantics.d.ts +14 -0
- package/dist/apps/design-system-dashboard/scoring/naming-semantics.d.ts.map +1 -0
- package/dist/apps/design-system-dashboard/scoring/naming-semantics.js +309 -0
- package/dist/apps/design-system-dashboard/scoring/naming-semantics.js.map +1 -0
- package/dist/apps/design-system-dashboard/scoring/token-architecture.d.ts +14 -0
- package/dist/apps/design-system-dashboard/scoring/token-architecture.d.ts.map +1 -0
- package/dist/apps/design-system-dashboard/scoring/token-architecture.js +350 -0
- package/dist/apps/design-system-dashboard/scoring/token-architecture.js.map +1 -0
- package/dist/apps/design-system-dashboard/scoring/types.d.ts +89 -0
- package/dist/apps/design-system-dashboard/scoring/types.d.ts.map +1 -0
- package/dist/apps/design-system-dashboard/scoring/types.js +41 -0
- package/dist/apps/design-system-dashboard/scoring/types.js.map +1 -0
- package/dist/apps/design-system-dashboard/server.d.ts +24 -0
- package/dist/apps/design-system-dashboard/server.d.ts.map +1 -0
- package/dist/apps/design-system-dashboard/server.js +160 -0
- package/dist/apps/design-system-dashboard/server.js.map +1 -0
- package/dist/apps/token-browser/server.d.ts +26 -0
- package/dist/apps/token-browser/server.d.ts.map +1 -0
- package/dist/apps/token-browser/server.js +137 -0
- package/dist/apps/token-browser/server.js.map +1 -0
- package/dist/browser/base.d.ts +58 -0
- package/dist/browser/base.d.ts.map +1 -0
- package/dist/browser/base.js +6 -0
- package/dist/browser/base.js.map +1 -0
- package/dist/browser/local.d.ts +87 -0
- package/dist/browser/local.d.ts.map +1 -0
- package/dist/browser/local.js +318 -0
- package/dist/browser/local.js.map +1 -0
- package/dist/core/accessibility-tools.d.ts +21 -0
- package/dist/core/accessibility-tools.d.ts.map +1 -0
- package/dist/core/accessibility-tools.js +307 -0
- package/dist/core/accessibility-tools.js.map +1 -0
- package/dist/core/annotation-tools.d.ts +14 -0
- package/dist/core/annotation-tools.d.ts.map +1 -0
- package/dist/core/annotation-tools.js +231 -0
- package/dist/core/annotation-tools.js.map +1 -0
- package/dist/core/autodocs-tools.d.ts +7 -0
- package/dist/core/autodocs-tools.d.ts.map +1 -0
- package/dist/core/autodocs-tools.js +195 -0
- package/dist/core/autodocs-tools.js.map +1 -0
- package/dist/core/comment-tools.d.ts +11 -0
- package/dist/core/comment-tools.d.ts.map +1 -0
- package/dist/core/comment-tools.js +293 -0
- package/dist/core/comment-tools.js.map +1 -0
- package/dist/core/config.d.ts +17 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +154 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/console-monitor.d.ts +82 -0
- package/dist/core/console-monitor.d.ts.map +1 -0
- package/dist/core/console-monitor.js +428 -0
- package/dist/core/console-monitor.js.map +1 -0
- package/dist/core/deep-component-tools.d.ts +14 -0
- package/dist/core/deep-component-tools.d.ts.map +1 -0
- package/dist/core/deep-component-tools.js +129 -0
- package/dist/core/deep-component-tools.js.map +1 -0
- package/dist/core/design-code-tools.d.ts +116 -0
- package/dist/core/design-code-tools.d.ts.map +1 -0
- package/dist/core/design-code-tools.js +2751 -0
- package/dist/core/design-code-tools.js.map +1 -0
- package/dist/core/design-system-manifest.d.ts +272 -0
- package/dist/core/design-system-manifest.d.ts.map +1 -0
- package/dist/core/design-system-manifest.js +261 -0
- package/dist/core/design-system-manifest.js.map +1 -0
- package/dist/core/design-system-tools.d.ts +67 -0
- package/dist/core/design-system-tools.d.ts.map +1 -0
- package/dist/core/design-system-tools.js +874 -0
- package/dist/core/design-system-tools.js.map +1 -0
- package/dist/core/diagnose-tool.d.ts +33 -0
- package/dist/core/diagnose-tool.d.ts.map +1 -0
- package/dist/core/diagnose-tool.js +97 -0
- package/dist/core/diagnose-tool.js.map +1 -0
- package/dist/core/diff/changelog-formatter.d.ts +35 -0
- package/dist/core/diff/changelog-formatter.d.ts.map +1 -0
- package/dist/core/diff/changelog-formatter.js +276 -0
- package/dist/core/diff/changelog-formatter.js.map +1 -0
- package/dist/core/diff/diff-engine.d.ts +127 -0
- package/dist/core/diff/diff-engine.d.ts.map +1 -0
- package/dist/core/diff/diff-engine.js +335 -0
- package/dist/core/diff/diff-engine.js.map +1 -0
- package/dist/core/diff/property-compare.d.ts +19 -0
- package/dist/core/diff/property-compare.d.ts.map +1 -0
- package/dist/core/diff/property-compare.js +37 -0
- package/dist/core/diff/property-compare.js.map +1 -0
- package/dist/core/diff/version-cache.d.ts +40 -0
- package/dist/core/diff/version-cache.d.ts.map +1 -0
- package/dist/core/diff/version-cache.js +75 -0
- package/dist/core/diff/version-cache.js.map +1 -0
- package/dist/core/enrichment/enrichment-service.d.ts +52 -0
- package/dist/core/enrichment/enrichment-service.d.ts.map +1 -0
- package/dist/core/enrichment/enrichment-service.js +369 -0
- package/dist/core/enrichment/enrichment-service.js.map +1 -0
- package/dist/core/enrichment/index.d.ts +8 -0
- package/dist/core/enrichment/index.d.ts.map +1 -0
- package/dist/core/enrichment/index.js +8 -0
- package/dist/core/enrichment/index.js.map +1 -0
- package/dist/core/enrichment/relationship-mapper.d.ts +106 -0
- package/dist/core/enrichment/relationship-mapper.d.ts.map +1 -0
- package/dist/core/enrichment/relationship-mapper.js +352 -0
- package/dist/core/enrichment/relationship-mapper.js.map +1 -0
- package/dist/core/enrichment/style-resolver.d.ts +80 -0
- package/dist/core/enrichment/style-resolver.d.ts.map +1 -0
- package/dist/core/enrichment/style-resolver.js +327 -0
- package/dist/core/enrichment/style-resolver.js.map +1 -0
- package/dist/core/figjam-tools.d.ts +8 -0
- package/dist/core/figjam-tools.d.ts.map +1 -0
- package/dist/core/figjam-tools.js +548 -0
- package/dist/core/figjam-tools.js.map +1 -0
- package/dist/core/figma-api.d.ts +245 -0
- package/dist/core/figma-api.d.ts.map +1 -0
- package/dist/core/figma-api.js +446 -0
- package/dist/core/figma-api.js.map +1 -0
- package/dist/core/figma-connector.d.ts +180 -0
- package/dist/core/figma-connector.d.ts.map +1 -0
- package/dist/core/figma-connector.js +8 -0
- package/dist/core/figma-connector.js.map +1 -0
- package/dist/core/figma-desktop-connector.d.ts +312 -0
- package/dist/core/figma-desktop-connector.d.ts.map +1 -0
- package/dist/core/figma-desktop-connector.js +1298 -0
- package/dist/core/figma-desktop-connector.js.map +1 -0
- package/dist/core/figma-reconstruction-spec.d.ts +166 -0
- package/dist/core/figma-reconstruction-spec.d.ts.map +1 -0
- package/dist/core/figma-reconstruction-spec.js +403 -0
- package/dist/core/figma-reconstruction-spec.js.map +1 -0
- package/dist/core/figma-style-extractor.d.ts +76 -0
- package/dist/core/figma-style-extractor.d.ts.map +1 -0
- package/dist/core/figma-style-extractor.js +312 -0
- package/dist/core/figma-style-extractor.js.map +1 -0
- package/dist/core/figma-tools.d.ts +22 -0
- package/dist/core/figma-tools.d.ts.map +1 -0
- package/dist/core/figma-tools.js +3187 -0
- package/dist/core/figma-tools.js.map +1 -0
- package/dist/core/identity.d.ts +41 -0
- package/dist/core/identity.d.ts.map +1 -0
- package/dist/core/identity.js +97 -0
- package/dist/core/identity.js.map +1 -0
- package/dist/core/library-tools.d.ts +17 -0
- package/dist/core/library-tools.d.ts.map +1 -0
- package/dist/core/library-tools.js +581 -0
- package/dist/core/library-tools.js.map +1 -0
- package/dist/core/logger.d.ts +22 -0
- package/dist/core/logger.d.ts.map +1 -0
- package/dist/core/logger.js +54 -0
- package/dist/core/logger.js.map +1 -0
- package/dist/core/port-discovery.d.ts +171 -0
- package/dist/core/port-discovery.d.ts.map +1 -0
- package/dist/core/port-discovery.js +563 -0
- package/dist/core/port-discovery.js.map +1 -0
- package/dist/core/resolve-package-root.d.ts +2 -0
- package/dist/core/resolve-package-root.d.ts.map +1 -0
- package/dist/core/resolve-package-root.js +12 -0
- package/dist/core/resolve-package-root.js.map +1 -0
- package/dist/core/slides-tools.d.ts +8 -0
- package/dist/core/slides-tools.d.ts.map +1 -0
- package/dist/core/slides-tools.js +715 -0
- package/dist/core/slides-tools.js.map +1 -0
- package/dist/core/snippet-injector.d.ts +24 -0
- package/dist/core/snippet-injector.d.ts.map +1 -0
- package/dist/core/snippet-injector.js +97 -0
- package/dist/core/snippet-injector.js.map +1 -0
- package/dist/core/tokens/alias-resolver.d.ts +55 -0
- package/dist/core/tokens/alias-resolver.d.ts.map +1 -0
- package/dist/core/tokens/alias-resolver.js +136 -0
- package/dist/core/tokens/alias-resolver.js.map +1 -0
- package/dist/core/tokens/config.d.ts +87 -0
- package/dist/core/tokens/config.d.ts.map +1 -0
- package/dist/core/tokens/config.js +285 -0
- package/dist/core/tokens/config.js.map +1 -0
- package/dist/core/tokens/figma-converter.d.ts +81 -0
- package/dist/core/tokens/figma-converter.d.ts.map +1 -0
- package/dist/core/tokens/figma-converter.js +196 -0
- package/dist/core/tokens/figma-converter.js.map +1 -0
- package/dist/core/tokens/formatters/css-vars.d.ts +24 -0
- package/dist/core/tokens/formatters/css-vars.d.ts.map +1 -0
- package/dist/core/tokens/formatters/css-vars.js +330 -0
- package/dist/core/tokens/formatters/css-vars.js.map +1 -0
- package/dist/core/tokens/formatters/dtcg.d.ts +28 -0
- package/dist/core/tokens/formatters/dtcg.d.ts.map +1 -0
- package/dist/core/tokens/formatters/dtcg.js +301 -0
- package/dist/core/tokens/formatters/dtcg.js.map +1 -0
- package/dist/core/tokens/formatters/index.d.ts +30 -0
- package/dist/core/tokens/formatters/index.d.ts.map +1 -0
- package/dist/core/tokens/formatters/index.js +46 -0
- package/dist/core/tokens/formatters/index.js.map +1 -0
- package/dist/core/tokens/formatters/json.d.ts +37 -0
- package/dist/core/tokens/formatters/json.d.ts.map +1 -0
- package/dist/core/tokens/formatters/json.js +188 -0
- package/dist/core/tokens/formatters/json.js.map +1 -0
- package/dist/core/tokens/formatters/less.d.ts +4 -0
- package/dist/core/tokens/formatters/less.d.ts.map +1 -0
- package/dist/core/tokens/formatters/less.js +5 -0
- package/dist/core/tokens/formatters/less.js.map +1 -0
- package/dist/core/tokens/formatters/scss.d.ts +26 -0
- package/dist/core/tokens/formatters/scss.d.ts.map +1 -0
- package/dist/core/tokens/formatters/scss.js +253 -0
- package/dist/core/tokens/formatters/scss.js.map +1 -0
- package/dist/core/tokens/formatters/stubs.d.ts +9 -0
- package/dist/core/tokens/formatters/stubs.d.ts.map +1 -0
- package/dist/core/tokens/formatters/stubs.js +14 -0
- package/dist/core/tokens/formatters/stubs.js.map +1 -0
- package/dist/core/tokens/formatters/style-dictionary-v3.d.ts +45 -0
- package/dist/core/tokens/formatters/style-dictionary-v3.d.ts.map +1 -0
- package/dist/core/tokens/formatters/style-dictionary-v3.js +208 -0
- package/dist/core/tokens/formatters/style-dictionary-v3.js.map +1 -0
- package/dist/core/tokens/formatters/tailwind-v3.d.ts +37 -0
- package/dist/core/tokens/formatters/tailwind-v3.d.ts.map +1 -0
- package/dist/core/tokens/formatters/tailwind-v3.js +238 -0
- package/dist/core/tokens/formatters/tailwind-v3.js.map +1 -0
- package/dist/core/tokens/formatters/tailwind-v4.d.ts +41 -0
- package/dist/core/tokens/formatters/tailwind-v4.d.ts.map +1 -0
- package/dist/core/tokens/formatters/tailwind-v4.js +331 -0
- package/dist/core/tokens/formatters/tailwind-v4.js.map +1 -0
- package/dist/core/tokens/formatters/tokens-studio.d.ts +44 -0
- package/dist/core/tokens/formatters/tokens-studio.d.ts.map +1 -0
- package/dist/core/tokens/formatters/tokens-studio.js +251 -0
- package/dist/core/tokens/formatters/tokens-studio.js.map +1 -0
- package/dist/core/tokens/formatters/ts-module.d.ts +35 -0
- package/dist/core/tokens/formatters/ts-module.d.ts.map +1 -0
- package/dist/core/tokens/formatters/ts-module.js +199 -0
- package/dist/core/tokens/formatters/ts-module.js.map +1 -0
- package/dist/core/tokens/index.d.ts +17 -0
- package/dist/core/tokens/index.d.ts.map +1 -0
- package/dist/core/tokens/index.js +16 -0
- package/dist/core/tokens/index.js.map +1 -0
- package/dist/core/tokens/parsers/css-vars.d.ts +3 -0
- package/dist/core/tokens/parsers/css-vars.d.ts.map +1 -0
- package/dist/core/tokens/parsers/css-vars.js +5 -0
- package/dist/core/tokens/parsers/css-vars.js.map +1 -0
- package/dist/core/tokens/parsers/dtcg.d.ts +21 -0
- package/dist/core/tokens/parsers/dtcg.d.ts.map +1 -0
- package/dist/core/tokens/parsers/dtcg.js +254 -0
- package/dist/core/tokens/parsers/dtcg.js.map +1 -0
- package/dist/core/tokens/parsers/index.d.ts +37 -0
- package/dist/core/tokens/parsers/index.d.ts.map +1 -0
- package/dist/core/tokens/parsers/index.js +139 -0
- package/dist/core/tokens/parsers/index.js.map +1 -0
- package/dist/core/tokens/parsers/json.d.ts +4 -0
- package/dist/core/tokens/parsers/json.d.ts.map +1 -0
- package/dist/core/tokens/parsers/json.js +8 -0
- package/dist/core/tokens/parsers/json.js.map +1 -0
- package/dist/core/tokens/parsers/scss.d.ts +3 -0
- package/dist/core/tokens/parsers/scss.d.ts.map +1 -0
- package/dist/core/tokens/parsers/scss.js +5 -0
- package/dist/core/tokens/parsers/scss.js.map +1 -0
- package/dist/core/tokens/parsers/stubs.d.ts +15 -0
- package/dist/core/tokens/parsers/stubs.d.ts.map +1 -0
- package/dist/core/tokens/parsers/stubs.js +21 -0
- package/dist/core/tokens/parsers/stubs.js.map +1 -0
- package/dist/core/tokens/parsers/style-dictionary-v3.d.ts +3 -0
- package/dist/core/tokens/parsers/style-dictionary-v3.d.ts.map +1 -0
- package/dist/core/tokens/parsers/style-dictionary-v3.js +5 -0
- package/dist/core/tokens/parsers/style-dictionary-v3.js.map +1 -0
- package/dist/core/tokens/parsers/tailwind-v3.d.ts +3 -0
- package/dist/core/tokens/parsers/tailwind-v3.d.ts.map +1 -0
- package/dist/core/tokens/parsers/tailwind-v3.js +5 -0
- package/dist/core/tokens/parsers/tailwind-v3.js.map +1 -0
- package/dist/core/tokens/parsers/tailwind-v4.d.ts +3 -0
- package/dist/core/tokens/parsers/tailwind-v4.d.ts.map +1 -0
- package/dist/core/tokens/parsers/tailwind-v4.js +5 -0
- package/dist/core/tokens/parsers/tailwind-v4.js.map +1 -0
- package/dist/core/tokens/parsers/tokens-studio.d.ts +3 -0
- package/dist/core/tokens/parsers/tokens-studio.d.ts.map +1 -0
- package/dist/core/tokens/parsers/tokens-studio.js +5 -0
- package/dist/core/tokens/parsers/tokens-studio.js.map +1 -0
- package/dist/core/tokens/schemas.d.ts +31 -0
- package/dist/core/tokens/schemas.d.ts.map +1 -0
- package/dist/core/tokens/schemas.js +149 -0
- package/dist/core/tokens/schemas.js.map +1 -0
- package/dist/core/tokens/transforms/color.d.ts +9 -0
- package/dist/core/tokens/transforms/color.d.ts.map +1 -0
- package/dist/core/tokens/transforms/color.js +13 -0
- package/dist/core/tokens/transforms/color.js.map +1 -0
- package/dist/core/tokens/transforms/index.d.ts +36 -0
- package/dist/core/tokens/transforms/index.d.ts.map +1 -0
- package/dist/core/tokens/transforms/index.js +30 -0
- package/dist/core/tokens/transforms/index.js.map +1 -0
- package/dist/core/tokens/transforms/size.d.ts +7 -0
- package/dist/core/tokens/transforms/size.d.ts.map +1 -0
- package/dist/core/tokens/transforms/size.js +8 -0
- package/dist/core/tokens/transforms/size.js.map +1 -0
- package/dist/core/tokens/types.d.ts +228 -0
- package/dist/core/tokens/types.d.ts.map +1 -0
- package/dist/core/tokens/types.js +19 -0
- package/dist/core/tokens/types.js.map +1 -0
- package/dist/core/tokens-tools.d.ts +42 -0
- package/dist/core/tokens-tools.d.ts.map +1 -0
- package/dist/core/tokens-tools.js +860 -0
- package/dist/core/tokens-tools.js.map +1 -0
- package/dist/core/types/design-code.d.ts +271 -0
- package/dist/core/types/design-code.d.ts.map +1 -0
- package/dist/core/types/design-code.js +5 -0
- package/dist/core/types/design-code.js.map +1 -0
- package/dist/core/types/enriched.d.ts +213 -0
- package/dist/core/types/enriched.d.ts.map +1 -0
- package/dist/core/types/enriched.js +6 -0
- package/dist/core/types/enriched.js.map +1 -0
- package/dist/core/types/index.d.ts +104 -0
- package/dist/core/types/index.d.ts.map +1 -0
- package/dist/core/types/index.js +5 -0
- package/dist/core/types/index.js.map +1 -0
- package/dist/core/variable-resolver.d.ts +45 -0
- package/dist/core/variable-resolver.d.ts.map +1 -0
- package/dist/core/variable-resolver.js +86 -0
- package/dist/core/variable-resolver.js.map +1 -0
- package/dist/core/version-tools.d.ts +59 -0
- package/dist/core/version-tools.d.ts.map +1 -0
- package/dist/core/version-tools.js +1159 -0
- package/dist/core/version-tools.js.map +1 -0
- package/dist/core/websocket-connector.d.ts +187 -0
- package/dist/core/websocket-connector.d.ts.map +1 -0
- package/dist/core/websocket-connector.js +378 -0
- package/dist/core/websocket-connector.js.map +1 -0
- package/dist/core/websocket-server.js +866 -0
- package/dist/core/websocket-server.js.map +1 -0
- package/dist/core/write-tools.d.ts +7 -0
- package/dist/core/write-tools.d.ts.map +1 -0
- package/dist/core/write-tools.js +2172 -0
- package/dist/core/write-tools.js.map +1 -0
- package/dist/local.d.ts +95 -0
- package/dist/local.d.ts.map +1 -0
- package/dist/local.js +3036 -0
- package/dist/local.js.map +1 -0
- package/dist/vendor/obra-autodocs/autodocs-body.generated.d.ts +2 -0
- package/dist/vendor/obra-autodocs/autodocs-body.generated.d.ts.map +1 -0
- package/dist/vendor/obra-autodocs/autodocs-body.generated.js +6 -0
- package/dist/vendor/obra-autodocs/autodocs-body.generated.js.map +1 -0
- package/figma-desktop-bridge/README.md +365 -0
- package/figma-desktop-bridge/code.js +6504 -0
- package/figma-desktop-bridge/icon.png +0 -0
- package/figma-desktop-bridge/manifest.json +67 -0
- package/figma-desktop-bridge/ui.html +2441 -0
- package/package.json +98 -0
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local Browser Manager (Legacy)
|
|
3
|
+
* Note: This module is maintained for backwards compatibility but is no longer
|
|
4
|
+
* the primary connection method. Use the WebSocket Desktop Bridge plugin instead.
|
|
5
|
+
*/
|
|
6
|
+
import puppeteer from 'puppeteer-core';
|
|
7
|
+
import { createChildLogger } from '../core/logger.js';
|
|
8
|
+
import { extractFileKey } from '../core/figma-api.js';
|
|
9
|
+
const logger = createChildLogger({ component: 'local-browser' });
|
|
10
|
+
/**
|
|
11
|
+
* Local Browser Manager
|
|
12
|
+
* Connects to existing Figma Desktop instance via remote debugging port
|
|
13
|
+
*/
|
|
14
|
+
export class LocalBrowserManager {
|
|
15
|
+
constructor(config) {
|
|
16
|
+
this.browser = null;
|
|
17
|
+
this.page = null;
|
|
18
|
+
this.config = config;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Connect to Figma Desktop via remote debugging port
|
|
22
|
+
*/
|
|
23
|
+
async launch() {
|
|
24
|
+
if (this.browser) {
|
|
25
|
+
logger.info('Browser already connected, reusing instance');
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const { debugHost, debugPort } = this.config;
|
|
29
|
+
const browserURL = `http://${debugHost}:${debugPort}`;
|
|
30
|
+
logger.info({ browserURL }, 'Connecting to Figma Desktop');
|
|
31
|
+
try {
|
|
32
|
+
// Connect to existing browser (Figma Desktop)
|
|
33
|
+
this.browser = await puppeteer.connect({
|
|
34
|
+
browserURL,
|
|
35
|
+
defaultViewport: null, // Use Figma's viewport
|
|
36
|
+
});
|
|
37
|
+
logger.info('Connected to Figma Desktop successfully');
|
|
38
|
+
// Handle disconnection
|
|
39
|
+
this.browser.on('disconnected', () => {
|
|
40
|
+
logger.warn('Disconnected from Figma Desktop');
|
|
41
|
+
this.browser = null;
|
|
42
|
+
this.page = null;
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
logger.error({ error, browserURL }, 'Failed to connect to Figma Desktop');
|
|
47
|
+
throw new Error(`Failed to connect to Figma Desktop.\n\n` +
|
|
48
|
+
`Please open the Desktop Bridge plugin in Figma:\n` +
|
|
49
|
+
` Plugins → Development → Figma Desktop Bridge\n\n` +
|
|
50
|
+
`Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Find the best page for plugin debugging
|
|
55
|
+
* Actively searches for pages with workers across ALL tabs
|
|
56
|
+
*/
|
|
57
|
+
async findBestPage() {
|
|
58
|
+
if (!this.browser) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
const pages = await this.browser.pages();
|
|
62
|
+
// Find Figma pages with workers
|
|
63
|
+
const figmaPages = pages.filter(p => {
|
|
64
|
+
const url = p.url();
|
|
65
|
+
return url.includes('figma.com') && !url.includes('devtools');
|
|
66
|
+
});
|
|
67
|
+
if (figmaPages.length === 0) {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
// Check each page for workers
|
|
71
|
+
const pagesWithWorkers = figmaPages
|
|
72
|
+
.map(p => ({
|
|
73
|
+
page: p,
|
|
74
|
+
workerCount: p.workers().length,
|
|
75
|
+
url: p.url()
|
|
76
|
+
}))
|
|
77
|
+
.filter(p => p.workerCount > 0)
|
|
78
|
+
.sort((a, b) => b.workerCount - a.workerCount); // Most workers first
|
|
79
|
+
if (pagesWithWorkers.length > 0) {
|
|
80
|
+
logger.info({
|
|
81
|
+
url: pagesWithWorkers[0].url,
|
|
82
|
+
workerCount: pagesWithWorkers[0].workerCount,
|
|
83
|
+
totalPagesWithWorkers: pagesWithWorkers.length
|
|
84
|
+
}, 'Found page with active plugin workers');
|
|
85
|
+
return pagesWithWorkers[0].page;
|
|
86
|
+
}
|
|
87
|
+
// No workers found - prefer design/file pages
|
|
88
|
+
const designPage = figmaPages.find(p => p.url().includes('/design/') || p.url().includes('/file/'));
|
|
89
|
+
return designPage || figmaPages[0];
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Find an existing browser tab whose URL matches the given Figma file key
|
|
93
|
+
*/
|
|
94
|
+
async findPageByFileKey(targetFileKey) {
|
|
95
|
+
if (!this.browser) {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
const pages = await this.browser.pages();
|
|
99
|
+
for (const page of pages) {
|
|
100
|
+
const pageUrl = page.url();
|
|
101
|
+
if (!pageUrl.includes('figma.com') || pageUrl.includes('devtools')) {
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
const pageFileKey = extractFileKey(pageUrl);
|
|
105
|
+
if (pageFileKey && pageFileKey === targetFileKey) {
|
|
106
|
+
logger.info({ url: pageUrl, fileKey: pageFileKey }, 'Found existing tab for file key');
|
|
107
|
+
return page;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Get active Figma page or create new one
|
|
114
|
+
* Prefers pages with active plugin workers for plugin debugging
|
|
115
|
+
*/
|
|
116
|
+
async getPage() {
|
|
117
|
+
// Ensure connection is alive before proceeding
|
|
118
|
+
await this.ensureConnection();
|
|
119
|
+
if (!this.browser) {
|
|
120
|
+
await this.launch();
|
|
121
|
+
}
|
|
122
|
+
// If we already have a page from explicit navigation, use it (don't override with findBestPage)
|
|
123
|
+
if (this.page && !this.page.isClosed()) {
|
|
124
|
+
return this.page;
|
|
125
|
+
}
|
|
126
|
+
// No explicit page set — find the best page (most workers) for initial connection
|
|
127
|
+
const bestPage = await this.findBestPage();
|
|
128
|
+
if (bestPage) {
|
|
129
|
+
const workerCount = bestPage.workers().length;
|
|
130
|
+
logger.info({
|
|
131
|
+
url: bestPage.url(),
|
|
132
|
+
workerCount
|
|
133
|
+
}, 'Selected page for monitoring (auto-detected)');
|
|
134
|
+
this.page = bestPage;
|
|
135
|
+
return this.page;
|
|
136
|
+
}
|
|
137
|
+
// Fallback: Get any existing page or create new one
|
|
138
|
+
const pages = await this.browser.pages();
|
|
139
|
+
if (pages.length > 0 && pages[0].url() !== 'about:blank') {
|
|
140
|
+
logger.warn({ url: pages[0].url() }, 'No Figma pages found, using first available page');
|
|
141
|
+
this.page = pages[0];
|
|
142
|
+
return this.page;
|
|
143
|
+
}
|
|
144
|
+
// Last resort: Create new page
|
|
145
|
+
logger.warn('No suitable pages found, creating new page in Figma Desktop');
|
|
146
|
+
this.page = await this.browser.newPage();
|
|
147
|
+
return this.page;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Navigate to Figma URL
|
|
151
|
+
* If the target file is already open in a tab, switches to it instead of navigating.
|
|
152
|
+
*/
|
|
153
|
+
async navigateToFigma(figmaUrl) {
|
|
154
|
+
// Ensure connection is alive before navigation
|
|
155
|
+
await this.ensureConnection();
|
|
156
|
+
// Default to Figma homepage if no URL provided
|
|
157
|
+
const url = figmaUrl || 'https://www.figma.com';
|
|
158
|
+
// Check if the target file is already open in an existing tab
|
|
159
|
+
const targetFileKey = extractFileKey(url);
|
|
160
|
+
if (targetFileKey) {
|
|
161
|
+
const existingPage = await this.findPageByFileKey(targetFileKey);
|
|
162
|
+
if (existingPage) {
|
|
163
|
+
logger.info({ url, fileKey: targetFileKey }, 'Switching to existing tab instead of navigating');
|
|
164
|
+
await existingPage.bringToFront();
|
|
165
|
+
this.page = existingPage;
|
|
166
|
+
return { page: existingPage, action: 'switched_to_existing', url: existingPage.url() };
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
// No existing tab found — fall through to normal navigation
|
|
170
|
+
const page = await this.getPage();
|
|
171
|
+
logger.info({ url }, 'Navigating to Figma');
|
|
172
|
+
try {
|
|
173
|
+
await page.goto(url, {
|
|
174
|
+
waitUntil: 'networkidle2',
|
|
175
|
+
timeout: 30000,
|
|
176
|
+
});
|
|
177
|
+
logger.info({ url }, 'Navigation successful');
|
|
178
|
+
return { page, action: 'navigated', url };
|
|
179
|
+
}
|
|
180
|
+
catch (error) {
|
|
181
|
+
logger.error({ error, url }, 'Navigation failed');
|
|
182
|
+
throw new Error(`Failed to navigate to ${url}: ${error}`);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Reload current page
|
|
187
|
+
*/
|
|
188
|
+
async reload(hardReload = false) {
|
|
189
|
+
if (!this.page || this.page.isClosed()) {
|
|
190
|
+
throw new Error('No active page to reload');
|
|
191
|
+
}
|
|
192
|
+
logger.info({ hardReload }, 'Reloading page');
|
|
193
|
+
try {
|
|
194
|
+
await this.page.reload({
|
|
195
|
+
waitUntil: 'networkidle2',
|
|
196
|
+
timeout: 30000,
|
|
197
|
+
});
|
|
198
|
+
logger.info('Page reloaded successfully');
|
|
199
|
+
}
|
|
200
|
+
catch (error) {
|
|
201
|
+
logger.error({ error }, 'Page reload failed');
|
|
202
|
+
throw new Error(`Page reload failed: ${error}`);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Execute JavaScript in page context
|
|
207
|
+
*/
|
|
208
|
+
async evaluate(fn) {
|
|
209
|
+
const page = await this.getPage();
|
|
210
|
+
return page.evaluate(fn);
|
|
211
|
+
}
|
|
212
|
+
// Screenshot functionality removed - use Figma REST API's getImages() instead
|
|
213
|
+
// See: figma_take_screenshot and figma_get_component_image tools
|
|
214
|
+
/**
|
|
215
|
+
* Check if browser is connected
|
|
216
|
+
*/
|
|
217
|
+
isRunning() {
|
|
218
|
+
return this.browser !== null && this.browser.isConnected();
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Disconnect from browser (doesn't close Figma Desktop)
|
|
222
|
+
*/
|
|
223
|
+
async close() {
|
|
224
|
+
if (!this.browser) {
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
logger.info('Disconnecting from Figma Desktop');
|
|
228
|
+
try {
|
|
229
|
+
// Just disconnect, don't close Figma Desktop
|
|
230
|
+
this.browser.disconnect();
|
|
231
|
+
this.browser = null;
|
|
232
|
+
this.page = null;
|
|
233
|
+
logger.info('Disconnected from Figma Desktop successfully');
|
|
234
|
+
}
|
|
235
|
+
catch (error) {
|
|
236
|
+
logger.error({ error }, 'Failed to disconnect from browser');
|
|
237
|
+
throw error;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Get current page URL
|
|
242
|
+
*/
|
|
243
|
+
getCurrentUrl() {
|
|
244
|
+
if (!this.page || this.page.isClosed()) {
|
|
245
|
+
return null;
|
|
246
|
+
}
|
|
247
|
+
return this.page.url();
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Check if the browser connection is still alive
|
|
251
|
+
* Returns false if connection is stale (e.g., after computer sleep)
|
|
252
|
+
*/
|
|
253
|
+
async isConnectionAlive() {
|
|
254
|
+
try {
|
|
255
|
+
if (!this.browser || !this.page) {
|
|
256
|
+
return false;
|
|
257
|
+
}
|
|
258
|
+
// Try to get the page title - this will fail if connection is dead
|
|
259
|
+
await this.page.title();
|
|
260
|
+
return true;
|
|
261
|
+
}
|
|
262
|
+
catch (error) {
|
|
263
|
+
logger.warn({ error }, 'Browser connection appears to be dead');
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Reconnect to Figma Desktop if connection was lost
|
|
269
|
+
* Call this before any operation that requires a live connection
|
|
270
|
+
*/
|
|
271
|
+
async ensureConnection() {
|
|
272
|
+
const isAlive = await this.isConnectionAlive();
|
|
273
|
+
if (!isAlive) {
|
|
274
|
+
logger.info('Connection lost, attempting to reconnect to Figma Desktop');
|
|
275
|
+
// Clear stale references
|
|
276
|
+
this.browser = null;
|
|
277
|
+
this.page = null;
|
|
278
|
+
// Reconnect
|
|
279
|
+
await this.launch();
|
|
280
|
+
logger.info('Successfully reconnected to Figma Desktop');
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Force a complete reconnection to Figma Desktop
|
|
285
|
+
* Use this when frames become detached or stale even though the browser appears connected
|
|
286
|
+
*/
|
|
287
|
+
async forceReconnect() {
|
|
288
|
+
logger.info('Force reconnecting to Figma Desktop');
|
|
289
|
+
// Disconnect current connection if exists
|
|
290
|
+
if (this.browser) {
|
|
291
|
+
try {
|
|
292
|
+
this.browser.disconnect();
|
|
293
|
+
}
|
|
294
|
+
catch (e) {
|
|
295
|
+
// Ignore disconnect errors
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
// Clear all references
|
|
299
|
+
this.browser = null;
|
|
300
|
+
this.page = null;
|
|
301
|
+
// Reconnect
|
|
302
|
+
await this.launch();
|
|
303
|
+
logger.info('Force reconnect completed');
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Wait for navigation
|
|
307
|
+
*/
|
|
308
|
+
async waitForNavigation(timeout = 30000) {
|
|
309
|
+
if (!this.page || this.page.isClosed()) {
|
|
310
|
+
throw new Error('No active page');
|
|
311
|
+
}
|
|
312
|
+
await this.page.waitForNavigation({
|
|
313
|
+
waitUntil: 'networkidle2',
|
|
314
|
+
timeout,
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
//# sourceMappingURL=local.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"local.js","sourceRoot":"","sources":["../../src/browser/local.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Code-side accessibility scanning via axe-core + JSDOM.
|
|
3
|
+
*
|
|
4
|
+
* Delegates all rule logic to axe-core (Deque) — the MCP never owns
|
|
5
|
+
* a rule database. JSDOM provides a lightweight DOM for structural checks
|
|
6
|
+
* (~50 rules: ARIA, semantics, alt text, form labels, headings, landmarks).
|
|
7
|
+
*
|
|
8
|
+
* Visual rules (color contrast, focus-visible) are NOT available via JSDOM —
|
|
9
|
+
* those are handled by the design-side figma_lint_design tool.
|
|
10
|
+
*/
|
|
11
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
12
|
+
/**
|
|
13
|
+
* Extract a CodeSpec.accessibility object from HTML + axe-core results.
|
|
14
|
+
* This bridges Phase 3 (code scanning) → Phase 4 (parity comparison).
|
|
15
|
+
*
|
|
16
|
+
* Parses the HTML to extract semantic element, ARIA attributes, and states.
|
|
17
|
+
* Uses axe-core results to infer what the code supports.
|
|
18
|
+
*/
|
|
19
|
+
export declare function axeResultsToCodeSpec(html: string, axeResults: any): Record<string, any>;
|
|
20
|
+
export declare function registerAccessibilityTools(server: McpServer): void;
|
|
21
|
+
//# sourceMappingURL=accessibility-tools.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"accessibility-tools.d.ts","sourceRoot":"","sources":["../../src/core/accessibility-tools.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AA6FpE;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,GAAG,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAoGvF;AA+DD,wBAAgB,0BAA0B,CACzC,MAAM,EAAE,SAAS,GACf,IAAI,CAyEN"}
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Code-side accessibility scanning via axe-core + JSDOM.
|
|
3
|
+
*
|
|
4
|
+
* Delegates all rule logic to axe-core (Deque) — the MCP never owns
|
|
5
|
+
* a rule database. JSDOM provides a lightweight DOM for structural checks
|
|
6
|
+
* (~50 rules: ARIA, semantics, alt text, form labels, headings, landmarks).
|
|
7
|
+
*
|
|
8
|
+
* Visual rules (color contrast, focus-visible) are NOT available via JSDOM —
|
|
9
|
+
* those are handled by the design-side figma_lint_design tool.
|
|
10
|
+
*/
|
|
11
|
+
import { z } from "zod";
|
|
12
|
+
import { logger } from "./logger.js";
|
|
13
|
+
// Lazy-load axe-core and jsdom to keep them optional
|
|
14
|
+
let axeCore = null;
|
|
15
|
+
let JSDOM = null;
|
|
16
|
+
let depsLoaded = false;
|
|
17
|
+
let depsError = null;
|
|
18
|
+
async function loadDeps() {
|
|
19
|
+
if (depsLoaded)
|
|
20
|
+
return;
|
|
21
|
+
try {
|
|
22
|
+
axeCore = await import("axe-core");
|
|
23
|
+
// axe-core's default export structure
|
|
24
|
+
if (axeCore.default)
|
|
25
|
+
axeCore = axeCore.default;
|
|
26
|
+
const jsdomModule = await import("jsdom");
|
|
27
|
+
JSDOM = jsdomModule.JSDOM;
|
|
28
|
+
depsLoaded = true;
|
|
29
|
+
}
|
|
30
|
+
catch (e) {
|
|
31
|
+
depsError = `axe-core or jsdom not installed. Run: npm install axe-core jsdom\n${e.message}`;
|
|
32
|
+
throw new Error(depsError);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Run axe-core against an HTML string using JSDOM.
|
|
37
|
+
*
|
|
38
|
+
* JSDOM limitations: no computed styles, no layout, no visual rendering.
|
|
39
|
+
* This means ~50-60 structural rules work, but visual rules
|
|
40
|
+
* (color-contrast, focus-visible, etc.) will report as "incomplete".
|
|
41
|
+
*/
|
|
42
|
+
async function scanHtmlWithAxe(html, options = {}) {
|
|
43
|
+
await loadDeps();
|
|
44
|
+
// Wrap HTML fragment in a full document if needed
|
|
45
|
+
const fullHtml = html.includes("<html") || html.includes("<!DOCTYPE")
|
|
46
|
+
? html
|
|
47
|
+
: `<!DOCTYPE html><html lang="en"><head><title>Scan</title></head><body>${html}</body></html>`;
|
|
48
|
+
const dom = new JSDOM(fullHtml, {
|
|
49
|
+
runScripts: "dangerously",
|
|
50
|
+
pretendToBeVisual: true,
|
|
51
|
+
url: "http://localhost",
|
|
52
|
+
});
|
|
53
|
+
const { document, window } = dom.window;
|
|
54
|
+
// Inject axe-core into the JSDOM window
|
|
55
|
+
const axeSource = axeCore.source;
|
|
56
|
+
const scriptEl = document.createElement("script");
|
|
57
|
+
scriptEl.textContent = axeSource;
|
|
58
|
+
document.head.appendChild(scriptEl);
|
|
59
|
+
// Configure axe run options
|
|
60
|
+
const runOptions = {};
|
|
61
|
+
if (options.tags && options.tags.length > 0) {
|
|
62
|
+
runOptions.runOnly = { type: "tag", values: options.tags };
|
|
63
|
+
}
|
|
64
|
+
// Disable rules that require visual rendering (always fail/incomplete in JSDOM)
|
|
65
|
+
if (options.disableVisualRules !== false) {
|
|
66
|
+
runOptions.rules = {
|
|
67
|
+
"color-contrast": { enabled: false },
|
|
68
|
+
"color-contrast-enhanced": { enabled: false },
|
|
69
|
+
"link-in-text-block": { enabled: false },
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
// Determine scan context
|
|
73
|
+
const context = options.context || document;
|
|
74
|
+
try {
|
|
75
|
+
const results = await window.axe.run(context, runOptions);
|
|
76
|
+
// Clean up
|
|
77
|
+
dom.window.close();
|
|
78
|
+
return results;
|
|
79
|
+
}
|
|
80
|
+
catch (err) {
|
|
81
|
+
dom.window.close();
|
|
82
|
+
throw new Error(`axe-core scan failed: ${err.message}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Extract a CodeSpec.accessibility object from HTML + axe-core results.
|
|
87
|
+
* This bridges Phase 3 (code scanning) → Phase 4 (parity comparison).
|
|
88
|
+
*
|
|
89
|
+
* Parses the HTML to extract semantic element, ARIA attributes, and states.
|
|
90
|
+
* Uses axe-core results to infer what the code supports.
|
|
91
|
+
*/
|
|
92
|
+
export function axeResultsToCodeSpec(html, axeResults) {
|
|
93
|
+
const spec = {};
|
|
94
|
+
// Parse HTML to extract attributes (lightweight regex-based, no DOM needed)
|
|
95
|
+
const htmlLower = html.toLowerCase();
|
|
96
|
+
// Semantic element: find the root/first meaningful element
|
|
97
|
+
const rootElementMatch = html.match(/<(button|a|input|select|textarea|details|dialog|nav|main|form|label|fieldset)\b/i);
|
|
98
|
+
if (rootElementMatch) {
|
|
99
|
+
spec.semanticElement = rootElementMatch[1].toLowerCase();
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
const firstElementMatch = html.match(/<(\w+)[\s>]/);
|
|
103
|
+
if (firstElementMatch && !["div", "span", "html", "head", "body", "script", "style", "!doctype"].includes(firstElementMatch[1].toLowerCase())) {
|
|
104
|
+
spec.semanticElement = firstElementMatch[1].toLowerCase();
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
spec.semanticElement = "div";
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
// ARIA role
|
|
111
|
+
const roleMatch = html.match(/role=["']([^"']+)["']/i);
|
|
112
|
+
if (roleMatch) {
|
|
113
|
+
spec.role = roleMatch[1];
|
|
114
|
+
}
|
|
115
|
+
// ARIA label
|
|
116
|
+
const ariaLabelMatch = html.match(/aria-label=["']([^"']+)["']/i);
|
|
117
|
+
if (ariaLabelMatch) {
|
|
118
|
+
spec.ariaLabel = ariaLabelMatch[1];
|
|
119
|
+
}
|
|
120
|
+
// Focus visible: check for :focus-visible or :focus in inline styles/class names,
|
|
121
|
+
// or infer from element type (native interactive elements have default focus)
|
|
122
|
+
const nativeFocusElements = ["button", "a", "input", "select", "textarea"];
|
|
123
|
+
const hasFocusCSS = /focus-visible|:focus\b|outline.*focus|ring.*focus|focus.*ring/i.test(html);
|
|
124
|
+
spec.focusVisible = hasFocusCSS || nativeFocusElements.includes(spec.semanticElement || "");
|
|
125
|
+
// Disabled support: only assert true when we find positive evidence.
|
|
126
|
+
// Absence of disabled/aria-disabled in a single HTML snapshot does NOT mean
|
|
127
|
+
// the component lacks disabled support — it may be in a non-disabled state.
|
|
128
|
+
if (/\bdisabled\b|aria-disabled/i.test(htmlLower)) {
|
|
129
|
+
spec.supportsDisabled = true;
|
|
130
|
+
}
|
|
131
|
+
// (leave undefined when not found — absence ≠ lack of support)
|
|
132
|
+
// Error support: same principle — only assert true on positive evidence.
|
|
133
|
+
// A default-state HTML snippet won't have aria-invalid; that doesn't mean
|
|
134
|
+
// the component can't enter an error state.
|
|
135
|
+
if (/aria-invalid|aria-errormessage|aria-describedby.*error/i.test(htmlLower)) {
|
|
136
|
+
spec.supportsError = true;
|
|
137
|
+
}
|
|
138
|
+
// (leave undefined when not found — scan a different state to confirm)
|
|
139
|
+
// Required: check for required or aria-required attributes
|
|
140
|
+
if (/aria-required=["']true["']|required(?!=)/i.test(html)) {
|
|
141
|
+
spec.ariaRequired = true;
|
|
142
|
+
}
|
|
143
|
+
else if (/aria-required=["']false["']/i.test(html)) {
|
|
144
|
+
spec.ariaRequired = false;
|
|
145
|
+
}
|
|
146
|
+
// Keyboard interactions: infer from element type
|
|
147
|
+
const keyboardInteractions = [];
|
|
148
|
+
if (spec.semanticElement === "button" || spec.role === "button") {
|
|
149
|
+
keyboardInteractions.push("Enter", "Space");
|
|
150
|
+
}
|
|
151
|
+
else if (spec.semanticElement === "a" || spec.role === "link") {
|
|
152
|
+
keyboardInteractions.push("Enter");
|
|
153
|
+
}
|
|
154
|
+
else if (spec.semanticElement === "input" || spec.semanticElement === "textarea") {
|
|
155
|
+
keyboardInteractions.push("Tab (focus)", "Type (input)");
|
|
156
|
+
}
|
|
157
|
+
else if (spec.semanticElement === "select" || spec.role === "listbox") {
|
|
158
|
+
keyboardInteractions.push("Arrow keys", "Enter", "Space");
|
|
159
|
+
}
|
|
160
|
+
else if (spec.role === "checkbox" || spec.role === "switch") {
|
|
161
|
+
keyboardInteractions.push("Space");
|
|
162
|
+
}
|
|
163
|
+
else if (spec.role === "tab") {
|
|
164
|
+
keyboardInteractions.push("Arrow keys");
|
|
165
|
+
}
|
|
166
|
+
// Check HTML for custom keyboard handlers
|
|
167
|
+
if (/onkeydown|onkeyup|onkeypress|@keydown|@keyup|v-on:keydown/i.test(html)) {
|
|
168
|
+
if (!keyboardInteractions.includes("Custom key handler")) {
|
|
169
|
+
keyboardInteractions.push("Custom key handler");
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
if (keyboardInteractions.length > 0) {
|
|
173
|
+
spec.keyboardInteractions = keyboardInteractions;
|
|
174
|
+
}
|
|
175
|
+
// Use axe-core results to refine: if certain violations exist, it tells us what's missing
|
|
176
|
+
if (axeResults?.violations) {
|
|
177
|
+
for (const v of axeResults.violations) {
|
|
178
|
+
// If button-name violation exists, the button has no accessible name
|
|
179
|
+
if (v.id === "button-name") {
|
|
180
|
+
spec.ariaLabel = undefined; // Explicitly missing
|
|
181
|
+
}
|
|
182
|
+
// If label violation exists, input lacks a label
|
|
183
|
+
if (v.id === "label") {
|
|
184
|
+
spec.ariaLabel = undefined;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return spec;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Format axe-core results into our standard lint-like output structure.
|
|
192
|
+
*/
|
|
193
|
+
function formatAxeResults(axeResults) {
|
|
194
|
+
const categories = [];
|
|
195
|
+
const severityMap = {
|
|
196
|
+
critical: "critical",
|
|
197
|
+
serious: "critical",
|
|
198
|
+
moderate: "warning",
|
|
199
|
+
minor: "info",
|
|
200
|
+
};
|
|
201
|
+
// Group violations
|
|
202
|
+
for (const violation of axeResults.violations || []) {
|
|
203
|
+
const severity = severityMap[violation.impact] || "warning";
|
|
204
|
+
const nodes = violation.nodes.map((node) => ({
|
|
205
|
+
html: node.html?.substring(0, 200),
|
|
206
|
+
target: node.target,
|
|
207
|
+
failureSummary: node.failureSummary?.substring(0, 300),
|
|
208
|
+
}));
|
|
209
|
+
categories.push({
|
|
210
|
+
rule: violation.id,
|
|
211
|
+
severity,
|
|
212
|
+
count: violation.nodes.length,
|
|
213
|
+
description: violation.help,
|
|
214
|
+
wcagTags: violation.tags.filter((t) => t.startsWith("wcag") || t.startsWith("best-practice")),
|
|
215
|
+
helpUrl: violation.helpUrl,
|
|
216
|
+
nodes: nodes.slice(0, 10), // Cap at 10 per rule
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
// Sort: critical first, then by count
|
|
220
|
+
categories.sort((a, b) => {
|
|
221
|
+
const sevOrder = { critical: 0, warning: 1, info: 2 };
|
|
222
|
+
if (sevOrder[a.severity] !== sevOrder[b.severity]) {
|
|
223
|
+
return sevOrder[a.severity] - sevOrder[b.severity];
|
|
224
|
+
}
|
|
225
|
+
return b.count - a.count;
|
|
226
|
+
});
|
|
227
|
+
// Summary
|
|
228
|
+
const summary = { critical: 0, warning: 0, info: 0, total: 0 };
|
|
229
|
+
for (const cat of categories) {
|
|
230
|
+
summary[cat.severity] += cat.count;
|
|
231
|
+
summary.total += cat.count;
|
|
232
|
+
}
|
|
233
|
+
return {
|
|
234
|
+
engine: "axe-core",
|
|
235
|
+
version: axeResults.testEngine?.version || "unknown",
|
|
236
|
+
mode: "jsdom-structural",
|
|
237
|
+
note: "JSDOM mode: structural/semantic checks only. Visual rules (color contrast, focus visibility) are disabled — use figma_lint_design for visual accessibility checks.",
|
|
238
|
+
categories,
|
|
239
|
+
summary,
|
|
240
|
+
passes: axeResults.passes?.length || 0,
|
|
241
|
+
incomplete: axeResults.incomplete?.length || 0,
|
|
242
|
+
inapplicable: axeResults.inapplicable?.length || 0,
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
export function registerAccessibilityTools(server) {
|
|
246
|
+
server.tool("figma_scan_code_accessibility", "Scan HTML code for accessibility violations using axe-core (Deque). " +
|
|
247
|
+
"Runs structural/semantic checks via JSDOM: ARIA attributes, roles, labels, alt text, " +
|
|
248
|
+
"form labels, heading order, landmarks, semantic HTML, tabindex, duplicate IDs, lang attribute, and ~50 more rules. " +
|
|
249
|
+
"Visual checks (color contrast, focus visibility) are disabled in this mode — use figma_lint_design for visual a11y on the design side. " +
|
|
250
|
+
"Together, these two tools provide full-spectrum accessibility coverage across design and code. " +
|
|
251
|
+
"Pass component HTML directly or use with figma_check_design_parity for design-to-code a11y comparison. " +
|
|
252
|
+
"No Figma connection required — this is a standalone code analysis tool.", {
|
|
253
|
+
html: z.string().describe("HTML string to scan. Can be a full document or a component fragment (will be wrapped in a valid document)."),
|
|
254
|
+
tags: z.array(z.string()).optional().describe("WCAG tag filter. Examples: ['wcag2a'], ['wcag2aa'], ['wcag21aa'], ['wcag22aa'], ['best-practice']. " +
|
|
255
|
+
"Defaults to all structural rules if omitted."),
|
|
256
|
+
context: z.string().optional().describe("CSS selector to scope the scan to a specific element (e.g., '#my-component', '.card'). Scans entire document if omitted."),
|
|
257
|
+
includePassingRules: z.boolean().optional().describe("If true, includes count of passing and incomplete rules in the response (default: false)."),
|
|
258
|
+
mapToCodeSpec: z.boolean().optional().describe("If true, includes a codeSpec.accessibility object auto-extracted from the HTML + scan results. " +
|
|
259
|
+
"Pass this directly into figma_check_design_parity's codeSpec.accessibility field for automated design-to-code a11y parity checking."),
|
|
260
|
+
}, async ({ html, tags, context, includePassingRules, mapToCodeSpec }) => {
|
|
261
|
+
try {
|
|
262
|
+
const axeResults = await scanHtmlWithAxe(html, {
|
|
263
|
+
tags: tags || undefined,
|
|
264
|
+
context: context || undefined,
|
|
265
|
+
});
|
|
266
|
+
const formatted = formatAxeResults(axeResults);
|
|
267
|
+
// Optionally strip pass/incomplete counts to save tokens
|
|
268
|
+
if (!includePassingRules) {
|
|
269
|
+
delete formatted.passes;
|
|
270
|
+
delete formatted.incomplete;
|
|
271
|
+
delete formatted.inapplicable;
|
|
272
|
+
}
|
|
273
|
+
// Auto-generate CodeSpec.accessibility from HTML + results
|
|
274
|
+
if (mapToCodeSpec) {
|
|
275
|
+
formatted.codeSpecAccessibility = axeResultsToCodeSpec(html, axeResults);
|
|
276
|
+
formatted.codeSpecAccessibility._usage = "Pass this object as codeSpec.accessibility in figma_check_design_parity for automated a11y parity checking.";
|
|
277
|
+
}
|
|
278
|
+
return {
|
|
279
|
+
content: [
|
|
280
|
+
{
|
|
281
|
+
type: "text",
|
|
282
|
+
text: JSON.stringify(formatted, null, 2),
|
|
283
|
+
},
|
|
284
|
+
],
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
catch (error) {
|
|
288
|
+
const isDepsError = error.message?.includes("not installed");
|
|
289
|
+
logger.error({ error }, "Failed to scan code accessibility");
|
|
290
|
+
return {
|
|
291
|
+
content: [
|
|
292
|
+
{
|
|
293
|
+
type: "text",
|
|
294
|
+
text: JSON.stringify({
|
|
295
|
+
error: error.message,
|
|
296
|
+
hint: isDepsError
|
|
297
|
+
? "Install dependencies: npm install axe-core jsdom"
|
|
298
|
+
: "Check that the HTML is valid. For visual accessibility checks, use figma_lint_design instead.",
|
|
299
|
+
}),
|
|
300
|
+
},
|
|
301
|
+
],
|
|
302
|
+
isError: true,
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
//# sourceMappingURL=accessibility-tools.js.map
|