mirror of
https://github.com/Manavarya09/design-extract.git
synced 2026-09-19 02:41:14 +08:00
2e9710c1cd
* docs: v7.0 spec and implementation plan
* chore: add @modelcontextprotocol/sdk for v7 MCP server
* feat(formatters): add DTCG v1 tokens with primitive + semantic + composites
* feat: use DTCG token format by default; legacy via --tokens-legacy
* feat(extractors): add stack-fingerprint for framework + Tailwind detection
Detects framework (next/nuxt/gatsby/remix/react/vue/astro/sveltekit/
shopify/wordpress/webflow/framer), CSS layer (tailwind with top-100
utility frequencies), and analytics providers from crawled page
signals (scripts, meta tags, class-name sample, window globals).
Crawler now emits rawData.light.stack carrying those signals.
design.stack lands on the extraction output.
* feat(extractors): CSS health audit (specificity, !important, unused, keyframes)
Uses Playwright CSS coverage to compute used/unused bytes, counts
!important usage, duplicate prop:value pairs, vendor-prefix hits,
selector specificity distribution, and catalogs @keyframes.
Crawler starts coverage before navigation and stops after the page
settles; gracefully degrades to empty coverage on unsupported targets.
scoring.js gains an additive cssHealth dimension (does not change
existing weighted overall score for back-compat).
* feat(a11y): remediation suggestions — nearest palette color passing WCAG
For every failing contrast pair emitted by the accessibility extractor,
search design.colors.all for a palette entry that clears the matching
WCAG threshold (AA-normal=4.5, AA-large=3, AAA variants).
Exposes design.accessibility.failingPairs (derived) and
design.accessibility.remediation (array of { fg, bg, ratio, rule,
suggestion }). Suggestion is null when no palette color qualifies.
* feat(extractors): semantic-regions classifier (nav/hero/pricing/footer/…)
Classifies page sections using landmark tag/role plus keyword and
layout heuristics into: nav, hero, pricing, testimonials, features,
cta, footer, sidebar, content.
Crawler emits rawData.light.sections carrying tag, role, class/id,
text sample, headings, button/card counts, and bounds for each
landmark/section element.
* feat(extractors): reusable component clustering with variant detection
Groups button/input/link/card candidates by structural hash (tag >
child tags), then splits each group into variants by cosine
similarity on a style vector (padding, bg/fg channels, radius,
border, font metrics). Cosine >= 0.95 collapses into one variant.
Crawler serializes rawData.light.componentCandidates with kind,
structuralHash, styleVector, and human-readable css per element.
Markdown formatter renders a new "Component Clusters" section
(additive — the legacy "Component Patterns" section is preserved).
* chore(repo): add issue templates, funding config, contact links
* feat(formatters): iOS SwiftUI emitter from DTCG tokens
Adds a shared internal `resolveRef(tokens, path)` helper plus
`formatIosSwiftUI(tokens)` that emits a SwiftUI file with:
- semantic Colors (resolved through {primitive.*} references)
- CGFloat extensions for primitive spacing/radius
- a primitive palette for direct use
- typography hint comments (Font generation deferred)
9 new tests (4 iOS + 5 resolveRef).
* feat(formatters): Android Compose emitter
Adds `formatAndroidCompose(tokens)` producing Theme.kt (Kotlin Compose
object with Color/dp values), plus colors.xml and dimens.xml resources.
PascalCase in Kotlin, snake_case in XML, #FF-prefixed hex for alpha.
3 new tests.
* feat(formatters): Flutter Dart emitter
Adds `formatFlutterDart(tokens)` producing a Dart file with a
DesignTokens class (semantic colors, primitive spacing/radius/colors)
and a buildDesignlangTheme() helper wiring primary/surface into a
ColorScheme.
2 new tests.
* feat(formatters): WordPress block theme skeleton with theme.json
Adds `formatWordPressTheme(tokens, design)` returning a full block-theme
file map:
- theme.json (WP schema v3 with color palette, spacing, font sizes/families
derived from DTCG tokens)
- style.css (valid WP theme header + :root custom props)
- functions.php (minimal theme support)
- index.php (template-part fallback)
- templates/index.html (block-markup entry)
Legacy `formatWordPress(design)` export preserved for existing consumers.
4 new tests.
* feat: --platforms dispatcher for web/ios/android/flutter/wordpress
Adds a comma-separated --platforms CLI option that emits additional
platform-native outputs alongside the existing web set:
- ios → ios/DesignTokens.swift
- android → android/Theme.kt + colors.xml + dimens.xml
- flutter → flutter/design_tokens.dart
- wordpress → wordpress-theme/{theme.json, style.css, functions.php,
index.php, templates/index.html}
"all" expands to every known platform. Web is always emitted (additive).
Threads `platforms` through mergeConfig with a parsePlatforms helper that
normalizes CSV/array inputs, ignores unknown values, and guarantees web
is present.
10 new tests (6 parsePlatforms, 3 mergeConfig platforms, 1 CLI help).
85 lines
2.9 KiB
JavaScript
85 lines
2.9 KiB
JavaScript
import { describe, it } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { execFileSync } from 'node:child_process';
|
|
import { resolve } from 'node:path';
|
|
import { parsePlatforms, mergeConfig } from '../src/config.js';
|
|
|
|
const CLI_PATH = resolve(import.meta.dirname, '..', 'bin', 'design-extract.js');
|
|
|
|
describe('CLI', () => {
|
|
it('shows help with --help', () => {
|
|
const output = execFileSync('node', [CLI_PATH, '--help'], { encoding: 'utf-8' });
|
|
assert.ok(output.includes('designlang'));
|
|
assert.ok(output.includes('Extract'));
|
|
});
|
|
|
|
it('shows version with --version', () => {
|
|
const output = execFileSync('node', [CLI_PATH, '--version'], { encoding: 'utf-8' });
|
|
assert.ok(output.trim().match(/^\d+\.\d+\.\d+$/));
|
|
});
|
|
|
|
it('shows version number 6.0.0', () => {
|
|
const output = execFileSync('node', [CLI_PATH, '--version'], { encoding: 'utf-8' });
|
|
assert.equal(output.trim(), '6.0.0');
|
|
});
|
|
|
|
it('exits with error when no arguments provided', () => {
|
|
try {
|
|
execFileSync('node', [CLI_PATH], { encoding: 'utf-8', stdio: 'pipe' });
|
|
assert.fail('Should have thrown');
|
|
} catch (err) {
|
|
// Commander exits with code 1 when required argument is missing
|
|
assert.ok(err.status !== 0);
|
|
}
|
|
});
|
|
|
|
it('lists --platforms option in help output', () => {
|
|
const output = execFileSync('node', [CLI_PATH, '--help'], { encoding: 'utf-8' });
|
|
assert.ok(output.includes('--platforms'));
|
|
});
|
|
});
|
|
|
|
describe('parsePlatforms', () => {
|
|
it('defaults to web only', () => {
|
|
assert.deepEqual(parsePlatforms('web'), ['web']);
|
|
});
|
|
|
|
it('parses comma-separated values', () => {
|
|
assert.deepEqual(parsePlatforms('ios,android'), ['web', 'ios', 'android']);
|
|
});
|
|
|
|
it('expands "all" to every known platform', () => {
|
|
assert.deepEqual(parsePlatforms('all'), ['web', 'ios', 'android', 'flutter', 'wordpress']);
|
|
});
|
|
|
|
it('always includes web (additive)', () => {
|
|
assert.ok(parsePlatforms('ios').includes('web'));
|
|
assert.ok(parsePlatforms('wordpress').includes('web'));
|
|
});
|
|
|
|
it('ignores unknown platforms', () => {
|
|
assert.deepEqual(parsePlatforms('ios,badplatform,android'), ['web', 'ios', 'android']);
|
|
});
|
|
|
|
it('accepts arrays', () => {
|
|
assert.deepEqual(parsePlatforms(['ios', 'flutter']), ['web', 'ios', 'flutter']);
|
|
});
|
|
});
|
|
|
|
describe('mergeConfig platforms', () => {
|
|
it('threads CLI --platforms through mergeConfig', () => {
|
|
const merged = mergeConfig({ platforms: 'ios,flutter' }, {});
|
|
assert.deepEqual(merged.platforms, ['web', 'ios', 'flutter']);
|
|
});
|
|
|
|
it('honors platforms from config file', () => {
|
|
const merged = mergeConfig({}, { platforms: 'android' });
|
|
assert.deepEqual(merged.platforms, ['web', 'android']);
|
|
});
|
|
|
|
it('defaults to [web] when neither CLI nor config provides platforms', () => {
|
|
const merged = mergeConfig({}, {});
|
|
assert.deepEqual(merged.platforms, ['web']);
|
|
});
|
|
});
|