@astryxdesign/cli 0.6.3-canary.3ee796c → 0.6.3-canary.45a9bf5
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/api/theme/build/build.mjs +20 -6
- package/api/theme/build/build.test.mjs +127 -0
- package/package.json +9 -9
|
@@ -12,9 +12,10 @@
|
|
|
12
12
|
* - A .d.ts (plus an optional .variants.d.ts for custom prop values)
|
|
13
13
|
*
|
|
14
14
|
* It performs the writes and returns a `theme.build` receipt — its `warnings`
|
|
15
|
-
* carry override problems
|
|
16
|
-
*
|
|
17
|
-
*
|
|
15
|
+
* carry override problems, every declaration core's generator dropped because
|
|
16
|
+
* its value could not stay one CSS declaration, and any fonts the theme names
|
|
17
|
+
* but does not load (font-warning.mjs) — or `null` when the theme produced no
|
|
18
|
+
* CSS (nothing to build). Errors throw AstryxError (with
|
|
18
19
|
* a stable code). Human progress is emitted through the shared `logger`
|
|
19
20
|
* (silent by default), so the CLI keeps its exact output while a programmatic
|
|
20
21
|
* caller stays quiet.
|
|
@@ -2303,7 +2304,15 @@ async function themeBuildInternal(
|
|
|
2303
2304
|
const scopeSelector = themeScopeStart(themeDef.name);
|
|
2304
2305
|
const scopeTo = THEME_SCOPE_TO;
|
|
2305
2306
|
|
|
2306
|
-
|
|
2307
|
+
// Older cores ignore this optional collector. Current core writes the same
|
|
2308
|
+
// warning text here that runtime callers receive on the console.
|
|
2309
|
+
/** @type {string[]} */
|
|
2310
|
+
const droppedDeclarations = [];
|
|
2311
|
+
|
|
2312
|
+
const {component, prose} = _generateThemeRulesSplit(
|
|
2313
|
+
resolvedTheme,
|
|
2314
|
+
droppedDeclarations,
|
|
2315
|
+
);
|
|
2307
2316
|
const cssParts = [];
|
|
2308
2317
|
// Prose element defaults always ship — the `<Theme>` runtime
|
|
2309
2318
|
// (generateThemeCSS) always emits them, so the build must too, or the
|
|
@@ -2322,7 +2331,7 @@ async function themeBuildInternal(
|
|
|
2322
2331
|
let adaptationCss;
|
|
2323
2332
|
try {
|
|
2324
2333
|
adaptationCss = _generateAdaptationCSS
|
|
2325
|
-
? _generateAdaptationCSS(resolvedTheme)
|
|
2334
|
+
? _generateAdaptationCSS(resolvedTheme, droppedDeclarations)
|
|
2326
2335
|
: {component: '', prose: ''};
|
|
2327
2336
|
} catch (error) {
|
|
2328
2337
|
const message =
|
|
@@ -2368,11 +2377,16 @@ async function themeBuildInternal(
|
|
|
2368
2377
|
// adaptations on the same resolved leaf.
|
|
2369
2378
|
let onMediaCss = '';
|
|
2370
2379
|
if (_generateOnMediaCSS) {
|
|
2371
|
-
onMediaCss = _generateOnMediaCSS(resolvedTheme);
|
|
2380
|
+
onMediaCss = _generateOnMediaCSS(resolvedTheme, droppedDeclarations);
|
|
2372
2381
|
if (onMediaCss) {
|
|
2373
2382
|
cssParts.push(`@layer astryx-theme {\n${onMediaCss}\n}`);
|
|
2374
2383
|
}
|
|
2375
2384
|
}
|
|
2385
|
+
for (const message of droppedDeclarations) {
|
|
2386
|
+
const w = `Declaration ${message}. The generated CSS omits it; fix the value in the theme source.`;
|
|
2387
|
+
warningMessages.push(w);
|
|
2388
|
+
logger.warn(` ⚠ ${w}`);
|
|
2389
|
+
}
|
|
2376
2390
|
if (cssParts.length === 0) {
|
|
2377
2391
|
logger.log('No overrides found — nothing to build.');
|
|
2378
2392
|
return null;
|
|
@@ -206,6 +206,133 @@ describe('themeBuild() — receipt', () => {
|
|
|
206
206
|
});
|
|
207
207
|
});
|
|
208
208
|
|
|
209
|
+
describe('themeBuild() — dropped declarations reach the receipt', () => {
|
|
210
|
+
// Core's generator refuses a declaration whose value would end it early
|
|
211
|
+
// (an unquoted `;` or brace, an unclosed string/comment/url). The runtime
|
|
212
|
+
// says so on the console; a build has a receipt, so every drop must land in
|
|
213
|
+
// `warnings` — a programmatic caller otherwise sees `warnings: []` while the
|
|
214
|
+
// CSS silently omits a value the generated JS still carries.
|
|
215
|
+
it('reports every dropped declaration, from every generator path, in warnings', async () => {
|
|
216
|
+
const themeFile = path.join(tmpDir, 'dropped.mjs');
|
|
217
|
+
fs.writeFileSync(
|
|
218
|
+
themeFile,
|
|
219
|
+
`export default {
|
|
220
|
+
name: 'dropped',
|
|
221
|
+
tokens: {
|
|
222
|
+
'--color-bg': '#0a0a0a } body { background: url(https://example.com/token) ',
|
|
223
|
+
},
|
|
224
|
+
components: {
|
|
225
|
+
button: {
|
|
226
|
+
'variant:secondary': {
|
|
227
|
+
backgroundColor: 'red; background-image: url(https://example.com/component)',
|
|
228
|
+
':hover': {color: '"https://example.com/pseudo'},
|
|
229
|
+
},
|
|
230
|
+
},
|
|
231
|
+
},
|
|
232
|
+
onDark: {
|
|
233
|
+
tokens: {'--color-bg': 'url(https://example.com/dark'},
|
|
234
|
+
},
|
|
235
|
+
adaptations: {
|
|
236
|
+
rules: [
|
|
237
|
+
{
|
|
238
|
+
when: {pointer: 'coarse'},
|
|
239
|
+
value: {tokens: {'--color-bg': 'red /* https://example.com/adaptation'}},
|
|
240
|
+
},
|
|
241
|
+
],
|
|
242
|
+
},
|
|
243
|
+
};\n`,
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
247
|
+
let result;
|
|
248
|
+
try {
|
|
249
|
+
result = await themeBuild('dropped.mjs', {}, {cwd: tmpDir});
|
|
250
|
+
// Drops are collected, not printed: the programmatic API stays silent.
|
|
251
|
+
expect(warnSpy).not.toHaveBeenCalled();
|
|
252
|
+
} finally {
|
|
253
|
+
warnSpy.mockRestore();
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const css = fs.readFileSync(path.join(tmpDir, 'dropped.css'), 'utf8');
|
|
257
|
+
expect(css).not.toContain('example.com');
|
|
258
|
+
|
|
259
|
+
const warnings = result?.data.warnings ?? [];
|
|
260
|
+
expect(warnings).toHaveLength(5);
|
|
261
|
+
expect(warnings).toEqual(
|
|
262
|
+
expect.arrayContaining([
|
|
263
|
+
expect.stringMatching(
|
|
264
|
+
/^Declaration dropped "--color-bg" in tokens: an unquoted "}" would close the rule/,
|
|
265
|
+
),
|
|
266
|
+
expect.stringMatching(
|
|
267
|
+
/^Declaration dropped "background-color" in components\.button\["variant:secondary"\]: an unquoted ";" would end the declaration/,
|
|
268
|
+
),
|
|
269
|
+
expect.stringMatching(
|
|
270
|
+
/^Declaration dropped "color" in components\.button\["variant:secondary"\]\[":hover"\]: an unclosed " string/,
|
|
271
|
+
),
|
|
272
|
+
expect.stringMatching(
|
|
273
|
+
/^Declaration dropped "--color-bg" in onDark\.tokens: an unclosed url\(/,
|
|
274
|
+
),
|
|
275
|
+
expect.stringMatching(
|
|
276
|
+
/^Declaration dropped "--color-bg" in adaptations\[0\]\.tokens: an unclosed \/\* comment/,
|
|
277
|
+
),
|
|
278
|
+
]),
|
|
279
|
+
);
|
|
280
|
+
for (const w of warnings) {
|
|
281
|
+
expect(w).toContain('The generated CSS omits it');
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
it('generates CSS for legacy null tokens without losing neighboring declarations', async () => {
|
|
286
|
+
fs.writeFileSync(
|
|
287
|
+
path.join(tmpDir, 'legacy.mjs'),
|
|
288
|
+
`export default {
|
|
289
|
+
name: 'legacy',
|
|
290
|
+
tokens: {'--color-background-body': null, '--spacing-4': 12},
|
|
291
|
+
onDark: {tokens: {'--color-background-body': null}},
|
|
292
|
+
components: {button: {base: {borderRadius: '4px'}}},
|
|
293
|
+
};\n`,
|
|
294
|
+
);
|
|
295
|
+
|
|
296
|
+
const result = await themeBuild('legacy.mjs', {}, {cwd: tmpDir});
|
|
297
|
+
const css = fs.readFileSync(path.join(tmpDir, 'legacy.css'), 'utf8');
|
|
298
|
+
expect(css).toContain('--color-background-body: null;');
|
|
299
|
+
expect(css).toContain('--spacing-4: 12;');
|
|
300
|
+
expect(css).toContain('border-radius: 4px;');
|
|
301
|
+
expect(result?.data.warnings).toEqual([]);
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
it('keeps valid CSS that carries semicolons, and reports nothing', async () => {
|
|
305
|
+
const themeFile = path.join(tmpDir, 'kept.mjs');
|
|
306
|
+
fs.writeFileSync(
|
|
307
|
+
themeFile,
|
|
308
|
+
`export default {
|
|
309
|
+
name: 'kept',
|
|
310
|
+
tokens: {'--font-family-body': 'Gill\\\\ Sans, "Segoe;UI", serif /* ; */'},
|
|
311
|
+
components: {
|
|
312
|
+
button: {
|
|
313
|
+
'variant:secondary': {
|
|
314
|
+
backgroundImage: 'URL(data:image/svg+xml;base64,PHN2Zz4=)',
|
|
315
|
+
WebkitLineClamp: '2',
|
|
316
|
+
},
|
|
317
|
+
},
|
|
318
|
+
},
|
|
319
|
+
};\n`,
|
|
320
|
+
);
|
|
321
|
+
|
|
322
|
+
const result = await themeBuild('kept.mjs', {}, {cwd: tmpDir});
|
|
323
|
+
const css = fs.readFileSync(path.join(tmpDir, 'kept.css'), 'utf8');
|
|
324
|
+
|
|
325
|
+
expect(css).toContain(
|
|
326
|
+
'--font-family-body: Gill\\ Sans, "Segoe;UI", serif /* ; */;',
|
|
327
|
+
);
|
|
328
|
+
expect(css).toContain(
|
|
329
|
+
'background-image: URL(data:image/svg+xml;base64,PHN2Zz4=);',
|
|
330
|
+
);
|
|
331
|
+
expect(css).toContain('-webkit-line-clamp: 2;');
|
|
332
|
+
expect(result?.data.warnings).toEqual([]);
|
|
333
|
+
});
|
|
334
|
+
});
|
|
335
|
+
|
|
209
336
|
describe('themeBuild() — nothing to build', () => {
|
|
210
337
|
it('returns null and writes nothing when the generator yields no CSS', async () => {
|
|
211
338
|
const themeFile = path.join(tmpDir, 'empty.mjs');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astryxdesign/cli",
|
|
3
|
-
"version": "0.6.3-canary.
|
|
3
|
+
"version": "0.6.3-canary.45a9bf5",
|
|
4
4
|
"displayName": "CLI",
|
|
5
5
|
"description": "Scaffold projects, browse templates, generate themes, and get agent-ready docs from the command line.",
|
|
6
6
|
"author": "Meta Open Source",
|
|
@@ -100,10 +100,10 @@
|
|
|
100
100
|
"zod": "^4.4.3"
|
|
101
101
|
},
|
|
102
102
|
"peerDependencies": {
|
|
103
|
-
"@astryxdesign/charts": "0.6.3-canary.
|
|
104
|
-
"@astryxdesign/core": "0.6.3-canary.
|
|
105
|
-
"@astryxdesign/lab": "0.6.3-canary.
|
|
106
|
-
"@astryxdesign/theme-neutral": "0.6.3-canary.
|
|
103
|
+
"@astryxdesign/charts": "0.6.3-canary.45a9bf5",
|
|
104
|
+
"@astryxdesign/core": "0.6.3-canary.45a9bf5",
|
|
105
|
+
"@astryxdesign/lab": "0.6.3-canary.45a9bf5",
|
|
106
|
+
"@astryxdesign/theme-neutral": "0.6.3-canary.45a9bf5",
|
|
107
107
|
"gpt-tokenizer": "^3.4.0"
|
|
108
108
|
},
|
|
109
109
|
"peerDependenciesMeta": {
|
|
@@ -121,10 +121,10 @@
|
|
|
121
121
|
}
|
|
122
122
|
},
|
|
123
123
|
"devDependencies": {
|
|
124
|
-
"@astryxdesign/charts": "0.6.3-canary.
|
|
125
|
-
"@astryxdesign/core": "0.6.3-canary.
|
|
126
|
-
"@astryxdesign/lab": "0.6.3-canary.
|
|
127
|
-
"@astryxdesign/theme-neutral": "0.6.3-canary.
|
|
124
|
+
"@astryxdesign/charts": "0.6.3-canary.45a9bf5",
|
|
125
|
+
"@astryxdesign/core": "0.6.3-canary.45a9bf5",
|
|
126
|
+
"@astryxdesign/lab": "0.6.3-canary.45a9bf5",
|
|
127
|
+
"@astryxdesign/theme-neutral": "0.6.3-canary.45a9bf5",
|
|
128
128
|
"@heroicons/react": "^2.2.0",
|
|
129
129
|
"@stylexjs/stylex": "^0.19.0",
|
|
130
130
|
"@types/babel__core": "^7.20.5",
|