@astryxdesign/cli 0.4.0 → 0.4.1

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 CHANGED
@@ -1,5 +1,19 @@
1
1
  # @xds/cli
2
2
 
3
+ # 0.4.1
4
+
5
+ #### Fixes
6
+
7
+ - `astryx theme build` no longer warns `Unknown prop` for documented state override keys. Component docs declare state-driven selectors under `theming.targets[].states` (`radio` → `checked`/`disabled`, `calendar-day` → `today`/`selected`, …), but override validation only loaded `visualProps`, so the state syntax the Theming Infrastructure wiki documents — `components: {radio: {checked: {...}}}` — warned on every build. The CSS was always generated correctly; only the warning was wrong. 30 targets across core were affected (#4778).
8
+
9
+ #### Contributors
10
+
11
+ Thanks to everyone who contributed to this release:
12
+
13
+ - @cixzhang
14
+
15
+ ---
16
+
3
17
  # 0.4.0
4
18
 
5
19
  #### Breaking Changes
@@ -17,7 +17,7 @@ export const doc = {
17
17
  'One entry point for the template family: with no name it lists the discovered ' +
18
18
  "templates; with a name it returns that template's source, a layout skeleton, or " +
19
19
  'scaffolds it into the project. Templates are discovered across core, external ' +
20
- 'packages, and integrations, so the same id can appear in more than one place ' +
20
+ 'packages, and integrations, so the same id can appear in more than one place; ' +
21
21
  'narrow an ambiguous name with type and/or package.',
22
22
  importPath: '@astryxdesign/cli/api',
23
23
  signature:
@@ -110,7 +110,7 @@ export const doc = {
110
110
  },
111
111
  {
112
112
  code: 'ERR_AMBIGUOUS_TEMPLATE',
113
- when: 'the name matches more than one template across kinds/packages narrow it with type and/or package',
113
+ when: 'the name matches more than one template across kinds/packages; narrow it with type and/or package',
114
114
  },
115
115
  {
116
116
  code: 'ERR_NO_SOURCE',
@@ -815,7 +815,11 @@ ${iconType}export declare const ${toIdentifier(themeDef.name)}Theme: DefinedThem
815
815
  // =============================================================================
816
816
 
817
817
  /**
818
- * Load known theme target keys and visual props from core component docs.
818
+ * Load known theme target keys from core component docs: the visual props AND
819
+ * the runtime states each target reflects. Both are legal override keys — the
820
+ * Theming Infrastructure wiki documents `radio: {checked}` and
821
+ * `'calendar-day': {today, selected}` alongside `button: {'variant:secondary'}`
822
+ * — so validation has to know both or documented syntax warns as unknown.
819
823
  * Returns null when docs are unavailable so validation can skip unknown-key
820
824
  * warnings rather than guessing from a second registry.
821
825
  *
@@ -854,9 +858,10 @@ async function loadKnownComponents() {
854
858
  if (typeof className !== 'string') continue;
855
859
  const key = className.replace(/^astryx-/, '');
856
860
  if (!key) continue;
857
- const props = Array.isArray(target.visualProps)
858
- ? target.visualProps.filter((/** @type {unknown} */ p) => typeof p === 'string')
859
- : [];
861
+ const props = [target.visualProps, target.states]
862
+ .filter(list => Array.isArray(list))
863
+ .flat()
864
+ .filter((/** @type {unknown} */ p) => typeof p === 'string');
860
865
  targets[key] = [...new Set([...(targets[key] || []), ...props])];
861
866
  }
862
867
  }
@@ -922,7 +927,8 @@ async function validateComponentOverrides(themeDef) {
922
927
  continue;
923
928
  }
924
929
 
925
- // Check prop names in prop:value keys
930
+ // Check prop/state names in the override keys. A key is either `base`, a
931
+ // `prop:value` pair (possibly `+`-joined), or a bare state name.
926
932
  const knownProps = knownComponents[component];
927
933
  for (const key of Object.keys(rules)) {
928
934
  if (key === 'base') continue;
@@ -934,8 +940,8 @@ async function validateComponentOverrides(themeDef) {
934
940
  if (prop && !knownProps.includes(prop)) {
935
941
  const hint =
936
942
  knownProps.length > 0
937
- ? ` Known props: ${knownProps.join(', ')}`
938
- : ' This component has no variant props.';
943
+ ? ` Known props/states: ${knownProps.join(', ')}`
944
+ : ' This component has no variant props or states.';
939
945
  warnings.push(
940
946
  `Unknown prop "${prop}" on component "${component}".${hint}`,
941
947
  );
@@ -222,3 +222,54 @@ describe('themeBuild() — check mode', () => {
222
222
  expect(result?.data.stale).toEqual([]);
223
223
  });
224
224
  });
225
+
226
+ describe('themeBuild() — component override validation', () => {
227
+ it('accepts documented state keys without an "Unknown prop" warning', async () => {
228
+ // The state-key syntax the Theming Infrastructure wiki documents —
229
+ // `radio: {checked}`, `calendar-day: {today, selected}` — is declared in
230
+ // each component's doc under `theming.targets[].states`, not
231
+ // `visualProps`. `loadKnownComponents()` read only `visualProps`, so every
232
+ // one of these warned "Unknown prop": documented syntax that looked broken.
233
+ const themeFile = path.join(tmpDir, 'states.mjs');
234
+ fs.writeFileSync(
235
+ themeFile,
236
+ `export default {
237
+ name: 'states',
238
+ tokens: {'--color-bg': '#0a0a0a'},
239
+ components: {
240
+ radio: {
241
+ checked: {borderColor: 'var(--color-accent)'},
242
+ 'checked+disabled': {opacity: '0.5'},
243
+ },
244
+ 'calendar-day': {
245
+ today: {fontWeight: '700'},
246
+ selected: {backgroundColor: 'var(--color-accent)'},
247
+ },
248
+ },
249
+ };\n`,
250
+ );
251
+
252
+ const result = await themeBuild('states.mjs', {}, {cwd: tmpDir});
253
+
254
+ expect(result?.data.warnings).toEqual([]);
255
+ });
256
+
257
+ it('still warns on a key that is neither a visual prop nor a state', async () => {
258
+ // Widening the known set to states must not turn the guard off.
259
+ const themeFile = path.join(tmpDir, 'bogus.mjs');
260
+ fs.writeFileSync(
261
+ themeFile,
262
+ `export default {
263
+ name: 'bogus',
264
+ tokens: {'--color-bg': '#0a0a0a'},
265
+ components: {radio: {notAState: {opacity: '0.5'}}},
266
+ };\n`,
267
+ );
268
+
269
+ const result = await themeBuild('bogus.mjs', {}, {cwd: tmpDir});
270
+
271
+ expect(result?.data.warnings).toEqual([
272
+ expect.stringContaining('Unknown prop "notAState" on component "radio"'),
273
+ ]);
274
+ });
275
+ });
@@ -359,7 +359,7 @@ function SaveButton() {
359
359
  {
360
360
  type: 'heading',
361
361
  level: 4,
362
- text: '2. Directional icons mirror with CSS, not a name-swap',
362
+ text: '2. Directional icons: mirror with CSS, not a name-swap',
363
363
  },
364
364
  {
365
365
  type: 'prose',
@@ -384,7 +384,7 @@ function NextButton() {
384
384
  {
385
385
  type: 'heading',
386
386
  level: 4,
387
- text: '3. Behavioral logic read the DOM lazily, on the event',
387
+ text: '3. Behavioral logic: read the DOM lazily, on the event',
388
388
  },
389
389
  {
390
390
  type: 'prose',
@@ -393,7 +393,7 @@ function NextButton() {
393
393
  {
394
394
  type: 'heading',
395
395
  level: 4,
396
- text: '4. useDirection() context the last resort',
396
+ text: '4. useDirection() context: the last resort',
397
397
  },
398
398
  {
399
399
  type: 'prose',
@@ -3,7 +3,7 @@
3
3
  // AUTO-GENERATED — do not edit manually.
4
4
  // Source: packages/core/src/theme/tokens.stylex.ts
5
5
  // Run: node scripts/generate-token-docs.mjs
6
- // Total: 184 tokens across 12 categories.
6
+ // Total: 188 tokens across 13 categories.
7
7
 
8
8
  /** @type {import('@astryxdesign/cli/authoring').ReferenceDoc} */
9
9
 
@@ -561,6 +561,40 @@ export const docs = {
561
561
  ],
562
562
  "previewType": "border-line"
563
563
  },
564
+ {
565
+ "title": "Focus Tokens",
566
+ "content": [
567
+ {
568
+ "type": "prose",
569
+ "text": "The keyboard focus ring, shared by every component that draws one. Override these to restyle focus across the system."
570
+ },
571
+ {
572
+ "type": "table",
573
+ "headers": [
574
+ "Token",
575
+ "Value"
576
+ ],
577
+ "rows": [
578
+ [
579
+ "--focus-outline-width",
580
+ "2px"
581
+ ],
582
+ [
583
+ "--focus-outline-style",
584
+ "solid"
585
+ ],
586
+ [
587
+ "--focus-outline-color",
588
+ "var(--color-accent)"
589
+ ],
590
+ [
591
+ "--focus-outline-offset",
592
+ "3px"
593
+ ]
594
+ ]
595
+ }
596
+ ]
597
+ },
564
598
  {
565
599
  "title": "Radius Tokens",
566
600
  "content": [
@@ -60,7 +60,7 @@ export const doc = {
60
60
  {
61
61
  type: 'prose',
62
62
  text:
63
- "Identity the integration's name and version comes from the " +
63
+ "Identity, the integration's name and version, comes from the " +
64
64
  "package's package.json, not from this manifest. The manifest only " +
65
65
  'declares where the CLI finds each kind of artifact.',
66
66
  },
@@ -74,6 +74,15 @@ function renderedClassLiterals() {
74
74
  classes.add(m[1]);
75
75
  }
76
76
  }
77
+ // A component can also name its popup SURFACE — an element usePopover
78
+ // owns, so the class cannot be rendered from the component itself.
79
+ // `surfaceTarget: 'x'` puts `astryx-x` on that surface, which makes it
80
+ // just as rendered as a direct themeProps() call.
81
+ const surfaceRe = /surfaceTarget:\s*'([^']+)'/g;
82
+ let sm;
83
+ while ((sm = surfaceRe.exec(text)) !== null) {
84
+ classes.add(sm[1]);
85
+ }
77
86
  // Renamed targets emit their old name too, via themeProps'
78
87
  // `legacyNames`. Those classes are just as rendered as the primary
79
88
  // one, so a doc entry for the old name is still backed by real output.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astryxdesign/cli",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
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",