@astryxdesign/cli 0.1.7 → 0.1.8-canary.07362a9
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 +40 -0
- package/README.md +115 -19
- package/{src/api → api/blog}/blog.mjs +42 -6
- package/{src/api → api/blog}/blog.test.mjs +2 -2
- package/api/build/build.mjs +92 -0
- package/api/build/build.test.mjs +92 -0
- package/{src/api → api/component}/component.mjs +49 -26
- package/{src/api → api/discover}/discover.mjs +55 -18
- package/{src/api → api/docs}/docs.mjs +47 -21
- package/{src/api → api/doctor}/doctor.mjs +13 -10
- package/{src/api → api}/error.mjs +2 -2
- package/{src/api → api/hook}/hook.mjs +11 -11
- package/{src/api → api}/index.mjs +16 -9
- package/{src/api → api/integration}/validate-integration.mjs +48 -15
- package/{src/api → api/integration}/validate-integration.test.mjs +1 -1
- package/{src/api → api/layout}/layout.mjs +66 -17
- package/{src/api → api/layout}/layout.test.mjs +1 -1
- package/{src/api → api/search}/search.mjs +71 -12
- package/api/swizzle/swizzle.mjs +283 -0
- package/{src/commands → api/swizzle}/swizzle.test.mjs +26 -1
- package/{src/api → api/template}/template-integration.test.mjs +2 -2
- package/{src/api → api/template}/template-suffix.test.mjs +3 -3
- package/{src/api → api/template}/template.mjs +96 -19
- package/{src/api → api/theme}/theme-add.mjs +35 -7
- package/{src → authoring}/codemod.mjs +6 -6
- package/{src → authoring}/config.test.mjs +1 -1
- package/{src → authoring}/doc.mjs +1 -1
- package/{src → authoring}/doc.test.mjs +1 -1
- package/{src → authoring}/template.mjs +1 -1
- package/bin/astryx.mjs +12 -10
- package/{src → cli}/commands/blog.mjs +48 -7
- package/{src → cli}/commands/build-theme.mjs +133 -115
- package/cli/commands/build.mjs +172 -0
- package/cli/commands/cli-postinstall.test.mjs +42 -0
- package/{src → cli}/commands/component/index.mjs +52 -25
- package/{src → cli}/commands/component-ownership.test.mjs +3 -3
- package/{src → cli}/commands/component-package.test.mjs +1 -1
- package/{src → cli}/commands/component-resolution.test.mjs +2 -2
- package/{src → cli}/commands/discover.mjs +23 -12
- package/{src → cli}/commands/docs.mjs +42 -10
- package/{src → cli}/commands/docs.test.mjs +1 -1
- package/{src → cli}/commands/doctor.mjs +3 -3
- package/{src → cli}/commands/doctor.test.mjs +2 -2
- package/{src → cli}/commands/ensure-core-built.mjs +3 -2
- package/{src → cli}/commands/external-showcase.test.mjs +1 -1
- package/{src → cli}/commands/hook/index.mjs +40 -17
- package/{src → cli}/commands/import-hint-correctness.test.mjs +1 -1
- package/cli/commands/init.mjs +195 -0
- package/{src → cli}/commands/init.next-steps.test.mjs +1 -1
- package/cli/commands/interactive-guard.test.mjs +66 -0
- package/{src → cli}/commands/json-contract.test.mjs +2 -2
- package/{src → cli}/commands/layout.mjs +59 -19
- package/{src → cli}/commands/search.mjs +22 -15
- package/{src → cli}/commands/search.test.mjs +1 -1
- package/cli/commands/setup-nudge.test.mjs +108 -0
- package/cli/commands/swizzle.mjs +103 -0
- package/{src → cli}/commands/template.mjs +84 -53
- package/{src → cli}/commands/template.path-safety.test.mjs +2 -2
- package/{src → cli}/commands/upgrade.integration-policy.test.mjs +1 -1
- package/{src → cli}/commands/upgrade.mjs +291 -77
- package/cli/commands/upgrade.test.mjs +314 -0
- package/{src → cli}/commands/validate-integration.mjs +17 -37
- package/{src → cli}/index.mjs +55 -19
- package/{src/codemods → codemods}/__tests__/registry.test.mjs +1 -0
- package/{src/codemods → codemods}/ensure-jscodeshift.mjs +12 -28
- package/{src/codemods → codemods}/integration-discovery.mjs +6 -3
- package/{src/codemods → codemods}/integration-discovery.test.mjs +1 -1
- package/{src/codemods → codemods}/integration-runner.mjs +8 -3
- package/{src/codemods → codemods}/registry.mjs +4 -1
- package/{src/codemods → codemods}/run-codemod.mjs +18 -11
- package/{src/codemods → codemods}/runner.mjs +24 -14
- package/{src/codemods → codemods}/transforms/v0.0.10/remove-size-props.mjs +9 -4
- package/{src/codemods → codemods}/transforms/v0.0.12/add-is-icon-only.mjs +27 -22
- package/{src/codemods → codemods}/transforms/v0.0.13/icon-name-deprecations.mjs +12 -5
- package/{src/codemods → codemods}/transforms/v0.0.13/rename-attachments-to-drawer.mjs +15 -8
- package/{src/codemods → codemods}/transforms/v0.0.13/toolbar-density-to-size.mjs +10 -4
- package/{src/codemods → codemods}/transforms/v0.0.14/rename-action-props.mjs +11 -5
- package/{src/codemods → codemods}/transforms/v0.0.14/rename-section-wash-to-muted.mjs +11 -6
- package/{src/codemods → codemods}/transforms/v0.0.14/rename-status-variants.mjs +15 -10
- package/{src/codemods → codemods}/transforms/v0.0.15/migrate-item-children-to-endcontent.mjs +13 -8
- package/{src/codemods → codemods}/transforms/v0.0.15/migrate-selector-children-to-render-option.mjs +18 -13
- package/{src/codemods → codemods}/transforms/v0.0.15/migrate-theme-selectors-to-data-attrs.mjs +8 -4
- package/{src/codemods → codemods}/transforms/v0.0.15/rename-date-picker-to-input.mjs +11 -6
- package/{src/codemods → codemods}/transforms/v0.0.15/rename-imperative-ref-to-handleRef.mjs +6 -1
- package/{src/codemods → codemods}/transforms/v0.0.15/rename-isStreaming-to-isStopShown.mjs +6 -1
- package/{src/codemods → codemods}/transforms/v0.0.15/rename-stack-element-to-as.mjs +8 -3
- package/{src/codemods → codemods}/transforms/v0.0.2/migrate-badge-dot-to-statusdot.mjs +17 -10
- package/{src/codemods → codemods}/transforms/v0.0.2/migrate-gap-to-numeric.mjs +7 -1
- package/{src/codemods → codemods}/transforms/v0.0.2/migrate-isFullBleed-to-padding.mjs +9 -4
- package/{src/codemods → codemods}/transforms/v0.0.2/migrate-useXDSIcon-to-getIcon.mjs +12 -7
- package/{src/codemods → codemods}/transforms/v0.0.2/rename-banner-endButton-to-endContent.mjs +7 -2
- package/{src/codemods → codemods}/transforms/v0.0.2/rename-form-tooltip-startIcon.mjs +8 -2
- package/{src/codemods → codemods}/transforms/v0.0.2/rename-isShown-to-isOpen.mjs +7 -2
- package/{src/codemods → codemods}/transforms/v0.0.2/rename-selector-items-to-options.mjs +7 -2
- package/{src/codemods → codemods}/transforms/v0.0.2/rename-sidenav-header-to-heading.mjs +11 -4
- package/{src/codemods → codemods}/transforms/v0.0.2/rename-topnav-title-to-heading.mjs +10 -4
- package/{src/codemods → codemods}/transforms/v0.0.2/unify-uncontrolled-to-defaultX.mjs +7 -2
- package/{src/codemods → codemods}/transforms/v0.0.2/unify-visibility-to-onOpenChange.mjs +10 -5
- package/{src/codemods → codemods}/transforms/v0.0.6/migrate-badge-children-to-label.mjs +7 -2
- package/{src/codemods → codemods}/transforms/v0.0.6/migrate-collapse-to-collapsible.mjs +9 -3
- package/{src/codemods → codemods}/transforms/v0.0.6/migrate-radius-tokens.mjs +11 -5
- package/{src/codemods → codemods}/transforms/v0.0.6/migrate-shadow-tokens.mjs +13 -6
- package/{src/codemods → codemods}/transforms/v0.0.6/migrate-skeleton-radius.mjs +7 -1
- package/{src/codemods → codemods}/transforms/v0.0.6/migrate-token-names.mjs +11 -5
- package/{src/codemods → codemods}/transforms/v0.0.7/rename-banner-variant-to-container.mjs +8 -3
- package/{src/codemods → codemods}/transforms/v0.0.8/migrate-token-renames.mjs +15 -8
- package/{src/codemods → codemods}/transforms/v0.0.8/rename-endslot-to-endcontent.mjs +7 -2
- package/{src/codemods → codemods}/transforms/v0.1.0/__tests__/drop-xds-prefix-imports.test.mjs +42 -7
- package/{src/codemods → codemods}/transforms/v0.1.0/__tests__/migrate-xds-module-specifiers.test.mjs +43 -0
- package/{src/codemods → codemods}/transforms/v0.1.0/drop-xds-prefix-imports.mjs +117 -13
- package/{src/codemods → codemods}/transforms/v0.1.0/migrate-xds-css-surfaces.mjs +21 -17
- package/{src/codemods → codemods}/transforms/v0.1.0/migrate-xds-declare-module.mjs +7 -2
- package/{src/codemods → codemods}/transforms/v0.1.0/migrate-xds-module-specifiers.mjs +86 -15
- package/{src/codemods → codemods}/transforms/v0.1.2/rename-text-color-active-to-accent.mjs +15 -9
- package/{src/codemods → codemods}/transforms/v0.1.3/migrate-layout-components-to-experimental.mjs +17 -12
- package/{src/codemods → codemods}/transforms/v0.1.5/rename-switch-label-spacing-default-to-hug.mjs +15 -9
- package/{src/codemods → codemods}/transforms/v0.1.7/migrate-table-tableprops-to-direct-props.mjs +17 -10
- package/{src/codemods → codemods}/transforms/v0.1.7/rename-table-renderprops-styles-to-xstyle.mjs +18 -13
- package/codemods/transforms/v0.1.8/__tests__/rename-avatar-size-scale.test.mjs +161 -0
- package/codemods/transforms/v0.1.8/index.mjs +19 -0
- package/codemods/transforms/v0.1.8/rename-avatar-size-scale.mjs +239 -0
- package/docs/cli-integrations.doc.mjs +150 -0
- package/docs/elevation.doc.mjs +79 -1
- package/docs/getting-started.doc.mjs +9 -9
- package/docs/internationalization.doc.mjs +92 -46
- package/docs/migration.doc.mjs +18 -18
- package/docs/principles.doc.dense.mjs +1 -1
- package/docs/principles.doc.mjs +6 -6
- package/docs/principles.doc.zh.mjs +1 -1
- package/docs/styling-libraries.doc.mjs +3 -3
- package/docs/styling.doc.mjs +4 -4
- package/docs/theme.doc.dense.mjs +2 -2
- package/docs/theme.doc.mjs +7 -7
- package/docs/theme.doc.zh.mjs +1 -1
- package/docs/tokens.doc.mjs +1 -1
- package/docs/working-with-ai.doc.mjs +19 -19
- package/{src/commands → lib/agent-docs}/agent-docs.mjs +164 -29
- package/{src/commands → lib/agent-docs}/agent-docs.path-safety.test.mjs +1 -1
- package/{src/commands → lib/agent-docs}/agent-docs.test.mjs +123 -10
- package/{src/lib → lib}/cli-error.mjs +2 -2
- package/{src/lib → lib}/component-discovery.mjs +44 -2
- package/{src/lib → lib}/component-format.mjs +67 -25
- package/{src/lib → lib}/component-loader.mjs +27 -13
- package/{src/lib → lib}/componentDocOverlay.test.mjs +0 -1
- package/{src/lib → lib}/error-codes.test.mjs +1 -1
- package/{src/lib → lib}/hook-discovery.mjs +14 -1
- package/{src/lib → lib}/hook-format.mjs +29 -5
- package/{src/lib → lib}/integration-warnings.mjs +2 -2
- package/{src/lib → lib}/integrations.mjs +20 -0
- package/{src/lib → lib}/integrations.test.mjs +1 -1
- package/{src/lib → lib}/json-contract.test.mjs +1 -1
- package/{src/lib → lib}/json-shim.mjs +11 -6
- package/{src/lib → lib}/json-shim.test.mjs +1 -1
- package/{src/lib → lib}/json.mjs +3 -3
- package/{src/lib → lib}/levenshtein.mjs +6 -0
- package/{src/lib → lib}/manifest.mjs +7 -5
- package/{src/lib → lib}/manifest.test.mjs +2 -2
- package/{src/lib → lib}/module-loader.mjs +1 -0
- package/{src/lib → lib}/package-scanner.mjs +47 -0
- package/{src/lib → lib}/project.mjs +48 -18
- package/{src/lib → lib}/project.test.mjs +1 -1
- package/{src/lib → lib}/resolve-theme.mjs +5 -0
- package/{src/lib → lib}/string-utils.mjs +15 -0
- package/lib/term-log.mjs +57 -0
- package/{src/lib → lib}/xle/browser.mjs +18 -8
- package/{src/lib → lib}/xle/expand.mjs +155 -22
- package/{src/lib → lib}/xle/parse.mjs +119 -13
- package/{src/lib → lib}/xle/print.mjs +54 -14
- package/{src/lib → lib}/xle/registry-core.mjs +28 -3
- package/{src/lib → lib}/xle/registry.mjs +9 -4
- package/{src/lib → lib}/xle/splice.mjs +13 -2
- package/{src/lib → lib}/xle/validate.mjs +95 -14
- package/lib/xle/xle-ast.d.ts +214 -0
- package/package.json +37 -28
- package/scripts/postinstall.mjs +74 -0
- package/templates/blocks/components/Avatar/AvatarFallbackChain.tsx +4 -4
- package/templates/blocks/components/Avatar/AvatarGroup.tsx +2 -2
- package/templates/blocks/components/Avatar/AvatarInitialsFallback.tsx +1 -1
- package/templates/blocks/components/Avatar/AvatarShowcase.tsx +5 -5
- package/templates/blocks/components/Avatar/AvatarUserCard.tsx +1 -1
- package/templates/blocks/components/Avatar/AvatarWithImage.tsx +4 -4
- package/templates/blocks/components/Avatar/AvatarWithStatus.tsx +3 -3
- package/templates/blocks/components/AvatarGroup/AvatarGroupShowcase.tsx +2 -2
- package/templates/blocks/components/AvatarGroupOverflow/AvatarGroupOverflowCustomText.tsx +1 -1
- package/templates/blocks/components/AvatarGroupOverflow/AvatarGroupOverflowDefault.tsx +1 -1
- package/templates/blocks/components/AvatarGroupOverflow/AvatarGroupOverflowShowcase.tsx +2 -2
- package/templates/blocks/components/AvatarStatusDot/AvatarStatusDotShowcase.tsx +3 -3
- package/templates/blocks/components/AvatarStatusDot/AvatarStatusDotVariants.tsx +3 -3
- package/templates/blocks/components/Banner/BannerFloating.doc.mjs +14 -0
- package/templates/blocks/components/Banner/BannerFloating.tsx +16 -0
- package/templates/blocks/components/Button/ButtonFloating.doc.mjs +14 -0
- package/templates/blocks/components/Button/ButtonFloating.tsx +37 -0
- package/templates/blocks/components/ButtonGroup/ButtonGroupFloating.doc.mjs +14 -0
- package/templates/blocks/components/ButtonGroup/ButtonGroupFloating.tsx +23 -0
- package/templates/blocks/components/Card/CardElevations.doc.mjs +14 -0
- package/templates/blocks/components/Card/CardElevations.tsx +32 -0
- package/templates/blocks/components/Card/ClickableCardElevated.doc.mjs +14 -0
- package/templates/blocks/components/Card/ClickableCardElevated.tsx +21 -0
- package/templates/blocks/components/Card/SelectableCardElevated.doc.mjs +14 -0
- package/templates/blocks/components/Card/SelectableCardElevated.tsx +37 -0
- package/templates/blocks/components/Carousel/CarouselSnap.tsx +1 -1
- package/templates/blocks/components/ChatComposer/ChatComposerFlat.doc.mjs +14 -0
- package/templates/blocks/components/ChatComposer/ChatComposerFlat.tsx +75 -0
- package/templates/blocks/components/ChatMessage/ChatMessageAvatarName.tsx +2 -2
- package/templates/blocks/components/ChatMessage/ChatMessageMultiBubble.tsx +1 -1
- package/templates/blocks/components/ChatMessageBubble/ChatMessageBubbleGrouping.tsx +1 -1
- package/templates/blocks/components/ChatMessageBubble/ChatMessageBubbleMetadata.tsx +1 -1
- package/templates/blocks/components/ChatMessageList/ChatMessageListDensity.tsx +5 -9
- package/templates/blocks/components/ChatMessageList/ChatMessageListFullFeatured.tsx +1 -1
- package/templates/blocks/components/CodeBlock/CodeBlockTerminal.tsx +1 -1
- package/templates/blocks/components/HoverCard/HoverCardShowcase.tsx +1 -1
- package/templates/blocks/components/IconButton/IconButtonFloating.doc.mjs +14 -0
- package/templates/blocks/components/IconButton/IconButtonFloating.tsx +37 -0
- package/templates/blocks/components/InternationalizationProvider/InternationalizationProvider01ShippedLocale.doc.mjs +19 -0
- package/templates/blocks/components/InternationalizationProvider/InternationalizationProvider01ShippedLocale.tsx +46 -0
- package/templates/blocks/components/InternationalizationProvider/InternationalizationProvider02Overrides.doc.mjs +14 -0
- package/templates/blocks/components/InternationalizationProvider/InternationalizationProvider02Overrides.tsx +33 -0
- package/templates/blocks/components/Item/ItemWithMedia.tsx +2 -2
- package/templates/blocks/components/ListItem/ListItemWithMedia.tsx +2 -2
- package/templates/blocks/components/OverflowList/OverflowListCappedToolbar.doc.mjs +14 -0
- package/templates/blocks/components/OverflowList/OverflowListCappedToolbar.tsx +39 -0
- package/templates/blocks/components/OverflowList/OverflowListMultiRowTags.doc.mjs +14 -0
- package/templates/blocks/components/OverflowList/OverflowListMultiRowTags.tsx +44 -0
- package/templates/blocks/components/Pagination/PaginationDotsCarousel.tsx +2 -6
- package/templates/blocks/components/Stack/StackFillItem.tsx +2 -6
- package/templates/blocks/components/TabList/TabListTabsWithActions.doc.mjs +1 -1
- package/templates/blocks/components/TabList/TabListTabsWithActions.tsx +2 -7
- package/templates/blocks/components/Table/TableRowStatusTable.doc.mjs +14 -0
- package/templates/blocks/components/Table/TableRowStatusTable.tsx +59 -0
- package/templates/blocks/components/Thumbnail/ThumbnailDisabled.tsx +6 -1
- package/templates/blocks/components/Thumbnail/ThumbnailGallery.tsx +1 -0
- package/templates/blocks/components/Thumbnail/ThumbnailRemovable.tsx +1 -0
- package/templates/blocks/components/TypeaheadItem/TypeaheadItemShowcase.tsx +1 -1
- package/templates/pages/ai-chat/page.tsx +4 -4
- package/templates/pages/dashboard-portfolio/page.tsx +3 -11
- package/templates/pages/detail-page/page.tsx +5 -12
- package/templates/pages/documentation-design/page.tsx +1 -1
- package/templates/pages/messaging-shell/page.tsx +6 -8
- package/templates/pages/table-grouped/page.tsx +9 -14
- package/templates/pages/table-page/page.tsx +7 -9
- package/templates/pages/table-page-heatmap-status/page.tsx +5 -13
- package/{src/types → types}/api.contract.assert.ts +17 -8
- package/{src/types → types}/api.d.ts +110 -9
- package/{src/types → types}/base.d.ts +40 -4
- package/types/build.d.ts +53 -0
- package/types/codemod.d.ts +166 -0
- package/{src/types → types}/component.d.ts +1 -1
- package/{src/types → types}/discover.d.ts +1 -1
- package/{src/types → types}/docs.d.ts +1 -4
- package/{src/types → types}/hook.d.ts +1 -1
- package/{src/types → types}/index.d.ts +1 -0
- package/types/jscodeshift.d.ts +19 -0
- package/types/layout.d.ts +74 -0
- package/{src/types → types}/swizzle.d.ts +4 -0
- package/{src/types → types}/template-api.d.ts +4 -1
- package/types/theme.d.ts +53 -0
- package/types/upgrade.d.ts +108 -0
- package/utils/package-manager.mjs +178 -0
- package/utils/package-manager.test.mjs +220 -0
- package/{src/utils → utils}/path-safety.mjs +0 -18
- package/{src/utils → utils}/paths.mjs +4 -1
- package/{src/utils → utils}/update-check.mjs +5 -4
- package/{src/utils → utils}/update-check.test.mjs +3 -3
- package/docs/integration-authoring.md +0 -105
- package/src/commands/build.mjs +0 -196
- package/src/commands/init.mjs +0 -288
- package/src/commands/interactive-guard.test.mjs +0 -69
- package/src/commands/swizzle.mjs +0 -440
- package/src/commands/upgrade.test.mjs +0 -160
- package/src/types/codemod.d.ts +0 -81
- package/src/types/theme.d.ts +0 -23
- package/src/types/upgrade.d.ts +0 -64
- package/src/utils/interactive.mjs +0 -76
- package/src/utils/interactive.test.mjs +0 -70
- package/src/utils/package-manager.mjs +0 -74
- package/src/utils/package-manager.test.mjs +0 -113
- /package/{src/api → api/docs}/docOverlays.test.mjs +0 -0
- /package/{src/api → api/integration}/integration-block-exports.test.mjs +0 -0
- /package/{src/api → api/template}/template.test.mjs +0 -0
- /package/{src → authoring}/codemod.test.mjs +0 -0
- /package/{src → authoring}/config.mjs +0 -0
- /package/{src → authoring}/integration.mjs +0 -0
- /package/{src → authoring}/template.test.mjs +0 -0
- /package/{src → cli}/cli-exit-codes.test.mjs +0 -0
- /package/{src → cli}/commands/build-theme.color-scheme.test.mjs +0 -0
- /package/{src → cli}/commands/build-theme.import-path.test.mjs +0 -0
- /package/{src → cli}/commands/build-theme.path-safety.test.mjs +0 -0
- /package/{src → cli}/commands/build-theme.prose.test.mjs +0 -0
- /package/{src → cli}/commands/build-theme.variants.test.mjs +0 -0
- /package/{src → cli}/commands/build-theme.watch.test.mjs +0 -0
- /package/{src → cli}/commands/component.test.mjs +0 -0
- /package/{src → cli}/commands/detail-levels.test.mjs +0 -0
- /package/{src → cli}/commands/swizzle.path-safety.test.mjs +0 -0
- /package/{src → cli}/commands/swizzle.routing.test.mjs +0 -0
- /package/{src → cli}/commands/template.test.mjs +0 -0
- /package/{src → cli}/commands/upgrade.config-ordering.test.mjs +0 -0
- /package/{src → cli}/commands/validate-integration.test.mjs +0 -0
- /package/{src → cli}/update-hint-commands.test.mjs +0 -0
- /package/{src/codemods → codemods}/__tests__/rename-imperative-ref-to-handleRef.test.mjs +0 -0
- /package/{src/codemods → codemods}/__tests__/rename-isStreaming-to-isStopShown.test.mjs +0 -0
- /package/{src/codemods → codemods}/__tests__/rename-section-wash-to-muted.test.mjs +0 -0
- /package/{src/codemods → codemods}/__tests__/runner.test.mjs +0 -0
- /package/{src/codemods → codemods}/__tests__/toolbar-density-to-size.test.mjs +0 -0
- /package/{src/codemods → codemods}/__tests__/validation.test.mjs +0 -0
- /package/{src/codemods → codemods}/transforms/v0.0.10/__tests__/remove-size-props.test.mjs +0 -0
- /package/{src/codemods → codemods}/transforms/v0.0.10/index.mjs +0 -0
- /package/{src/codemods → codemods}/transforms/v0.0.12/__tests__/add-is-icon-only.test.mjs +0 -0
- /package/{src/codemods → codemods}/transforms/v0.0.12/index.mjs +0 -0
- /package/{src/codemods → codemods}/transforms/v0.0.13/__tests__/icon-name-deprecations.test.mjs +0 -0
- /package/{src/codemods → codemods}/transforms/v0.0.13/__tests__/rename-attachments-to-drawer.test.mjs +0 -0
- /package/{src/codemods → codemods}/transforms/v0.0.13/index.mjs +0 -0
- /package/{src/codemods → codemods}/transforms/v0.0.14/__tests__/rename-action-props.test.mjs +0 -0
- /package/{src/codemods → codemods}/transforms/v0.0.14/__tests__/rename-status-variants.test.mjs +0 -0
- /package/{src/codemods → codemods}/transforms/v0.0.14/index.mjs +0 -0
- /package/{src/codemods → codemods}/transforms/v0.0.15/__tests__/migrate-item-children-to-endcontent.test.mjs +0 -0
- /package/{src/codemods → codemods}/transforms/v0.0.15/__tests__/migrate-selector-children-to-render-option.test.mjs +0 -0
- /package/{src/codemods → codemods}/transforms/v0.0.15/__tests__/migrate-theme-selectors-to-data-attrs.test.mjs +0 -0
- /package/{src/codemods → codemods}/transforms/v0.0.15/__tests__/rename-date-picker-to-input.test.mjs +0 -0
- /package/{src/codemods → codemods}/transforms/v0.0.15/__tests__/rename-stack-element-to-as.test.mjs +0 -0
- /package/{src/codemods → codemods}/transforms/v0.0.15/index.mjs +0 -0
- /package/{src/codemods → codemods}/transforms/v0.0.2/__tests__/migrate-badge-dot-to-statusdot.test.mjs +0 -0
- /package/{src/codemods → codemods}/transforms/v0.0.2/__tests__/migrate-gap-to-numeric.test.mjs +0 -0
- /package/{src/codemods → codemods}/transforms/v0.0.2/__tests__/migrate-isFullBleed-to-padding.test.mjs +0 -0
- /package/{src/codemods → codemods}/transforms/v0.0.2/__tests__/migrate-useXDSIcon-to-getIcon.test.mjs +0 -0
- /package/{src/codemods → codemods}/transforms/v0.0.2/__tests__/rename-banner-endButton-to-endContent.test.mjs +0 -0
- /package/{src/codemods → codemods}/transforms/v0.0.2/__tests__/rename-form-tooltip-startIcon.test.mjs +0 -0
- /package/{src/codemods → codemods}/transforms/v0.0.2/__tests__/rename-isShown-to-isOpen.test.mjs +0 -0
- /package/{src/codemods → codemods}/transforms/v0.0.2/__tests__/rename-selector-items-to-options.test.mjs +0 -0
- /package/{src/codemods → codemods}/transforms/v0.0.2/__tests__/rename-sidenav-header-to-heading.test.mjs +0 -0
- /package/{src/codemods → codemods}/transforms/v0.0.2/__tests__/rename-topnav-title-to-heading.test.mjs +0 -0
- /package/{src/codemods → codemods}/transforms/v0.0.2/__tests__/unify-uncontrolled-to-defaultX.test.mjs +0 -0
- /package/{src/codemods → codemods}/transforms/v0.0.2/__tests__/unify-visibility-to-onOpenChange.test.mjs +0 -0
- /package/{src/codemods → codemods}/transforms/v0.0.2/index.mjs +0 -0
- /package/{src/codemods → codemods}/transforms/v0.0.6/__tests__/migrate-collapse-to-collapsible.test.mjs +0 -0
- /package/{src/codemods → codemods}/transforms/v0.0.6/__tests__/migrate-radius-tokens.test.mjs +0 -0
- /package/{src/codemods → codemods}/transforms/v0.0.6/__tests__/migrate-shadow-tokens.test.mjs +0 -0
- /package/{src/codemods → codemods}/transforms/v0.0.6/__tests__/migrate-skeleton-radius.test.mjs +0 -0
- /package/{src/codemods → codemods}/transforms/v0.0.6/__tests__/migrate-token-names.test.mjs +0 -0
- /package/{src/codemods → codemods}/transforms/v0.0.6/index.mjs +0 -0
- /package/{src/codemods → codemods}/transforms/v0.0.7/__tests__/rename-banner-variant-to-container.test.mjs +0 -0
- /package/{src/codemods → codemods}/transforms/v0.0.7/index.mjs +0 -0
- /package/{src/codemods → codemods}/transforms/v0.0.8/__tests__/migrate-token-renames.test.mjs +0 -0
- /package/{src/codemods → codemods}/transforms/v0.0.8/__tests__/rename-endslot-to-endcontent.test.mjs +0 -0
- /package/{src/codemods → codemods}/transforms/v0.0.8/index.mjs +0 -0
- /package/{src/codemods → codemods}/transforms/v0.1.0/__tests__/migrate-xds-css-surfaces.test.mjs +0 -0
- /package/{src/codemods → codemods}/transforms/v0.1.0/__tests__/migrate-xds-declare-module.test.mjs +0 -0
- /package/{src/codemods → codemods}/transforms/v0.1.0/__tests__/v0.1.0-ordering.test.mjs +0 -0
- /package/{src/codemods → codemods}/transforms/v0.1.0/index.mjs +0 -0
- /package/{src/codemods → codemods}/transforms/v0.1.2/__tests__/rename-text-color-active-to-accent.test.mjs +0 -0
- /package/{src/codemods → codemods}/transforms/v0.1.2/index.mjs +0 -0
- /package/{src/codemods → codemods}/transforms/v0.1.3/__tests__/migrate-layout-components-to-experimental.test.mjs +0 -0
- /package/{src/codemods → codemods}/transforms/v0.1.3/index.mjs +0 -0
- /package/{src/codemods → codemods}/transforms/v0.1.5/__tests__/rename-switch-label-spacing-default-to-hug.test.mjs +0 -0
- /package/{src/codemods → codemods}/transforms/v0.1.5/index.mjs +0 -0
- /package/{src/codemods → codemods}/transforms/v0.1.7/__tests__/migrate-table-tableprops-to-direct-props.test.mjs +0 -0
- /package/{src/codemods → codemods}/transforms/v0.1.7/__tests__/rename-table-renderprops-styles-to-xstyle.test.mjs +0 -0
- /package/{src/codemods → codemods}/transforms/v0.1.7/index.mjs +0 -0
- /package/{src/lib → lib}/cli-error.test.mjs +0 -0
- /package/{src/lib → lib}/component-discovery.importpath.test.mjs +0 -0
- /package/{src/lib → lib}/component-format.test.mjs +0 -0
- /package/{src/lib → lib}/component-loader.test.mjs +0 -0
- /package/{src/lib → lib}/config-cache.mjs +0 -0
- /package/{src/lib → lib}/config-cache.test.mjs +0 -0
- /package/{src/lib → lib}/config-schema.mjs +0 -0
- /package/{src/lib → lib}/error-codes.mjs +0 -0
- /package/{src/lib → lib}/integration-warnings.test.mjs +0 -0
- /package/{src/lib → lib}/module-loader.test.mjs +0 -0
- /package/{src/lib → lib}/node-version.mjs +0 -0
- /package/{src/lib → lib}/node-version.test.mjs +0 -0
- /package/{src/lib → lib}/parse.mjs +0 -0
- /package/{src/lib → lib}/site.mjs +0 -0
- /package/{src/lib → lib}/xle/browser.d.ts +0 -0
- /package/{src/lib → lib}/xle/xle.test.mjs +0 -0
- /package/{src/schemas → schemas}/doc-schema.mjs +0 -0
- /package/{src/schemas → schemas}/template-schema.mjs +0 -0
- /package/{src/types → types}/config.d.ts +0 -0
- /package/{src/types → types}/doc.d.ts +0 -0
- /package/{src/types → types}/doctor.d.ts +0 -0
- /package/{src/types → types}/error-codes.d.ts +0 -0
- /package/{src/types → types}/integration.d.ts +0 -0
- /package/{src/types → types}/manifest.d.ts +0 -0
- /package/{src/types → types}/search.d.ts +0 -0
- /package/{src/types → types}/template.d.ts +0 -0
- /package/{src/types → types}/validate-integration.d.ts +0 -0
- /package/{src/utils → utils}/github.mjs +0 -0
- /package/{src/utils → utils}/path-safety.test.mjs +0 -0
- /package/{src/utils → utils}/paths.test.mjs +0 -0
- /package/{src/utils → utils}/semver.mjs +0 -0
- /package/{src/utils → utils}/semver.test.mjs +0 -0
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
import {describe, it, expect, beforeEach, afterEach, vi} from 'vitest';
|
|
4
|
+
import * as fs from 'node:fs';
|
|
5
|
+
import * as path from 'node:path';
|
|
6
|
+
import * as os from 'node:os';
|
|
7
|
+
import {Command} from 'commander';
|
|
8
|
+
import {registerUpgrade} from './upgrade.mjs';
|
|
9
|
+
import {generateCompressedIndex} from '../../lib/agent-docs/agent-docs.mjs';
|
|
10
|
+
|
|
11
|
+
let tmpDir;
|
|
12
|
+
let originalCwd;
|
|
13
|
+
let logCalls;
|
|
14
|
+
let stdoutCalls;
|
|
15
|
+
let exitCode;
|
|
16
|
+
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'astryx-upgrade-test-'));
|
|
19
|
+
originalCwd = process.cwd();
|
|
20
|
+
process.chdir(tmpDir);
|
|
21
|
+
logCalls = [];
|
|
22
|
+
stdoutCalls = [];
|
|
23
|
+
exitCode = undefined;
|
|
24
|
+
vi.spyOn(console, 'log').mockImplementation((...args) => {
|
|
25
|
+
logCalls.push(args.join(' '));
|
|
26
|
+
});
|
|
27
|
+
vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
28
|
+
// Some human logs are written straight to process.stdout — capture that too.
|
|
29
|
+
vi.spyOn(process.stdout, 'write').mockImplementation((chunk) => {
|
|
30
|
+
stdoutCalls.push(typeof chunk === 'string' ? chunk : chunk.toString());
|
|
31
|
+
return true;
|
|
32
|
+
});
|
|
33
|
+
// jsonError() calls process.exit(1) directly. Trap it so tests can assert.
|
|
34
|
+
vi.spyOn(process, 'exit').mockImplementation((code) => {
|
|
35
|
+
exitCode = code;
|
|
36
|
+
throw new Error(`__exit ${code}`);
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
afterEach(() => {
|
|
41
|
+
process.chdir(originalCwd);
|
|
42
|
+
fs.rmSync(tmpDir, {recursive: true, force: true});
|
|
43
|
+
vi.restoreAllMocks();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
function createProgram() {
|
|
47
|
+
const program = new Command();
|
|
48
|
+
program.exitOverride();
|
|
49
|
+
// Mirror the global --json flag from src/index.mjs so program.opts().json
|
|
50
|
+
// resolves the same way in tests.
|
|
51
|
+
program.option('--json', 'Output as typed JSON');
|
|
52
|
+
registerUpgrade(program);
|
|
53
|
+
return program;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function writePkg(deps = {}) {
|
|
57
|
+
fs.writeFileSync(
|
|
58
|
+
path.join(tmpDir, 'package.json'),
|
|
59
|
+
JSON.stringify({name: 'fixture', dependencies: deps}, null, 2),
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function writeInstalledCore(version, packageName = '@astryxdesign/core') {
|
|
64
|
+
const parts = packageName.split('/');
|
|
65
|
+
const dir = path.join(tmpDir, 'node_modules', ...parts);
|
|
66
|
+
fs.mkdirSync(dir, {recursive: true});
|
|
67
|
+
fs.writeFileSync(
|
|
68
|
+
path.join(dir, 'package.json'),
|
|
69
|
+
JSON.stringify({name: packageName, version}, null, 2),
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function writeSourceFile() {
|
|
74
|
+
fs.mkdirSync(path.join(tmpDir, 'src'), {recursive: true});
|
|
75
|
+
fs.writeFileSync(path.join(tmpDir, 'src', 'index.ts'), 'const x = 1;\n');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Run a command and capture the parsed JSON response (last printed JSON line). */
|
|
79
|
+
async function runJson(args) {
|
|
80
|
+
const program = createProgram();
|
|
81
|
+
try {
|
|
82
|
+
await program.parseAsync(['node', 'astryx', ...args]);
|
|
83
|
+
} catch (err) {
|
|
84
|
+
if (!String(err?.message || '').startsWith('__exit')) throw err;
|
|
85
|
+
}
|
|
86
|
+
// Find the most recent stringified JSON envelope in console.log output.
|
|
87
|
+
for (let i = logCalls.length - 1; i >= 0; i--) {
|
|
88
|
+
const line = logCalls[i];
|
|
89
|
+
if (line.startsWith('{')) {
|
|
90
|
+
try {
|
|
91
|
+
return JSON.parse(line);
|
|
92
|
+
} catch {
|
|
93
|
+
// not JSON, keep looking
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
describe('upgrade gate (semver comparison)', () => {
|
|
101
|
+
it('does NOT block an upgrade from 0.0.9 to installed 0.0.10 (regression)', async () => {
|
|
102
|
+
// The original bug: string compare said '0.0.9' >= '0.0.10', so the
|
|
103
|
+
// gate told users "Already up to date" without --force.
|
|
104
|
+
writePkg();
|
|
105
|
+
writeInstalledCore('0.0.10');
|
|
106
|
+
writeSourceFile();
|
|
107
|
+
|
|
108
|
+
const result = await runJson(['--json', 'upgrade', '--from', '0.0.9', '--path', 'src']);
|
|
109
|
+
expect(result).not.toBeNull();
|
|
110
|
+
expect(result.type === 'upgrade.run' || result.error || logCalls.some(l => l.includes('No codemods'))).toBeTruthy();
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('blocks when --from >= installed target by semver (e.g. 0.0.10 → 0.0.9)', async () => {
|
|
114
|
+
writePkg();
|
|
115
|
+
writeInstalledCore('0.0.9');
|
|
116
|
+
const program = createProgram();
|
|
117
|
+
await program.parseAsync(['node', 'astryx', 'upgrade', '--from', '0.0.10']);
|
|
118
|
+
const output = stdoutCalls.join('') + logCalls.join('\n');
|
|
119
|
+
expect(output).toMatch(/up to date|Already/i);
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
describe('upgrade argument validation', () => {
|
|
124
|
+
it('requires --from for upgrade runs', async () => {
|
|
125
|
+
writePkg();
|
|
126
|
+
writeInstalledCore('0.0.15');
|
|
127
|
+
const result = await runJson(['--json', 'upgrade']);
|
|
128
|
+
expect(result).not.toBeNull();
|
|
129
|
+
expect(result.error).toMatch(/Missing required --from/);
|
|
130
|
+
expect(exitCode).toBe(1);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('rejects bogus --from values', async () => {
|
|
134
|
+
writePkg();
|
|
135
|
+
writeInstalledCore('0.0.15');
|
|
136
|
+
const result = await runJson(['--json', 'upgrade', '--from', 'not-a-version']);
|
|
137
|
+
expect(result).not.toBeNull();
|
|
138
|
+
expect(result.error).toMatch(/Invalid --from/);
|
|
139
|
+
expect(exitCode).toBe(1);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('detects the installed target version from @astryxdesign/core', async () => {
|
|
143
|
+
writePkg();
|
|
144
|
+
writeInstalledCore('0.0.15');
|
|
145
|
+
writeSourceFile();
|
|
146
|
+
const result = await runJson(['--json', 'upgrade', '--from', '0.0.14', '--path', 'src']);
|
|
147
|
+
expect(result?.error || '').not.toMatch(/Could not find installed/);
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
describe('upgrade --list dedup', () => {
|
|
152
|
+
it('lists each codemod exactly once', async () => {
|
|
153
|
+
const result = await runJson(['--json', 'upgrade', '--list']);
|
|
154
|
+
expect(result).not.toBeNull();
|
|
155
|
+
expect(result.type).toBe('upgrade.list');
|
|
156
|
+
const names = result.data.map((c) => c.name);
|
|
157
|
+
const unique = new Set(names);
|
|
158
|
+
// The bug: 31 unique codemods printed 9× → 201 entries.
|
|
159
|
+
expect(names.length).toBe(unique.size);
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// Issue #4168 — `upgrade` must refresh the managed agent-docs block after a
|
|
164
|
+
// version bump, on EVERY path (the block documents the installed library, not
|
|
165
|
+
// the codemod outcome), and must NOT write during a dry run.
|
|
166
|
+
describe('upgrade agent-docs refresh (#4168)', () => {
|
|
167
|
+
/** Write an agent-doc file with a managed block generated for `version`. */
|
|
168
|
+
function writeAgentBlock(rel, version) {
|
|
169
|
+
const filePath = path.join(tmpDir, rel);
|
|
170
|
+
fs.mkdirSync(path.dirname(filePath), {recursive: true});
|
|
171
|
+
fs.writeFileSync(filePath, `# Doc\n\n${generateCompressedIndex(version)}\n`);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
it('refreshes a stale block with --apply, even when there are no codemods (up-to-date path)', async () => {
|
|
175
|
+
writePkg();
|
|
176
|
+
writeInstalledCore('0.0.15');
|
|
177
|
+
writeAgentBlock('AGENTS.md', '0.0.1');
|
|
178
|
+
|
|
179
|
+
// --from == installed → up-to-date short-circuit: no codemods run, yet the
|
|
180
|
+
// stale block must still be brought current. This is the exact gap in #4168.
|
|
181
|
+
const result = await runJson(['--json', 'upgrade', '--from', '0.0.15', '--apply']);
|
|
182
|
+
|
|
183
|
+
expect(result.type).toBe('upgrade.status');
|
|
184
|
+
expect(result.data.status).toBe('up_to_date');
|
|
185
|
+
expect(result.data.agentDocs.status).toBe('stale');
|
|
186
|
+
expect(result.data.agentDocs.action).toBe('refreshed');
|
|
187
|
+
expect(result.data.agentDocs.refreshed).toBe(true);
|
|
188
|
+
expect(result.data.agentDocs.fromVersions).toEqual(['0.0.1']);
|
|
189
|
+
expect(result.data.agentDocs.files).toContain('AGENTS.md');
|
|
190
|
+
|
|
191
|
+
const content = fs.readFileSync(path.join(tmpDir, 'AGENTS.md'), 'utf-8');
|
|
192
|
+
expect(content).toMatch(/Astryx v0\.0\.15 ·/);
|
|
193
|
+
expect(content).not.toMatch(/Astryx v0\.0\.1 ·/);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it('reports a stale block on a dry run WITHOUT writing', async () => {
|
|
197
|
+
writePkg();
|
|
198
|
+
writeInstalledCore('0.0.15');
|
|
199
|
+
writeAgentBlock('AGENTS.md', '0.0.1');
|
|
200
|
+
const before = fs.readFileSync(path.join(tmpDir, 'AGENTS.md'), 'utf-8');
|
|
201
|
+
|
|
202
|
+
const result = await runJson(['--json', 'upgrade', '--from', '0.0.15']); // no --apply
|
|
203
|
+
|
|
204
|
+
expect(result.data.agentDocs.status).toBe('stale');
|
|
205
|
+
expect(result.data.agentDocs.action).toBe('would-refresh');
|
|
206
|
+
expect(result.data.agentDocs.refreshed).toBe(false);
|
|
207
|
+
// The file must be untouched by a dry run.
|
|
208
|
+
expect(fs.readFileSync(path.join(tmpDir, 'AGENTS.md'), 'utf-8')).toBe(before);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it('refreshes a stale block on the no-codemods path (--apply)', async () => {
|
|
212
|
+
writePkg();
|
|
213
|
+
writeInstalledCore('0.0.11'); // (0.0.10, 0.0.11] has no registered codemods
|
|
214
|
+
writeSourceFile();
|
|
215
|
+
writeAgentBlock('CLAUDE.md', '0.0.1');
|
|
216
|
+
|
|
217
|
+
const result = await runJson(['--json', 'upgrade', '--from', '0.0.10', '--apply', '--path', 'src']);
|
|
218
|
+
|
|
219
|
+
expect(result.type).toBe('upgrade.status');
|
|
220
|
+
expect(result.data.status).toBe('no_codemods');
|
|
221
|
+
expect(result.data.agentDocs.refreshed).toBe(true);
|
|
222
|
+
expect(fs.readFileSync(path.join(tmpDir, 'CLAUDE.md'), 'utf-8')).toMatch(/Astryx v0\.0\.11 ·/);
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it('nudges to run init when core is installed but no managed block exists', async () => {
|
|
226
|
+
writePkg();
|
|
227
|
+
writeInstalledCore('0.0.15');
|
|
228
|
+
// No agent-doc files at all.
|
|
229
|
+
|
|
230
|
+
const result = await runJson(['--json', 'upgrade', '--from', '0.0.15', '--apply']);
|
|
231
|
+
|
|
232
|
+
expect(result.data.agentDocs.status).toBe('missing');
|
|
233
|
+
expect(result.data.agentDocs.action).toBe('nudge-init');
|
|
234
|
+
expect(result.data.agentDocs.refreshed).toBe(false);
|
|
235
|
+
// Must NOT silently create docs during an upgrade.
|
|
236
|
+
expect(fs.existsSync(path.join(tmpDir, 'AGENTS.md'))).toBe(false);
|
|
237
|
+
expect(fs.existsSync(path.join(tmpDir, '.claude', 'CLAUDE.md'))).toBe(false);
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it('treats an agent file without our markers as "never initialized" (missing)', async () => {
|
|
241
|
+
writePkg();
|
|
242
|
+
writeInstalledCore('0.0.15');
|
|
243
|
+
fs.writeFileSync(path.join(tmpDir, 'AGENTS.md'), '# Agents\n\nHand-written, no astryx block.\n');
|
|
244
|
+
const before = fs.readFileSync(path.join(tmpDir, 'AGENTS.md'), 'utf-8');
|
|
245
|
+
|
|
246
|
+
const result = await runJson(['--json', 'upgrade', '--from', '0.0.15', '--apply']);
|
|
247
|
+
|
|
248
|
+
expect(result.data.agentDocs.status).toBe('missing');
|
|
249
|
+
expect(result.data.agentDocs.action).toBe('nudge-init');
|
|
250
|
+
// An unmarked file is left exactly as-is.
|
|
251
|
+
expect(fs.readFileSync(path.join(tmpDir, 'AGENTS.md'), 'utf-8')).toBe(before);
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
it('does not silently no-op a stale block with malformed markers (START without END)', async () => {
|
|
255
|
+
writePkg();
|
|
256
|
+
writeInstalledCore('0.0.15');
|
|
257
|
+
// A corrupted block: START + version header but no matching END marker, so
|
|
258
|
+
// the writer can't safely splice it. This must not crash, corrupt the file,
|
|
259
|
+
// or falsely claim success — it should surface an error and nudge re-init.
|
|
260
|
+
const corrupted =
|
|
261
|
+
'# A\n\n<!-- ASTRYX:START -->\nAstryx v0.0.1 · 9 components\nguidance, but no end marker\n';
|
|
262
|
+
fs.writeFileSync(path.join(tmpDir, 'AGENTS.md'), corrupted);
|
|
263
|
+
|
|
264
|
+
const result = await runJson(['--json', 'upgrade', '--from', '0.0.15', '--apply']);
|
|
265
|
+
|
|
266
|
+
expect(result.data.agentDocs.status).toBe('stale');
|
|
267
|
+
expect(result.data.agentDocs.action).toBe('error');
|
|
268
|
+
expect(result.data.agentDocs.refreshed).toBe(false);
|
|
269
|
+
// File left byte-for-byte intact — never corrupted.
|
|
270
|
+
expect(fs.readFileSync(path.join(tmpDir, 'AGENTS.md'), 'utf-8')).toBe(corrupted);
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
it('stays silent when the block already matches the installed version', async () => {
|
|
274
|
+
writePkg();
|
|
275
|
+
writeInstalledCore('0.0.15');
|
|
276
|
+
writeAgentBlock('AGENTS.md', '0.0.15');
|
|
277
|
+
|
|
278
|
+
const result = await runJson(['--json', 'upgrade', '--from', '0.0.15', '--apply']);
|
|
279
|
+
|
|
280
|
+
expect(result.data.agentDocs.status).toBe('current');
|
|
281
|
+
expect(result.data.agentDocs.action).toBe('none');
|
|
282
|
+
expect(result.data.agentDocs.refreshed).toBe(false);
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
it('migrates a legacy XDS block to the current Astryx block (--apply)', async () => {
|
|
286
|
+
writePkg();
|
|
287
|
+
writeInstalledCore('0.0.15');
|
|
288
|
+
fs.writeFileSync(
|
|
289
|
+
path.join(tmpDir, 'CLAUDE.md'),
|
|
290
|
+
'# Claude\n\n<!-- XDS:START -->\nlegacy index\n<!-- XDS:END -->\n\nMore rules.\n',
|
|
291
|
+
);
|
|
292
|
+
|
|
293
|
+
const result = await runJson(['--json', 'upgrade', '--from', '0.0.15', '--apply']);
|
|
294
|
+
|
|
295
|
+
expect(result.data.agentDocs.status).toBe('stale');
|
|
296
|
+
expect(result.data.agentDocs.refreshed).toBe(true);
|
|
297
|
+
const content = fs.readFileSync(path.join(tmpDir, 'CLAUDE.md'), 'utf-8');
|
|
298
|
+
expect(content).toContain('<!-- ASTRYX:START -->');
|
|
299
|
+
expect(content).toMatch(/Astryx v0\.0\.15 ·/);
|
|
300
|
+
expect(content).not.toContain('legacy index');
|
|
301
|
+
expect(content).toContain('More rules.');
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
it('surfaces agentDocs in the human output on the up-to-date path', async () => {
|
|
305
|
+
writePkg();
|
|
306
|
+
writeInstalledCore('0.0.15');
|
|
307
|
+
writeAgentBlock('AGENTS.md', '0.0.1');
|
|
308
|
+
|
|
309
|
+
const program = createProgram();
|
|
310
|
+
await program.parseAsync(['node', 'astryx', 'upgrade', '--from', '0.0.15', '--apply']);
|
|
311
|
+
const output = stdoutCalls.join('') + logCalls.join('\n');
|
|
312
|
+
expect(output).toMatch(/Agent docs refreshed/i);
|
|
313
|
+
});
|
|
314
|
+
});
|
|
@@ -14,37 +14,34 @@
|
|
|
14
14
|
* an integration package is not a failure).
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
import {jsonOut, humanLog} from '
|
|
17
|
+
import {jsonOut, humanLog} from '../../lib/json.mjs';
|
|
18
18
|
import {
|
|
19
|
-
|
|
20
|
-
validateInstalledIntegration,
|
|
19
|
+
validateIntegration,
|
|
21
20
|
summarizeIssues,
|
|
22
|
-
} from '
|
|
21
|
+
} from '../../api/integration/validate-integration.mjs';
|
|
23
22
|
|
|
24
23
|
/**
|
|
25
24
|
* Render a validation result for humans.
|
|
26
|
-
* @param {import('
|
|
25
|
+
* @param {import('../../types/validate-integration').ValidateIntegrationResponse['data']} data
|
|
27
26
|
*/
|
|
28
|
-
function printHuman(
|
|
27
|
+
function printHuman(data) {
|
|
29
28
|
const label =
|
|
30
|
-
|
|
31
|
-
? `${result.name}@${result.version}`
|
|
32
|
-
: result.name;
|
|
29
|
+
data.version != null ? `${data.name}@${data.version}` : data.name;
|
|
33
30
|
humanLog(`Validating integration: ${label}`);
|
|
34
31
|
|
|
35
|
-
if (
|
|
32
|
+
if (data.issues.length === 0) {
|
|
36
33
|
humanLog('\n\u2713 No issues found.');
|
|
37
34
|
return;
|
|
38
35
|
}
|
|
39
36
|
|
|
40
37
|
humanLog('');
|
|
41
|
-
for (const issue of
|
|
38
|
+
for (const issue of data.issues) {
|
|
42
39
|
humanLog(` ${issue.severity} ${issue.code}: ${issue.message}`);
|
|
43
40
|
}
|
|
44
41
|
|
|
45
|
-
const {errors, warnings} = summarizeIssues(
|
|
42
|
+
const {errors, warnings} = summarizeIssues(data.issues);
|
|
46
43
|
humanLog(
|
|
47
|
-
`\n${
|
|
44
|
+
`\n${data.issues.length} issue(s): ${errors} error(s), ${warnings} warning(s)`,
|
|
48
45
|
);
|
|
49
46
|
}
|
|
50
47
|
|
|
@@ -74,35 +71,18 @@ export function registerValidateIntegration(program) {
|
|
|
74
71
|
.action(async pkg => {
|
|
75
72
|
const json = program.opts().json || false;
|
|
76
73
|
|
|
77
|
-
const result = pkg
|
|
78
|
-
? await validateInstalledIntegration(pkg)
|
|
79
|
-
: await validateLocalIntegration();
|
|
80
|
-
|
|
81
|
-
// No-arg + no local manifest: guidance, not an error.
|
|
82
|
-
if (!result.found) {
|
|
83
|
-
if (json) {
|
|
84
|
-
jsonOut('integration.validate', {
|
|
85
|
-
name: null,
|
|
86
|
-
version: null,
|
|
87
|
-
issues: [],
|
|
88
|
-
});
|
|
89
|
-
} else {
|
|
90
|
-
humanLog(NO_MANIFEST_GUIDANCE);
|
|
91
|
-
}
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
74
|
+
const result = await validateIntegration(pkg);
|
|
94
75
|
|
|
95
76
|
if (json) {
|
|
96
|
-
jsonOut(
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
});
|
|
77
|
+
jsonOut(result.type, result.data);
|
|
78
|
+
} else if (result.data.name === null) {
|
|
79
|
+
// No-arg + no local manifest: guidance, not an error.
|
|
80
|
+
humanLog(NO_MANIFEST_GUIDANCE);
|
|
101
81
|
} else {
|
|
102
|
-
printHuman(result);
|
|
82
|
+
printHuman(result.data);
|
|
103
83
|
}
|
|
104
84
|
|
|
105
|
-
const {errors} = summarizeIssues(result.issues);
|
|
85
|
+
const {errors} = summarizeIssues(result.data.issues);
|
|
106
86
|
if (errors > 0) {
|
|
107
87
|
process.exitCode = 1;
|
|
108
88
|
}
|
package/{src → cli}/index.mjs
RENAMED
|
@@ -11,14 +11,15 @@ import {Command, Option} from 'commander';
|
|
|
11
11
|
import {fileURLToPath} from 'node:url';
|
|
12
12
|
import * as fs from 'node:fs';
|
|
13
13
|
import * as path from 'node:path';
|
|
14
|
-
import {checkForUpdate} from '
|
|
15
|
-
import {
|
|
16
|
-
import {API_VERSION, setJsonMode} from '
|
|
17
|
-
import {buildManifest} from '
|
|
18
|
-
import {cliError} from '
|
|
19
|
-
import {ERROR_CODES} from '
|
|
20
|
-
import {levenshteinDistance} from '
|
|
21
|
-
import {installJsonShim} from '
|
|
14
|
+
import {checkForUpdate} from '../utils/update-check.mjs';
|
|
15
|
+
import {getCliInvocation} from '../utils/package-manager.mjs';
|
|
16
|
+
import {API_VERSION, setJsonMode} from '../lib/json.mjs';
|
|
17
|
+
import {buildManifest} from '../lib/manifest.mjs';
|
|
18
|
+
import {cliError} from '../lib/cli-error.mjs';
|
|
19
|
+
import {ERROR_CODES} from '../lib/error-codes.mjs';
|
|
20
|
+
import {levenshteinDistance} from '../lib/string-utils.mjs';
|
|
21
|
+
import {installJsonShim} from '../lib/json-shim.mjs';
|
|
22
|
+
import {isAstryxInitialized} from '../lib/agent-docs/agent-docs.mjs';
|
|
22
23
|
|
|
23
24
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
24
25
|
|
|
@@ -55,6 +56,7 @@ export const JSON_SUPPORTED = new Set([
|
|
|
55
56
|
'docs',
|
|
56
57
|
'discover',
|
|
57
58
|
'search',
|
|
59
|
+
'build',
|
|
58
60
|
'swizzle',
|
|
59
61
|
'template',
|
|
60
62
|
'hook',
|
|
@@ -101,7 +103,7 @@ program
|
|
|
101
103
|
if (extras.length > 0) {
|
|
102
104
|
const unknown = String(extras[0]);
|
|
103
105
|
const known = (program.commands || [])
|
|
104
|
-
.filter((c) => !c._hidden && c.name() !== 'help')
|
|
106
|
+
.filter((c) => !(/** @type {any} */ (c)._hidden) && c.name() !== 'help')
|
|
105
107
|
.map((c) => c.name());
|
|
106
108
|
const close = known
|
|
107
109
|
.map((name) => ({name, distance: levenshteinDistance(unknown.toLowerCase(), name.toLowerCase())}))
|
|
@@ -161,6 +163,7 @@ program
|
|
|
161
163
|
*/
|
|
162
164
|
function fullCommandName(actionCommand) {
|
|
163
165
|
const parts = [];
|
|
166
|
+
/** @type {import('commander').Command | null} */
|
|
164
167
|
let cmd = actionCommand;
|
|
165
168
|
while (cmd && cmd !== program) {
|
|
166
169
|
parts.unshift(cmd.name());
|
|
@@ -174,7 +177,7 @@ function fullCommandName(actionCommand) {
|
|
|
174
177
|
*
|
|
175
178
|
* If --json is set on a command that is not on the JSON_SUPPORTED allowlist,
|
|
176
179
|
* emit a structured error envelope and exit 1 — without running the command's
|
|
177
|
-
* action (so no filesystem mutations, no
|
|
180
|
+
* action (so no filesystem mutations, no interactive prompts, no spawned processes).
|
|
178
181
|
*
|
|
179
182
|
* This is the single source of truth for "command does not support --json".
|
|
180
183
|
* Individual commands should NOT re-check this; they may assume that if their
|
|
@@ -237,6 +240,40 @@ program.hook('postAction', (thisCommand, actionCommand) => {
|
|
|
237
240
|
}
|
|
238
241
|
});
|
|
239
242
|
|
|
243
|
+
/**
|
|
244
|
+
* Enforcement layer 3 — setup nudge. If this project hasn't run `astryx init`
|
|
245
|
+
* yet (no Astryx marker in any agent-doc file — see isAstryxInitialized), remind
|
|
246
|
+
* the user/agent that setup is missing.
|
|
247
|
+
*
|
|
248
|
+
* Uses `preAction` (not postAction) so it fires for EVERY valid command — even
|
|
249
|
+
* ones whose action errors or calls process.exit (postAction is skipped then).
|
|
250
|
+
*
|
|
251
|
+
* Suppressed in --json: that is machine output with a strict clean stdout+stderr
|
|
252
|
+
* contract (json-shim.test: "error envelopes have empty stderr"), and --json
|
|
253
|
+
* consumers parse stdout, not stderr — a stderr nudge would not reach them anyway.
|
|
254
|
+
* The core/cli postinstall layers already nudge at install time regardless of
|
|
255
|
+
* --json; a machine-readable nudge could later be an envelope field. Also skipped
|
|
256
|
+
* for the installer commands themselves and outside a project (no package.json).
|
|
257
|
+
*/
|
|
258
|
+
const SETUP_NUDGE_EXEMPT = new Set(['init', 'agent-docs']);
|
|
259
|
+
program.hook('preAction', (thisCommand, actionCommand) => {
|
|
260
|
+
try {
|
|
261
|
+
if (program.opts().json) return; // machine mode — keep --json output clean
|
|
262
|
+
if (SETUP_NUDGE_EXEMPT.has(actionCommand.name())) return;
|
|
263
|
+
const cwd = process.cwd();
|
|
264
|
+
if (!fs.existsSync(path.join(cwd, 'package.json'))) return; // not a project
|
|
265
|
+
if (isAstryxInitialized(cwd)) return; // already set up — stay quiet
|
|
266
|
+
// Same wording as the core/cli postinstall nudges. #4151's getCliInvocation()
|
|
267
|
+
// renders the correct form for THIS project — scoped `npx @astryxdesign/cli`
|
|
268
|
+
// one-off, or `<pm> astryx` when installed — never the bare `npx astryx` footgun.
|
|
269
|
+
console.error(
|
|
270
|
+
`\nNext step: run \`${getCliInvocation(cwd)} init\` to finish setup and install the Astryx agent prompt.`,
|
|
271
|
+
);
|
|
272
|
+
} catch {
|
|
273
|
+
// Never let the nudge break a command.
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
|
|
240
277
|
/**
|
|
241
278
|
* Command registry — each command is lazy-loaded so a broken command
|
|
242
279
|
* doesn't take down the entire CLI.
|
|
@@ -272,10 +309,10 @@ for (const cmd of commands) {
|
|
|
272
309
|
// Command fails to load but CLI still works
|
|
273
310
|
program
|
|
274
311
|
.command(cmd.name)
|
|
275
|
-
.description(`(failed to load: ${e.message})`)
|
|
312
|
+
.description(`(failed to load: ${/** @type {any} */ (e).message})`)
|
|
276
313
|
.action(() => {
|
|
277
314
|
console.error(`Command "${cmd.name}" failed to load:`);
|
|
278
|
-
console.error(e.message);
|
|
315
|
+
console.error(/** @type {any} */ (e).message);
|
|
279
316
|
process.exit(1);
|
|
280
317
|
});
|
|
281
318
|
}
|
|
@@ -305,25 +342,24 @@ program
|
|
|
305
342
|
console.log(` ${c.name}${tag}`);
|
|
306
343
|
if (c.description) console.log(` ${c.description}`);
|
|
307
344
|
}
|
|
308
|
-
console.log(`\nRun
|
|
345
|
+
console.log(`\nRun \`${getCliInvocation()} manifest --json\` for the full structured manifest.\n`);
|
|
309
346
|
});
|
|
310
347
|
|
|
311
348
|
// Hidden command used by package.json postinstall scripts
|
|
312
349
|
program
|
|
313
350
|
.command('postinstall', {hidden: true})
|
|
314
351
|
.action(() => {
|
|
315
|
-
const
|
|
316
|
-
const
|
|
317
|
-
const pad = (s, len) => s + ' '.repeat(Math.max(0, len - s.length));
|
|
352
|
+
const r = getCliInvocation();
|
|
353
|
+
const pad = (/** @type {string} */ s, /** @type {number} */ len) => s + ' '.repeat(Math.max(0, len - s.length));
|
|
318
354
|
const W = 49; // inner width of the box
|
|
319
|
-
const line = (s) => ` │ ${pad(s, W)}│`;
|
|
355
|
+
const line = (/** @type {string} */ s) => ` │ ${pad(s, W)}│`;
|
|
320
356
|
console.log(`
|
|
321
357
|
╭${'─'.repeat(W + 2)}╮
|
|
322
358
|
${line('')}
|
|
323
359
|
${line(' Design system installed!')}
|
|
324
360
|
${line('')}
|
|
325
361
|
${line(' Get started:')}
|
|
326
|
-
${line(` ${r} init
|
|
362
|
+
${line(` ${r} init Setup + AI agent docs`)}
|
|
327
363
|
${line(` ${r} --help See all commands`)}
|
|
328
364
|
${line('')}
|
|
329
365
|
${line(' Or run directly:')}
|
|
@@ -342,5 +378,5 @@ ${line('')}
|
|
|
342
378
|
// patch outputHelp on every command (root + subcommands). The shim
|
|
343
379
|
// extends the --json contract to cover Commander's parse-time short
|
|
344
380
|
// circuits (parse errors, unknown options, --help, unknown commands).
|
|
345
|
-
// See packages/cli/
|
|
381
|
+
// See packages/cli/lib/json-shim.mjs for the rationale.
|
|
346
382
|
installJsonShim(program);
|
|
@@ -3,16 +3,15 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* @file Lazy jscodeshift installer
|
|
5
5
|
*
|
|
6
|
-
* Checks if jscodeshift is available and
|
|
6
|
+
* Checks if jscodeshift is available and installs it on-demand.
|
|
7
7
|
* Keeps the CLI lean — jscodeshift is only needed for codemods.
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* or the command will fail with a helpful error message.
|
|
9
|
+
* Non-interactive by default (no prompts): pass `installDeps: true` to
|
|
10
|
+
* auto-install, otherwise the command fails fast with a helpful error.
|
|
12
11
|
*/
|
|
13
12
|
|
|
14
13
|
import {execSync} from 'node:child_process';
|
|
15
|
-
import * as p from '
|
|
14
|
+
import * as p from '../lib/term-log.mjs';
|
|
16
15
|
import {detectPackageManager} from '../utils/package-manager.mjs';
|
|
17
16
|
|
|
18
17
|
/**
|
|
@@ -30,32 +29,17 @@ export async function ensureJscodeshift({installDeps = false, silent = false} =
|
|
|
30
29
|
} catch {
|
|
31
30
|
log.warn('jscodeshift is required for codemods but not installed.');
|
|
32
31
|
|
|
33
|
-
const isInteractive = process.stdout.isTTY && !process.env.CI;
|
|
34
|
-
|
|
35
32
|
if (installDeps) {
|
|
36
|
-
// Explicit opt-in — install without prompting
|
|
33
|
+
// Explicit opt-in — install without prompting.
|
|
37
34
|
return installJscodeshift(silent);
|
|
38
35
|
}
|
|
39
36
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
return false;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// Interactive TTY — prompt as before
|
|
50
|
-
const shouldInstall = await p.confirm({
|
|
51
|
-
message: 'Install jscodeshift now?',
|
|
52
|
-
initialValue: true,
|
|
53
|
-
});
|
|
54
|
-
if (p.isCancel(shouldInstall) || !shouldInstall) {
|
|
55
|
-
p.log.error('Cannot run codemods without jscodeshift.');
|
|
56
|
-
return false;
|
|
57
|
-
}
|
|
58
|
-
return installJscodeshift(silent);
|
|
37
|
+
// Non-interactive by default: fail fast with a helpful message instead of
|
|
38
|
+
// prompting (the CLI never blocks on a TTY).
|
|
39
|
+
log.error(
|
|
40
|
+
'Cannot run codemods without jscodeshift. Use --install-deps to auto-install.',
|
|
41
|
+
);
|
|
42
|
+
return false;
|
|
59
43
|
}
|
|
60
44
|
}
|
|
61
45
|
|
|
@@ -80,7 +64,7 @@ function installJscodeshift(silent = false) {
|
|
|
80
64
|
log.success('jscodeshift installed.');
|
|
81
65
|
return true;
|
|
82
66
|
} catch (err) {
|
|
83
|
-
log.error(`Failed to install jscodeshift: ${err.message}`);
|
|
67
|
+
log.error(`Failed to install jscodeshift: ${/** @type {any} */ (err).message}`);
|
|
84
68
|
return false;
|
|
85
69
|
}
|
|
86
70
|
}
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
import * as fs from 'node:fs';
|
|
28
28
|
import * as path from 'node:path';
|
|
29
29
|
import {loadModuleWithSchema} from '../lib/module-loader.mjs';
|
|
30
|
-
import {CodemodEnvelopeSchema} from '../codemod.mjs';
|
|
30
|
+
import {CodemodEnvelopeSchema} from '../authoring/codemod.mjs';
|
|
31
31
|
import {semverCompare} from '../utils/semver.mjs';
|
|
32
32
|
|
|
33
33
|
/** File extensions recognized as codemod modules. */
|
|
@@ -41,6 +41,7 @@ const CODEMOD_EXTENSIONS = ['.ts', '.mjs', '.js'];
|
|
|
41
41
|
* @returns {Array<{id: string, file: string}>}
|
|
42
42
|
*/
|
|
43
43
|
function collectCodemodFiles(versionDir) {
|
|
44
|
+
/** @type {Array<{id: string, file: string}>} */
|
|
44
45
|
const out = [];
|
|
45
46
|
|
|
46
47
|
/** @param {string} dir */
|
|
@@ -74,7 +75,7 @@ function collectCodemodFiles(versionDir) {
|
|
|
74
75
|
* for that version (across all integrations).
|
|
75
76
|
*/
|
|
76
77
|
export async function discoverIntegrationCodemods(loadedIntegrations = []) {
|
|
77
|
-
/** @type {Map<string, Array<{id: string, type:
|
|
78
|
+
/** @type {Map<string, Array<{id: string, type: 'code'|'config', codemod: object, package: string}>>} */
|
|
78
79
|
const byVersion = new Map();
|
|
79
80
|
|
|
80
81
|
for (const integration of loadedIntegrations ?? []) {
|
|
@@ -158,10 +159,12 @@ export async function discoverIntegrationCodemods(loadedIntegrations = []) {
|
|
|
158
159
|
*/
|
|
159
160
|
export function selectIntegrationCodemods(byVersion, from, to) {
|
|
160
161
|
const versions = [...byVersion.keys()].sort(semverCompare);
|
|
162
|
+
/** @type {Array<{version: string, codemods: Array<object>}>} */
|
|
161
163
|
const results = [];
|
|
162
164
|
for (const version of versions) {
|
|
163
165
|
if (semverCompare(version, from) > 0 && semverCompare(version, to) <= 0) {
|
|
164
|
-
|
|
166
|
+
const codemods = byVersion.get(version);
|
|
167
|
+
if (codemods) results.push({version, codemods});
|
|
165
168
|
}
|
|
166
169
|
}
|
|
167
170
|
return results;
|