Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/build-list-json-registry-integrity.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@sanring/cli": minor
---

`build`/`list --outdated` gain `--json` output. A new registry-integrity module (dangling `componentDeps`/`sharedDeps`/group references, unparseable peer versions, optional file-fetchability) is now shared across `doctor`, `build`, and the MCP `doctor_project` tool. `fetchRegistry`/`fetchFile` now throw a typed `RegistryFetchError` instead of calling `process.exit` directly — this also fixes `doctor`'s dead "Unreachable" catch block and 6 of 7 MCP tool handlers that had no `try`/`catch` around `getRegistry()` (a single failed fetch could previously kill the whole long-running MCP server). Also fixes a flaky-test root cause where `sync-registry.mjs`'s rm-then-async-copy raced with `mcp.e2e.test.ts`'s `npm run build`.
5 changes: 5 additions & 0 deletions .changeset/remove-exit-code-consistency.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@sanring/cli": patch
---

`remove` now distinguishes unknown targets (hard exit 1, matching `diff`/`update`) from known-but-not-installed ones (soft skip), instead of silently exiting 0 whenever any target succeeded. Fixes a crash in `info`'s `alias:component` lookup, surfaced by newly added test coverage for `info`/`migrate`/`search`.
7 changes: 7 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,13 @@ jobs:
- name: Install dependencies
run: pnpm install --frozen-lockfile

# packages/cli/registry/ is gitignored, generated from the top-level
# registry/ by sync-registry.mjs — normally populated as part of `build`.
# Several tests (e.g. registry.test.ts's bundled-registry fallback) read
# it directly, so a clean checkout needs it synced before `test` runs.
- name: Sync registry fixtures
run: pnpm --filter @sanring/cli run sync-registry

- name: Test
run: pnpm --filter @sanring/cli test

Expand Down
89 changes: 89 additions & 0 deletions DEVLOG.md

Large diffs are not rendered by default.

90 changes: 0 additions & 90 deletions TODOLIST.md

Large diffs are not rendered by default.

14 changes: 11 additions & 3 deletions apps/docs/src/app/i18n/locales/en/components/alert-dialog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,24 @@ export const alertDialogTranslations = {
'Additional classes merged with `AlertDialogContent` layout styles.',
'alertDialog.api.showClose.description':
'Controls whether the built-in close button is rendered. Defaults to `false`, unlike Dialog.',
'alertDialog.api.ariaLabel.description':
'Accessible-name fallback used when no sanringDialogTitle is projected. Untitled content defaults to “Alert dialog”.',
'alertDialog.api.ariaLabelledBy.description':
'Ids of external elements that label the alert dialog. Takes precedence over the projected title and ariaLabel.',
'alertDialog.api.ariaDescribedBy.description':
'Ids of external elements that describe the alert dialog. Takes precedence over sanringDialogDescription.',
'alertDialog.api.action.description':
'Optional result value passed to `DialogRef.close()` when clicked. Defaults to `true`.',
'alertDialog.api.cancel.description':
'Optional result value passed to `DialogRef.close()` when clicked. Defaults to `false`.',
'alertDialog.accessibility.description':
"The dialog container receives role='alertdialog' and aria-modal='true' (enforced by AlertDialogService — callers cannot override these). Backdrop click and Escape are both disabled, requiring users to explicitly choose a confirm or cancel button.",
"The dialog container receives role='alertdialog', aria-modal='true', and an accessible name from its title, ariaLabel, or the 'Alert dialog' fallback. Backdrop click and Escape are both disabled, requiring an explicit choice.",
'alertDialog.keyboard.description': 'Focus is trapped inside the alert dialog while it is open.',
'alertDialog.keyboard.tab': 'Move focus to the next focusable element within the dialog.',
'alertDialog.keyboard.shiftTab': 'Move focus to the previous focusable element within the dialog.',
'alertDialog.keyboard.escape': 'Has no effect — backdrop dismiss and Escape are disabled by default to prevent accidental dismissal.',
'alertDialog.keyboard.shiftTab':
'Move focus to the previous focusable element within the dialog.',
'alertDialog.keyboard.escape':
'Has no effect — backdrop dismiss and Escape are disabled by default to prevent accidental dismissal.',
'alertDialog.stateModel.description':
"Service-based. Call AlertDialogService.open(template, config) to open an alert dialog programmatically, or use [sanringAlertDialogTrigger] for template-driven use. The service always forces role='alertdialog' and disableClose:true — callers cannot opt out. Not a form control.",
} as const;
14 changes: 11 additions & 3 deletions apps/docs/src/app/i18n/locales/en/components/avatar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,15 @@ export const avatarTranslations = {
'Places the badge at the visual start or end edge, respecting RTL direction.',
'avatar.api.overlap.description': 'Stack overlap amount in rem for avatar groups.',
'avatar.api.count.description': 'Number displayed by the avatar group count item.',
'avatar.accessibility.description': "role='img' on the host. Provide ariaLabel or ariaLabelledBy to name a non-decorative avatar. For purely decorative use — such as next to a user name already present in text — add aria-hidden='true' on <sanring-avatar> to suppress redundant announcements.",
'avatar.keyboard.description': 'Not focusable by default. No keyboard interaction.',
'avatar.stateModel.description': 'Stateless. src loads the image; on failure the fallback slot renders; initials are a last resort. No internal selection or value state.',
'avatar.api.clickable.description':
'Gives the group count button semantics and enables pointer and keyboard activation.',
'avatar.api.disabled.description':
'Makes a clickable group count unavailable and removes it from the tab sequence.',
'avatar.api.clicked.description': 'Emitted when an enabled clickable group count is activated.',
'avatar.accessibility.description':
"role='img' on the host. Provide ariaLabel or ariaLabelledBy to name a non-decorative avatar. For purely decorative use — such as next to a user name already present in text — add aria-hidden='true' on <sanring-avatar> to suppress redundant announcements. A clickable group count exposes button semantics and reflects disabled state.",
'avatar.keyboard.description':
'Avatars are not focusable by default. A clickable group count responds to Enter and Space.',
'avatar.stateModel.description':
'Stateless. src loads the image; on failure the fallback slot renders; initials are a last resort. No internal selection or value state.',
} as const;
9 changes: 7 additions & 2 deletions apps/docs/src/app/i18n/locales/en/components/breadcrumb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,20 @@ export const breadcrumbTranslations = {
'breadcrumb.demo.divider': 'Divider',
'breadcrumb.demo.withEllipsis': 'With Ellipsis',
'breadcrumb.demo.customDivider': 'Custom Divider',
'breadcrumb.demo.ariaLabel': 'Breadcrumb',
'breadcrumb.api.description': 'Inputs supported by the breadcrumb primitives.',
'breadcrumb.api.ariaLabel.description':
'Accessible name for the breadcrumb navigation; override it for localization or page-specific context.',
'breadcrumb.api.type.description':
"Divider icon: 'chevron' (default, ›) or 'dot' (·). Pass custom content via ng-content to override entirely.",
'breadcrumb.api.routerLink.description':
'Angular RouterLink value passed to the inner anchor element.',
'breadcrumb.api.class.description': 'Additional classes merged onto the host element.',
'breadcrumb.accessibility.description': "role='navigation' and aria-label='breadcrumb' on the host element. Mark the current page with aria-current='page' on the <sanring-breadcrumb-page> element so screen readers announce the user's current location.",
'breadcrumb.accessibility.description':
"The host uses role='navigation'; set ariaLabel to a localized or context-specific accessible name. Mark the current page with aria-current='page' on <sanring-breadcrumb-page> so screen readers announce the user's current location.",
'breadcrumb.keyboard.description': 'Standard anchor keyboard behavior.',
'breadcrumb.keyboard.tab': 'Navigate forward through breadcrumb links.',
'breadcrumb.keyboard.shiftTab': 'Navigate backward through breadcrumb links.',
'breadcrumb.stateModel.description': 'Stateless — items are rendered via template projection. No internal selection state.',
'breadcrumb.stateModel.description':
'Stateless — items are rendered via template projection. No internal selection state.',
} as const;
9 changes: 7 additions & 2 deletions apps/docs/src/app/i18n/locales/en/components/combobox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ export const comboboxTranslations = {
'Allows selecting more than one item and pairing the field with chips.',
'combobox.api.disabled.description':
'Disables the combobox input and prevents selection changes.',
'combobox.api.inputId.description':
'ID shared by the input or custom trigger and its label. Generated when omitted and overridable for app-level associations.',
'combobox.api.listId.description':
'ID of the listbox referenced by aria-controls. Generated when omitted and overridable when integrating external ARIA relationships.',
'combobox.api.placeholder.description':
'Placeholder text for the combobox input. Pass this from i18n in app code.',
'combobox.api.showClear.description':
Expand All @@ -52,11 +56,12 @@ export const comboboxTranslations = {
'Additional classes merged with the corresponding combobox primitive.',
'combobox.accessibility.description':
"The input element has role='combobox', aria-expanded, aria-controls pointing to the list id, and aria-autocomplete='list'. The list container has role='listbox' and each visible item has role='option'. When inside <sanring-field>, aria-required, aria-invalid, and aria-describedby are forwarded automatically.",
'combobox.keyboard.description': 'Type in the input to filter; navigate the matching options with arrow keys.',
'combobox.keyboard.description':
'Type in the input to filter; navigate the matching options with arrow keys.',
'combobox.keyboard.type': 'Filter the option list to matching entries.',
'combobox.keyboard.navigateList': 'Move focus between matching options, skipping disabled items.',
'combobox.keyboard.selectItem': 'Select the focused option and close the list.',
'combobox.keyboard.escape': 'Close the option list without changing the selection.',
'combobox.stateModel.description':
"CVA (ControlValueAccessor). Use [(ngModel)] or [formControl] for Angular Forms. The text typed in <sanring-combobox-input> drives filtering — the parent component controls which items are rendered. For multi-select, use <sanring-combobox-chips> to display selected values as removable chips.",
'CVA (ControlValueAccessor). Use [(ngModel)] or [formControl] for Angular Forms. The text typed in <sanring-combobox-input> drives filtering — the parent component controls which items are rendered. For multi-select, use <sanring-combobox-chips> to display selected values as removable chips.',
} as const;
12 changes: 8 additions & 4 deletions apps/docs/src/app/i18n/locales/en/components/context-menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export const contextMenuTranslations = {
'contextMenu.api.description':
'Inputs, outputs, and classes supported by the context menu primitives.',
'contextMenu.api.itemSelected.description':
"Emits the value of whichever item was activated (click, Enter, or Space), right before the whole menu — including any open submenu — closes. Declared on sanring-context-menu (the root), so it fires no matter how deep the selected item is nested.",
'Emits the value of whichever item was activated (click, Enter, or Space), right before the whole menu — including any open submenu — closes. Declared on sanring-context-menu (the root), so it fires no matter how deep the selected item is nested.',
'contextMenu.api.value.description':
'The value reported to itemSelected when this item is activated. Required.',
'contextMenu.api.disabled.description':
Expand All @@ -32,10 +32,14 @@ export const contextMenuTranslations = {
'Additional classes merged with the corresponding context menu primitive.',
'contextMenu.accessibility.description':
"The panel has role='menu'. Items receive role='menuitem', role='menuitemcheckbox', or role='menuitemradio'. The trigger zone ([sanringContextMenuTrigger]) does not expose ARIA state — consider pairing it with a visible affordance or keyboard shortcut hint for accessibility.",
'contextMenu.keyboard.description': 'The context menu can also be opened from a keyboard trigger.',
'contextMenu.keyboard.navigateItems': 'Move focus between menu items, skipping disabled items (wraps).',
'contextMenu.keyboard.description':
'The context menu can also be opened from a keyboard trigger.',
'contextMenu.keyboard.navigateItems':
'Move focus between menu items, skipping disabled items (wraps).',
'contextMenu.keyboard.enter': 'Activate the focused menu item.',
'contextMenu.keyboard.tab':
'Close the menu and move to the previous or next control beside its trigger.',
'contextMenu.keyboard.escape': 'Close the context menu.',
'contextMenu.stateModel.description':
"Stateless. Items emit events on activation. The trigger context is set by the [sanringContextMenuTrigger] directive on any host element. Open/close is driven by right-click events or programmatically via the open(x, y) method on the ContextMenuComponent.",
'Stateless. Items emit events on activation. The trigger context is set by the [sanringContextMenuTrigger] directive on any host element. Open/close is driven by right-click events or programmatically via the open(x, y) method on the ContextMenuComponent.',
} as const;
19 changes: 14 additions & 5 deletions apps/docs/src/app/i18n/locales/en/components/date-picker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,16 @@ export const datePickerTranslations = {
'Number of cells in the year-granularity sliding window.',
'datePicker.api.gridColumns.description':
'Grid column count used for arrow-key focus math; defaults to 3 for month/year and 4 for quarter.',
'datePicker.api.disabled.description': 'Date matcher(s) marking periods as unselectable.',
'datePicker.api.disabled.description':
'Pass a date matcher (or matcher array) to disable individual periods, or a boolean to disable the entire picker without clearing its value.',
'datePicker.api.allowDeselect.description':
'Whether re-picking a selected value in single mode clears it.',
'datePicker.api.required.description':
'Marks the picker as required for field integration and aria-required.',
'datePicker.api.ariaLabel.description':
'Accessible name for the picker host when no visible label names it.',
'datePicker.api.ariaLabelledBy.description':
'ID of visible text that provides the accessible name for the picker host.',
'datePicker.api.ariaDescribedBy.description':
'ID of helper text that describes the picker; merged with Field-provided description ids.',
'datePicker.api.rangePeriodCountLimit.description':
Expand All @@ -91,12 +96,16 @@ export const datePickerTranslations = {
'datePicker.api.removeDate.description':
'Removes a single date from the selection (multi mode only).',
'datePicker.api.focus.description': 'Moves focus to the date-picker host element.',
'datePicker.accessibility.description': "role='grid' on the calendar panel, aria-label on the grid from the month/year header, role='gridcell' on each cell. The host element receives aria-required, aria-invalid, and aria-describedby, which auto-wire to the surrounding sanring-field.",
'datePicker.keyboard.description': 'Calendar grid navigation is handled by @sanring/date-picker-core.',
'datePicker.accessibility.description':
"The host group accepts aria-label or aria-labelledby and receives aria-invalid, aria-disabled, and aria-describedby. The calendar panel has role='grid' with an aria-label derived from its header; each role='gridcell' receives its selection, disabled, and required states. Field descriptions are wired automatically.",
'datePicker.keyboard.description':
'Calendar grid navigation is handled by @sanring/date-picker-core.',
'datePicker.keyboard.arrowKeys': 'Navigate between cells in the calendar grid.',
'datePicker.keyboard.pageUpDown': 'Previous / next page (month, quarter, or year depending on granularity).',
'datePicker.keyboard.pageUpDown':
'Previous / next page (month, quarter, or year depending on granularity).',
'datePicker.keyboard.homeEnd': 'Jump to the first / last cell in the current view.',
'datePicker.keyboard.enter': 'Select the focused cell.',
'datePicker.keyboard.escape': 'Close the date-picker popover without committing a selection.',
'datePicker.stateModel.description': 'Implements ControlValueAccessor. Use [(ngModel)] or formControl. Value type: DatePickerValue (single date, a range pair, or an array of dates depending on mode). mode and granularity can be changed at runtime.',
'datePicker.stateModel.description':
'Implements ControlValueAccessor. Use [(ngModel)] or formControl. Value type: DatePickerValue (single date, a range pair, or an array of dates depending on mode). mode and granularity can be changed at runtime.',
} as const;
8 changes: 7 additions & 1 deletion apps/docs/src/app/i18n/locales/en/components/dialog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,19 @@ export const dialogTranslations = {
'dialog.api.description': 'Inputs supported by the dialog primitives.',
'dialog.api.class.description': 'Additional classes merged with DialogContent layout styles.',
'dialog.api.showClose.description': 'Controls whether the built-in close button is rendered.',
'dialog.api.ariaLabel.description':
'Accessible-name fallback used when no sanringDialogTitle is projected. DialogConfig ariaLabel remains supported.',
'dialog.api.ariaLabelledBy.description':
'Ids of external elements that label the dialog. Takes precedence over the projected title and ariaLabel.',
'dialog.api.ariaDescribedBy.description':
'Ids of external elements that describe the dialog. Takes precedence over sanringDialogDescription.',
'dialog.api.triggerConfig.description':
'CDK DialogConfig passed when sanringDialogTrigger opens the template.',
'dialog.api.closeResult.description':
'Optional result value emitted when sanringDialogClose closes the dialog.',
'dialog.api.mediaClass.description': 'Additional classes merged with the dialog media container.',
'dialog.accessibility.description':
"The CDK Dialog container receives role='dialog' and aria-modal='true'. When sanringDialogTitle or sanringDialogDescription are present, their ids are automatically wired to aria-labelledby and aria-describedby on the container. Angular CDK's FocusTrap keeps Tab and Shift+Tab cycling within the open dialog.",
"The CDK Dialog container receives role='dialog' and aria-modal='true'. Projected titles and descriptions are wired automatically; ariaLabel provides a fallback name for untitled content. Angular CDK's FocusTrap keeps Tab and Shift+Tab cycling within the open dialog.",
'dialog.keyboard.description': 'Focus is trapped inside the dialog while it is open.',
'dialog.keyboard.tab': 'Move focus to the next focusable element within the dialog.',
'dialog.keyboard.shiftTab': 'Move focus to the previous focusable element within the dialog.',
Expand Down
Loading
Loading