chaincss 1.13.3 → 2.0.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/CHANGELOG.md +81 -0
- package/LICENSE +2 -3
- package/README.md +238 -105
- package/dist/cli/commands/build.d.ts +3 -0
- package/dist/cli/commands/build.d.ts.map +1 -0
- package/dist/cli/commands/compile.d.ts +3 -0
- package/dist/cli/commands/compile.d.ts.map +1 -0
- package/dist/cli/commands/init.d.ts +5 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/timeline.d.ts +2 -0
- package/dist/cli/commands/timeline.d.ts.map +1 -0
- package/dist/cli/commands/watch.d.ts +6 -0
- package/dist/cli/commands/watch.d.ts.map +1 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +5960 -0
- package/dist/cli/types.d.ts +51 -0
- package/dist/cli/types.d.ts.map +1 -0
- package/dist/cli/utils/config-loader.d.ts +8 -0
- package/dist/cli/utils/config-loader.d.ts.map +1 -0
- package/dist/cli/utils/file-utils.d.ts +9 -0
- package/dist/cli/utils/file-utils.d.ts.map +1 -0
- package/dist/cli/utils/logger.d.ts +17 -0
- package/dist/cli/utils/logger.d.ts.map +1 -0
- package/dist/compiler/atomic-optimizer.d.ts +76 -0
- package/dist/compiler/atomic-optimizer.d.ts.map +1 -0
- package/dist/compiler/btt.d.ts +138 -0
- package/dist/compiler/btt.d.ts.map +1 -0
- package/dist/compiler/cache-manager.d.ts +20 -0
- package/dist/compiler/cache-manager.d.ts.map +1 -0
- package/dist/compiler/commonProps.d.ts +2 -0
- package/dist/compiler/commonProps.d.ts.map +1 -0
- package/dist/compiler/index.d.ts +12 -0
- package/dist/compiler/index.d.ts.map +1 -0
- package/dist/compiler/index.js +5177 -0
- package/dist/compiler/prefixer.d.ts +42 -0
- package/dist/compiler/prefixer.d.ts.map +1 -0
- package/dist/compiler/theme-contract.d.ts +61 -0
- package/dist/compiler/theme-contract.d.ts.map +1 -0
- package/dist/compiler/tokens.d.ts +52 -0
- package/dist/compiler/tokens.d.ts.map +1 -0
- package/dist/compiler/types.d.ts +57 -0
- package/dist/compiler/types.d.ts.map +1 -0
- package/dist/core/compiler.d.ts +32 -0
- package/dist/core/compiler.d.ts.map +1 -0
- package/dist/core/constants.d.ts +129 -0
- package/dist/core/constants.d.ts.map +1 -0
- package/dist/core/index.d.ts +4 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/types.d.ts +88 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/utils.d.ts +37 -0
- package/dist/core/utils.d.ts.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5667 -0
- package/dist/plugins/vite.d.ts +11 -0
- package/dist/plugins/vite.d.ts.map +1 -0
- package/dist/plugins/vite.js +25839 -0
- package/dist/plugins/webpack.d.ts +45 -0
- package/dist/plugins/webpack.d.ts.map +1 -0
- package/dist/plugins/webpack.js +107 -0
- package/dist/runtime/hmr.d.ts +3 -0
- package/dist/runtime/hmr.d.ts.map +1 -0
- package/dist/runtime/index.d.ts +15 -0
- package/dist/runtime/index.d.ts.map +1 -0
- package/dist/runtime/index.js +552 -0
- package/dist/runtime/injector.d.ts +85 -0
- package/dist/runtime/injector.d.ts.map +1 -0
- package/dist/runtime/react.d.ts +54 -0
- package/dist/runtime/react.d.ts.map +1 -0
- package/dist/runtime/react.js +270 -0
- package/dist/runtime/types.d.ts +45 -0
- package/dist/runtime/types.d.ts.map +1 -0
- package/dist/runtime/utils.d.ts +62 -0
- package/dist/runtime/utils.d.ts.map +1 -0
- package/dist/runtime/vue.d.ts +52 -0
- package/dist/runtime/vue.d.ts.map +1 -0
- package/dist/runtime/vue.js +232 -0
- package/package.json +90 -119
- package/browser/commonProps.js +0 -14
- package/browser/index.js +0 -3
- package/browser/react-hooks.js +0 -162
- package/browser/rtt.js +0 -400
- package/browser/vue-composables.js +0 -200
- package/node/atomic-optimizer.js +0 -526
- package/node/btt.js +0 -1009
- package/node/cache-manager.js +0 -56
- package/node/chaincss.js +0 -642
- package/node/index.js +0 -2
- package/node/loaders/chaincss-loader.js +0 -62
- package/node/plugins/next-plugin.js +0 -120
- package/node/plugins/vite-plugin.js +0 -383
- package/node/plugins/webpack-plugin.js +0 -41
- package/node/prefixer.js +0 -237
- package/node/strVal.js +0 -92
- package/node/theme-validator.js +0 -32
- package/shared/theme-contract.js +0 -98
- package/shared/tokens.cjs +0 -256
- package/shared/tokens.mjs +0 -320
- package/types.d.ts +0 -325
package/node/prefixer.js
DELETED
|
@@ -1,237 +0,0 @@
|
|
|
1
|
-
let postcss, browserslist, caniuse, autoprefixer;
|
|
2
|
-
try {
|
|
3
|
-
postcss = require('postcss');
|
|
4
|
-
browserslist = require('browserslist');
|
|
5
|
-
caniuse = require('caniuse-db/fulldata-json/data-2.0.json');
|
|
6
|
-
} catch (err) {
|
|
7
|
-
}
|
|
8
|
-
try {
|
|
9
|
-
autoprefixer = require('autoprefixer');
|
|
10
|
-
} catch (err) {
|
|
11
|
-
}
|
|
12
|
-
class ChainCSSPrefixer {
|
|
13
|
-
constructor(config = {}) {
|
|
14
|
-
this.config = {
|
|
15
|
-
browsers: config.browsers || ['> 0.5%', 'last 2 versions', 'not dead'],
|
|
16
|
-
enabled: config.enabled !== false,
|
|
17
|
-
mode: config.mode || 'auto',
|
|
18
|
-
sourceMap: config.sourceMap !== false, // Enable source maps by default
|
|
19
|
-
sourceMapInline: config.sourceMapInline || false,
|
|
20
|
-
...config
|
|
21
|
-
};
|
|
22
|
-
this.hasBuiltInDeps = !!(postcss && browserslist && caniuse);
|
|
23
|
-
this.hasAutoprefixer = !!autoprefixer;
|
|
24
|
-
this.prefixerMode = this.determineMode();
|
|
25
|
-
this.caniuseData = caniuse ? caniuse.data : null;
|
|
26
|
-
this.commonProperties = this.getCommonProperties();
|
|
27
|
-
this.specialValues = {
|
|
28
|
-
'display': ['flex', 'inline-flex', 'grid', 'inline-grid'],
|
|
29
|
-
'background-clip': ['text'],
|
|
30
|
-
'position': ['sticky']
|
|
31
|
-
};
|
|
32
|
-
this.browserPrefixMap = {
|
|
33
|
-
'chrome': 'webkit', 'safari': 'webkit', 'firefox': 'moz',
|
|
34
|
-
'ie': 'ms', 'edge': 'webkit', 'ios_saf': 'webkit',
|
|
35
|
-
'and_chr': 'webkit', 'android': 'webkit', 'opera': 'webkit',
|
|
36
|
-
'op_mob': 'webkit', 'samsung': 'webkit', 'and_ff': 'moz'
|
|
37
|
-
};
|
|
38
|
-
this.targetBrowsers = null;
|
|
39
|
-
}
|
|
40
|
-
determineMode() {
|
|
41
|
-
if (this.config.mode === 'full' && !this.hasAutoprefixer) {
|
|
42
|
-
console.warn('Full mode requested but autoprefixer not installed. Falling back to lightweight mode.');
|
|
43
|
-
console.warn(' To use full mode: npm install autoprefixer postcss caniuse-db browserslist\n');
|
|
44
|
-
return 'lightweight';
|
|
45
|
-
}
|
|
46
|
-
if (this.config.mode === 'lightweight') {
|
|
47
|
-
return 'lightweight';
|
|
48
|
-
}
|
|
49
|
-
if (this.config.mode === 'full' && this.hasAutoprefixer) {
|
|
50
|
-
return 'full';
|
|
51
|
-
}
|
|
52
|
-
if (this.config.mode === 'auto') {
|
|
53
|
-
return this.hasAutoprefixer ? 'full' : 'lightweight';
|
|
54
|
-
}
|
|
55
|
-
return 'lightweight';
|
|
56
|
-
}
|
|
57
|
-
async process(cssString, options = {}) {
|
|
58
|
-
if (!this.config.enabled) {
|
|
59
|
-
return { css: cssString, map: null };
|
|
60
|
-
}
|
|
61
|
-
try {
|
|
62
|
-
const mapOptions = {
|
|
63
|
-
inline: this.config.sourceMapInline,
|
|
64
|
-
annotation: false,
|
|
65
|
-
sourcesContent: true
|
|
66
|
-
};
|
|
67
|
-
if (this.prefixerMode === 'full') {
|
|
68
|
-
return await this.processWithAutoprefixer(cssString, options, mapOptions);
|
|
69
|
-
}
|
|
70
|
-
return await this.processWithBuiltIn(cssString, options, mapOptions);
|
|
71
|
-
} catch (err) {
|
|
72
|
-
console.error('Prefixer error:', err.message);
|
|
73
|
-
return { css: cssString, map: null };
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
async processWithAutoprefixer(cssString, options, mapOptions) {
|
|
77
|
-
const from = options.from || 'input.css';
|
|
78
|
-
const to = options.to || 'output.css';
|
|
79
|
-
const result = await postcss([
|
|
80
|
-
autoprefixer({ overrideBrowserslist: this.config.browsers })
|
|
81
|
-
]).process(cssString, {
|
|
82
|
-
from,
|
|
83
|
-
to,
|
|
84
|
-
map: this.config.sourceMap ? mapOptions : false
|
|
85
|
-
});
|
|
86
|
-
return {
|
|
87
|
-
css: result.css,
|
|
88
|
-
map: result.map ? result.map.toString() : null
|
|
89
|
-
};
|
|
90
|
-
}
|
|
91
|
-
async processWithBuiltIn(cssString, options, mapOptions) {
|
|
92
|
-
if (!this.hasBuiltInDeps) {
|
|
93
|
-
return { css: cssString, map: null };
|
|
94
|
-
}
|
|
95
|
-
this.targetBrowsers = browserslist(this.config.browsers);
|
|
96
|
-
const from = options.from || 'input.css';
|
|
97
|
-
const to = options.to || 'output.css';
|
|
98
|
-
const result = await postcss([
|
|
99
|
-
this.createBuiltInPlugin()
|
|
100
|
-
]).process(cssString, {
|
|
101
|
-
from,
|
|
102
|
-
to,
|
|
103
|
-
map: this.config.sourceMap ? mapOptions : false
|
|
104
|
-
});
|
|
105
|
-
return {
|
|
106
|
-
css: result.css,
|
|
107
|
-
map: result.map ? result.map.toString() : null
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
createBuiltInPlugin() {
|
|
111
|
-
return (root) => {
|
|
112
|
-
root.walkDecls(decl => {
|
|
113
|
-
this.processBuiltInDeclaration(decl);
|
|
114
|
-
});
|
|
115
|
-
};
|
|
116
|
-
}
|
|
117
|
-
processBuiltInDeclaration(decl) {
|
|
118
|
-
const { prop, value } = decl;
|
|
119
|
-
if (this.commonProperties.includes(prop)) {
|
|
120
|
-
this.addPrefixesFromCaniuse(decl);
|
|
121
|
-
}
|
|
122
|
-
if (this.specialValues[prop]?.includes(value)) {
|
|
123
|
-
this.addSpecialValuePrefixes(decl);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
addPrefixesFromCaniuse(decl) {
|
|
127
|
-
if (!this.caniuseData) return;
|
|
128
|
-
const feature = this.findFeature(decl.prop);
|
|
129
|
-
if (!feature) return;
|
|
130
|
-
const prefixes = new Set();
|
|
131
|
-
this.targetBrowsers.forEach(browser => {
|
|
132
|
-
const [id, versionStr] = browser.split(' ');
|
|
133
|
-
const version = parseFloat(versionStr.split('-')[0]);
|
|
134
|
-
const stats = feature.stats[id];
|
|
135
|
-
if (stats) {
|
|
136
|
-
const versions = Object.keys(stats)
|
|
137
|
-
.map(v => parseFloat(v.split('-')[0]))
|
|
138
|
-
.filter(v => !isNaN(v))
|
|
139
|
-
.sort((a, b) => a - b);
|
|
140
|
-
const closestVersion = versions.find(v => v <= version) || versions[0];
|
|
141
|
-
if (closestVersion) {
|
|
142
|
-
const support = stats[closestVersion.toString()];
|
|
143
|
-
if (support && support.includes('x')) {
|
|
144
|
-
const prefix = this.browserPrefixMap[id.split('-')[0]];
|
|
145
|
-
if (prefix) prefixes.add(prefix);
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
prefixes.forEach(prefix => {
|
|
152
|
-
decl.cloneBefore({
|
|
153
|
-
prop: `-${prefix}-${decl.prop}`,
|
|
154
|
-
value: decl.value
|
|
155
|
-
});
|
|
156
|
-
});
|
|
157
|
-
}
|
|
158
|
-
addSpecialValuePrefixes(decl) {
|
|
159
|
-
const { prop, value } = decl;
|
|
160
|
-
if (prop === 'display') {
|
|
161
|
-
if (value === 'flex' || value === 'inline-flex') {
|
|
162
|
-
decl.cloneBefore({ prop: 'display', value: `-webkit-${value}` });
|
|
163
|
-
decl.cloneBefore({
|
|
164
|
-
prop: 'display',
|
|
165
|
-
value: value === 'flex' ? '-ms-flexbox' : '-ms-inline-flexbox'
|
|
166
|
-
});
|
|
167
|
-
}
|
|
168
|
-
if (value === 'grid' || value === 'inline-grid') {
|
|
169
|
-
decl.cloneBefore({
|
|
170
|
-
prop: 'display',
|
|
171
|
-
value: value === 'grid' ? '-ms-grid' : '-ms-inline-grid'
|
|
172
|
-
});
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
if (prop === 'background-clip' && value === 'text') {
|
|
176
|
-
decl.cloneBefore({ prop: '-webkit-background-clip', value: 'text' });
|
|
177
|
-
}
|
|
178
|
-
if (prop === 'position' && value === 'sticky') {
|
|
179
|
-
decl.cloneBefore({ prop: 'position', value: '-webkit-sticky' });
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
findFeature(property) {
|
|
184
|
-
if (!this.caniuseData) return null;
|
|
185
|
-
const featureMap = {
|
|
186
|
-
'transform': 'transforms2d',
|
|
187
|
-
'transform-origin': 'transforms2d',
|
|
188
|
-
'transform-style': 'transforms3d',
|
|
189
|
-
'perspective': 'transforms3d',
|
|
190
|
-
'backface-visibility': 'transforms3d',
|
|
191
|
-
'transition': 'css-transitions',
|
|
192
|
-
'animation': 'css-animation',
|
|
193
|
-
'backdrop-filter': 'backdrop-filter',
|
|
194
|
-
'filter': 'css-filters',
|
|
195
|
-
'user-select': 'user-select-none',
|
|
196
|
-
'appearance': 'css-appearance',
|
|
197
|
-
'mask-image': 'css-masks',
|
|
198
|
-
'box-shadow': 'css-boxshadow',
|
|
199
|
-
'border-radius': 'border-radius',
|
|
200
|
-
'text-fill-color': 'text-stroke',
|
|
201
|
-
'text-stroke': 'text-stroke',
|
|
202
|
-
'background-clip': 'background-img-opts',
|
|
203
|
-
'flex': 'flexbox',
|
|
204
|
-
'flex-grow': 'flexbox',
|
|
205
|
-
'flex-shrink': 'flexbox',
|
|
206
|
-
'flex-basis': 'flexbox',
|
|
207
|
-
'justify-content': 'flexbox',
|
|
208
|
-
'align-items': 'flexbox',
|
|
209
|
-
'grid': 'css-grid',
|
|
210
|
-
'grid-template': 'css-grid',
|
|
211
|
-
'grid-column': 'css-grid',
|
|
212
|
-
'grid-row': 'css-grid'
|
|
213
|
-
};
|
|
214
|
-
const featureId = featureMap[property];
|
|
215
|
-
return featureId ? this.caniuseData[featureId] : null;
|
|
216
|
-
}
|
|
217
|
-
getCommonProperties() {
|
|
218
|
-
return [
|
|
219
|
-
'transform', 'transform-origin', 'transform-style',
|
|
220
|
-
'transition', 'transition-property', 'transition-duration', 'transition-timing-function',
|
|
221
|
-
'animation', 'animation-name', 'animation-duration', 'animation-timing-function',
|
|
222
|
-
'animation-delay', 'animation-iteration-count', 'animation-direction',
|
|
223
|
-
'animation-fill-mode', 'animation-play-state',
|
|
224
|
-
'backdrop-filter', 'filter',
|
|
225
|
-
'user-select', 'appearance',
|
|
226
|
-
'text-fill-color', 'text-stroke', 'text-stroke-color', 'text-stroke-width',
|
|
227
|
-
'background-clip',
|
|
228
|
-
'mask-image', 'mask-clip', 'mask-composite', 'mask-origin',
|
|
229
|
-
'mask-position', 'mask-repeat', 'mask-size',
|
|
230
|
-
'box-shadow', 'border-radius', 'box-sizing',
|
|
231
|
-
'display', 'flex', 'flex-grow', 'flex-shrink', 'flex-basis',
|
|
232
|
-
'justify-content', 'align-items', 'align-self', 'align-content',
|
|
233
|
-
'grid', 'grid-template', 'grid-column', 'grid-row'
|
|
234
|
-
];
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
module.exports = ChainCSSPrefixer;
|
package/node/strVal.js
DELETED
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
const strVal = {
|
|
2
|
-
userConf: `// ChainCSS Configuration
|
|
3
|
-
// Generated: ${new Date().toISOString()}
|
|
4
|
-
|
|
5
|
-
module.exports = {
|
|
6
|
-
atomic: {
|
|
7
|
-
enabled: true, // Enable atomic CSS optimization
|
|
8
|
-
threshold: 3, // Minimum usage count for atomic conversion
|
|
9
|
-
naming: 'hash', // 'hash' (c_3b82f6) or 'readable' (bg-blue-500)
|
|
10
|
-
cache: true, // Cache atomic classes between builds
|
|
11
|
-
cachePath: './.chaincss-cache',
|
|
12
|
-
minify: true, // Minify CSS output
|
|
13
|
-
mode: 'hybrid', // 'atomic' | 'standard' | 'hybrid'
|
|
14
|
-
alwaysAtomic: [], // Force these properties to be atomic
|
|
15
|
-
neverAtomic: [ // Never make these properties atomic
|
|
16
|
-
'content', 'animation', 'transition', 'keyframes',
|
|
17
|
-
'counterIncrement', 'counterReset'
|
|
18
|
-
],
|
|
19
|
-
outputStrategy: 'component-first',
|
|
20
|
-
frameworkOutput: {
|
|
21
|
-
react: false, // Generate React hooks
|
|
22
|
-
vue: false, // Generate Vue composables
|
|
23
|
-
vanilla: true // Generate vanilla JS class map
|
|
24
|
-
},
|
|
25
|
-
preserveSelectors: false, // Keep original selector names in comments
|
|
26
|
-
verbose: true // Show detailed atomic optimization stats
|
|
27
|
-
},
|
|
28
|
-
prefixer: {
|
|
29
|
-
enabled: true,
|
|
30
|
-
mode: 'auto', // 'auto' | 'always' | 'never'
|
|
31
|
-
browsers: ['> 0.5%', 'last 2 versions', 'not dead'],
|
|
32
|
-
sourceMap: true,
|
|
33
|
-
sourceMapInline: false
|
|
34
|
-
}
|
|
35
|
-
};
|
|
36
|
-
`,
|
|
37
|
-
cli_opt_guide: `
|
|
38
|
-
ChainCSS - JavaScript-powered CSS preprocessor
|
|
39
|
-
|
|
40
|
-
Usage:
|
|
41
|
-
chaincss <inputFile> <outputFile> [options]
|
|
42
|
-
|
|
43
|
-
Options:
|
|
44
|
-
--watch Watch for changes and auto-recompile
|
|
45
|
-
--no-prefix Disable automatic prefixing
|
|
46
|
-
--prefixer-mode <mode> Set prefixer mode (auto|lightweight|full)
|
|
47
|
-
--browsers <list> Browser support list (comma-separated)
|
|
48
|
-
--no-source-map Disable source maps
|
|
49
|
-
--source-map-inline Use inline source maps
|
|
50
|
-
|
|
51
|
-
# Atomic CSS Optimization
|
|
52
|
-
--atomic Enable atomic CSS optimization
|
|
53
|
-
--atomic-mode <mode> Atomic mode: atomic, standard, hybrid (default: hybrid)
|
|
54
|
-
--atomic-naming <scheme> Naming scheme: hash, readable (default: hash)
|
|
55
|
-
--atomic-verbose Show detailed atomic optimization stats
|
|
56
|
-
--preserve-selectors Keep original selector names in comments
|
|
57
|
-
--no-atomic-cache Disable atomic CSS cache
|
|
58
|
-
|
|
59
|
-
# Output Control
|
|
60
|
-
--no-minify Disable CSS minification
|
|
61
|
-
|
|
62
|
-
# Help
|
|
63
|
-
--help, -h Show this help message
|
|
64
|
-
--version, -v Show version number
|
|
65
|
-
|
|
66
|
-
Examples:
|
|
67
|
-
# Basic compilation
|
|
68
|
-
chaincss style.jcss dist/
|
|
69
|
-
|
|
70
|
-
# Watch mode for development
|
|
71
|
-
chaincss style.jcss dist/ --watch
|
|
72
|
-
|
|
73
|
-
# Atomic CSS optimization
|
|
74
|
-
chaincss style.jcss dist/ --atomic
|
|
75
|
-
|
|
76
|
-
# With custom naming scheme
|
|
77
|
-
chaincss style.jcss dist/ --atomic --atomic-naming readable
|
|
78
|
-
|
|
79
|
-
# Verbose output for debugging
|
|
80
|
-
chaincss style.jcss dist/ --atomic --atomic-verbose
|
|
81
|
-
|
|
82
|
-
# Custom browser support
|
|
83
|
-
chaincss style.jcss dist/ --browsers "> 1%, last 2 versions, not dead"
|
|
84
|
-
|
|
85
|
-
Notes:
|
|
86
|
-
- Atomic CSS optimization reduces CSS size by reusing common style patterns
|
|
87
|
-
- Use --watch during development for instant updates
|
|
88
|
-
- Use --atomic for production builds to optimize CSS bundle size
|
|
89
|
-
`
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
module.exports = strVal;
|
package/node/theme-validator.js
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
|
|
4
|
-
export function validateThemeFiles(configPath) {
|
|
5
|
-
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
6
|
-
|
|
7
|
-
if (!config.themes) return;
|
|
8
|
-
|
|
9
|
-
const { contract, themes } = config;
|
|
10
|
-
|
|
11
|
-
console.log('\nValidating Theme Contract...\n');
|
|
12
|
-
|
|
13
|
-
const errors = [];
|
|
14
|
-
|
|
15
|
-
themes.forEach((theme, index) => {
|
|
16
|
-
const themeName = theme.name || `theme-${index}`;
|
|
17
|
-
try {
|
|
18
|
-
validateTheme(contract, theme.values);
|
|
19
|
-
console.log(`${themeName}: Valid`);
|
|
20
|
-
} catch (err) {
|
|
21
|
-
errors.push(`${themeName}: ${err.message}`);
|
|
22
|
-
}
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
if (errors.length > 0) {
|
|
26
|
-
console.error('\nTheme Contract Validation Failed:\n');
|
|
27
|
-
errors.forEach(err => console.error(err));
|
|
28
|
-
process.exit(1);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
console.log('\nAll themes valid!\n');
|
|
32
|
-
}
|
package/shared/theme-contract.js
DELETED
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
export function createThemeContract(contractShape) {
|
|
2
|
-
// Store the contract for validation
|
|
3
|
-
const contract = contractShape;
|
|
4
|
-
|
|
5
|
-
// Create a proxy that validates token access
|
|
6
|
-
const contractProxy = new Proxy(contract, {
|
|
7
|
-
get(target, prop) {
|
|
8
|
-
if (prop === '__isContract') return true;
|
|
9
|
-
if (prop === '__validate') return (theme) => validateTheme(contract, theme);
|
|
10
|
-
return target[prop];
|
|
11
|
-
}
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
return contractProxy;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export function validateTheme(contract, theme, path = '') {
|
|
18
|
-
const errors = [];
|
|
19
|
-
|
|
20
|
-
function validate(contractPart, themePart, currentPath) {
|
|
21
|
-
if (typeof contractPart === 'object' && contractPart !== null) {
|
|
22
|
-
// Check if theme has all required keys
|
|
23
|
-
const requiredKeys = Object.keys(contractPart);
|
|
24
|
-
const themeKeys = Object.keys(themePart || {});
|
|
25
|
-
|
|
26
|
-
requiredKeys.forEach(key => {
|
|
27
|
-
const newPath = currentPath ? `${currentPath}.${key}` : key;
|
|
28
|
-
|
|
29
|
-
if (!themePart || !themePart.hasOwnProperty(key)) {
|
|
30
|
-
errors.push(` Missing required token: "${newPath}"`);
|
|
31
|
-
} else {
|
|
32
|
-
validate(contractPart[key], themePart[key], newPath);
|
|
33
|
-
}
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
// Warn about extra keys (optional, could be allowed)
|
|
37
|
-
themeKeys.forEach(key => {
|
|
38
|
-
if (!contractPart.hasOwnProperty(key)) {
|
|
39
|
-
const newPath = currentPath ? `${currentPath}.${key}` : key;
|
|
40
|
-
console.warn(` Extra token not in contract: "${newPath}"`);
|
|
41
|
-
}
|
|
42
|
-
});
|
|
43
|
-
} else {
|
|
44
|
-
// Leaf node - just check type (optional)
|
|
45
|
-
if (typeof themePart !== 'string') {
|
|
46
|
-
errors.push(` Token "${currentPath}" must be a string, got ${typeof themePart}`);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
validate(contract, theme, path);
|
|
52
|
-
|
|
53
|
-
if (errors.length > 0) {
|
|
54
|
-
throw new Error(`Theme Contract Validation Failed:\n${errors.join('\n')}`);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
return true;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export function createTheme(contract, themeValues) {
|
|
61
|
-
// Validate at creation time
|
|
62
|
-
if (contract.__isContract) {
|
|
63
|
-
contract.__validate(themeValues);
|
|
64
|
-
} else {
|
|
65
|
-
validateTheme(contract, themeValues);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// Create the actual theme tokens
|
|
69
|
-
const tokens = {};
|
|
70
|
-
|
|
71
|
-
function buildTokens(contractPart, themePart, target, path = '') {
|
|
72
|
-
Object.keys(contractPart).forEach(key => {
|
|
73
|
-
const newPath = path ? `${path}.${key}` : key;
|
|
74
|
-
|
|
75
|
-
if (typeof contractPart[key] === 'object' && contractPart[key] !== null) {
|
|
76
|
-
target[key] = {};
|
|
77
|
-
buildTokens(contractPart[key], themePart[key] || {}, target[key], newPath);
|
|
78
|
-
} else {
|
|
79
|
-
target[key] = themePart[key];
|
|
80
|
-
}
|
|
81
|
-
});
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
buildTokens(contract, themeValues, tokens);
|
|
85
|
-
|
|
86
|
-
// Add getter method
|
|
87
|
-
tokens.get = (path) => {
|
|
88
|
-
const parts = path.split('.');
|
|
89
|
-
let current = tokens;
|
|
90
|
-
for (const part of parts) {
|
|
91
|
-
if (current === undefined) return undefined;
|
|
92
|
-
current = current[part];
|
|
93
|
-
}
|
|
94
|
-
return current;
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
return tokens;
|
|
98
|
-
}
|