@astryxdesign/cli 0.4.6 → 0.4.7-canary.20bae6c
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 +4 -0
- package/README.md +53 -51
- package/api/docs/docs.doc.mjs +2 -2
- package/api/index.d.mts +1 -1
- package/api/index.mjs +1 -1
- package/api/template/template.doc.mjs +2 -2
- package/api/theme/build/build.mjs +8 -38
- package/api/theme/targets/targets.d.mts +18 -0
- package/api/theme/targets/targets.mjs +87 -0
- package/api/theme/targets/targets.test.mjs +65 -0
- package/api/theme/theme.d.mts +1 -0
- package/api/theme/theme.mjs +3 -1
- package/api/theme/theme.type.d.mts +23 -0
- package/api/theme/theme.type.mjs +21 -1
- package/api/theme/themeTargets.doc.d.mts +11 -0
- package/api/theme/themeTargets.doc.mjs +58 -0
- package/api/theme/themeTemplate.doc.mjs +3 -3
- package/assets/docs/README.md +50 -0
- package/assets/docs/cli-integrations.doc.mjs +4 -4
- package/assets/docs/theme.doc.dense.mjs +1 -1
- package/assets/docs/theme.doc.mjs +1 -1
- package/assets/docs/typography.doc.mjs +2 -2
- package/assets/templates/blocks/components/Dialog/DialogAdaptivePresentation.doc.mjs +25 -0
- package/assets/templates/blocks/components/Dialog/DialogAdaptivePresentation.tsx +167 -0
- package/assets/templates/blocks/components/Dialog/DialogScrollingContent.tsx +1 -1
- package/assets/templates/blocks/components/Step/StepContent.doc.mjs +14 -0
- package/assets/templates/blocks/components/Step/StepContent.tsx +32 -0
- package/assets/templates/blocks/components/Step/StepIndicator.doc.mjs +14 -0
- package/assets/templates/blocks/components/Step/StepIndicator.tsx +60 -0
- package/assets/templates/blocks/components/Step/StepShowcase.doc.mjs +15 -0
- package/assets/templates/blocks/components/Step/StepShowcase.tsx +26 -0
- package/assets/templates/blocks/components/Step/StepStates.doc.mjs +14 -0
- package/assets/templates/blocks/components/Step/StepStates.tsx +46 -0
- package/assets/templates/blocks/components/Stepper/StepperCustomContent.doc.mjs +22 -0
- package/assets/templates/blocks/components/Stepper/StepperCustomContent.tsx +126 -0
- package/assets/templates/blocks/components/Stepper/StepperIndicatorModes.doc.mjs +1 -1
- package/assets/templates/blocks/components/Stepper/StepperIndicatorModes.tsx +17 -5
- package/assets/templates/blocks/components/Stepper/StepperOnTrackHorizontal.doc.mjs +14 -0
- package/assets/templates/blocks/components/Stepper/StepperOnTrackHorizontal.tsx +25 -0
- package/assets/templates/blocks/components/Stepper/StepperOnTrackVertical.doc.mjs +2 -2
- package/assets/templates/blocks/components/Stepper/StepperOnTrackVertical.tsx +1 -1
- package/assets/templates/blocks/components/Stepper/StepperShowcase.doc.mjs +1 -1
- package/assets/templates/blocks/components/Stepper/StepperShowcase.tsx +6 -7
- package/assets/templates/blocks/components/Stepper/StepperStatus.tsx +1 -1
- package/assets/templates/pages/mixed-gallery/page.tsx +12 -3
- package/assets/templates/themes/neutral/neutralTheme.ts +13 -8
- package/clients/cli/commands/build-theme.mjs +85 -0
- package/clients/cli/commands/dialog-adaptive-template.test.mjs +24 -0
- package/clients/cli/commands/theme-targets.behavior.test.mjs +64 -0
- package/clients/cli/commands/theme-targets.doc.mjs +38 -0
- package/clients/cli/commands/theme-template.doc.mjs +2 -2
- package/clients/cli/commands/theme.doc.mjs +4 -2
- package/clients/cli/index.mjs +1 -0
- package/clients/cli/lib/manifest.mjs +2 -0
- package/foundation/discovery/theming-targets.d.mts +48 -0
- package/foundation/discovery/theming-targets.mjs +135 -0
- package/foundation/discovery/theming-targets.test.mjs +127 -0
- package/foundation/response/response-types.doc.mjs +7 -2
- package/package.json +9 -9
- package/assets/templates/blocks/components/Stepper/StepperHorizontal.doc.mjs +0 -14
- package/assets/templates/blocks/components/Stepper/StepperHorizontal.tsx +0 -24
- package/assets/templates/blocks/components/Stepper/StepperMultiStepForm.doc.mjs +0 -14
- package/assets/templates/blocks/components/Stepper/StepperMultiStepForm.tsx +0 -92
- package/assets/templates/blocks/components/Stepper/StepperVerticalOnboarding.doc.mjs +0 -14
- package/assets/templates/blocks/components/Stepper/StepperVerticalOnboarding.tsx +0 -40
package/CHANGELOG.md
CHANGED
package/README.md
CHANGED
|
@@ -383,51 +383,52 @@ Every response has a `type` discriminant. The full set is below (generated from
|
|
|
383
383
|
|
|
384
384
|
<!-- BEGIN GENERATED: response-types -->
|
|
385
385
|
|
|
386
|
-
| Type | What `data` carries
|
|
387
|
-
| --------------------------- |
|
|
388
|
-
| `component.list` | The component catalog grouped by category: `detail` (the level: names \| compact \| full) and `components`, the grouped map of names+package, brief entries, or a full ComponentDoc per entry.
|
|
389
|
-
| `component.detail` | One component's authored ComponentDoc plus ownership metadata (owner package, import specifier, and whether source is available).
|
|
390
|
-
| `component.detail.props` | Just one component's props table (ComponentPropDoc[]).
|
|
391
|
-
| `component.detail.source` | One component's source file, as {component, source}.
|
|
392
|
-
| `component.detail.showcase` | One component's showcase example, as {component, aspectRatio, source}.
|
|
393
|
-
| `component.detail.blocks` | One component's example blocks, as {component, showcase, examples, related} of BlockEntry.
|
|
394
|
-
| `docs.list` | All reference-doc topics as DocsListEntry[] ({topic, description}), in discovery order.
|
|
395
|
-
| `docs.detail` | One topic's full ReferenceDoc, with token-ref blocks inlined.
|
|
396
|
-
| `docs.detail.section` | A single ReferenceSection of a topic: the first whose title contains the section query.
|
|
397
|
-
| `blog.list` | The feed URL plus every post parsed from the RSS feed, each with slug, title, description, date, type, authors, link, and plaintext URL.
|
|
398
|
-
| `blog.detail` | One post's metadata plus the feed URL and the post's full plaintext body.
|
|
399
|
-
| `discover.list` | The configured external packages (name, category, components, version, description); when empty it carries meta.configured to tell "nothing configured" from "nothing discovered".
|
|
400
|
-
| `discover.detail` | A single external package entry, for an @scope/name query.
|
|
401
|
-
| `discover.detail.doc` | The validated ComponentDoc for one external component: an @scope/name/Component query, or a free-text term resolving to exactly one component.
|
|
402
|
-
| `discover.search` | The echoed query plus the matching {package, component} pairs, when a free-text term matches several components.
|
|
403
|
-
| `search` | The echoed query plus a ranked SearchResultEntry[] (domain, name, score, reason, description, follow-up command, and import path where relevant).
|
|
404
|
-
| `build.help` | A marker (`playbook: true`) that the renderer expands into the how-to-build-a-page workflow; emitted when no query is given.
|
|
405
|
-
| `build.kit` | The grouped composition kit: echoed query, hasResults/directMatch flags, the closest page templates, drop-in block patterns, idea-specific components/hooks, and the always-on frame + foundation component-name arrays.
|
|
406
|
-
| `swizzle.list` | The names of swizzlable components discoverable from cwd's @astryxdesign/core.
|
|
407
|
-
| `swizzle.copy` | An eject receipt: component name, owning package, output directory, files-copied count, the written file names, whether any file uses StyleX, and an optional maintainer note.
|
|
408
|
-
| `template.list` | Every discovered template (page + block); each entry carries id, name, description, kind, owning package, optional category and componentsUsed, and readiness flags.
|
|
409
|
-
| `template.show` | The resolved template's raw source plus its description, kind, and the component names it composes.
|
|
410
|
-
| `template.skeleton` | A layout skeleton (structural tags with spatial annotations) plus the template's description and the components it composes.
|
|
411
|
-
| `template.copy` | A scaffold receipt: template id, output directory, written file name, and file count.
|
|
412
|
-
| `template.cdn` | A write receipt for the no-build-step CDN starter page: the path (relative to cwd), the Astryx version every CDN URL was pinned to, whether it was written, and the reason it was not
|
|
413
|
-
| `hook.list` | The hook catalog grouped by category: `detail` (the level: names \| compact \| full) and `components`, the grouped map of hook names, brief entries, or a full HookDoc per entry.
|
|
414
|
-
| `hook.detail` | One hook's full authored HookDoc.
|
|
415
|
-
| `hook.detail.params` | Just one hook's parameters table (HookParamDoc[]).
|
|
416
|
-
| `theme.build` | A theme build receipt: name, token- and component-override counts, output size, the written outputs {css, js, dts, and variantsDts when applicable}, and any validation warnings.
|
|
417
|
-
| `theme.build.check` | The --check receipt: theme name, an upToDate flag, the stale outputs (each {path, reason: missing \| outdated}), and the full list of checked paths. Writes nothing.
|
|
418
|
-
| `theme.build.batch` | Several themes built in one invocation: `count` plus one {file, receipt} per theme in argument order, where receipt is that theme's theme.build (or theme.build.check) envelope, or null when it produced no CSS.
|
|
419
|
-
| `theme.list` | Every bundled theme as a ThemeListEntry[]: each with slug, displayName, description, and a maintained flag.
|
|
420
|
-
| `theme.add` | A scaffold receipt: resolved slug, displayName, maintained flag, outputDir (relative to cwd), the theme entry file, its exportName, and the files written.
|
|
421
|
-
| `theme.template` | A write receipt for the annotated theme template: the path (relative to cwd), whether it was written, and the reason it was not
|
|
422
|
-
| `
|
|
423
|
-
| `upgrade.
|
|
424
|
-
| `upgrade.
|
|
425
|
-
| `
|
|
426
|
-
| `
|
|
427
|
-
| `
|
|
428
|
-
| `
|
|
429
|
-
| `layout.
|
|
430
|
-
| `layout.
|
|
386
|
+
| Type | What `data` carries |
|
|
387
|
+
| --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
388
|
+
| `component.list` | The component catalog grouped by category: `detail` (the level: names \| compact \| full) and `components`, the grouped map of names+package, brief entries, or a full ComponentDoc per entry. |
|
|
389
|
+
| `component.detail` | One component's authored ComponentDoc plus ownership metadata (owner package, import specifier, and whether source is available). |
|
|
390
|
+
| `component.detail.props` | Just one component's props table (ComponentPropDoc[]). |
|
|
391
|
+
| `component.detail.source` | One component's source file, as {component, source}. |
|
|
392
|
+
| `component.detail.showcase` | One component's showcase example, as {component, aspectRatio, source}. |
|
|
393
|
+
| `component.detail.blocks` | One component's example blocks, as {component, showcase, examples, related} of BlockEntry. |
|
|
394
|
+
| `docs.list` | All reference-doc topics as DocsListEntry[] ({topic, description}), in discovery order. |
|
|
395
|
+
| `docs.detail` | One topic's full ReferenceDoc, with token-ref blocks inlined. |
|
|
396
|
+
| `docs.detail.section` | A single ReferenceSection of a topic: the first whose title contains the section query. |
|
|
397
|
+
| `blog.list` | The feed URL plus every post parsed from the RSS feed, each with slug, title, description, date, type, authors, link, and plaintext URL. |
|
|
398
|
+
| `blog.detail` | One post's metadata plus the feed URL and the post's full plaintext body. |
|
|
399
|
+
| `discover.list` | The configured external packages (name, category, components, version, description); when empty it carries meta.configured to tell "nothing configured" from "nothing discovered". |
|
|
400
|
+
| `discover.detail` | A single external package entry, for an @scope/name query. |
|
|
401
|
+
| `discover.detail.doc` | The validated ComponentDoc for one external component: an @scope/name/Component query, or a free-text term resolving to exactly one component. |
|
|
402
|
+
| `discover.search` | The echoed query plus the matching {package, component} pairs, when a free-text term matches several components. |
|
|
403
|
+
| `search` | The echoed query plus a ranked SearchResultEntry[] (domain, name, score, reason, description, follow-up command, and import path where relevant). |
|
|
404
|
+
| `build.help` | A marker (`playbook: true`) that the renderer expands into the how-to-build-a-page workflow; emitted when no query is given. |
|
|
405
|
+
| `build.kit` | The grouped composition kit: echoed query, hasResults/directMatch flags, the closest page templates, drop-in block patterns, idea-specific components/hooks, and the always-on frame + foundation component-name arrays. |
|
|
406
|
+
| `swizzle.list` | The names of swizzlable components discoverable from cwd's @astryxdesign/core. |
|
|
407
|
+
| `swizzle.copy` | An eject receipt: component name, owning package, output directory, files-copied count, the written file names, whether any file uses StyleX, and an optional maintainer note. |
|
|
408
|
+
| `template.list` | Every discovered template (page + block); each entry carries id, name, description, kind, owning package, optional category and componentsUsed, and readiness flags. |
|
|
409
|
+
| `template.show` | The resolved template's raw source plus its description, kind, and the component names it composes. |
|
|
410
|
+
| `template.skeleton` | A layout skeleton (structural tags with spatial annotations) plus the template's description and the components it composes. |
|
|
411
|
+
| `template.copy` | A scaffold receipt: template id, output directory, written file name, and file count. |
|
|
412
|
+
| `template.cdn` | A write receipt for the no-build-step CDN starter page: the path (relative to cwd), the Astryx version every CDN URL was pinned to, whether it was written, and the reason it was not. `exists` when a file was already there, which is a success. |
|
|
413
|
+
| `hook.list` | The hook catalog grouped by category: `detail` (the level: names \| compact \| full) and `components`, the grouped map of hook names, brief entries, or a full HookDoc per entry. |
|
|
414
|
+
| `hook.detail` | One hook's full authored HookDoc. |
|
|
415
|
+
| `hook.detail.params` | Just one hook's parameters table (HookParamDoc[]). |
|
|
416
|
+
| `theme.build` | A theme build receipt: name, token- and component-override counts, output size, the written outputs {css, js, dts, and variantsDts when applicable}, and any validation warnings. |
|
|
417
|
+
| `theme.build.check` | The --check receipt: theme name, an upToDate flag, the stale outputs (each {path, reason: missing \| outdated}), and the full list of checked paths. Writes nothing. |
|
|
418
|
+
| `theme.build.batch` | Several themes built in one invocation: `count` plus one {file, receipt} per theme in argument order, where receipt is that theme's theme.build (or theme.build.check) envelope, or null when it produced no CSS. |
|
|
419
|
+
| `theme.list` | Every bundled theme as a ThemeListEntry[]: each with slug, displayName, description, and a maintained flag. |
|
|
420
|
+
| `theme.add` | A scaffold receipt: resolved slug, displayName, maintained flag, outputDir (relative to cwd), the theme entry file, its exportName, and the files written. |
|
|
421
|
+
| `theme.template` | A write receipt for the annotated theme template: the path (relative to cwd), whether it was written, and the reason it was not. `exists` when a file was already there, which is a success. |
|
|
422
|
+
| `theme.targets` | The whole themeable surface: the echoed filter, the component count, and one entry per theming target — {key, className, component, props, states}, where props and states are its legal override keys. |
|
|
423
|
+
| `upgrade.list` | Every available codemod, oldest→newest, as {name, title, version, optional}; returned for --list without running anything. |
|
|
424
|
+
| `upgrade.status` | A short-circuit outcome with no codemods run (up_to_date, no_codemods, or config_fixable), each carrying the agent-docs summary. |
|
|
425
|
+
| `upgrade.run` | The run receipt: from/to versions, codemod count, integrations processed, the agent-docs summary, and (apply mode) filesChanged, transformsApplied, and per-codemod errors. |
|
|
426
|
+
| `manifest` | The self-describing CLI capability manifest: name, version, apiVersion, global options, the command tree (args, options, json flag, response types, examples), the jsonSupported allowlist, and the flat responseTypes index. |
|
|
427
|
+
| `doctor` | The health-check report: `checks` (each with id, label, status: pass \| warn \| fail \| info, a message, and a fix when not passing) plus a `summary` of counts per status. |
|
|
428
|
+
| `integration.validate` | The validation result: the package name and version (both null when no local manifest is found) plus issues, an AstryxIntegrationIssue[] of {code, severity: warning \| error, message}. |
|
|
429
|
+
| `layout.expand` | The expansion: parsed form, generated TSX code, componentsUsed, states (count of useState hooks scaffolded), todos, blocksReferenced (each {name, mode}), warnings, and written (the output path, or null when nothing was written). |
|
|
430
|
+
| `layout.check` | The validation result: a valid flag, the detected form, errors (each with line/col, message, formatted text, and suggestions), warnings, and the expression re-printed in both canonical surfaces (compact and outline). |
|
|
431
|
+
| `layout.grammar` | The XLE/XLO grammar cheatsheet: a text field with the full reference plus an aliases map (short name → canonical component) generated from this install's registry. |
|
|
431
432
|
|
|
432
433
|
<!-- END GENERATED: response-types -->
|
|
433
434
|
<!-- Generated by scripts/generate-cli-readme.mjs from the response-types EnumDoc. Run `pnpm -F @astryxdesign/cli readme`. -->
|
|
@@ -580,12 +581,13 @@ export default {
|
|
|
580
581
|
};
|
|
581
582
|
```
|
|
582
583
|
|
|
583
|
-
| Field | Type | Purpose
|
|
584
|
-
| ------------ | -------- |
|
|
585
|
-
| `components` | `string` | Directory holding the package's components and their `.doc.*` files.
|
|
586
|
-
| `templates` | `string` | Directory holding the package's page/block templates.
|
|
587
|
-
| `codemods` | `string` | Directory holding upgrade codemods run by `astryx upgrade`.
|
|
588
|
-
| `
|
|
584
|
+
| Field | Type | Purpose |
|
|
585
|
+
| ------------ | -------- | --------------------------------------------------------------------------------- |
|
|
586
|
+
| `components` | `string` | Directory holding the package's components and their `.doc.*` files. |
|
|
587
|
+
| `templates` | `string` | Directory holding the package's page/block templates. |
|
|
588
|
+
| `codemods` | `string` | Directory holding upgrade codemods run by `astryx upgrade`. |
|
|
589
|
+
| `docs` | `string` | Directory of reference docs; each `{topic}.doc.*` becomes a topic the CLI serves. |
|
|
590
|
+
| `issuesUrl` | `string` | Where "report an issue" links for this package's contributions point. |
|
|
589
591
|
|
|
590
592
|
Every field is optional; declare only the roots the package ships. There is no
|
|
591
593
|
factory: write a plain object, and annotate it with the `AstryxIntegration` type
|
package/api/docs/docs.doc.mjs
CHANGED
|
@@ -19,8 +19,8 @@ export const doc = {
|
|
|
19
19
|
'returns that full ReferenceDoc (with token-ref blocks inlined); a topic ' +
|
|
20
20
|
'plus a section returns the first section whose title contains the ' +
|
|
21
21
|
'(case-insensitive) query. The topic set is the CLI\'s own docs plus the ' +
|
|
22
|
-
'ones the project\'s configured integrations contribute
|
|
23
|
-
'topic an integration replaces or extends
|
|
22
|
+
'ones the project\'s configured integrations contribute, including any ' +
|
|
23
|
+
'topic an integration replaces or extends, so it depends on the cwd. ' +
|
|
24
24
|
'Overlay options select localized or dense variants.',
|
|
25
25
|
importPath: '@astryxdesign/cli/api',
|
|
26
26
|
signature:
|
package/api/index.d.mts
CHANGED
|
@@ -31,6 +31,6 @@ export * from "./doctor/doctor.type.mjs";
|
|
|
31
31
|
export * from "./layout/layout.type.mjs";
|
|
32
32
|
export * from "./integration/validate-integration.type.mjs";
|
|
33
33
|
export type Logger = import("./logger.mjs").Logger;
|
|
34
|
-
export { themeBuild, themeAdd, themeList, listThemes } from "./theme/theme.mjs";
|
|
34
|
+
export { themeBuild, themeAdd, themeList, themeTargets, listThemes } from "./theme/theme.mjs";
|
|
35
35
|
export { layoutExpand, layoutCheck, layoutGrammar } from "./layout/layout.mjs";
|
|
36
36
|
export { validateIntegration, summarizeIssues } from "./integration/validate-integration.mjs";
|
package/api/index.mjs
CHANGED
|
@@ -25,7 +25,7 @@ export {docs} from './docs/docs.mjs';
|
|
|
25
25
|
export {blog} from './blog/blog.mjs';
|
|
26
26
|
export {discover} from './discover/discover.mjs';
|
|
27
27
|
export {template} from './template/template.mjs';
|
|
28
|
-
export {themeBuild, themeAdd, themeList, listThemes} from './theme/theme.mjs';
|
|
28
|
+
export {themeBuild, themeAdd, themeList, themeTargets, listThemes} from './theme/theme.mjs';
|
|
29
29
|
export {hook} from './hook/hook.mjs';
|
|
30
30
|
export {search} from './search/search.mjs';
|
|
31
31
|
export {build} from './build/build.mjs';
|
|
@@ -104,7 +104,7 @@ export const doc = {
|
|
|
104
104
|
{
|
|
105
105
|
type: 'template.skeleton',
|
|
106
106
|
description:
|
|
107
|
-
"A layout skeleton
|
|
107
|
+
"A layout skeleton (structural tags with spatial annotations) plus the template's description and the components it composes.",
|
|
108
108
|
},
|
|
109
109
|
{
|
|
110
110
|
type: 'template.copy',
|
|
@@ -114,7 +114,7 @@ export const doc = {
|
|
|
114
114
|
{
|
|
115
115
|
type: 'template.cdn',
|
|
116
116
|
description:
|
|
117
|
-
'A write receipt for the CDN starter page: the path (relative to cwd), the Astryx version every CDN URL was pinned to, whether it was written, and the reason it was not
|
|
117
|
+
'A write receipt for the CDN starter page: the path (relative to cwd), the Astryx version every CDN URL was pinned to, whether it was written, and the reason it was not. `exists` when a file was already there, which is a success.',
|
|
118
118
|
},
|
|
119
119
|
],
|
|
120
120
|
throws: [
|
|
@@ -43,6 +43,10 @@ import {ERROR_CODES} from '../../../foundation/response/error-codes.mjs';
|
|
|
43
43
|
import {AstryxError} from '../../error.mjs';
|
|
44
44
|
import {logger} from '../../logger.mjs';
|
|
45
45
|
import {loadComponentDoc} from '../../../foundation/discovery/component-loader.mjs';
|
|
46
|
+
import {
|
|
47
|
+
collectThemingTargets,
|
|
48
|
+
targetsByKey,
|
|
49
|
+
} from '../../../foundation/discovery/theming-targets.mjs';
|
|
46
50
|
import {
|
|
47
51
|
collectUnloadedFonts,
|
|
48
52
|
formatFontLoadingHelp,
|
|
@@ -882,6 +886,9 @@ ${iconType}export declare const ${toIdentifier(themeDef.name)}Theme: DefinedThem
|
|
|
882
886
|
* Returns null when docs are unavailable so validation can skip unknown-key
|
|
883
887
|
* warnings rather than guessing from a second registry.
|
|
884
888
|
*
|
|
889
|
+
* Shares its enumeration with `theme targets`, so what a theme author can list
|
|
890
|
+
* is exactly what this validator accepts.
|
|
891
|
+
*
|
|
885
892
|
* @returns {Promise<Record<string, string[]> | null>}
|
|
886
893
|
*/
|
|
887
894
|
async function loadKnownComponents() {
|
|
@@ -889,44 +896,7 @@ async function loadKnownComponents() {
|
|
|
889
896
|
const coreSrc = coreRoot ? path.join(coreRoot, 'src') : null;
|
|
890
897
|
if (!coreSrc || !fs.existsSync(coreSrc)) return null;
|
|
891
898
|
|
|
892
|
-
|
|
893
|
-
const targets = {};
|
|
894
|
-
|
|
895
|
-
/** @param {string} dir */
|
|
896
|
-
async function scan(dir) {
|
|
897
|
-
const entries = fs.readdirSync(dir, {withFileTypes: true});
|
|
898
|
-
for (const entry of entries) {
|
|
899
|
-
const full = path.join(dir, entry.name);
|
|
900
|
-
if (entry.isDirectory()) {
|
|
901
|
-
if (entry.name === 'node_modules' || entry.name === '__tests__') continue;
|
|
902
|
-
await scan(full);
|
|
903
|
-
continue;
|
|
904
|
-
}
|
|
905
|
-
if (!entry.name.endsWith('.doc.mjs')) continue;
|
|
906
|
-
|
|
907
|
-
/** @type {any} */
|
|
908
|
-
let doc;
|
|
909
|
-
try {
|
|
910
|
-
doc = await loadComponentDoc(full);
|
|
911
|
-
} catch {
|
|
912
|
-
continue;
|
|
913
|
-
}
|
|
914
|
-
|
|
915
|
-
for (const target of doc?.theming?.targets || []) {
|
|
916
|
-
const className = target?.className;
|
|
917
|
-
if (typeof className !== 'string') continue;
|
|
918
|
-
const key = className.replace(/^astryx-/, '');
|
|
919
|
-
if (!key) continue;
|
|
920
|
-
const props = [target.visualProps, target.states]
|
|
921
|
-
.filter(list => Array.isArray(list))
|
|
922
|
-
.flat()
|
|
923
|
-
.filter((/** @type {unknown} */ p) => typeof p === 'string');
|
|
924
|
-
targets[key] = [...new Set([...(targets[key] || []), ...props])];
|
|
925
|
-
}
|
|
926
|
-
}
|
|
927
|
-
}
|
|
928
|
-
|
|
929
|
-
await scan(coreSrc);
|
|
899
|
+
const targets = targetsByKey(await collectThemingTargets(coreSrc));
|
|
930
900
|
return Object.keys(targets).length > 0 ? targets : null;
|
|
931
901
|
}
|
|
932
902
|
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// @generated by scripts/sync-api-types.mjs from the JSDoc in api/**/*.mjs.
|
|
2
|
+
// DO NOT EDIT — run `pnpm sync:api-types` to regenerate.
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* List every component theming target — the `defineTheme` `components` keys,
|
|
6
|
+
* with the props and states each one accepts.
|
|
7
|
+
*
|
|
8
|
+
* A filter naming a component exactly wins over a substring search, so
|
|
9
|
+
* `theme targets Button` is Button's own set (what `astryx component Button`
|
|
10
|
+
* prints) rather than every key that happens to contain "button".
|
|
11
|
+
*
|
|
12
|
+
* @param {string} [filter] - component name, or a substring of a target key
|
|
13
|
+
* @param {{cwd?: string}} [ctx]
|
|
14
|
+
* @returns {Promise<import('../theme.type.mjs').ThemeTargetsResponse>}
|
|
15
|
+
*/
|
|
16
|
+
export function themeTargets(filter?: string, { cwd }?: {
|
|
17
|
+
cwd?: string;
|
|
18
|
+
}): Promise<import("../theme.type.mjs").ThemeTargetsResponse>;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file `astryx theme targets` leaf — the whole themeable surface, enumerated.
|
|
5
|
+
*
|
|
6
|
+
* @input a cwd (to resolve the project's `@astryxdesign/core`) and an optional
|
|
7
|
+
* component/key filter
|
|
8
|
+
* @output the `theme.targets` envelope: one row per theming target
|
|
9
|
+
* @position api/theme/targets — projection over
|
|
10
|
+
* foundation/discovery/theming-targets.mjs, the same component docs
|
|
11
|
+
* `astryx component <Name>` prints its Theming table from.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import * as path from 'node:path';
|
|
15
|
+
import {findCoreDir} from '../../../foundation/fs/paths.mjs';
|
|
16
|
+
import {collectThemingTargets} from '../../../foundation/discovery/theming-targets.mjs';
|
|
17
|
+
import {ERROR_CODES} from '../../../foundation/response/error-codes.mjs';
|
|
18
|
+
import {AstryxError} from '../../error.mjs';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Whether a target matches the caller's filter loosely: any target whose key,
|
|
22
|
+
* class, or component contains it — so `theme targets thumb` finds the switch
|
|
23
|
+
* thumb without knowing which component owns it.
|
|
24
|
+
* @param {import('../../../foundation/discovery/theming-targets.mjs').ThemingTarget} target
|
|
25
|
+
* @param {string} filter - already lowercased
|
|
26
|
+
* @returns {boolean}
|
|
27
|
+
*/
|
|
28
|
+
function matchesLoosely(target, filter) {
|
|
29
|
+
return (
|
|
30
|
+
target.key.toLowerCase().includes(filter) ||
|
|
31
|
+
target.className.toLowerCase().includes(filter) ||
|
|
32
|
+
target.component.toLowerCase().includes(filter)
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* List every component theming target — the `defineTheme` `components` keys,
|
|
38
|
+
* with the props and states each one accepts.
|
|
39
|
+
*
|
|
40
|
+
* A filter naming a component exactly wins over a substring search, so
|
|
41
|
+
* `theme targets Button` is Button's own set (what `astryx component Button`
|
|
42
|
+
* prints) rather than every key that happens to contain "button".
|
|
43
|
+
*
|
|
44
|
+
* @param {string} [filter] - component name, or a substring of a target key
|
|
45
|
+
* @param {{cwd?: string}} [ctx]
|
|
46
|
+
* @returns {Promise<import('../theme.type.mjs').ThemeTargetsResponse>}
|
|
47
|
+
*/
|
|
48
|
+
export async function themeTargets(filter, {cwd = process.cwd()} = {}) {
|
|
49
|
+
const coreDir = findCoreDir(cwd);
|
|
50
|
+
if (!coreDir) {
|
|
51
|
+
throw new AstryxError(
|
|
52
|
+
'Could not find @astryxdesign/core package',
|
|
53
|
+
undefined,
|
|
54
|
+
ERROR_CODES.ERR_CORE_NOT_FOUND,
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const all = await collectThemingTargets(path.join(coreDir, 'src'));
|
|
59
|
+
const needle = filter ? String(filter).toLowerCase() : null;
|
|
60
|
+
let targets = all;
|
|
61
|
+
if (needle) {
|
|
62
|
+
const named = all.filter(t => t.component.toLowerCase() === needle);
|
|
63
|
+
targets = named.length > 0 ? named : all.filter(t => matchesLoosely(t, needle));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (needle && targets.length === 0) {
|
|
67
|
+
const near = [...new Set(all.map(t => t.component))]
|
|
68
|
+
.filter(name => name.toLowerCase().startsWith(needle.slice(0, 3)))
|
|
69
|
+
.sort()
|
|
70
|
+
.slice(0, 5)
|
|
71
|
+
.map(name => ({name, reason: 'has theming targets'}));
|
|
72
|
+
throw new AstryxError(
|
|
73
|
+
`No theming target matches "${filter}". Run \`theme targets\` with no filter for the whole list.`,
|
|
74
|
+
near.length > 0 ? near : undefined,
|
|
75
|
+
ERROR_CODES.ERR_UNKNOWN_COMPONENT,
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
type: 'theme.targets',
|
|
81
|
+
data: {
|
|
82
|
+
filter: filter ?? null,
|
|
83
|
+
componentCount: new Set(targets.map(t => t.component)).size,
|
|
84
|
+
targets,
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file Direct-API tests for the `theme targets` leaf. Runs against the real
|
|
5
|
+
* core docs, so it doubles as a guard that the themeable surface stays
|
|
6
|
+
* readable and shaped as `theme.targets` promises.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import {describe, it, expect} from 'vitest';
|
|
10
|
+
import {themeTargets} from './targets.mjs';
|
|
11
|
+
|
|
12
|
+
describe('themeTargets (api/theme/targets)', () => {
|
|
13
|
+
it('returns a theme.targets envelope covering the whole surface', async () => {
|
|
14
|
+
const result = await themeTargets();
|
|
15
|
+
expect(result.type).toBe('theme.targets');
|
|
16
|
+
expect(result.data.filter).toBeNull();
|
|
17
|
+
expect(result.data.targets.length).toBeGreaterThan(100);
|
|
18
|
+
expect(result.data.componentCount).toBeGreaterThan(50);
|
|
19
|
+
for (const t of result.data.targets) {
|
|
20
|
+
expect(Object.keys(t).sort()).toEqual([
|
|
21
|
+
'className',
|
|
22
|
+
'component',
|
|
23
|
+
'key',
|
|
24
|
+
'props',
|
|
25
|
+
'states',
|
|
26
|
+
]);
|
|
27
|
+
}
|
|
28
|
+
}, 60_000);
|
|
29
|
+
|
|
30
|
+
it('scopes to one component by name', async () => {
|
|
31
|
+
const {data} = await themeTargets('Switch');
|
|
32
|
+
expect(data.filter).toBe('Switch');
|
|
33
|
+
expect(data.componentCount).toBe(1);
|
|
34
|
+
expect(data.targets.map(t => t.key)).toEqual([
|
|
35
|
+
'switch',
|
|
36
|
+
'switch-field',
|
|
37
|
+
'switch-thumb',
|
|
38
|
+
]);
|
|
39
|
+
}, 60_000);
|
|
40
|
+
|
|
41
|
+
// Half the system's keys contain "button" (chat-send-button, toggle-button,
|
|
42
|
+
// …). A component name has to mean the component, or `theme targets Button`
|
|
43
|
+
// answers a different question than `component Button` and the two views
|
|
44
|
+
// look like they disagree.
|
|
45
|
+
it('prefers an exact component name over a substring match', async () => {
|
|
46
|
+
const {data} = await themeTargets('Button');
|
|
47
|
+
expect(data.targets.map(t => t.key)).toEqual(['button']);
|
|
48
|
+
}, 60_000);
|
|
49
|
+
|
|
50
|
+
// This command answers "which theme slot paints the switch thumb?" — a
|
|
51
|
+
// question you can only ask by the part, not the component, until you
|
|
52
|
+
// already know which component owns it.
|
|
53
|
+
it('searches keys by substring, across components', async () => {
|
|
54
|
+
const {data} = await themeTargets('thumb');
|
|
55
|
+
expect(data.componentCount).toBeGreaterThan(1);
|
|
56
|
+
expect(data.targets.map(t => t.key)).toContain('switch-thumb');
|
|
57
|
+
for (const t of data.targets) expect(t.key).toContain('thumb');
|
|
58
|
+
}, 60_000);
|
|
59
|
+
|
|
60
|
+
it('rejects a filter that matches nothing, with components to try', async () => {
|
|
61
|
+
await expect(themeTargets('nosuchthing')).rejects.toMatchObject({
|
|
62
|
+
code: 'ERR_UNKNOWN_COMPONENT',
|
|
63
|
+
});
|
|
64
|
+
}, 60_000);
|
|
65
|
+
});
|
package/api/theme/theme.d.mts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
export { themeAdd } from "./add/add.mjs";
|
|
5
5
|
export { themeTemplate } from "./template/template.mjs";
|
|
6
|
+
export { themeTargets } from "./targets/targets.mjs";
|
|
6
7
|
export { themeList } from "./list/list.mjs";
|
|
7
8
|
export { listThemes } from "./_adapter.mjs";
|
|
8
9
|
export { themeBuild, importSpecifier } from "./build/build.mjs";
|
package/api/theme/theme.mjs
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* @file `theme` command barrel — re-exports the build/add/list
|
|
4
|
+
* @file `theme` command barrel — re-exports the build/add/list/template/targets
|
|
5
|
+
* leaves so the CLI
|
|
5
6
|
* (cli/commands/build-theme.mjs) and scripted callers import from one place.
|
|
6
7
|
* Each leaf is also importable directly (e.g. api/theme/add/add.mjs). `theme`
|
|
7
8
|
* has real subcommands, so there is no flag-dispatch here — the CLI calls the
|
|
@@ -11,5 +12,6 @@
|
|
|
11
12
|
export {themeBuild, importSpecifier} from './build/build.mjs';
|
|
12
13
|
export {themeAdd} from './add/add.mjs';
|
|
13
14
|
export {themeTemplate} from './template/template.mjs';
|
|
15
|
+
export {themeTargets} from './targets/targets.mjs';
|
|
14
16
|
export {themeList} from './list/list.mjs';
|
|
15
17
|
export {listThemes} from './_adapter.mjs';
|
|
@@ -101,3 +101,26 @@ export type ThemeTemplateResponse = {
|
|
|
101
101
|
reason: "exists" | null;
|
|
102
102
|
};
|
|
103
103
|
};
|
|
104
|
+
/**
|
|
105
|
+
* One themeable target: the `defineTheme` `components` key, the class it
|
|
106
|
+
* renders as, the component whose doc declares it, and the props and states
|
|
107
|
+
* that are legal override keys under it.
|
|
108
|
+
*/
|
|
109
|
+
export type ThemeTargetEntry = {
|
|
110
|
+
key: string;
|
|
111
|
+
className: string;
|
|
112
|
+
component: string;
|
|
113
|
+
props: string[];
|
|
114
|
+
states: string[];
|
|
115
|
+
};
|
|
116
|
+
/**
|
|
117
|
+
* xds --json theme targets [filter]
|
|
118
|
+
*/
|
|
119
|
+
export type ThemeTargetsResponse = {
|
|
120
|
+
type: "theme.targets";
|
|
121
|
+
data: {
|
|
122
|
+
filter: string | null;
|
|
123
|
+
componentCount: number;
|
|
124
|
+
targets: ThemeTargetEntry[];
|
|
125
|
+
};
|
|
126
|
+
};
|
package/api/theme/theme.type.mjs
CHANGED
|
@@ -14,9 +14,10 @@
|
|
|
14
14
|
* xds --json theme list -> theme.list
|
|
15
15
|
* xds --json theme add <slug> -> theme.add
|
|
16
16
|
* xds --json theme template -> theme.template
|
|
17
|
+
* xds --json theme targets [filter] -> theme.targets
|
|
17
18
|
* (file not found / parse error) -> CLIError
|
|
18
19
|
*
|
|
19
|
-
* @position api — colocated typedefs for api/theme/{theme,build,add,list,template,_adapter}
|
|
20
|
+
* @position api — colocated typedefs for api/theme/{theme,build,add,list,template,targets,_adapter}
|
|
20
21
|
*/
|
|
21
22
|
|
|
22
23
|
/**
|
|
@@ -78,6 +79,25 @@
|
|
|
78
79
|
* @property {{path: string, written: boolean, reason: 'exists' | null}} data
|
|
79
80
|
*/
|
|
80
81
|
|
|
82
|
+
/**
|
|
83
|
+
* One themeable target: the `defineTheme` `components` key, the class it
|
|
84
|
+
* renders as, the component whose doc declares it, and the props and states
|
|
85
|
+
* that are legal override keys under it.
|
|
86
|
+
* @typedef {object} ThemeTargetEntry
|
|
87
|
+
* @property {string} key
|
|
88
|
+
* @property {string} className
|
|
89
|
+
* @property {string} component
|
|
90
|
+
* @property {string[]} props
|
|
91
|
+
* @property {string[]} states
|
|
92
|
+
*/
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* xds --json theme targets [filter]
|
|
96
|
+
* @typedef {object} ThemeTargetsResponse
|
|
97
|
+
* @property {'theme.targets'} type
|
|
98
|
+
* @property {{filter: string | null, componentCount: number, targets: ThemeTargetEntry[]}} data
|
|
99
|
+
*/
|
|
100
|
+
|
|
81
101
|
// Make this a module so the @typedefs above are importable as types via
|
|
82
102
|
// `import('./theme.type.mjs').ThemeBuildResponse` (and re-exportable from a .d.ts).
|
|
83
103
|
export {};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// @generated by scripts/sync-api-types.mjs from the JSDoc in api/**/*.mjs.
|
|
2
|
+
// DO NOT EDIT — run `pnpm sync:api-types` to regenerate.
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @file FunctionDoc for `themeTargets()` / `astryx theme targets`. Colocated
|
|
6
|
+
* with the API function it documents; the response-shape source of truth stays
|
|
7
|
+
* in `theme.type.mjs`.
|
|
8
|
+
* @position packages/cli/api/theme — function documentation
|
|
9
|
+
*/
|
|
10
|
+
/** @type {import('@astryxdesign/cli/authoring').FunctionDoc} */
|
|
11
|
+
export const doc: import("@astryxdesign/cli/authoring").FunctionDoc;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file FunctionDoc for `themeTargets()` / `astryx theme targets`. Colocated
|
|
5
|
+
* with the API function it documents; the response-shape source of truth stays
|
|
6
|
+
* in `theme.type.mjs`.
|
|
7
|
+
* @position packages/cli/api/theme — function documentation
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/** @type {import('@astryxdesign/cli/authoring').FunctionDoc} */
|
|
11
|
+
export const doc = {
|
|
12
|
+
type: 'function',
|
|
13
|
+
kind: 'api',
|
|
14
|
+
name: 'themeTargets',
|
|
15
|
+
displayName: 'themeTargets()',
|
|
16
|
+
summary: 'List every component theming target a theme can override.',
|
|
17
|
+
description:
|
|
18
|
+
'Enumerates the whole themeable surface: each `defineTheme` components key, the stable ' +
|
|
19
|
+
'class it paints, the component that declares it, and the props and states that are legal ' +
|
|
20
|
+
'override keys under it. Same source as the Theming table `astryx component <Name>` prints ' +
|
|
21
|
+
'— the component docs — so the list cannot drift from the components, and `theme build` ' +
|
|
22
|
+
'validates overrides against this exact set. A filter naming a component gives that ' +
|
|
23
|
+
'component\u2019s set; anything else is a substring search over the keys.',
|
|
24
|
+
importPath: '@astryxdesign/cli/api',
|
|
25
|
+
signature:
|
|
26
|
+
'themeTargets(filter?: string, ctx?: {cwd?: string}): Promise<ThemeTargetsResponse>',
|
|
27
|
+
keywords: ['theme', 'targets', 'defineTheme', 'components', 'override', 'class', 'states', 'audit'],
|
|
28
|
+
params: [
|
|
29
|
+
{
|
|
30
|
+
name: 'filter',
|
|
31
|
+
type: 'string',
|
|
32
|
+
description:
|
|
33
|
+
'A component name (exact, case-insensitive) or a substring of a target key. Omit for the whole surface.',
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
name: 'ctx.cwd',
|
|
37
|
+
type: 'string',
|
|
38
|
+
description: 'Directory the project’s @astryxdesign/core is resolved from.',
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
returns: [
|
|
42
|
+
{
|
|
43
|
+
type: 'theme.targets',
|
|
44
|
+
description:
|
|
45
|
+
'The echoed filter, how many components are represented, and the targets: each {key, className, component, props, states}.',
|
|
46
|
+
},
|
|
47
|
+
],
|
|
48
|
+
throws: [
|
|
49
|
+
{code: 'ERR_CORE_NOT_FOUND', when: '@astryxdesign/core cannot be resolved from cwd'},
|
|
50
|
+
{code: 'ERR_UNKNOWN_COMPONENT', when: 'a filter matches no target'},
|
|
51
|
+
],
|
|
52
|
+
examples: [
|
|
53
|
+
{label: 'The whole themeable surface', code: 'await themeTargets();'},
|
|
54
|
+
{label: "One component's targets", code: "await themeTargets('Switch');"},
|
|
55
|
+
],
|
|
56
|
+
command: 'theme targets',
|
|
57
|
+
related: ['themeBuild', 'themeTemplate', 'component'],
|
|
58
|
+
};
|