@astryxdesign/cli 0.4.7 → 0.5.0-canary.009bcb3
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 +64 -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/search/search.mjs +50 -4
- package/api/search/search.test.mjs +71 -0
- package/api/template/data-token-fallbacks.test.mjs +85 -0
- package/api/template/template.doc.mjs +2 -2
- package/api/theme/build/build.mjs +43 -44
- package/api/theme/targets/targets.d.mts +18 -0
- package/api/theme/targets/targets.mjs +87 -0
- package/api/theme/targets/targets.test.mjs +66 -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/codemods/__tests__/registry.test.mjs +1 -0
- package/assets/codemods/registry.mjs +1 -0
- package/assets/codemods/transforms/v0.5.0/__tests__/next-codemods.test.mjs +127 -0
- package/assets/codemods/transforms/v0.5.0/banner-collapsible-content.mjs +171 -0
- package/assets/codemods/transforms/v0.5.0/index.mjs +20 -0
- 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/Banner/BannerCollapsibleContent.doc.mjs +1 -1
- package/assets/templates/blocks/components/Banner/BannerCollapsibleContent.tsx +1 -1
- package/assets/templates/blocks/components/DateInput/DateInputClearable.tsx +5 -1
- package/assets/templates/blocks/components/DateInput/DateInputDateRange.tsx +6 -1
- package/assets/templates/blocks/components/DateInput/DateInputFormats.tsx +5 -1
- package/assets/templates/blocks/components/DateInput/DateInputShowcase.tsx +4 -1
- package/assets/templates/blocks/components/DateInput/DateInputWithDescription.tsx +5 -1
- package/assets/templates/blocks/components/DateInput/DateInputWithValidation.tsx +5 -1
- 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/HoverCard/HoverCardHookUsage.tsx +4 -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/dashboard-cohort-funnel/page.tsx +1 -1
- package/assets/templates/pages/dashboard-data/page.tsx +1 -1
- package/assets/templates/pages/dashboard-portfolio/page.tsx +5 -5
- package/assets/templates/pages/dashboard-service-monitoring/page.tsx +1 -1
- package/assets/templates/pages/mixed-gallery/page.tsx +12 -3
- package/assets/templates/pages/settings-dialog/page.tsx +2543 -757
- package/assets/templates/pages/settings-dialog/template.doc.mjs +1 -2
- package/assets/templates/pages/table-filter/page.tsx +4093 -0
- package/assets/templates/pages/table-filter/template.doc.mjs +12 -0
- package/assets/templates/pages/table-grouped/page.tsx +151 -144
- package/assets/templates/pages/theme-showcase/page.tsx +36 -17
- package/assets/templates/themes/chocolate/chocolateTheme.ts +3 -1
- package/assets/templates/themes/matcha/matchaTheme.ts +3 -1
- package/assets/templates/themes/neutral/neutralTheme.ts +16 -9
- package/assets/templates/themes/stone/stoneTheme.ts +3 -1
- package/clients/cli/commands/build-theme.color-scheme.test.mjs +7 -6
- package/clients/cli/commands/build-theme.data-tokens.test.mjs +144 -0
- package/clients/cli/commands/build-theme.mjs +85 -0
- package/clients/cli/commands/build-theme.variants.test.mjs +3 -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/component-format.mjs +2 -2
- package/clients/cli/lib/manifest.mjs +2 -0
- package/foundation/discovery/component-discovery.mjs +10 -9
- package/foundation/discovery/hook-discovery.mjs +2 -1
- package/foundation/discovery/hook-discovery.test.mjs +156 -37
- package/foundation/discovery/theming-targets.d.mts +86 -0
- package/foundation/discovery/theming-targets.mjs +202 -0
- package/foundation/discovery/theming-targets.test.mjs +245 -0
- package/foundation/fs/paths.d.mts +16 -0
- package/foundation/fs/paths.mjs +36 -0
- package/foundation/fs/paths.test.mjs +29 -1
- 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
|
@@ -1,5 +1,69 @@
|
|
|
1
1
|
# @xds/cli
|
|
2
2
|
|
|
3
|
+
# 0.5.0
|
|
4
|
+
|
|
5
|
+
#### Breaking Changes
|
|
6
|
+
|
|
7
|
+
- Banner: the collapse axis moves onto one `collapsible` prop, and content can opt out of collapsing (#5255)
|
|
8
|
+
Banner inferred its disclosure from its content: any `children` got a chevron in the header and were hidden until it was pressed. There was no way to show content without a toggle — the case a banner most often wants, a list of the three fields that failed validation — and `defaultIsExpanded` was the only knob, with no controlled mode.
|
|
9
|
+
|
|
10
|
+
The whole axis is now one `boolean | CollapsibleConfig` prop, following the boolean-or-config convention `SideNav.collapsible` set, and backed by the shared `useCollapsible` hook rather than Banner's own state:
|
|
11
|
+
|
|
12
|
+
```tsx
|
|
13
|
+
<Banner status="error" title="3 fields need attention">…</Banner> // unchanged: collapsible, starts closed
|
|
14
|
+
<Banner collapsible={false}>…</Banner> // new: always visible, no toggle
|
|
15
|
+
<Banner collapsible={{defaultIsOpen: true}}>…</Banner> // replaces defaultIsExpanded
|
|
16
|
+
<Banner collapsible={{isOpen, onOpenChange}}>…</Banner> // new: controlled
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
**The default is unchanged** — a banner that never mentioned `defaultIsExpanded` behaves exactly as it did. The breaking part is the prop itself: `defaultIsExpanded` is removed in favour of the config, which is a type error at every JSX call site that names it.
|
|
20
|
+
|
|
21
|
+
**Codemod:** `npx astryx upgrade --codemod banner-collapsible-content`
|
|
22
|
+
|
|
23
|
+
It rewrites `defaultIsExpanded` to `collapsible={{defaultIsOpen: true}}` and drops `defaultIsExpanded={false}`, which is now the default. Banners that never set the prop are left alone.
|
|
24
|
+
|
|
25
|
+
**One case the codemod and the compiler both miss: a spread.** `defaultIsExpanded` inside a props object is out of the transform's scope. A props object in a typed position still fails to compile — but an inferred one that is spread, `<Banner {...args} />`, does not, because TypeScript does not excess-property-check a spread. The prop then falls through to the DOM and the banner quietly starts collapsed. **Grep for `defaultIsExpanded` after running the codemod** and migrate any spread sites by hand.
|
|
26
|
+
|
|
27
|
+
#### New Components
|
|
28
|
+
|
|
29
|
+
- Promote `Stepper` and `Step` from the canary-only Lab package to Core. The stable package now ships their existing horizontal/vertical layouts, separated and on-track indicators, semantic status, density, and non-linear navigation, plus Core documentation and rendered examples. The default `aria-label` is now localized.
|
|
30
|
+
Advancing one step now animates the connector. Every connector the four layouts draw — the separated bars and the on-track segments alike — grows its accent fill out of the segment's leading edge instead of swapping a background color, so moving forward reads as progress travelling the track. That one gesture is the only thing that animates: going back, jumping forward by more than one step, and mounting mid-flow all apply at once, as does any change under `prefers-reduced-motion`. Retreats are deliberately instant — run in reverse the same transition ends on a shrinking stub of accent, and a remnant still on the track reads as unfinished where the identical curve growing forward reads as arrived — and multi-step jumps are instant because a jump is a navigation rather than a progression, so sweeping a front across the crossed segments only makes the user sit out a journey they asked to skip. Where one span is drawn by several segments (the on-track layouts split a span between two steps, three when a content slot sits between them) the segments take abutting slices of the span's time and run linearly, so the fill reads as one line growing at a constant speed rather than pieces lighting in turn.
|
|
31
|
+
|
|
32
|
+
Five visual fixes land with the promotion. Horizontal steps now divide the track evenly instead of sizing to their own labels, so every progress segment is the same width regardless of how long a step is named. Number indicators shrink from 20px to 16px to match the check, ring, and custom-icon indicators, so a step swapping its number for a check as it completes no longer nudges the label beside it. A step description now occupies a 16px box rather than a 24px one — it previously inherited the page's line box instead of applying its own leading, which opened an 8px gap under the label. A step's content slot now starts flush with the label above it at every density: the slot renders outside the density-padded label area, so it was hanging one pad short of it. And a vertical on-track step carrying content keeps its connector unbroken — the content renders below the row that draws the line, so the track used to split open around any step with content (#5201).
|
|
33
|
+
|
|
34
|
+
#### New Features
|
|
35
|
+
|
|
36
|
+
- AspectRatio: emit `ratio` as a class-level declaration instead of a hard inline style, so the ratio can be overridden responsively: StyleX consumers pass an `aspect-ratio` rule via `xstyle` (including under `@media`/`@container` conditions), and plain-CSS/Tailwind consumers override `aspect-ratio` from their own unlayered rules, which beat the `astryx-base` cascade layer regardless of specificity. The mixed-gallery template's hero now switches 3:1 to 3:2 when the grid stacks with a one-line override on a single element, replacing the duplicated hero markup the fixed inline ratio previously forced (#3883, closes #2798)
|
|
37
|
+
- CLI: `astryx theme targets` lists every component theming target — the `defineTheme` key, the class it paints, and the props and states it accepts — for one component or the whole system, with `--json` for lint and audit scripts. `astryx theme --help` now points at component overrides instead of reading as a build-tool menu. The listing and `theme build`'s override validation share one enumeration of the component docs, so neither can drift from the components (#5115).
|
|
38
|
+
|
|
39
|
+
#### Fixes
|
|
40
|
+
|
|
41
|
+
- neutral theme: darken the light-mode error red from `#e33f4a` to `#c9303a` so the filled `Badge variant="error"` label clears WCAG 2.1 AA. White on `#e33f4a` is 4.14:1 and the badge label is 12px/weight 500, so the 4.5:1 normal-text threshold applies rather than the 3:1 large-text allowance; `#c9303a` gives 5.29:1 while holding the hue (OKLCH H 21.9 -> 22.8, C 0.200 -> 0.189). StatusDot and the ProgressBar `--color-error` rebinding move with it — both are documented as tracking the badge fill so the dot and its badge read as one status language. Dark mode is untouched (dark text on `#ff705d`, 6.60:1). Adds `scripts/check-badge-contrast.test.mjs`, which resolves every theme's badge label/fill pair through `light-dark()`, `var()` indirection and alpha compositing, and holds all of them to 4.5:1 (#4446).
|
|
42
|
+
- Unified search and build now include components contributed by integrations, so a component registered through an integration is findable and buildable alongside the built-in set instead of silently missing from both (#5259).
|
|
43
|
+
- Table - Grouped page template: wrap the rows in `TableBody`
|
|
44
|
+
The template rendered `<TableRow>` straight into `<Table>`, so the emitted DOM was `<table><tr>`. `<table>` cannot contain a row directly: the HTML parser inserts an implied `<tbody>` when it parses server-rendered markup and React does not when it renders on the client, so anyone who copied the template into an app as a server-rendered page inherited a hydration mismatch in their own app. Client-only the DOM is still invalid — nothing reparents the rows, so the table ends up with `<tr>` children and no `<tbody>` at all, and any CSS or query aimed at `tbody` silently misses.
|
|
45
|
+
|
|
46
|
+
The rows now sit in `<TableBody>`, the same element the data-driven `data={...}` path renders, so styling, dividers, and column widths are unchanged (#5278).
|
|
47
|
+
|
|
48
|
+
#### Other Changes
|
|
49
|
+
|
|
50
|
+
- Public component theming vars are enumerable, and guarded against being documented but unsettable
|
|
51
|
+
`collectThemingVars` joins `collectThemingTargets` as part of the one enumeration the theming surface is read from. Two guards ride on it: a documented public var no component reads compiles to a declaration that never applies, and a var the component writes inline outranks every cascade layer, so no theme can reach it. Both had shipped; neither is visible in the generated theme CSS the jsdom suites assert on (#5409).
|
|
52
|
+
|
|
53
|
+
#### Contributors
|
|
54
|
+
|
|
55
|
+
Thanks to everyone who contributed to this release:
|
|
56
|
+
|
|
57
|
+
- @AKnassa
|
|
58
|
+
- @andrskr
|
|
59
|
+
- @cixzhang
|
|
60
|
+
- @ernestt
|
|
61
|
+
- @freddymeta
|
|
62
|
+
- @jiunshinn
|
|
63
|
+
- @rubyycheung
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
3
67
|
# 0.4.7
|
|
4
68
|
|
|
5
69
|
---
|
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';
|
package/api/search/search.mjs
CHANGED
|
@@ -34,10 +34,12 @@ import {pathToFileURL} from 'node:url';
|
|
|
34
34
|
import {findCoreDir} from '../../foundation/fs/paths.mjs';
|
|
35
35
|
import {
|
|
36
36
|
discoverComponents,
|
|
37
|
+
discoverIntegrationComponents,
|
|
37
38
|
findComponentReadme,
|
|
38
39
|
resolveImportPath,
|
|
39
40
|
} from '../../foundation/discovery/component-discovery.mjs';
|
|
40
41
|
import {discoverHooks, findHookDoc} from '../../foundation/discovery/hook-discovery.mjs';
|
|
42
|
+
import {loadIntegrationsSafely} from '../component/_adapter.mjs';
|
|
41
43
|
import {levenshteinDistance} from '../../foundation/text/string-utils.mjs';
|
|
42
44
|
import {discoverTemplates, extractComponents} from '../template/template.mjs';
|
|
43
45
|
import {loadDocsCatalog, loadTopicDoc} from '../docs/_adapter.mjs';
|
|
@@ -350,12 +352,12 @@ async function loadModuleDoc(docPath, exportName = 'docs') {
|
|
|
350
352
|
}
|
|
351
353
|
|
|
352
354
|
/**
|
|
353
|
-
* Build component candidates: name + keywords +
|
|
354
|
-
* component's .doc.mjs.
|
|
355
|
+
* Build component candidates from core's own tree: name + keywords +
|
|
356
|
+
* usage/description from the component's .doc.mjs.
|
|
355
357
|
* @param {string} coreDir
|
|
356
358
|
* @returns {Promise<Candidate[]>}
|
|
357
359
|
*/
|
|
358
|
-
async function
|
|
360
|
+
async function gatherCoreComponents(coreDir) {
|
|
359
361
|
const grouped = discoverComponents(coreDir);
|
|
360
362
|
const names = Object.values(grouped).flat();
|
|
361
363
|
/** @type {Candidate[]} */
|
|
@@ -383,6 +385,50 @@ async function gatherComponents(coreDir) {
|
|
|
383
385
|
return candidates;
|
|
384
386
|
}
|
|
385
387
|
|
|
388
|
+
/**
|
|
389
|
+
* Build component candidates contributed by the project's configured
|
|
390
|
+
* integrations (astryx.config's `integrations`): name + keywords +
|
|
391
|
+
* usage/description from each component's .doc.mjs, same as core. Without
|
|
392
|
+
* this, an integration component is invisible to `search`/`build` even
|
|
393
|
+
* though `component --list`/`component <Name>` already resolve it — the two
|
|
394
|
+
* discovery paths silently disagreed.
|
|
395
|
+
* @param {string} cwd
|
|
396
|
+
* @returns {Promise<Candidate[]>}
|
|
397
|
+
*/
|
|
398
|
+
async function gatherIntegrationComponents(cwd) {
|
|
399
|
+
const loadedIntegrations = await loadIntegrationsSafely(cwd);
|
|
400
|
+
/** @type {Candidate[]} */
|
|
401
|
+
const candidates = [];
|
|
402
|
+
for (const integration of loadedIntegrations) {
|
|
403
|
+
for (const rec of discoverIntegrationComponents(integration)) {
|
|
404
|
+
const doc = await loadModuleDoc(rec.docPath);
|
|
405
|
+
candidates.push({
|
|
406
|
+
domain: 'component',
|
|
407
|
+
name: rec.name,
|
|
408
|
+
keywords: doc && Array.isArray(doc.keywords) ? doc.keywords : [],
|
|
409
|
+
description: doc ? doc.usage?.description || doc.description || '' : '',
|
|
410
|
+
_import: rec.package,
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
return candidates;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* Build component candidates: core's own tree plus every configured
|
|
419
|
+
* integration's components.
|
|
420
|
+
* @param {string} coreDir
|
|
421
|
+
* @param {string} cwd
|
|
422
|
+
* @returns {Promise<Candidate[]>}
|
|
423
|
+
*/
|
|
424
|
+
async function gatherComponents(coreDir, cwd) {
|
|
425
|
+
const [core, integrations] = await Promise.all([
|
|
426
|
+
gatherCoreComponents(coreDir),
|
|
427
|
+
gatherIntegrationComponents(cwd),
|
|
428
|
+
]);
|
|
429
|
+
return [...core, ...integrations];
|
|
430
|
+
}
|
|
431
|
+
|
|
386
432
|
/**
|
|
387
433
|
* Build hook candidates: name + keywords + usage/description from the hook's
|
|
388
434
|
* .doc.mjs.
|
|
@@ -611,7 +657,7 @@ export async function search(query, options = {}) {
|
|
|
611
657
|
/** @param {string} d */
|
|
612
658
|
const wants = d => !type || type === d;
|
|
613
659
|
const [components, hooks, docTopics, templates] = await Promise.all([
|
|
614
|
-
wants('component') ? gatherComponents(coreDir) : [],
|
|
660
|
+
wants('component') ? gatherComponents(coreDir, cwd) : [],
|
|
615
661
|
wants('hook') ? gatherHooks(coreDir) : [],
|
|
616
662
|
wants('doc') ? gatherDocs(cwd) : [],
|
|
617
663
|
wants('template') ? gatherTemplates(cwd) : [],
|
|
@@ -10,9 +10,17 @@
|
|
|
10
10
|
* `limit`, an empty query, and a bad `--type` all throw AstryxError with the
|
|
11
11
|
* ERR_INVALID_ARGUMENT code, so a direct `@astryxdesign/cli/api` caller gets the
|
|
12
12
|
* same contract as `astryx search` on the command line.
|
|
13
|
+
*
|
|
14
|
+
* The last describe block covers integration-contributed components, using the
|
|
15
|
+
* same temp-consumer harness as template-integration.test.mjs. Before this,
|
|
16
|
+
* `search`/`build` only ever scanned @astryxdesign/core — an integration's own
|
|
17
|
+
* components were invisible to both, even though `component --list` and
|
|
18
|
+
* `component <Name>` already resolved them. The two discovery paths silently
|
|
19
|
+
* disagreed.
|
|
13
20
|
*/
|
|
14
21
|
|
|
15
22
|
import {describe, it, expect} from 'vitest';
|
|
23
|
+
import * as fs from 'node:fs';
|
|
16
24
|
import * as path from 'node:path';
|
|
17
25
|
import {fileURLToPath} from 'node:url';
|
|
18
26
|
import {search, SEARCH_DOMAINS} from './search.mjs';
|
|
@@ -92,3 +100,66 @@ describe('search leaf — limit validation (API matches the CLI contract)', () =
|
|
|
92
100
|
});
|
|
93
101
|
}, SLOW);
|
|
94
102
|
});
|
|
103
|
+
|
|
104
|
+
describe('search leaf — integration components', () => {
|
|
105
|
+
/**
|
|
106
|
+
* A minimal consumer project: a stub `@astryxdesign/core` (so `findCoreDir`
|
|
107
|
+
* resolves without needing the real package) plus an installed
|
|
108
|
+
* `@acme/widgets` integration that contributes one component.
|
|
109
|
+
*/
|
|
110
|
+
function makeConsumerWithIntegrationComponent() {
|
|
111
|
+
const dir = fs.mkdtempSync(path.join(process.cwd(), '.astryx-search-it-'));
|
|
112
|
+
fs.writeFileSync(path.join(dir, 'package.json'), JSON.stringify({name: 'consumer'}));
|
|
113
|
+
fs.writeFileSync(
|
|
114
|
+
path.join(dir, 'astryx.config.mjs'),
|
|
115
|
+
`export default { integrations: ['@acme/widgets'] };\n`,
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
// Stub core: just needs to exist with an (empty) src/ so discoverComponents
|
|
119
|
+
// doesn't throw. Its own component list is irrelevant to this test.
|
|
120
|
+
const coreDir = path.join(dir, 'node_modules', '@astryxdesign', 'core');
|
|
121
|
+
fs.mkdirSync(path.join(coreDir, 'src'), {recursive: true});
|
|
122
|
+
|
|
123
|
+
const widgetsDir = path.join(dir, 'node_modules', '@acme', 'widgets');
|
|
124
|
+
fs.mkdirSync(path.join(widgetsDir, 'components'), {recursive: true});
|
|
125
|
+
fs.writeFileSync(
|
|
126
|
+
path.join(widgetsDir, 'package.json'),
|
|
127
|
+
JSON.stringify({name: '@acme/widgets', version: '1.0.0'}),
|
|
128
|
+
);
|
|
129
|
+
fs.writeFileSync(
|
|
130
|
+
path.join(widgetsDir, 'astryx.integration.mjs'),
|
|
131
|
+
`export default { components: './components' };\n`,
|
|
132
|
+
);
|
|
133
|
+
fs.writeFileSync(
|
|
134
|
+
path.join(widgetsDir, 'components', 'FancyGizmo.doc.mjs'),
|
|
135
|
+
`export const docs = {
|
|
136
|
+
name: 'FancyGizmo',
|
|
137
|
+
keywords: ['gizmo', 'widget'],
|
|
138
|
+
usage: {description: 'A fancy gizmo widget.'},
|
|
139
|
+
};\n`,
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
return dir;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
it('includes a component contributed by a configured integration', async () => {
|
|
146
|
+
const dir = makeConsumerWithIntegrationComponent();
|
|
147
|
+
try {
|
|
148
|
+
const r = await search('gizmo', {cwd: dir, type: 'component'});
|
|
149
|
+
expect(r.data.results.some(x => x.name === 'FancyGizmo')).toBe(true);
|
|
150
|
+
} finally {
|
|
151
|
+
fs.rmSync(dir, {recursive: true, force: true});
|
|
152
|
+
}
|
|
153
|
+
}, SLOW);
|
|
154
|
+
|
|
155
|
+
it('reports the contributing package as the import hint', async () => {
|
|
156
|
+
const dir = makeConsumerWithIntegrationComponent();
|
|
157
|
+
try {
|
|
158
|
+
const r = await search('FancyGizmo', {cwd: dir, type: 'component'});
|
|
159
|
+
const hit = r.data.results.find(x => x.name === 'FancyGizmo');
|
|
160
|
+
expect(hit?.import).toBe('@acme/widgets');
|
|
161
|
+
} finally {
|
|
162
|
+
fs.rmSync(dir, {recursive: true, force: true});
|
|
163
|
+
}
|
|
164
|
+
}, SLOW);
|
|
165
|
+
});
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file Asserts every `var(--color-data-*, <hex>)` fallback in the dashboard
|
|
5
|
+
* templates equals the token's own default.
|
|
6
|
+
*
|
|
7
|
+
* The fallback is what paints when the token does not resolve, so a fallback
|
|
8
|
+
* that differs from its token makes the same chart series two different colours
|
|
9
|
+
* depending on whether a theme is present. Three of them had drifted
|
|
10
|
+
* (`#22c55e`, `#E5484D`, `#008E80`), which is also why "the defaults are
|
|
11
|
+
* byte-identical to the template fallbacks" was not true.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import {describe, it, expect} from 'vitest';
|
|
15
|
+
import * as fs from 'node:fs';
|
|
16
|
+
import * as path from 'node:path';
|
|
17
|
+
import {fileURLToPath} from 'node:url';
|
|
18
|
+
import {dataTokenDefaults} from '@astryxdesign/core/theme';
|
|
19
|
+
|
|
20
|
+
const TEMPLATES_DIR = path.resolve(
|
|
21
|
+
path.dirname(fileURLToPath(import.meta.url)),
|
|
22
|
+
'../../assets/templates',
|
|
23
|
+
);
|
|
24
|
+
const CALL_SITE = /var\(\s*(--color-data-[a-z0-9-]+)\s*,\s*([^)]+?)\s*\)/g;
|
|
25
|
+
|
|
26
|
+
/** The light side of a `light-dark(a, b)` pair — what a template falls back to. */
|
|
27
|
+
function lightSide(value) {
|
|
28
|
+
const match = /^light-dark\(\s*([^,]+?)\s*,/.exec(value);
|
|
29
|
+
return match ? match[1] : value;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function walk(dir) {
|
|
33
|
+
return fs.readdirSync(dir, {withFileTypes: true}).flatMap(entry => {
|
|
34
|
+
const full = path.join(dir, entry.name);
|
|
35
|
+
return entry.isDirectory() ? walk(full) : [full];
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function callSites() {
|
|
40
|
+
const sites = [];
|
|
41
|
+
for (const file of walk(TEMPLATES_DIR)) {
|
|
42
|
+
if (!/\.(tsx|ts|jsx|js|mjs|css)$/.test(file)) {
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
fs.readFileSync(file, 'utf-8')
|
|
46
|
+
.split('\n')
|
|
47
|
+
.forEach((line, index) => {
|
|
48
|
+
for (const match of line.matchAll(CALL_SITE)) {
|
|
49
|
+
sites.push({
|
|
50
|
+
where: `${path.relative(TEMPLATES_DIR, file)}:${index + 1}`,
|
|
51
|
+
token: match[1],
|
|
52
|
+
fallback: match[2],
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
return sites;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
describe('template --color-data-* fallbacks', () => {
|
|
61
|
+
const sites = callSites();
|
|
62
|
+
|
|
63
|
+
it('finds the call sites at all', () => {
|
|
64
|
+
expect(sites.length).toBeGreaterThan(40);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('names a real token at every call site', () => {
|
|
68
|
+
const unknown = sites.filter(site => !dataTokenDefaults[site.token]);
|
|
69
|
+
expect(unknown.map(site => `${site.where} ${site.token}`)).toEqual([]);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('matches each token default exactly', () => {
|
|
73
|
+
const drifted = sites
|
|
74
|
+
.filter(site => dataTokenDefaults[site.token])
|
|
75
|
+
.filter(
|
|
76
|
+
site => site.fallback !== lightSide(dataTokenDefaults[site.token]),
|
|
77
|
+
)
|
|
78
|
+
.map(
|
|
79
|
+
site =>
|
|
80
|
+
`${site.where} ${site.token}: ${site.fallback} != ${lightSide(dataTokenDefaults[site.token])}`,
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
expect(drifted).toEqual([]);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
@@ -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: [
|