@astryxdesign/cli 0.6.4 → 0.6.5-canary.01972bc
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 +56 -0
- package/README.md +103 -95
- package/api/build/_adapter.d.mts +36 -2
- package/api/build/_adapter.mjs +41 -10
- package/api/build/build.doc.mjs +8 -3
- package/api/build/build.test.mjs +60 -2
- package/api/build/kit/kit.mjs +109 -26
- package/api/build/kit/rank.d.mts +24 -8
- package/api/build/kit/rank.mjs +277 -97
- package/api/build/kit/rank.test.mjs +231 -48
- package/api/component/_adapter.d.mts +25 -0
- package/api/component/_adapter.mjs +59 -5
- package/api/component/component.d.mts +6 -3
- package/api/component/component.doc.mjs +37 -17
- package/api/component/component.mjs +249 -9
- package/api/component/component.type.d.mts +25 -0
- package/api/component/component.type.mjs +44 -0
- package/api/discover/_adapter.d.mts +114 -6
- package/api/discover/_adapter.mjs +372 -17
- package/api/discover/_adapter.test.mjs +215 -0
- package/api/discover/_catalog-view.d.mts +115 -0
- package/api/discover/_catalog-view.mjs +203 -0
- package/api/discover/_catalog-view.test.mjs +128 -0
- package/api/discover/detail/detail.d.mts +18 -6
- package/api/discover/detail/detail.mjs +67 -13
- package/api/discover/detail/detail.test.mjs +85 -0
- package/api/discover/detail/item/item.d.mts +26 -0
- package/api/discover/detail/item/item.mjs +78 -0
- package/api/discover/detail/item/item.test.mjs +73 -0
- package/api/discover/discover.d.mts +3 -9
- package/api/discover/discover.doc.mjs +61 -18
- package/api/discover/discover.mjs +220 -36
- package/api/discover/discover.test.mjs +11 -2
- package/api/discover/discover.type.d.mts +147 -8
- package/api/discover/discover.type.mjs +102 -12
- package/api/discover/list/list.d.mts +20 -6
- package/api/discover/list/list.mjs +45 -12
- package/api/discover/list/list.test.mjs +46 -0
- package/api/discover/search/search.d.mts +18 -16
- package/api/discover/search/search.mjs +102 -56
- package/api/discover/search/search.test.mjs +144 -10
- package/api/docs/_adapter.d.mts +8 -3
- package/api/docs/_adapter.mjs +14 -6
- package/api/docs/docOverlays.test.mjs +27 -1
- package/api/docs/docs.doc.mjs +2 -2
- package/api/docs/docs.test.mjs +54 -18
- package/api/docs/integration-tree.test.mjs +17 -0
- package/api/docs/integrationDocs.test.mjs +27 -5
- package/api/doctor/doctor.d.mts +8 -3
- package/api/doctor/doctor.doc.mjs +17 -8
- package/api/doctor/doctor.mjs +90 -9
- package/api/doctor/doctor.test.mjs +122 -10
- package/api/doctor/doctor.type.d.mts +1 -1
- package/api/doctor/doctor.type.mjs +1 -1
- package/api/error.d.mts +22 -0
- package/api/error.mjs +42 -0
- package/api/gap-report/gap-report.doc.mjs +19 -10
- package/api/hook/hook.doc.mjs +6 -3
- package/api/index.d.mts +1 -0
- package/api/index.mjs +5 -3
- package/api/init/init.doc.mjs +17 -12
- package/api/integration/add-contribution.d.mts +2 -1
- package/api/integration/add-contribution.mjs +7 -3
- package/api/integration/add-contribution.test.mjs +3 -3
- package/api/integration/add-helpers.d.mts +5 -2
- package/api/integration/add-helpers.mjs +36 -9
- package/api/integration/add-theme.mjs +266 -23
- package/api/integration/add-theme.test.mjs +247 -0
- package/api/integration/authoring-checks.mjs +12 -9
- package/api/integration/authoring-checks.type.d.mts +8 -0
- package/api/integration/authoring-checks.type.mjs +7 -3
- package/api/integration/integration-authoring.type.d.mts +4 -1
- package/api/integration/integration-authoring.type.mjs +6 -1
- package/api/integration/integrationAdd.doc.mjs +6 -0
- package/api/integration/integrationAddTheme.doc.mjs +10 -0
- package/api/integration/integrationComponentConflicts.doc.mjs +1 -1
- package/api/integration/integrationDocConflicts.doc.mjs +1 -1
- package/api/integration/integrationPackCheck.doc.mjs +3 -3
- package/api/integration/integrationTemplateConflicts.doc.mjs +1 -1
- package/api/integration/pack-check.lifecycle-output.test.mjs +107 -0
- package/api/integration/pack-check.mjs +92 -10
- package/api/integration/pack-check.test.mjs +140 -1
- package/api/integration/pack-check.type.mjs +1 -1
- package/api/integration/validate-integration.d.mts +4 -2
- package/api/integration/validate-integration.mjs +7 -2
- package/api/integration/validate-integration.test.mjs +55 -0
- package/api/integration/validate-integration.type.d.mts +5 -0
- package/api/integration/validate-integration.type.mjs +5 -1
- package/api/integration/validateIntegration.doc.mjs +1 -1
- package/api/json/assertResponse.doc.mjs +1 -1
- package/api/json/isError.doc.mjs +1 -1
- package/api/layout/expand/expand.mjs +12 -7
- package/api/layout/expand/expand.receipt.test.mjs +74 -0
- package/api/layout/layout.type.d.mts +1 -0
- package/api/layout/layout.type.mjs +1 -0
- package/api/layout/layoutExpand.doc.mjs +1 -1
- package/api/search/search.d.mts +51 -1
- package/api/search/search.doc.mjs +2 -2
- package/api/search/search.mjs +299 -17
- package/api/search/search.test.mjs +216 -18
- package/api/swizzle/copy/copy.mjs +66 -3
- package/api/swizzle/swizzle.doc.mjs +11 -5
- package/api/template/copy/copy.mjs +15 -9
- package/api/template/copy/copy.receipt.test.mjs +77 -0
- package/api/template/copy/copy.test.mjs +9 -0
- package/api/template/show/show.mjs +15 -4
- package/api/template/show/show.test.mjs +76 -0
- package/api/template/template-integration.test.mjs +14 -0
- package/api/template/template.d.mts +1 -1
- package/api/template/template.doc.mjs +8 -3
- package/api/template/template.mjs +1 -0
- package/api/template/template.type.d.mts +2 -0
- package/api/template/template.type.mjs +2 -0
- package/api/theme/add/add.mjs +17 -25
- package/api/theme/add/add.rollback.test.mjs +158 -0
- package/api/theme/add/add.staging.test.mjs +40 -23
- package/api/theme/build/build.d.mts +24 -0
- package/api/theme/build/build.family.test.mjs +7 -12
- package/api/theme/build/build.mjs +243 -26
- package/api/theme/build/build.project-core.test.mjs +165 -0
- package/api/theme/build/build.rollback.test.mjs +148 -0
- package/api/theme/generateTonalPalette.doc.mjs +1 -2
- package/api/theme/listThemes.doc.mjs +1 -1
- package/api/theme/themeAdd.doc.mjs +9 -10
- package/api/theme/themeBuild.doc.mjs +13 -13
- package/api/theme/themeList.doc.mjs +1 -1
- package/api/theme/themeListAvailable.doc.mjs +2 -1
- package/api/theme/themePaletteGenerate.doc.mjs +15 -8
- package/api/theme/themeTargets.doc.mjs +3 -2
- package/api/theme/themeTemplate.doc.mjs +2 -1
- package/api/upgrade/run/files-changed.test.mjs +111 -0
- package/api/upgrade/run/run.mjs +25 -6
- package/api/upgrade/run/run.test.mjs +45 -1
- package/api/upgrade/upgrade.doc.mjs +24 -22
- package/api/upgrade/upgrade.type.d.mts +1 -0
- package/api/upgrade/upgrade.type.mjs +3 -2
- package/assets/codemods/__tests__/runner.test.mjs +3 -1
- package/assets/codemods/file-count.test.mjs +163 -0
- package/assets/codemods/integration-runner.mjs +3 -3
- package/assets/codemods/runner.mjs +5 -4
- package/assets/docs/README.md +4 -2
- package/assets/docs/browser-support.doc.mjs +11 -11
- package/assets/docs/color.doc.mjs +8 -2
- package/assets/docs/elevation.doc.mjs +6 -4
- package/assets/docs/getting-started.doc.mjs +5 -16
- package/assets/docs/icons.doc.mjs +3 -21
- package/assets/docs/illustrations.doc.mjs +7 -15
- package/assets/docs/internationalization.doc.mjs +7 -5
- package/assets/docs/layout.doc.dense.mjs +130 -82
- package/assets/docs/layout.doc.mjs +133 -77
- package/assets/docs/migration.doc.mjs +19 -21
- package/assets/docs/motion.doc.mjs +16 -3
- package/assets/docs/principles.doc.dense.mjs +5 -5
- package/assets/docs/principles.doc.mjs +8 -0
- package/assets/docs/principles.doc.zh.mjs +6 -6
- package/assets/docs/shape.doc.mjs +8 -3
- package/assets/docs/spacing.doc.mjs +7 -2
- package/assets/docs/styling-libraries.doc.mjs +6 -2
- package/assets/docs/styling.doc.mjs +19 -23
- package/assets/docs/theme.doc.dense.mjs +58 -18
- package/assets/docs/theme.doc.mjs +57 -47
- package/assets/docs/theme.doc.zh.mjs +9 -8
- package/assets/docs/tokens.doc.dense.mjs +2 -2
- package/assets/docs/tokens.doc.mjs +389 -8
- package/assets/docs/tokens.doc.zh.mjs +2 -2
- package/assets/docs/tree/add-a-component.doc.mjs +75 -0
- package/assets/docs/tree/add-a-theme.doc.mjs +85 -0
- package/assets/docs/tree/add-a-topic.doc.mjs +144 -0
- package/assets/docs/tree/agent-guidance.doc.mjs +138 -0
- package/assets/docs/tree/block-template.doc.mjs +130 -0
- package/assets/docs/tree/build-the-template.doc.mjs +28 -0
- package/assets/docs/tree/building-blocks.doc.mjs +46 -0
- package/assets/docs/tree/check-your-docs.doc.mjs +137 -0
- package/assets/docs/tree/checks.doc.mjs +119 -0
- package/assets/docs/tree/codemods.doc.mjs +147 -0
- package/assets/docs/tree/component-family.doc.mjs +113 -0
- package/assets/docs/tree/component-imports.doc.mjs +69 -0
- package/assets/docs/tree/component-lookups.doc.mjs +149 -0
- package/assets/docs/tree/components.doc.mjs +23 -0
- package/assets/docs/tree/configuration.doc.mjs +23 -0
- package/assets/docs/tree/debug-and-gap-reports.doc.mjs +182 -0
- package/assets/docs/tree/define-the-theme.doc.mjs +118 -0
- package/assets/docs/tree/describe-the-component.doc.mjs +57 -0
- package/assets/docs/tree/docs.doc.mjs +21 -0
- package/assets/docs/tree/document-the-template.doc.mjs +28 -0
- package/assets/docs/tree/document-the-theme.doc.mjs +68 -0
- package/assets/docs/tree/export-template-assets.doc.mjs +147 -0
- package/assets/docs/tree/extend-or-replace.doc.mjs +103 -0
- package/assets/docs/tree/fonts-and-assets.doc.mjs +106 -0
- package/assets/docs/tree/generate-a-palette.doc.mjs +66 -0
- package/assets/docs/tree/grade-template-with-agent.doc.mjs +105 -0
- package/assets/docs/tree/help.doc.mjs +16 -0
- package/assets/docs/tree/integrations.doc.mjs +25 -451
- package/assets/docs/tree/links.doc.mjs +98 -0
- package/assets/docs/tree/package-and-test.doc.mjs +32 -0
- package/assets/docs/tree/page-template.doc.mjs +71 -0
- package/assets/docs/tree/publishing.doc.mjs +111 -0
- package/assets/docs/tree/quick-start.doc.mjs +272 -0
- package/assets/docs/tree/replace-a-core-component.doc.mjs +104 -0
- package/assets/docs/tree/replace-a-core-template.doc.mjs +172 -0
- package/assets/docs/tree/sections-and-placement.doc.mjs +108 -0
- package/assets/docs/tree/see-it-in-an-app.doc.mjs +59 -0
- package/assets/docs/tree/ship.doc.mjs +16 -0
- package/assets/docs/tree/short-and-findable.doc.mjs +108 -0
- package/assets/docs/tree/single-component.doc.mjs +165 -0
- package/assets/docs/tree/start-a-template.doc.mjs +143 -0
- package/assets/docs/tree/subcomponent.doc.mjs +115 -0
- package/assets/docs/tree/template-assets.doc.mjs +64 -0
- package/assets/docs/tree/template-doc-overview.doc.mjs +121 -0
- package/assets/docs/tree/template-fonts.doc.mjs +102 -0
- package/assets/docs/tree/template-grading-rubric.doc.mjs +452 -0
- package/assets/docs/tree/template-icons.doc.mjs +97 -0
- package/assets/docs/tree/template-images-media.doc.mjs +127 -0
- package/assets/docs/tree/template-styles.doc.mjs +93 -0
- package/assets/docs/tree/templates.doc.mjs +34 -0
- package/assets/docs/tree/test-in-an-app.doc.mjs +115 -0
- package/assets/docs/tree/test-template-in-app.doc.mjs +128 -0
- package/assets/docs/tree/themes.doc.mjs +39 -0
- package/assets/docs/tree/troubleshooting.doc.mjs +153 -0
- package/assets/docs/tree/upgrading.doc.mjs +103 -0
- package/assets/docs/tree/use-a-theme-in-an-app.doc.mjs +51 -0
- package/assets/docs/tree/verify-packed-template.doc.mjs +77 -0
- package/assets/docs/tree/versioning.doc.mjs +162 -0
- package/assets/docs/tree/write-good-templates.doc.mjs +64 -0
- package/assets/docs/tree/write-the-template-file.doc.mjs +154 -0
- package/assets/docs/typography.doc.mjs +24 -4
- package/assets/docs/working-with-ai.doc.mjs +30 -22
- package/assets/templates/blocks/components/InternationalizationProvider/InternationalizationProvider01ShippedLocale.tsx +1 -1
- package/assets/templates/pages/ai-chat/template.doc.mjs +16 -1
- package/assets/templates/pages/ai-chat-landing/template.doc.mjs +9 -1
- package/assets/templates/pages/blank/template.doc.mjs +3 -1
- package/assets/templates/pages/canvas-editor/template.doc.mjs +9 -1
- package/assets/templates/pages/centered-hero/template.doc.mjs +3 -1
- package/assets/templates/pages/checkout-wizard/template.doc.mjs +1 -0
- package/assets/templates/pages/classic-gallery/template.doc.mjs +3 -1
- package/assets/templates/pages/contact-form/template.doc.mjs +16 -1
- package/assets/templates/pages/dashboard/template.doc.mjs +15 -1
- package/assets/templates/pages/dashboard-alert-rail/template.doc.mjs +23 -1
- package/assets/templates/pages/dashboard-cohort-funnel/template.doc.mjs +10 -1
- package/assets/templates/pages/dashboard-comparison/template.doc.mjs +9 -1
- package/assets/templates/pages/dashboard-composition/template.doc.mjs +10 -1
- package/assets/templates/pages/dashboard-progress/template.doc.mjs +17 -1
- package/assets/templates/pages/dashboard-scorecard/template.doc.mjs +2 -1
- package/assets/templates/pages/detail-page/template.doc.mjs +8 -0
- package/assets/templates/pages/documentation/template.doc.mjs +10 -1
- package/assets/templates/pages/documentation-design/template.doc.mjs +10 -1
- package/assets/templates/pages/documentation-technical/template.doc.mjs +10 -1
- package/assets/templates/pages/editor/template.doc.mjs +9 -1
- package/assets/templates/pages/file-explorer/template.doc.mjs +3 -1
- package/assets/templates/pages/form-two-column/template.doc.mjs +17 -1
- package/assets/templates/pages/form-wizard/template.doc.mjs +14 -1
- package/assets/templates/pages/form-wizard-dialog/template.doc.mjs +1 -0
- package/assets/templates/pages/gallery-hero/template.doc.mjs +10 -1
- package/assets/templates/pages/ide/template.doc.mjs +9 -1
- package/assets/templates/pages/incident-console/template.doc.mjs +10 -1
- package/assets/templates/pages/kanban-board/template.doc.mjs +11 -1
- package/assets/templates/pages/library/template.doc.mjs +15 -1
- package/assets/templates/pages/login/template.doc.mjs +10 -1
- package/assets/templates/pages/login-card/template.doc.mjs +11 -1
- package/assets/templates/pages/login-split/template.doc.mjs +10 -1
- package/assets/templates/pages/login-sso/template.doc.mjs +10 -1
- package/assets/templates/pages/messaging-shell/template.doc.mjs +11 -1
- package/assets/templates/pages/mixed-gallery/template.doc.mjs +10 -1
- package/assets/templates/pages/payment-form/template.doc.mjs +3 -1
- package/assets/templates/pages/product-detail/template.doc.mjs +9 -1
- package/assets/templates/pages/product-gallery/template.doc.mjs +10 -1
- package/assets/templates/pages/settings/template.doc.mjs +3 -1
- package/assets/templates/pages/settings-dialog/template.doc.mjs +9 -1
- package/assets/templates/pages/settings-sidebar/template.doc.mjs +9 -1
- package/assets/templates/pages/shell-nav/template.doc.mjs +10 -1
- package/assets/templates/pages/shell-side-nav/template.doc.mjs +14 -1
- package/assets/templates/pages/shell-top-nav/template.doc.mjs +11 -1
- package/assets/templates/pages/side-gallery/template.doc.mjs +3 -1
- package/assets/templates/pages/table/template.doc.mjs +11 -1
- package/assets/templates/pages/table-filter/template.doc.mjs +21 -1
- package/assets/templates/pages/table-grouped/template.doc.mjs +15 -1
- package/assets/templates/pages/table-inbox/template.doc.mjs +18 -6
- package/assets/templates/pages/table-page/template.doc.mjs +20 -1
- package/assets/templates/pages/table-tree/template.doc.mjs +14 -1
- package/assets/templates/pages/theme-showcase/template.doc.mjs +10 -1
- package/assets/templates/pages/work-item-detail/template.doc.mjs +10 -0
- package/assets/templates/themes/butter/icons.tsx +2 -0
- package/assets/templates/themes/chocolate/icons.tsx +2 -0
- package/assets/templates/themes/gothic/icons.tsx +2 -0
- package/assets/templates/themes/matcha/icons.tsx +2 -0
- package/assets/templates/themes/neutral/icons.tsx +2 -0
- package/assets/templates/themes/stone/icons.tsx +2 -0
- package/assets/templates/themes/y2k/icons.tsx +2 -0
- package/authoring/config/config.doc.mjs +9 -1
- package/authoring/config/parse.d.mts +2 -0
- package/authoring/config/parse.mjs +19 -0
- package/authoring/config/parse.test.mjs +8 -0
- package/authoring/config/type.ts +11 -0
- package/authoring/discover/discover.doc.d.mts +13 -0
- package/authoring/discover/discover.doc.mjs +138 -0
- package/authoring/discover/parse.d.mts +24 -0
- package/authoring/discover/parse.mjs +128 -0
- package/authoring/discover/parse.test.mjs +124 -0
- package/authoring/discover/type.ts +87 -0
- package/authoring/doctypes/_schema.d.mts +3 -2
- package/authoring/doctypes/_schema.mjs +6 -0
- package/authoring/doctypes/base/graph-fields.doc.mjs +3 -3
- package/authoring/doctypes/base/type.ts +4 -2
- package/authoring/doctypes/component/component.doc.mjs +6 -0
- package/authoring/doctypes/component/type.ts +8 -0
- package/authoring/doctypes/reference/reference.doc.mjs +7 -0
- package/authoring/doctypes/reference/type.ts +5 -0
- package/authoring/doctypes/schema/schema.doc.mjs +2 -2
- package/authoring/doctypes/template/parse.d.mts +2 -0
- package/authoring/doctypes/template/parse.mjs +1 -0
- package/authoring/doctypes/template/parse.test.mjs +21 -0
- package/authoring/doctypes/template/template.doc.mjs +7 -1
- package/authoring/doctypes/template/type.ts +12 -2
- package/authoring/index.d.mts +1 -0
- package/authoring/index.d.ts +10 -0
- package/authoring/index.mjs +1 -0
- package/authoring/integration/integration.doc.mjs +12 -10
- package/clients/cli/commands/component/index.mjs +152 -55
- package/clients/cli/commands/component-batch.test.mjs +341 -0
- package/clients/cli/commands/component-ownership.test.mjs +89 -0
- package/clients/cli/commands/component.doc.mjs +27 -9
- package/clients/cli/commands/discover.doc.mjs +53 -9
- package/clients/cli/commands/discover.mjs +393 -118
- package/clients/cli/commands/discover.sources.test.mjs +267 -0
- package/clients/cli/commands/docs.doc.mjs +1 -1
- package/clients/cli/commands/docs.mjs +60 -17
- package/clients/cli/commands/docs.test.mjs +113 -24
- package/clients/cli/commands/doctor-integration-docs.doc.mjs +3 -2
- package/clients/cli/commands/doctor-integration.test.mjs +53 -0
- package/clients/cli/commands/doctor.doc.mjs +3 -1
- package/clients/cli/commands/doctor.mjs +53 -9
- package/clients/cli/commands/gap-report.doc.mjs +10 -9
- package/clients/cli/commands/init.doc.mjs +9 -6
- package/clients/cli/commands/integration-add.doc.mjs +17 -7
- package/clients/cli/commands/integration-authoring.test.mjs +70 -9
- package/clients/cli/commands/integration-pack.doc.mjs +5 -9
- package/clients/cli/commands/integration-real-world.test.mjs +1 -1
- package/clients/cli/commands/integration-verify.doc.mjs +22 -0
- package/clients/cli/commands/integration.doc.mjs +4 -4
- package/clients/cli/commands/integration.mjs +76 -43
- package/clients/cli/commands/layout-expand.doc.mjs +3 -1
- package/clients/cli/commands/layout.expand-receipt.test.mjs +94 -0
- package/clients/cli/commands/layout.mjs +16 -0
- package/clients/cli/commands/manifest.doc.mjs +1 -1
- package/clients/cli/commands/search.doc.mjs +10 -3
- package/clients/cli/commands/search.mjs +21 -2
- package/clients/cli/commands/search.test.mjs +21 -4
- package/clients/cli/commands/swizzle.doc.mjs +1 -1
- package/clients/cli/commands/template.copy-receipt.test.mjs +60 -0
- package/clients/cli/commands/template.doc.mjs +1 -1
- package/clients/cli/commands/template.mjs +19 -5
- package/clients/cli/commands/template.show-media.test.mjs +62 -0
- package/clients/cli/commands/text-json-parity.test.mjs +7 -1
- package/clients/cli/commands/theme-add.doc.mjs +1 -1
- package/clients/cli/commands/theme-palette-generate.doc.mjs +3 -2
- package/clients/cli/commands/theme-palette.doc.mjs +1 -2
- package/clients/cli/commands/theme-targets.doc.mjs +2 -2
- package/clients/cli/commands/theme.doc.mjs +2 -1
- package/clients/cli/commands/upgrade.ascii-output.test.mjs +14 -0
- package/clients/cli/commands/upgrade.doc.mjs +62 -3
- package/clients/cli/commands/write-failure.test.mjs +175 -0
- package/clients/cli/index.mjs +28 -6
- package/clients/cli/lib/define-command.mjs +28 -4
- package/clients/cli/lib/define-command.test.mjs +54 -0
- package/clients/cli/lib/exit-codes.test.mjs +17 -1
- package/clients/cli/lib/json-shim.mjs +24 -14
- package/clients/cli/lib/manifest.mjs +48 -156
- package/clients/cli/lib/manifest.test.mjs +103 -9
- package/clients/cli/lib/parse-error-format.test.mjs +81 -0
- package/foundation/agent-docs/agent-docs.mjs +1 -1
- package/foundation/agent-docs/agent-docs.test.mjs +3 -2
- package/foundation/discovery/authoring-self-docs.mjs +1 -0
- package/foundation/discovery/authoring-self-docs.test.mjs +6 -2
- package/foundation/discovery/cli-self-docs.mjs +16 -2
- package/foundation/discovery/cli-self-docs.test.mjs +20 -0
- package/foundation/discovery/docs-discovery.mjs +5 -1
- package/foundation/discovery/docs-discovery.test.mjs +21 -0
- package/foundation/discovery/docs-section-key.d.mts +1 -1
- package/foundation/discovery/docs-section-key.mjs +1 -1
- package/foundation/discovery/template-adapter.d.mts +14 -0
- package/foundation/discovery/template-adapter.fixture-refs.test.mjs +37 -1
- package/foundation/discovery/template-adapter.mjs +21 -1
- package/foundation/doc-compiler/doc-loads.test.mjs +5 -4
- package/foundation/doc-compiler/inputs.test.mjs +0 -1
- package/foundation/doc-compiler/tree.d.mts +4 -0
- package/foundation/doc-compiler/tree.mjs +6 -1
- package/foundation/doc-compiler/tree.test.mjs +65 -14
- package/foundation/integrations/cli-requirement.d.mts +75 -11
- package/foundation/integrations/cli-requirement.mjs +120 -23
- package/foundation/integrations/cli-requirement.test.mjs +141 -9
- package/foundation/integrations/contribution-inventory.mjs +1 -1
- package/foundation/integrations/integrations.d.mts +14 -1
- package/foundation/integrations/integrations.mjs +41 -1
- package/foundation/integrations/integrations.test.mjs +31 -0
- package/foundation/response/batch.type.d.mts +33 -0
- package/foundation/response/batch.type.mjs +34 -0
- package/foundation/response/error-codes.doc.mjs +6 -8
- package/foundation/response/error-codes.test.mjs +30 -5
- package/foundation/response/response-types.doc.d.mts +5 -4
- package/foundation/response/response-types.doc.mjs +49 -19
- package/foundation/response/response-types.doc.test.mjs +23 -0
- package/foundation/response/response.doc.mjs +11 -10
- package/package.json +9 -9
- package/assets/docs/tree/integrations.test.mjs +0 -62
- package/assets/docs/tree/writing-docs.doc.mjs +0 -286
|
@@ -36,6 +36,8 @@ function writePackage({
|
|
|
36
36
|
scripts,
|
|
37
37
|
} = {}) {
|
|
38
38
|
const pkg = {name, version};
|
|
39
|
+
// A theme needs a CLI that reads typed theme descriptors.
|
|
40
|
+
if (themes) pkg.peerDependencies = {'@astryxdesign/cli': '>=0.6.4'};
|
|
39
41
|
if (files !== undefined) pkg.files = files;
|
|
40
42
|
if (scripts !== undefined) pkg.scripts = scripts;
|
|
41
43
|
fs.writeFileSync(
|
|
@@ -94,6 +96,38 @@ describe('integrationPackCheck', () => {
|
|
|
94
96
|
);
|
|
95
97
|
});
|
|
96
98
|
|
|
99
|
+
it('fails a package that ships a theme on a CLI range that cannot read it', async () => {
|
|
100
|
+
writePackage({files: ['astryx.integration.mjs', 'themes']});
|
|
101
|
+
const pkgFile = path.join(tmpDir, 'package.json');
|
|
102
|
+
const pkg = JSON.parse(fs.readFileSync(pkgFile, 'utf-8'));
|
|
103
|
+
delete pkg.peerDependencies;
|
|
104
|
+
fs.writeFileSync(pkgFile, `${JSON.stringify(pkg, null, 2)}\n`);
|
|
105
|
+
|
|
106
|
+
const missing = await integrationPackCheck({cwd: tmpDir});
|
|
107
|
+
expect(missing.data.packable).toBe(false);
|
|
108
|
+
expect(missing.data.issues).toContainEqual(
|
|
109
|
+
expect.objectContaining({
|
|
110
|
+
code: 'themes_need_cli',
|
|
111
|
+
message: expect.stringContaining('ships a theme'),
|
|
112
|
+
}),
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
pkg.peerDependencies = {'@astryxdesign/cli': '^0.6.3'};
|
|
116
|
+
fs.writeFileSync(pkgFile, `${JSON.stringify(pkg, null, 2)}\n`);
|
|
117
|
+
const old = await integrationPackCheck({cwd: tmpDir});
|
|
118
|
+
expect(old.data.issues).toContainEqual(
|
|
119
|
+
expect.objectContaining({code: 'themes_need_cli'}),
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
// Published 0.6.4 reads typed theme descriptors.
|
|
123
|
+
pkg.peerDependencies = {'@astryxdesign/cli': '>=0.6.4'};
|
|
124
|
+
fs.writeFileSync(pkgFile, `${JSON.stringify(pkg, null, 2)}\n`);
|
|
125
|
+
const current = await integrationPackCheck({cwd: tmpDir});
|
|
126
|
+
expect(current.data.issues).not.toContainEqual(
|
|
127
|
+
expect.objectContaining({code: 'themes_need_cli'}),
|
|
128
|
+
);
|
|
129
|
+
});
|
|
130
|
+
|
|
97
131
|
it('fails when a theme entry omits its inferred runtime export', async () => {
|
|
98
132
|
writePackage({files: ['astryx.integration.mjs', 'themes']});
|
|
99
133
|
fs.writeFileSync(
|
|
@@ -193,6 +227,39 @@ describe('integrationPackCheck', () => {
|
|
|
193
227
|
);
|
|
194
228
|
});
|
|
195
229
|
|
|
230
|
+
it('resolves public imports in the packed package, not the source', async () => {
|
|
231
|
+
// The root export's target is left out of `files`: the source package
|
|
232
|
+
// resolves it, but an app that installs the tarball cannot.
|
|
233
|
+
fs.writeFileSync(
|
|
234
|
+
path.join(tmpDir, 'package.json'),
|
|
235
|
+
`${JSON.stringify({
|
|
236
|
+
name: '@acme/widgets',
|
|
237
|
+
version: '1.0.0',
|
|
238
|
+
files: ['astryx.integration.mjs'],
|
|
239
|
+
exports: {'.': './index.mjs'},
|
|
240
|
+
})}\n`,
|
|
241
|
+
);
|
|
242
|
+
fs.writeFileSync(
|
|
243
|
+
path.join(tmpDir, 'index.mjs'),
|
|
244
|
+
"export {AcmeWidget} from './components/AcmeWidget.tsx';\n",
|
|
245
|
+
);
|
|
246
|
+
await integrationAddComponent('AcmeWidget', {cwd: tmpDir});
|
|
247
|
+
const docFile = path.join(tmpDir, 'components', 'AcmeWidget.doc.mjs');
|
|
248
|
+
const doc = fs.readFileSync(docFile, 'utf-8');
|
|
249
|
+
expect(doc).toContain('@acme/widgets/components/AcmeWidget');
|
|
250
|
+
fs.writeFileSync(
|
|
251
|
+
docFile,
|
|
252
|
+
doc.replace('@acme/widgets/components/AcmeWidget', '@acme/widgets'),
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
const result = await integrationPackCheck({cwd: tmpDir});
|
|
256
|
+
|
|
257
|
+
expect(result.data.packable).toBe(false);
|
|
258
|
+
expect(result.data.issues).toContainEqual(
|
|
259
|
+
expect.objectContaining({code: 'component_export_missing'}),
|
|
260
|
+
);
|
|
261
|
+
});
|
|
262
|
+
|
|
196
263
|
it('fails when packed template source is hidden by package exports', async () => {
|
|
197
264
|
writePackage({
|
|
198
265
|
manifest: "export default {templates: './templates'};\n",
|
|
@@ -307,7 +374,10 @@ describe('integrationPackCheck', () => {
|
|
|
307
374
|
expect(await codes()).toContain('docs_tree_needs_cli');
|
|
308
375
|
peer('^0.6.0 || >=0.7.0');
|
|
309
376
|
expect(await codes()).toContain('docs_tree_needs_cli');
|
|
310
|
-
peer('>=0.
|
|
377
|
+
peer('>=0.6.3');
|
|
378
|
+
expect(await codes()).toContain('docs_tree_needs_cli');
|
|
379
|
+
// Published 0.6.4 reads the docs tree.
|
|
380
|
+
peer('>=0.6.4');
|
|
311
381
|
expect(await codes()).not.toContain('docs_tree_needs_cli');
|
|
312
382
|
}, 120_000);
|
|
313
383
|
|
|
@@ -336,6 +406,41 @@ describe('integrationPackCheck', () => {
|
|
|
336
406
|
expect(await codes()).not.toContain('docs_tree_needs_cli');
|
|
337
407
|
}, 120_000);
|
|
338
408
|
|
|
409
|
+
it('fails a package whose doc section sets id on a CLI range that rejects the field', async () => {
|
|
410
|
+
writePackage({manifest: "export default {docs: './docs'};\n", themes: false});
|
|
411
|
+
fs.mkdirSync(path.join(tmpDir, 'docs'), {recursive: true});
|
|
412
|
+
const topic = (/** @type {string} */ section) =>
|
|
413
|
+
`export default {type: 'generic', name: 'notes', title: 'Notes', description: 'Notes.', sections: [${section}]};\n`;
|
|
414
|
+
const file = path.join(tmpDir, 'docs', 'notes.doc.mjs');
|
|
415
|
+
const codes = async () =>
|
|
416
|
+
(await integrationPackCheck({cwd: tmpDir})).data.issues.map(
|
|
417
|
+
(/** @type {{code: string}} */ issue) => issue.code,
|
|
418
|
+
);
|
|
419
|
+
fs.writeFileSync(
|
|
420
|
+
file,
|
|
421
|
+
topic("{title: 'Take notes', content: [{type: 'prose', text: 'Notes.'}]}"),
|
|
422
|
+
);
|
|
423
|
+
expect(await codes()).not.toContain('section_ids_need_cli');
|
|
424
|
+
// A fresh file name: the module loader caches a path once it is imported.
|
|
425
|
+
fs.rmSync(file);
|
|
426
|
+
fs.writeFileSync(
|
|
427
|
+
path.join(tmpDir, 'docs', 'notes-with-ids.doc.mjs'),
|
|
428
|
+
topic(
|
|
429
|
+
"{id: 'take-notes', title: 'Take notes', content: [{type: 'prose', text: 'Notes.'}]}",
|
|
430
|
+
),
|
|
431
|
+
);
|
|
432
|
+
expect(await codes()).toContain('section_ids_need_cli');
|
|
433
|
+
const pkgFile = path.join(tmpDir, 'package.json');
|
|
434
|
+
const pkg = JSON.parse(fs.readFileSync(pkgFile, 'utf-8'));
|
|
435
|
+
pkg.peerDependencies = {'@astryxdesign/cli': '^0.6.3'};
|
|
436
|
+
fs.writeFileSync(pkgFile, `${JSON.stringify(pkg, null, 2)}\n`);
|
|
437
|
+
expect(await codes()).toContain('section_ids_need_cli');
|
|
438
|
+
// Published 0.6.4 reads a section id.
|
|
439
|
+
pkg.peerDependencies = {'@astryxdesign/cli': '>=0.6.4'};
|
|
440
|
+
fs.writeFileSync(pkgFile, `${JSON.stringify(pkg, null, 2)}\n`);
|
|
441
|
+
expect(await codes()).not.toContain('section_ids_need_cli');
|
|
442
|
+
}, 120_000);
|
|
443
|
+
|
|
339
444
|
it('fails a package with a template that sets replaces on a CLI range that rejects the field', async () => {
|
|
340
445
|
writePackage({manifest: "export default {templates: './templates'};\n", themes: false});
|
|
341
446
|
fs.mkdirSync(path.join(tmpDir, 'templates'), {recursive: true});
|
|
@@ -363,10 +468,44 @@ describe('integrationPackCheck', () => {
|
|
|
363
468
|
expect(await codes()).toContain('replaces_needs_cli');
|
|
364
469
|
peer('^0.6.0');
|
|
365
470
|
expect(await codes()).toContain('replaces_needs_cli');
|
|
471
|
+
// `replaces` still needs 0.7.0.
|
|
472
|
+
peer('>=0.6.4');
|
|
473
|
+
expect(await codes()).toContain('replaces_needs_cli');
|
|
366
474
|
peer('>=0.7.0');
|
|
367
475
|
expect(await codes()).not.toContain('replaces_needs_cli');
|
|
368
476
|
}, 120_000);
|
|
369
477
|
|
|
478
|
+
it('fails a package with a template that sets keywords on a CLI range that rejects the field', async () => {
|
|
479
|
+
writePackage({manifest: "export default {templates: './templates'};\n", themes: false});
|
|
480
|
+
fs.mkdirSync(path.join(tmpDir, 'templates'), {recursive: true});
|
|
481
|
+
fs.writeFileSync(
|
|
482
|
+
path.join(tmpDir, 'templates', 'acme-health.doc.mjs'),
|
|
483
|
+
"export default {type: 'page', name: 'acme-health', description: 'Status tiles.', keywords: ['uptime']};\n",
|
|
484
|
+
);
|
|
485
|
+
fs.writeFileSync(
|
|
486
|
+
path.join(tmpDir, 'templates', 'acme-health.tsx'),
|
|
487
|
+
'export default function AcmeHealth() { return null; }\n',
|
|
488
|
+
);
|
|
489
|
+
const file = path.join(tmpDir, 'package.json');
|
|
490
|
+
const peer = (/** @type {string | undefined} */ range) => {
|
|
491
|
+
const pkg = JSON.parse(fs.readFileSync(file, 'utf-8'));
|
|
492
|
+
if (range == null) delete pkg.peerDependencies;
|
|
493
|
+
else pkg.peerDependencies = {'@astryxdesign/cli': range};
|
|
494
|
+
fs.writeFileSync(file, `${JSON.stringify(pkg, null, 2)}\n`);
|
|
495
|
+
};
|
|
496
|
+
const codes = async () =>
|
|
497
|
+
(await integrationPackCheck({cwd: tmpDir})).data.issues.map(
|
|
498
|
+
(/** @type {{code: string}} */ issue) => issue.code,
|
|
499
|
+
);
|
|
500
|
+
|
|
501
|
+
peer(undefined);
|
|
502
|
+
expect(await codes()).toContain('keywords_needs_cli');
|
|
503
|
+
peer('^0.6.0');
|
|
504
|
+
expect(await codes()).toContain('keywords_needs_cli');
|
|
505
|
+
peer('>=0.7.0');
|
|
506
|
+
expect(await codes()).not.toContain('keywords_needs_cli');
|
|
507
|
+
}, 120_000);
|
|
508
|
+
|
|
370
509
|
it('passes when package.json has no files field', async () => {
|
|
371
510
|
writePackage({files: undefined});
|
|
372
511
|
const result = await integrationPackCheck({cwd: tmpDir});
|
|
@@ -19,8 +19,10 @@ export function validateInstalledIntegration(spec: string, cwd?: string): Promis
|
|
|
19
19
|
/**
|
|
20
20
|
* Unified entry: validate the LOCAL integration (no `pkg`) or an INSTALLED one
|
|
21
21
|
* (`pkg` given) and return the `integration.validate` envelope. The no-manifest
|
|
22
|
-
* local case is guidance, not an error — it comes back with `
|
|
23
|
-
* issues so the CLI can print a hint and stay exit-0.
|
|
22
|
+
* local case is guidance, not an error — it comes back with `validated: false`,
|
|
23
|
+
* `name: null` and no issues so the CLI can print a hint and stay exit-0.
|
|
24
|
+
* `validated` is what tells a machine consumer that empty `issues` means
|
|
25
|
+
* "nothing was checked" rather than "checked and healthy".
|
|
24
26
|
*
|
|
25
27
|
* This is the seam that keeps the CLI a thin wrapper: the command handler calls
|
|
26
28
|
* this and only chooses how to render (human vs --json) + the exit code.
|
|
@@ -427,8 +427,10 @@ export async function validateInstalledIntegration(spec, cwd = process.cwd()) {
|
|
|
427
427
|
/**
|
|
428
428
|
* Unified entry: validate the LOCAL integration (no `pkg`) or an INSTALLED one
|
|
429
429
|
* (`pkg` given) and return the `integration.validate` envelope. The no-manifest
|
|
430
|
-
* local case is guidance, not an error — it comes back with `
|
|
431
|
-
* issues so the CLI can print a hint and stay exit-0.
|
|
430
|
+
* local case is guidance, not an error — it comes back with `validated: false`,
|
|
431
|
+
* `name: null` and no issues so the CLI can print a hint and stay exit-0.
|
|
432
|
+
* `validated` is what tells a machine consumer that empty `issues` means
|
|
433
|
+
* "nothing was checked" rather than "checked and healthy".
|
|
432
434
|
*
|
|
433
435
|
* This is the seam that keeps the CLI a thin wrapper: the command handler calls
|
|
434
436
|
* this and only chooses how to render (human vs --json) + the exit code.
|
|
@@ -445,6 +447,9 @@ export async function validateIntegration(pkg, options = {}) {
|
|
|
445
447
|
return {
|
|
446
448
|
type: 'integration.validate',
|
|
447
449
|
data: {
|
|
450
|
+
// The one bit that separates "checked and clean" from "never checked":
|
|
451
|
+
// with no manifest there is nothing to validate, and issues stays [].
|
|
452
|
+
validated: result.found,
|
|
448
453
|
name: result.found ? (result.name ?? null) : null,
|
|
449
454
|
version: result.found ? (result.version ?? null) : null,
|
|
450
455
|
issues: result.issues,
|
|
@@ -625,3 +625,58 @@ describe('integration diagnostics are read-only', () => {
|
|
|
625
625
|
expect({pkg: snapshot(pkgDir), consumer: snapshot(consumer)}).toEqual(before);
|
|
626
626
|
});
|
|
627
627
|
});
|
|
628
|
+
|
|
629
|
+
describe('validated separates "checked and clean" from "never checked"', () => {
|
|
630
|
+
// `doctor integration validate --json` from a directory with no manifest
|
|
631
|
+
// returned {name: null, version: null, issues: []} and exit 0 — the same
|
|
632
|
+
// envelope a healthy, fully validated integration produces. Wire that into
|
|
633
|
+
// CI from the wrong directory and it is green forever.
|
|
634
|
+
it('is false for every check when no manifest is found', async () => {
|
|
635
|
+
const dir = path.join(tmpDir, 'plain');
|
|
636
|
+
fs.mkdirSync(dir, {recursive: true});
|
|
637
|
+
fs.writeFileSync(
|
|
638
|
+
path.join(dir, 'package.json'),
|
|
639
|
+
JSON.stringify({name: 'plain-app', version: '1.0.0'}),
|
|
640
|
+
);
|
|
641
|
+
|
|
642
|
+
const validate = await validateIntegration(undefined, {cwd: dir});
|
|
643
|
+
expect(validate.data.validated).toBe(false);
|
|
644
|
+
expect(validate.data.issues).toEqual([]);
|
|
645
|
+
|
|
646
|
+
const templates = await integrationTemplateConflicts(undefined, {cwd: dir});
|
|
647
|
+
const components = await integrationComponentConflicts(undefined, {cwd: dir});
|
|
648
|
+
const docs = await integrationDocConflicts(undefined, {cwd: dir});
|
|
649
|
+
expect(templates.data.validated).toBe(false);
|
|
650
|
+
expect(components.data.validated).toBe(false);
|
|
651
|
+
expect(docs.data.validated).toBe(false);
|
|
652
|
+
});
|
|
653
|
+
|
|
654
|
+
it('is true for a real integration, whose empty issue list then means healthy', async () => {
|
|
655
|
+
const dir = path.join(tmpDir, 'integration');
|
|
656
|
+
writePackage(dir, {manifest: 'export default {};\n'});
|
|
657
|
+
|
|
658
|
+
const validate = await validateIntegration(undefined, {cwd: dir});
|
|
659
|
+
expect(validate.data.validated).toBe(true);
|
|
660
|
+
expect(validate.data.name).toBe('@acme/widgets');
|
|
661
|
+
|
|
662
|
+
const templates = await integrationTemplateConflicts(undefined, {cwd: dir});
|
|
663
|
+
const components = await integrationComponentConflicts(undefined, {cwd: dir});
|
|
664
|
+
const docs = await integrationDocConflicts(undefined, {cwd: dir});
|
|
665
|
+
expect(templates.data.validated).toBe(true);
|
|
666
|
+
expect(components.data.validated).toBe(true);
|
|
667
|
+
expect(docs.data.validated).toBe(true);
|
|
668
|
+
});
|
|
669
|
+
|
|
670
|
+
it('is true for an installed package that could not be found — that is a real finding', async () => {
|
|
671
|
+
const consumer = path.join(tmpDir, 'consumer');
|
|
672
|
+
fs.mkdirSync(consumer, {recursive: true});
|
|
673
|
+
fs.writeFileSync(
|
|
674
|
+
path.join(consumer, 'package.json'),
|
|
675
|
+
JSON.stringify({name: 'consumer', version: '1.0.0'}),
|
|
676
|
+
);
|
|
677
|
+
|
|
678
|
+
const res = await validateIntegration('@acme/nope', {cwd: consumer});
|
|
679
|
+
expect(res.data.validated).toBe(true);
|
|
680
|
+
expect(summarizeIssues(res.data.issues).errors).toBeGreaterThan(0);
|
|
681
|
+
});
|
|
682
|
+
});
|
|
@@ -27,10 +27,15 @@ export type ValidateIntegrationOptions = {
|
|
|
27
27
|
};
|
|
28
28
|
/**
|
|
29
29
|
* `astryx --json doctor integration validate [package]`.
|
|
30
|
+
*
|
|
31
|
+
* `validated` is false in exactly one case: no integration manifest was found,
|
|
32
|
+
* so nothing was checked. An empty `issues` then means "not looked at", not
|
|
33
|
+
* "healthy" — the two are otherwise byte-identical.
|
|
30
34
|
*/
|
|
31
35
|
export type ValidateIntegrationResponse = {
|
|
32
36
|
type: "integration.validate";
|
|
33
37
|
data: {
|
|
38
|
+
validated: boolean;
|
|
34
39
|
name: string | null;
|
|
35
40
|
version: string | null;
|
|
36
41
|
issues: import("../../foundation/integrations/issue").AstryxIntegrationIssue[];
|
|
@@ -32,9 +32,13 @@
|
|
|
32
32
|
|
|
33
33
|
/**
|
|
34
34
|
* `astryx --json doctor integration validate [package]`.
|
|
35
|
+
*
|
|
36
|
+
* `validated` is false in exactly one case: no integration manifest was found,
|
|
37
|
+
* so nothing was checked. An empty `issues` then means "not looked at", not
|
|
38
|
+
* "healthy" — the two are otherwise byte-identical.
|
|
35
39
|
* @typedef {object} ValidateIntegrationResponse
|
|
36
40
|
* @property {'integration.validate'} type
|
|
37
|
-
* @property {{name: string | null, version: string | null, issues: import('../../foundation/integrations/issue').AstryxIntegrationIssue[]}} data
|
|
41
|
+
* @property {{validated: boolean, name: string | null, version: string | null, issues: import('../../foundation/integrations/issue').AstryxIntegrationIssue[]}} data
|
|
38
42
|
*/
|
|
39
43
|
|
|
40
44
|
export {};
|
|
@@ -46,7 +46,7 @@ export const doc = {
|
|
|
46
46
|
{
|
|
47
47
|
type: 'integration.validate',
|
|
48
48
|
description:
|
|
49
|
-
'The result envelope: `data.name` and `data.version` of the validated package (both null
|
|
49
|
+
'The result envelope: `data.validated`, false only when no integration manifest was found — nothing was checked, so the empty `data.issues` means "not looked at", not "healthy"; `data.name` and `data.version` of the validated package (both null when `data.validated` is false; `data.name` is `(local package)` when the local package.json has no readable name), plus `data.issues`, an AstryxIntegrationIssue[] of {code, severity: `warning` | `error`, message}.',
|
|
50
50
|
},
|
|
51
51
|
],
|
|
52
52
|
examples: [
|
|
@@ -49,7 +49,7 @@ export const doc = {
|
|
|
49
49
|
throws: [
|
|
50
50
|
{
|
|
51
51
|
code: 'Error',
|
|
52
|
-
when: 'the CLI returned an error envelope (
|
|
52
|
+
when: 'a plain Error with no code: the CLI returned an error envelope (its message is rethrown; code and suggestions are dropped), or the response `type` is not expectedType',
|
|
53
53
|
},
|
|
54
54
|
],
|
|
55
55
|
examples: [
|
package/api/json/isError.doc.mjs
CHANGED
|
@@ -15,7 +15,7 @@ export const doc = {
|
|
|
15
15
|
displayName: 'isError()',
|
|
16
16
|
summary: 'Did the CLI return an error envelope?',
|
|
17
17
|
description:
|
|
18
|
-
'Tests a parsed response for an `error` key. Branch on this before touching `data
|
|
18
|
+
'Tests a parsed response for an `error` key. Branch on this before touching `data`, ' +
|
|
19
19
|
'and prefer the stable `code` field over matching the human-readable message, which is ' +
|
|
20
20
|
'not a contract. Note this returns a plain boolean, not a TypeScript type predicate, so ' +
|
|
21
21
|
'it does not narrow on its own: cast to the matching *Response type to get typed access.',
|
|
@@ -18,7 +18,7 @@ import {ERROR_CODES} from '../../../foundation/response/error-codes.mjs';
|
|
|
18
18
|
import {assertWithin, isFilePathArg, PathSafetyError} from '../../../foundation/fs/path-safety.mjs';
|
|
19
19
|
import {detectForm} from '../../../foundation/xle/parse.mjs';
|
|
20
20
|
import {expand} from '../../../foundation/xle/expand.mjs';
|
|
21
|
-
import {
|
|
21
|
+
import {replaceDemoMedia} from '../../template/template.mjs';
|
|
22
22
|
import {analyze, formatIssue} from '../_adapter.mjs';
|
|
23
23
|
|
|
24
24
|
/** @param {string} name */
|
|
@@ -59,28 +59,32 @@ function collectHintNames(doc) {
|
|
|
59
59
|
/**
|
|
60
60
|
* Build the blockModules map expand() needs: import-mode for app components,
|
|
61
61
|
* splice-mode (reading + asset-stripping the block source) for template blocks.
|
|
62
|
-
* Only blocks actually referenced are read.
|
|
62
|
+
* Only blocks actually referenced are read. Also returns how many demo media
|
|
63
|
+
* references the splice-mode sources had replaced.
|
|
63
64
|
*
|
|
64
65
|
* @param {import('../../../foundation/xle/xle-ast').XLEDoc} doc
|
|
65
66
|
* @param {import('../_adapter.mjs').LayoutBlock[]} blocks
|
|
66
|
-
* @returns {Map<string, import('../../../foundation/xle/xle-ast').BlockModule
|
|
67
|
+
* @returns {{modules: Map<string, import('../../../foundation/xle/xle-ast').BlockModule>, demoMediaReplaced: number}}
|
|
67
68
|
*/
|
|
68
69
|
function buildBlockModules(doc, blocks) {
|
|
69
70
|
const referenced = collectHintNames(doc);
|
|
70
|
-
if (referenced.size === 0) return new Map();
|
|
71
|
+
if (referenced.size === 0) return {modules: new Map(), demoMediaReplaced: 0};
|
|
71
72
|
const byKey = new Map(blocks.map(b => [normKey(b.dirName), b]));
|
|
72
73
|
/** @type {Map<string, import('../../../foundation/xle/xle-ast').BlockModule>} */
|
|
73
74
|
const modules = new Map();
|
|
75
|
+
let demoMediaReplaced = 0;
|
|
74
76
|
for (const name of referenced) {
|
|
75
77
|
const block = byKey.get(normKey(name));
|
|
76
78
|
if (!block) continue;
|
|
77
79
|
if (block.kind === 'component') {
|
|
78
80
|
modules.set(name, /** @type {import('../../../foundation/xle/xle-ast').BlockModule} */ (/** @type {unknown} */ ({mode: 'import', componentName: block.name, importPath: block.importPath, isDefault: block.isDefault})));
|
|
79
81
|
} else if (block.filePath && fs.existsSync(block.filePath)) {
|
|
80
|
-
|
|
82
|
+
const spliced = replaceDemoMedia(fs.readFileSync(block.filePath, 'utf-8'));
|
|
83
|
+
demoMediaReplaced += spliced.demoMediaReplaced;
|
|
84
|
+
modules.set(name, /** @type {import('../../../foundation/xle/xle-ast').BlockModule} */ ({mode: 'splice', componentName: block.dirName, source: spliced.source}));
|
|
81
85
|
}
|
|
82
86
|
}
|
|
83
|
-
return modules;
|
|
87
|
+
return {modules, demoMediaReplaced};
|
|
84
88
|
}
|
|
85
89
|
|
|
86
90
|
/**
|
|
@@ -115,7 +119,7 @@ export async function layoutExpand(expression, options = {}) {
|
|
|
115
119
|
ERROR_CODES.ERR_INVALID_ARGUMENT,
|
|
116
120
|
);
|
|
117
121
|
}
|
|
118
|
-
const blockModules = buildBlockModules(doc, blocks);
|
|
122
|
+
const {modules: blockModules, demoMediaReplaced} = buildBlockModules(doc, blocks);
|
|
119
123
|
const result = expand(doc, registry, {componentName, blockModules});
|
|
120
124
|
|
|
121
125
|
let written = null;
|
|
@@ -150,6 +154,7 @@ export async function layoutExpand(expression, options = {}) {
|
|
|
150
154
|
blocksReferenced: [...blockModules.entries()].map(([name, m]) => ({name, mode: m.mode})),
|
|
151
155
|
warnings: warnings.map(formatIssue),
|
|
152
156
|
written,
|
|
157
|
+
demoMediaReplaced,
|
|
153
158
|
},
|
|
154
159
|
};
|
|
155
160
|
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file The layout.expand receipt discloses demo media replaced in the template
|
|
5
|
+
* blocks it splices. Blocks are picked by what their source contains, not by
|
|
6
|
+
* name, so catalog renames do not break the suite.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import {describe, it, expect, beforeAll} from 'vitest';
|
|
10
|
+
import * as fs from 'node:fs';
|
|
11
|
+
import {layoutExpand} from '../layout.mjs';
|
|
12
|
+
import {discoverTemplates} from '../../template/template.mjs';
|
|
13
|
+
import {buildRegistry} from '../../../foundation/xle/registry.mjs';
|
|
14
|
+
|
|
15
|
+
const SLOW = 60_000;
|
|
16
|
+
const FIXTURE_REF = /\/template-assets\/[\w.-]+\.\w+/g;
|
|
17
|
+
|
|
18
|
+
beforeAll(async () => {
|
|
19
|
+
await buildRegistry();
|
|
20
|
+
}, 120_000);
|
|
21
|
+
|
|
22
|
+
/** @param {string} name */
|
|
23
|
+
const hintKey = name => name.toLowerCase().replace(/[^a-z0-9]/g, '');
|
|
24
|
+
|
|
25
|
+
/** @type {Promise<Array<{hint: string, refs: number}>> | undefined} */
|
|
26
|
+
let catalog;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Template blocks a `{hint}` resolves to unambiguously, with the number of demo
|
|
30
|
+
* media references in each one's source.
|
|
31
|
+
* @returns {Promise<Array<{hint: string, refs: number}>>}
|
|
32
|
+
*/
|
|
33
|
+
function catalogBlocks() {
|
|
34
|
+
catalog ??= discoverTemplates().then(found => {
|
|
35
|
+
const all = /** @type {Array<{dirName: string, type: string, filePath: string}>} */ (
|
|
36
|
+
found
|
|
37
|
+
).filter(t => t.type === 'block' && t.filePath && fs.existsSync(t.filePath));
|
|
38
|
+
const seen = new Map();
|
|
39
|
+
for (const t of all) seen.set(hintKey(t.dirName), (seen.get(hintKey(t.dirName)) ?? 0) + 1);
|
|
40
|
+
return all
|
|
41
|
+
.filter(t => seen.get(hintKey(t.dirName)) === 1)
|
|
42
|
+
.map(t => ({
|
|
43
|
+
hint: hintKey(t.dirName),
|
|
44
|
+
refs: (fs.readFileSync(t.filePath, 'utf-8').match(FIXTURE_REF) ?? []).length,
|
|
45
|
+
}));
|
|
46
|
+
});
|
|
47
|
+
return catalog;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
describe('layout.expand receipt - replaced demo media', () => {
|
|
51
|
+
it('counts the demo media of a spliced block once, however often it is referenced', async () => {
|
|
52
|
+
const block = (await catalogBlocks()).find(b => b.refs > 0);
|
|
53
|
+
if (!block) throw new Error('no template block carries demo media');
|
|
54
|
+
const res = await layoutExpand(`V > {${block.hint}}*3`);
|
|
55
|
+
expect(res.type).toBe('layout.expand');
|
|
56
|
+
expect(res.data.blocksReferenced).toHaveLength(1);
|
|
57
|
+
expect(res.data.demoMediaReplaced).toBe(block.refs);
|
|
58
|
+
expect(res.data.code).not.toContain('/template-assets/');
|
|
59
|
+
}, SLOW);
|
|
60
|
+
|
|
61
|
+
it('sums the demo media of every spliced block', async () => {
|
|
62
|
+
const [a, b] = (await catalogBlocks()).filter(x => x.refs > 0);
|
|
63
|
+
if (!b) throw new Error('fewer than two template blocks carry demo media');
|
|
64
|
+
const res = await layoutExpand(`V > {${a.hint}} + {${b.hint}}`);
|
|
65
|
+
expect(res.data.demoMediaReplaced).toBe(a.refs + b.refs);
|
|
66
|
+
}, SLOW);
|
|
67
|
+
|
|
68
|
+
it('reports 0 when nothing spliced carries demo media', async () => {
|
|
69
|
+
const plain = (await catalogBlocks()).find(b => b.refs === 0);
|
|
70
|
+
if (!plain) throw new Error('every template block carries demo media');
|
|
71
|
+
expect((await layoutExpand(`V > {${plain.hint}}`)).data.demoMediaReplaced).toBe(0);
|
|
72
|
+
expect((await layoutExpand('V > B"Save"')).data.demoMediaReplaced).toBe(0);
|
|
73
|
+
}, SLOW);
|
|
74
|
+
});
|
|
@@ -48,6 +48,7 @@
|
|
|
48
48
|
* @property {LayoutBlockReference[]} data.blocksReferenced
|
|
49
49
|
* @property {string[]} data.warnings
|
|
50
50
|
* @property {string | null} data.written
|
|
51
|
+
* @property {number} data.demoMediaReplaced Astryx demo media references (images, posters, videos) replaced in the spliced template blocks: images with a neutral placeholder, videos with an empty source. Swap in your own media at those points in the code; no media is installed. 0 when no spliced block carried any.
|
|
51
52
|
*/
|
|
52
53
|
|
|
53
54
|
/**
|
|
@@ -70,7 +70,7 @@ export const doc = {
|
|
|
70
70
|
{
|
|
71
71
|
type: 'layout.expand',
|
|
72
72
|
description:
|
|
73
|
-
'The expansion: the parsed form, the generated TSX code, componentsUsed, states (count of useState hooks scaffolded), todos, blocksReferenced (each {name, mode}), warnings,
|
|
73
|
+
'The expansion: the parsed form, the generated TSX code, componentsUsed, states (count of useState hooks scaffolded), todos, blocksReferenced (each {name, mode}), warnings, written (the relative output path, or null when nothing was written), and demoMediaReplaced (how many Astryx demo media references in the spliced template blocks, such as images, posters and videos, were replaced with placeholders for you to swap for your own media).',
|
|
74
74
|
},
|
|
75
75
|
],
|
|
76
76
|
throws: [
|
package/api/search/search.d.mts
CHANGED
|
@@ -24,6 +24,23 @@ export function sameWord(a: string, b: string): boolean;
|
|
|
24
24
|
* @returns {string[]}
|
|
25
25
|
*/
|
|
26
26
|
export function tokenizeQuery(term: string): string[];
|
|
27
|
+
/**
|
|
28
|
+
* The first title or heading that holds every word of the query, in order and
|
|
29
|
+
* side by side, or null.
|
|
30
|
+
* @param {string} term - Lowercased full query.
|
|
31
|
+
* @param {string[] | undefined} titles
|
|
32
|
+
* @returns {string | null}
|
|
33
|
+
*/
|
|
34
|
+
export function headingWithPhrase(term: string, titles: string[] | undefined): string | null;
|
|
35
|
+
/**
|
|
36
|
+
* The first title or heading of two words or more that the query holds whole,
|
|
37
|
+
* in order and side by side, or null. A question such as "how do I add dark
|
|
38
|
+
* mode" names the "Dark mode" section outright, around words no title has.
|
|
39
|
+
* @param {string} term - Lowercased full query.
|
|
40
|
+
* @param {string[] | undefined} titles
|
|
41
|
+
* @returns {string | null}
|
|
42
|
+
*/
|
|
43
|
+
export function titleInQuery(term: string, titles: string[] | undefined): string | null;
|
|
27
44
|
/**
|
|
28
45
|
* @param {string} term - Lowercased full query.
|
|
29
46
|
* @param {string[]} tokens - Content tokens from tokenizeQuery(term).
|
|
@@ -49,6 +66,8 @@ export function scoreQuery(term: string, tokens: string[], candidate: Candidate)
|
|
|
49
66
|
* @param {string} term - Lowercased search term.
|
|
50
67
|
* @param {object} candidate
|
|
51
68
|
* @param {string} candidate.name - Primary identifier (component/hook name, topic, template name).
|
|
69
|
+
* @param {string} [candidate.domain] - A component, hook, or template name
|
|
70
|
+
* also matches typed as words: `command palette` is CommandPalette.
|
|
52
71
|
* @param {string[]} [candidate.keywords] - Authored intent (componentsUsed, category words).
|
|
53
72
|
* @param {string[]} [candidate.weakKeywords] - Derived signal (components a page renders).
|
|
54
73
|
* @param {string} [candidate.description]
|
|
@@ -57,8 +76,9 @@ export function scoreQuery(term: string, tokens: string[], candidate: Candidate)
|
|
|
57
76
|
* @param {{fuzzy?: boolean}} [opts] - `fuzzy`: allow edit-distance (typo) matches. Default true; multi-word queries pass false.
|
|
58
77
|
* @returns {{score: number, reason: string} | null}
|
|
59
78
|
*/
|
|
60
|
-
export function scoreCandidate(term: string, { name, keywords, weakKeywords, description, prose, guidance, }: {
|
|
79
|
+
export function scoreCandidate(term: string, { name, domain, keywords, weakKeywords, description, prose, guidance, }: {
|
|
61
80
|
name: string;
|
|
81
|
+
domain?: string | undefined;
|
|
62
82
|
keywords?: string[] | undefined;
|
|
63
83
|
weakKeywords?: string[] | undefined;
|
|
64
84
|
description?: string | undefined;
|
|
@@ -70,6 +90,30 @@ export function scoreCandidate(term: string, { name, keywords, weakKeywords, des
|
|
|
70
90
|
score: number;
|
|
71
91
|
reason: string;
|
|
72
92
|
} | null;
|
|
93
|
+
/**
|
|
94
|
+
* The components (name and keywords) a search response was scored against, or
|
|
95
|
+
* null when that search was narrowed away from components.
|
|
96
|
+
* @param {object} response
|
|
97
|
+
* @returns {{name: string, keywords: string[]}[] | null}
|
|
98
|
+
*/
|
|
99
|
+
export function searchedComponents(response: object): {
|
|
100
|
+
name: string;
|
|
101
|
+
keywords: string[];
|
|
102
|
+
}[] | null;
|
|
103
|
+
/**
|
|
104
|
+
* Every component the project can use, Core's and its integrations', with the
|
|
105
|
+
* keywords its own doc declares: the discovery and doc reads search's own
|
|
106
|
+
* candidates use, without the import paths and prose a result carries. `build`
|
|
107
|
+
* reads it to tell a part of a page from a page when its search was narrowed
|
|
108
|
+
* away from components.
|
|
109
|
+
* @param {string} coreDir
|
|
110
|
+
* @param {string} cwd
|
|
111
|
+
* @returns {Promise<{name: string, keywords: string[]}[]>}
|
|
112
|
+
*/
|
|
113
|
+
export function componentKeywords(coreDir: string, cwd: string): Promise<{
|
|
114
|
+
name: string;
|
|
115
|
+
keywords: string[];
|
|
116
|
+
}[]>;
|
|
73
117
|
/**
|
|
74
118
|
* Unified ranked search across components, hooks, docs, and templates.
|
|
75
119
|
*
|
|
@@ -122,6 +166,12 @@ export type Candidate = {
|
|
|
122
166
|
description?: string | undefined;
|
|
123
167
|
prose?: string[] | undefined;
|
|
124
168
|
guidance?: string[] | undefined;
|
|
169
|
+
/**
|
|
170
|
+
* - A doc's title and the headings inside it:
|
|
171
|
+
* the lines a reader scans to pick it. The whole query standing in one of
|
|
172
|
+
* them, or one of them standing whole in the query, is a top-tier match.
|
|
173
|
+
*/
|
|
174
|
+
titles?: string[] | undefined;
|
|
125
175
|
_import?: string | undefined;
|
|
126
176
|
_title?: string | undefined;
|
|
127
177
|
/**
|
|
@@ -48,7 +48,7 @@ export const doc = {
|
|
|
48
48
|
name: 'options.cwd',
|
|
49
49
|
type: 'string',
|
|
50
50
|
description:
|
|
51
|
-
"Directory to resolve @astryxdesign/core from. A docs-only search (`type: 'doc'`) does not need it.",
|
|
51
|
+
"Directory to resolve @astryxdesign/core from. A docs-only search (`type: 'doc'`) does not need it, and a search with no `type` covers the docs alone when core is missing.",
|
|
52
52
|
},
|
|
53
53
|
],
|
|
54
54
|
returns: [
|
|
@@ -65,7 +65,7 @@ export const doc = {
|
|
|
65
65
|
},
|
|
66
66
|
{
|
|
67
67
|
code: 'ERR_CORE_NOT_FOUND',
|
|
68
|
-
when: '@astryxdesign/core cannot be found from the cwd, and
|
|
68
|
+
when: '@astryxdesign/core cannot be found from the cwd, and `type` names a domain that reads it: `component`, `hook`, or `template`',
|
|
69
69
|
},
|
|
70
70
|
],
|
|
71
71
|
examples: [
|