inkhouse 0.1.0-beta.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 +201 -0
- package/bin/inkhouse.mjs +171 -0
- package/code.js +11802 -0
- package/manifest.json +30 -0
- package/package.json +45 -0
- package/scanner/blob-placement-regression.ts +132 -0
- package/scanner/class-collector.ts +69 -0
- package/scanner/cli.ts +336 -0
- package/scanner/component-scanner.ts +2876 -0
- package/scanner/css-patch-regression.ts +112 -0
- package/scanner/css-token-reader-regression.ts +92 -0
- package/scanner/css-token-reader.ts +477 -0
- package/scanner/font-style-resolver-regression.ts +32 -0
- package/scanner/index.ts +9 -0
- package/scanner/radial-gradient-regression.ts +53 -0
- package/scanner/style-map.ts +145 -0
- package/scanner/tailwind-parser.ts +644 -0
- package/scanner/transform-math-regression.ts +42 -0
- package/scanner/types.ts +298 -0
- package/src/blob-placement.ts +111 -0
- package/src/change-detection.ts +204 -0
- package/src/class-utils.ts +105 -0
- package/src/clip-path-decorative.ts +194 -0
- package/src/color-resolver.ts +98 -0
- package/src/colors.ts +196 -0
- package/src/component-defs.ts +54 -0
- package/src/component-gen.ts +561 -0
- package/src/component-lookup.ts +82 -0
- package/src/config.ts +115 -0
- package/src/design-system.ts +59 -0
- package/src/dev-server.ts +173 -0
- package/src/figma-globals.d.ts +3 -0
- package/src/font-style-resolver.ts +171 -0
- package/src/github.ts +1465 -0
- package/src/icon-builder.ts +607 -0
- package/src/image-cache.ts +22 -0
- package/src/inline-text.ts +271 -0
- package/src/layout-parser.ts +667 -0
- package/src/layout-utils.ts +155 -0
- package/src/main.ts +687 -0
- package/src/node-ir.ts +595 -0
- package/src/pack-provider.ts +148 -0
- package/src/packs.ts +126 -0
- package/src/radial-gradient.ts +84 -0
- package/src/render-context.ts +138 -0
- package/src/responsive-analyzer.ts +139 -0
- package/src/state-analyzer.ts +143 -0
- package/src/story-builder.ts +1706 -0
- package/src/story-layout.ts +38 -0
- package/src/tailwind.ts +2379 -0
- package/src/text-builder.ts +116 -0
- package/src/text-line.ts +42 -0
- package/src/token-source.ts +43 -0
- package/src/tokens.ts +717 -0
- package/src/transform-math.ts +44 -0
- package/src/ui-builder.ts +1996 -0
- package/src/utility-resolver.ts +125 -0
- package/src/variables.ts +1042 -0
- package/src/width-solver.ts +466 -0
- package/templates/patch-tokens-route.ts +165 -0
- package/templates/scan-components-route.ts +57 -0
- package/ui.html +1222 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { inferFontWeight, rankStylesForRequest } from '../src/font-style-resolver';
|
|
3
|
+
|
|
4
|
+
assert.equal(inferFontWeight('Regular'), 400);
|
|
5
|
+
assert.equal(inferFontWeight('Medium'), 500);
|
|
6
|
+
assert.equal(inferFontWeight('SemiBold'), 600);
|
|
7
|
+
assert.equal(inferFontWeight('Demi Bold'), 600);
|
|
8
|
+
assert.equal(inferFontWeight('Bold'), 700);
|
|
9
|
+
assert.equal(inferFontWeight('ExtraBold'), 800);
|
|
10
|
+
assert.equal(inferFontWeight('Black'), 900);
|
|
11
|
+
assert.equal(inferFontWeight('Book'), 350);
|
|
12
|
+
assert.equal(inferFontWeight('Weight 500'), 500);
|
|
13
|
+
|
|
14
|
+
const mediumExact = rankStylesForRequest(['Regular', 'Medium', 'Bold'], 'Medium');
|
|
15
|
+
assert.equal(mediumExact[0], 'Medium');
|
|
16
|
+
|
|
17
|
+
const mediumFallback = rankStylesForRequest(['Regular', 'DemiBold', 'Bold'], 'Medium');
|
|
18
|
+
assert.equal(mediumFallback[0], 'DemiBold');
|
|
19
|
+
|
|
20
|
+
const mediumTiePrefersHeavier = rankStylesForRequest(['Regular', 'SemiBold'], 'Medium');
|
|
21
|
+
assert.equal(mediumTiePrefersHeavier[0], 'SemiBold');
|
|
22
|
+
|
|
23
|
+
const semiboldExactBeatsCondensed = rankStylesForRequest(['Condensed SemiBold', 'SemiBold', 'Regular'], 'SemiBold');
|
|
24
|
+
assert.equal(semiboldExactBeatsCondensed[0], 'SemiBold');
|
|
25
|
+
|
|
26
|
+
const semiBold = rankStylesForRequest(['Regular', 'Book', 'Semibold', 'Bold'], 'SemiBold');
|
|
27
|
+
assert.equal(semiBold[0], 'Semibold');
|
|
28
|
+
|
|
29
|
+
const keepItalic = rankStylesForRequest(['Regular', 'Regular Italic', 'Medium Italic'], 'Medium Italic');
|
|
30
|
+
assert.equal(keepItalic[0], 'Medium Italic');
|
|
31
|
+
|
|
32
|
+
console.log('font style resolver regression passed');
|
package/scanner/index.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { parseRadialAnchorFromUtility, radialGradientTransformFromAnchor } from '../src/radial-gradient';
|
|
3
|
+
|
|
4
|
+
function approxEqual(actual: number, expected: number, epsilon = 0.0001): void {
|
|
5
|
+
assert.ok(Math.abs(actual - expected) <= epsilon, `expected ${expected}, got ${actual}`);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function assertMatrix(
|
|
9
|
+
actual: [[number, number, number], [number, number, number]],
|
|
10
|
+
expected: [[number, number, number], [number, number, number]],
|
|
11
|
+
): void {
|
|
12
|
+
approxEqual(actual[0][0], expected[0][0]);
|
|
13
|
+
approxEqual(actual[0][1], expected[0][1]);
|
|
14
|
+
approxEqual(actual[0][2], expected[0][2]);
|
|
15
|
+
approxEqual(actual[1][0], expected[1][0]);
|
|
16
|
+
approxEqual(actual[1][1], expected[1][1]);
|
|
17
|
+
approxEqual(actual[1][2], expected[1][2]);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Default Tailwind `bg-radial` == CSS `radial-gradient(...)`
|
|
21
|
+
// -> ellipse farthest-corner at center.
|
|
22
|
+
const defaultTransform = radialGradientTransformFromAnchor({ x: 0.5, y: 0.5 });
|
|
23
|
+
assertMatrix(defaultTransform, [
|
|
24
|
+
[Math.SQRT2 / 2, 0, 0.5 - ((Math.SQRT2 / 2) * 0.5)],
|
|
25
|
+
[0, Math.SQRT2 / 2, 0.5 - ((Math.SQRT2 / 2) * 0.5)],
|
|
26
|
+
]);
|
|
27
|
+
|
|
28
|
+
const topLeft = parseRadialAnchorFromUtility('bg-radial-[at_top_left]');
|
|
29
|
+
assert.deepEqual(topLeft, { x: 0, y: 0 });
|
|
30
|
+
|
|
31
|
+
const percent = parseRadialAnchorFromUtility('bg-radial-[at_25%_75%]');
|
|
32
|
+
assert.deepEqual(percent, { x: 0.25, y: 0.75 });
|
|
33
|
+
|
|
34
|
+
if (!topLeft || !percent) {
|
|
35
|
+
throw new Error('anchor parsing failed unexpectedly');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const topLeftTransform = radialGradientTransformFromAnchor(topLeft);
|
|
39
|
+
const topLeftScale = 0.5 / Math.SQRT2;
|
|
40
|
+
assertMatrix(topLeftTransform, [
|
|
41
|
+
[topLeftScale, 0, 0.5],
|
|
42
|
+
[0, topLeftScale, 0.5],
|
|
43
|
+
]);
|
|
44
|
+
|
|
45
|
+
const percentTransform = radialGradientTransformFromAnchor(percent);
|
|
46
|
+
const expectedPercentRadius = Math.sqrt((0.75 * 0.75) + (0.75 * 0.75));
|
|
47
|
+
const expectedPercentScale = 0.5 / expectedPercentRadius;
|
|
48
|
+
assertMatrix(percentTransform, [
|
|
49
|
+
[expectedPercentScale, 0, 0.5 - (expectedPercentScale * 0.25)],
|
|
50
|
+
[0, expectedPercentScale, 0.5 - (expectedPercentScale * 0.75)],
|
|
51
|
+
]);
|
|
52
|
+
|
|
53
|
+
console.log('radial gradient regression passed');
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import postcss, { AtRule, Declaration, Rule } from 'postcss';
|
|
4
|
+
import { compile } from 'tailwindcss';
|
|
5
|
+
|
|
6
|
+
export interface StyleRuleEntry {
|
|
7
|
+
declarations: Record<string, string>;
|
|
8
|
+
media?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export type StyleMap = Record<string, StyleRuleEntry[]>;
|
|
12
|
+
|
|
13
|
+
async function loadStylesheet(id: string, base: string): Promise<{ path: string; base: string; content: string }> {
|
|
14
|
+
const root = process.cwd();
|
|
15
|
+
let filePath: string;
|
|
16
|
+
|
|
17
|
+
if (id.startsWith('.')) {
|
|
18
|
+
filePath = path.resolve(base, id);
|
|
19
|
+
if (!fs.existsSync(filePath)) {
|
|
20
|
+
filePath = path.resolve(root, 'src/app', id);
|
|
21
|
+
}
|
|
22
|
+
} else if (id === 'tailwindcss') {
|
|
23
|
+
filePath = path.resolve(root, 'node_modules/tailwindcss/index.css');
|
|
24
|
+
} else if (id === 'tw-animate-css') {
|
|
25
|
+
filePath = path.resolve(root, 'node_modules/tw-animate-css/dist/tw-animate.css');
|
|
26
|
+
} else {
|
|
27
|
+
filePath = path.resolve(root, 'node_modules', id);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
31
|
+
return { path: filePath, base: path.dirname(filePath), content };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function extractClassName(selector: string): string | null {
|
|
35
|
+
const dot = selector.indexOf('.');
|
|
36
|
+
if (dot === -1) return null;
|
|
37
|
+
let out = '';
|
|
38
|
+
for (let i = dot + 1; i < selector.length; i++) {
|
|
39
|
+
const ch = selector[i];
|
|
40
|
+
if (ch === '\\') {
|
|
41
|
+
if (i + 1 < selector.length) {
|
|
42
|
+
out += selector[i + 1];
|
|
43
|
+
i++;
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (
|
|
48
|
+
ch === ':' ||
|
|
49
|
+
ch === ' ' ||
|
|
50
|
+
ch === '\n' ||
|
|
51
|
+
ch === '\t' ||
|
|
52
|
+
ch === '>' ||
|
|
53
|
+
ch === '+' ||
|
|
54
|
+
ch === '~' ||
|
|
55
|
+
ch === ',' ||
|
|
56
|
+
ch === ')' ||
|
|
57
|
+
ch === '['
|
|
58
|
+
) {
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
out += ch;
|
|
62
|
+
}
|
|
63
|
+
return out || null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function addToMap(
|
|
67
|
+
map: StyleMap,
|
|
68
|
+
className: string,
|
|
69
|
+
declarations: Record<string, string>,
|
|
70
|
+
media?: string
|
|
71
|
+
): void {
|
|
72
|
+
if (!map[className]) map[className] = [];
|
|
73
|
+
map[className].push({ declarations, media });
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function walkNodes(
|
|
77
|
+
node: postcss.Container,
|
|
78
|
+
selectors: string[],
|
|
79
|
+
media: string[],
|
|
80
|
+
candidates: Set<string>,
|
|
81
|
+
map: StyleMap
|
|
82
|
+
): void {
|
|
83
|
+
node.nodes?.forEach(child => {
|
|
84
|
+
if (child.type === 'rule') {
|
|
85
|
+
const rule = child as Rule;
|
|
86
|
+
const ruleSelectors = rule.selectors || [rule.selector];
|
|
87
|
+
const expanded = ruleSelectors.flatMap(sel => {
|
|
88
|
+
if (sel.includes('&') && selectors.length > 0) {
|
|
89
|
+
return selectors.map(parent => sel.replace(/&/g, parent));
|
|
90
|
+
}
|
|
91
|
+
return [sel];
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
const decls: Record<string, string> = {};
|
|
95
|
+
rule.nodes?.forEach(ruleChild => {
|
|
96
|
+
if (ruleChild.type === 'decl') {
|
|
97
|
+
const decl = ruleChild as Declaration;
|
|
98
|
+
if (decl.prop.startsWith('--tw-')) return;
|
|
99
|
+
decls[decl.prop] = decl.important ? `${decl.value} !important` : decl.value;
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
if (Object.keys(decls).length > 0) {
|
|
104
|
+
for (const sel of expanded) {
|
|
105
|
+
const className = extractClassName(sel);
|
|
106
|
+
if (!className) continue;
|
|
107
|
+
if (!candidates.has(className)) continue;
|
|
108
|
+
addToMap(map, className, decls, media.length > 0 ? media.join(' && ') : undefined);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
walkNodes(rule, expanded, media, candidates, map);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (child.type === 'atrule') {
|
|
117
|
+
const at = child as AtRule;
|
|
118
|
+
if (at.name === 'media' || at.name === 'supports') {
|
|
119
|
+
walkNodes(at, selectors, media.concat(`${at.name} ${at.params}`), candidates, map);
|
|
120
|
+
} else {
|
|
121
|
+
walkNodes(at, selectors, media, candidates, map);
|
|
122
|
+
}
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export async function buildStyleMap(classes: string[]): Promise<StyleMap> {
|
|
129
|
+
const unique = Array.from(new Set(classes.filter(Boolean)));
|
|
130
|
+
if (unique.length === 0) return {};
|
|
131
|
+
|
|
132
|
+
const css = fs.readFileSync(path.resolve(process.cwd(), 'src/app/globals.css'), 'utf-8');
|
|
133
|
+
const compiled = await compile(css, {
|
|
134
|
+
base: process.cwd(),
|
|
135
|
+
loadStylesheet,
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
const cssOutput = compiled.build(unique);
|
|
139
|
+
const root = postcss.parse(cssOutput);
|
|
140
|
+
const map: StyleMap = {};
|
|
141
|
+
const candidateSet = new Set(unique);
|
|
142
|
+
|
|
143
|
+
walkNodes(root, [], [], candidateSet, map);
|
|
144
|
+
return map;
|
|
145
|
+
}
|