@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
|
@@ -45,7 +45,11 @@ import {fileURLToPath, pathToFileURL} from 'node:url';
|
|
|
45
45
|
import {parse} from '@babel/parser';
|
|
46
46
|
import {createJiti} from 'jiti';
|
|
47
47
|
import {getCliInvocation} from '../../../foundation/env/package-manager.mjs';
|
|
48
|
-
import {
|
|
48
|
+
import {
|
|
49
|
+
CLI_ROOT,
|
|
50
|
+
findCoreDir,
|
|
51
|
+
findInstalledPackage,
|
|
52
|
+
} from '../../../foundation/fs/paths.mjs';
|
|
49
53
|
import {
|
|
50
54
|
assertWithin,
|
|
51
55
|
sanitizeName,
|
|
@@ -53,6 +57,7 @@ import {
|
|
|
53
57
|
} from '../../../foundation/fs/path-safety.mjs';
|
|
54
58
|
import {ERROR_CODES} from '../../../foundation/response/error-codes.mjs';
|
|
55
59
|
import {AstryxError} from '../../error.mjs';
|
|
60
|
+
import {applyWrites} from '../../integration/add-helpers.mjs';
|
|
56
61
|
import {logger} from '../../logger.mjs';
|
|
57
62
|
import {loadComponentDoc} from '../../../foundation/discovery/component-loader.mjs';
|
|
58
63
|
import {
|
|
@@ -124,6 +129,232 @@ try {
|
|
|
124
129
|
_coreImportError = e;
|
|
125
130
|
}
|
|
126
131
|
|
|
132
|
+
/**
|
|
133
|
+
* The bindings one Core provides: the namespaces interception spreads, the
|
|
134
|
+
* functions the build calls, and why the import failed, if it did.
|
|
135
|
+
* @typedef {{
|
|
136
|
+
* themeModule: any,
|
|
137
|
+
* rootModule: any,
|
|
138
|
+
* importError: any,
|
|
139
|
+
* defineTheme: any,
|
|
140
|
+
* generateThemeRulesSplit: any,
|
|
141
|
+
* generateOnMediaCSS: any,
|
|
142
|
+
* generateAdaptationCSS: any,
|
|
143
|
+
* dataTokenDefaults: any,
|
|
144
|
+
* }} CoreBindings
|
|
145
|
+
*/
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* The bindings exactly as the CLI's own import left them, kept so each build
|
|
149
|
+
* can choose between them and the project's installed Core.
|
|
150
|
+
* @type {CoreBindings}
|
|
151
|
+
*/
|
|
152
|
+
const _cliCore = {
|
|
153
|
+
themeModule: _coreThemeModule,
|
|
154
|
+
rootModule: _coreRootModule,
|
|
155
|
+
importError: _coreImportError,
|
|
156
|
+
defineTheme: _defineTheme,
|
|
157
|
+
generateThemeRulesSplit: _generateThemeRulesSplit,
|
|
158
|
+
generateOnMediaCSS: _generateOnMediaCSS,
|
|
159
|
+
generateAdaptationCSS: _generateAdaptationCSS,
|
|
160
|
+
dataTokenDefaults: _dataTokenDefaults,
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Point the build at one Core: the module-level bindings every step reads.
|
|
165
|
+
* @param {CoreBindings} core
|
|
166
|
+
*/
|
|
167
|
+
function setCore(core) {
|
|
168
|
+
_coreThemeModule = core.themeModule;
|
|
169
|
+
_coreRootModule = core.rootModule;
|
|
170
|
+
_coreImportError = core.importError;
|
|
171
|
+
_defineTheme = core.defineTheme;
|
|
172
|
+
_generateThemeRulesSplit = core.generateThemeRulesSplit;
|
|
173
|
+
_generateOnMediaCSS = core.generateOnMediaCSS;
|
|
174
|
+
_generateAdaptationCSS = core.generateAdaptationCSS;
|
|
175
|
+
_dataTokenDefaults = core.dataTokenDefaults;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* The file an `import` of `subpath` reaches through a package's `exports` map,
|
|
180
|
+
* or null when the map has no entry for it. Conditions are taken in the map's
|
|
181
|
+
* own order, as Node does, accepting the ones an import matches here: `node`,
|
|
182
|
+
* `import`, and `default` (so `source` and `types` are skipped).
|
|
183
|
+
*
|
|
184
|
+
* @param {string} dir - The package directory.
|
|
185
|
+
* @param {string} subpath - `'.'` or a subpath such as `'./theme'`.
|
|
186
|
+
* @returns {string|null}
|
|
187
|
+
*/
|
|
188
|
+
function importTarget(dir, subpath) {
|
|
189
|
+
let pkg;
|
|
190
|
+
try {
|
|
191
|
+
pkg = JSON.parse(fs.readFileSync(path.join(dir, 'package.json'), 'utf-8'));
|
|
192
|
+
} catch {
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
const map = pkg?.exports;
|
|
196
|
+
const bySubpath =
|
|
197
|
+
map &&
|
|
198
|
+
typeof map === 'object' &&
|
|
199
|
+
!Array.isArray(map) &&
|
|
200
|
+
Object.keys(map).some(key => key.startsWith('.'));
|
|
201
|
+
const entry = bySubpath ? map[subpath] : subpath === '.' ? map : undefined;
|
|
202
|
+
/** @param {any} value @returns {string|null} */
|
|
203
|
+
const pick = value => {
|
|
204
|
+
if (typeof value === 'string') return value;
|
|
205
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
206
|
+
return null;
|
|
207
|
+
for (const [condition, target] of Object.entries(value)) {
|
|
208
|
+
if (
|
|
209
|
+
condition !== 'node' &&
|
|
210
|
+
condition !== 'import' &&
|
|
211
|
+
condition !== 'default'
|
|
212
|
+
)
|
|
213
|
+
continue;
|
|
214
|
+
const picked = pick(target);
|
|
215
|
+
if (picked) return picked;
|
|
216
|
+
}
|
|
217
|
+
return null;
|
|
218
|
+
};
|
|
219
|
+
const target = pick(entry);
|
|
220
|
+
return target ? path.resolve(dir, target) : null;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/** @param {string} file @returns {string} its real path, or itself */
|
|
224
|
+
function realOrSelf(file) {
|
|
225
|
+
try {
|
|
226
|
+
return fs.realpathSync(file);
|
|
227
|
+
} catch {
|
|
228
|
+
return file;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* The Core the project at `cwd` installed, when it differs from the one the
|
|
234
|
+
* CLI's own import found and loads. The app's `<Theme>` runs on the app's
|
|
235
|
+
* Core, so building with it keeps the CSS identical; and a CLI run one-off
|
|
236
|
+
* (`npx @astryxdesign/cli`) has no Core beside it at all. Null when there is
|
|
237
|
+
* nothing better than the CLI's own import: no installed Core (the Astryx
|
|
238
|
+
* repository, a bare directory), the same Core, or one that does not load.
|
|
239
|
+
*
|
|
240
|
+
* @param {string} cwd
|
|
241
|
+
* @returns {Promise<CoreBindings | null>}
|
|
242
|
+
*/
|
|
243
|
+
async function loadProjectCore(cwd) {
|
|
244
|
+
const dir = findInstalledPackage(cwd, '@astryxdesign/core');
|
|
245
|
+
const themeFile = dir ? importTarget(dir, './theme') : null;
|
|
246
|
+
if (!dir || !themeFile) return null;
|
|
247
|
+
if (_cliCore.themeModule) {
|
|
248
|
+
try {
|
|
249
|
+
const own = fileURLToPath(
|
|
250
|
+
import.meta.resolve('@astryxdesign/core/theme'),
|
|
251
|
+
);
|
|
252
|
+
if (realOrSelf(own) === realOrSelf(themeFile)) return null;
|
|
253
|
+
} catch {
|
|
254
|
+
// The CLI's own Core cannot be located; load the project's.
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
let themeModule;
|
|
258
|
+
try {
|
|
259
|
+
themeModule = await import(pathToFileURL(themeFile).href);
|
|
260
|
+
} catch {
|
|
261
|
+
return null;
|
|
262
|
+
}
|
|
263
|
+
if (!themeModule.defineTheme || !themeModule.generateThemeRulesSplit)
|
|
264
|
+
return null;
|
|
265
|
+
let rootModule = null;
|
|
266
|
+
const rootFile = importTarget(dir, '.');
|
|
267
|
+
if (rootFile) {
|
|
268
|
+
try {
|
|
269
|
+
rootModule = await import(pathToFileURL(rootFile).href);
|
|
270
|
+
} catch {
|
|
271
|
+
// As with the CLI's own import, the theme namespace stands in for it.
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
return {
|
|
275
|
+
themeModule,
|
|
276
|
+
rootModule,
|
|
277
|
+
importError: null,
|
|
278
|
+
defineTheme: themeModule.defineTheme,
|
|
279
|
+
generateThemeRulesSplit: themeModule.generateThemeRulesSplit,
|
|
280
|
+
generateOnMediaCSS: themeModule.generateOnMediaCSS,
|
|
281
|
+
generateAdaptationCSS: themeModule.generateAdaptationCSS,
|
|
282
|
+
dataTokenDefaults: themeModule.dataTokenDefaults,
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/** @type {Map<string, Awaited<ReturnType<typeof loadProjectCore>>>} */
|
|
287
|
+
const _projectCores = new Map();
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Choose the Core this build generates with: the project's installed one when
|
|
291
|
+
* {@link loadProjectCore} finds it, else the CLI's own.
|
|
292
|
+
* @param {string} cwd
|
|
293
|
+
*/
|
|
294
|
+
async function selectCore(cwd) {
|
|
295
|
+
if (!_projectCores.has(cwd))
|
|
296
|
+
_projectCores.set(cwd, await loadProjectCore(cwd));
|
|
297
|
+
setCore(_projectCores.get(cwd) ?? _cliCore);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Whether `cwd` sits in the Astryx repository, where Core is built from
|
|
302
|
+
* source rather than installed.
|
|
303
|
+
* @param {string} cwd
|
|
304
|
+
*/
|
|
305
|
+
function inAstryxRepository(cwd) {
|
|
306
|
+
let dir = cwd;
|
|
307
|
+
for (let i = 0; i < 6; i++) {
|
|
308
|
+
try {
|
|
309
|
+
const pkg = JSON.parse(
|
|
310
|
+
fs.readFileSync(
|
|
311
|
+
path.join(dir, 'packages', 'core', 'package.json'),
|
|
312
|
+
'utf-8',
|
|
313
|
+
),
|
|
314
|
+
);
|
|
315
|
+
if (pkg?.name === '@astryxdesign/core') return true;
|
|
316
|
+
} catch {
|
|
317
|
+
// Not here; keep walking up.
|
|
318
|
+
}
|
|
319
|
+
const parent = path.dirname(dir);
|
|
320
|
+
if (parent === dir) break;
|
|
321
|
+
dir = parent;
|
|
322
|
+
}
|
|
323
|
+
return false;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Why `theme build` has no Core to generate with, and the fix for where it
|
|
328
|
+
* ran: the Astryx repository builds Core, an app installs it, and an installed
|
|
329
|
+
* Core that will not load is named.
|
|
330
|
+
*
|
|
331
|
+
* @param {string} cwd
|
|
332
|
+
* @param {any} importError - Why the import failed, when it threw.
|
|
333
|
+
* @returns {string}
|
|
334
|
+
*/
|
|
335
|
+
export function coreUnavailableMessage(cwd, importError) {
|
|
336
|
+
const why =
|
|
337
|
+
'`astryx theme build` generates CSS with @astryxdesign/core, the same code the runtime <Theme> uses';
|
|
338
|
+
const detail = importError ? `\n Import error: ${importError.message}` : '';
|
|
339
|
+
if (inAstryxRepository(cwd)) {
|
|
340
|
+
return (
|
|
341
|
+
`Could not load @astryxdesign/core/theme: ${why}. Build @astryxdesign/core ` +
|
|
342
|
+
`first (e.g. \`pnpm -F @astryxdesign/core build\`).${detail}`
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
const installed = findInstalledPackage(cwd, '@astryxdesign/core');
|
|
346
|
+
if (installed) {
|
|
347
|
+
return (
|
|
348
|
+
`Could not load the @astryxdesign/core installed at ` +
|
|
349
|
+
`${path.relative(cwd, installed) || '.'}: ${why}. Reinstall it.${detail}`
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
return (
|
|
353
|
+
`This project does not have @astryxdesign/core installed: ${why}. Install ` +
|
|
354
|
+
`it: \`npm install @astryxdesign/core\` (or yarn/pnpm/bun).${detail}`
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
|
|
127
358
|
/**
|
|
128
359
|
* Read a package's `version` from a resolved directory. Returns `'unknown'`
|
|
129
360
|
* when it can't be read so the header always has a value.
|
|
@@ -224,26 +455,15 @@ function staleBuildOutputs(writes, cwd) {
|
|
|
224
455
|
/** @param {Array<{dest: string, content: string}>} writes */
|
|
225
456
|
function writeBuildOutputs(writes) {
|
|
226
457
|
if (writes.length === 0) return;
|
|
227
|
-
/** @type {Array<{tmp: string, dest: string}>} */
|
|
228
|
-
const staged = [];
|
|
229
458
|
try {
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
fs.renameSync(stagedWrite.tmp, stagedWrite.dest);
|
|
238
|
-
}
|
|
459
|
+
applyWrites(
|
|
460
|
+
writes.map(write => ({
|
|
461
|
+
path: write.dest,
|
|
462
|
+
contents: write.content,
|
|
463
|
+
createOnly: false,
|
|
464
|
+
})),
|
|
465
|
+
);
|
|
239
466
|
} catch (error) {
|
|
240
|
-
for (const stagedWrite of staged) {
|
|
241
|
-
try {
|
|
242
|
-
fs.rmSync(stagedWrite.tmp, {force: true});
|
|
243
|
-
} catch {
|
|
244
|
-
// Best effort: the command still fails and never reports success.
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
467
|
const message = `Failed to write theme outputs: ${/** @type {Error} */ (error).message}`;
|
|
248
468
|
throw new AstryxError(message, undefined, ERROR_CODES.ERR_WRITE_FAILED);
|
|
249
469
|
}
|
|
@@ -2117,6 +2337,8 @@ async function themeBuildInternal(
|
|
|
2117
2337
|
|
|
2118
2338
|
logger.log(`\nBuilding theme from ${path.relative(cwd, filePath)}...`);
|
|
2119
2339
|
|
|
2340
|
+
await selectCore(cwd);
|
|
2341
|
+
|
|
2120
2342
|
// Standalone builds only need interception when an older Core could erase
|
|
2121
2343
|
// adaptations. Family preparation always supplies the same recorder as its
|
|
2122
2344
|
// shared loader so exact authored parent identity stays CLI-private instead
|
|
@@ -2243,13 +2465,7 @@ async function themeBuildInternal(
|
|
|
2243
2465
|
// capability exports are checked against what the theme actually asks for.
|
|
2244
2466
|
if (!_defineTheme || !_generateThemeRulesSplit) {
|
|
2245
2467
|
throw new AstryxError(
|
|
2246
|
-
|
|
2247
|
-
'built, resolvable @astryxdesign/core so it emits the same CSS as the ' +
|
|
2248
|
-
'runtime <Theme>. Build @astryxdesign/core first (e.g. `pnpm -F @astryxdesign/core ' +
|
|
2249
|
-
'build`)' +
|
|
2250
|
-
(_coreImportError
|
|
2251
|
-
? `.\n Import error: ${_coreImportError.message}`
|
|
2252
|
-
: '.'),
|
|
2468
|
+
coreUnavailableMessage(cwd, _coreImportError),
|
|
2253
2469
|
undefined,
|
|
2254
2470
|
ERROR_CODES.ERR_CORE_NOT_FOUND,
|
|
2255
2471
|
);
|
|
@@ -2817,6 +3033,7 @@ export async function themeBuildFamily(
|
|
|
2817
3033
|
error instanceof Error ? error.message : 'Invalid family key.';
|
|
2818
3034
|
throw new AstryxError(message, undefined, ERROR_CODES.ERR_THEME_INVALID);
|
|
2819
3035
|
}
|
|
3036
|
+
await selectCore(cwd);
|
|
2820
3037
|
const familyInterception = interceptCore(_coreThemeModule, _coreRootModule);
|
|
2821
3038
|
// prettier-ignore
|
|
2822
3039
|
const familyLoader = createJiti(import.meta.url, {moduleCache: true, interopDefault: false, jsx: true, extensions: THEME_MODULE_EXTENSIONS, virtualModules: familyInterception.modules});
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file `theme build` in an app generates with the Core the app installed.
|
|
5
|
+
*
|
|
6
|
+
* A CLI run one-off (`npx @astryxdesign/cli`) has no Core beside it, and the
|
|
7
|
+
* app's `<Theme>` runs on the app's Core either way, so the build loads the
|
|
8
|
+
* project's installed `@astryxdesign/core` when it differs from the CLI's own.
|
|
9
|
+
* When there is no Core to use, the error names the fix for where it ran:
|
|
10
|
+
* install it in an app, build it in the Astryx repository.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import {afterEach, beforeEach, describe, expect, it} from 'vitest';
|
|
14
|
+
import * as fs from 'node:fs';
|
|
15
|
+
import * as os from 'node:os';
|
|
16
|
+
import * as path from 'node:path';
|
|
17
|
+
import {fileURLToPath, pathToFileURL} from 'node:url';
|
|
18
|
+
import {coreUnavailableMessage, themeBuild} from './build.mjs';
|
|
19
|
+
|
|
20
|
+
const HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
21
|
+
const REPO_ROOT = path.resolve(HERE, '../../../../..');
|
|
22
|
+
const CORE_DIST = path.join(REPO_ROOT, 'packages/core/dist');
|
|
23
|
+
|
|
24
|
+
/** @type {string[]} */
|
|
25
|
+
const tmpDirs = [];
|
|
26
|
+
const makeDir = () => {
|
|
27
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'astryx-project-core-'));
|
|
28
|
+
tmpDirs.push(dir);
|
|
29
|
+
fs.writeFileSync(
|
|
30
|
+
path.join(dir, 'package.json'),
|
|
31
|
+
'{"name":"app","type":"module"}',
|
|
32
|
+
);
|
|
33
|
+
// A plain theme object: the file itself never imports Core, so only the
|
|
34
|
+
// build's own Core loading can set the marker below.
|
|
35
|
+
fs.writeFileSync(
|
|
36
|
+
path.join(dir, 'ocean.mjs'),
|
|
37
|
+
"export default {name: 'ocean', tokens: {'--color-bg': '#0a0a0a'}};\n",
|
|
38
|
+
);
|
|
39
|
+
return dir;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
beforeEach(() => {
|
|
43
|
+
delete globalThis.__astryxProjectCoreLoads;
|
|
44
|
+
});
|
|
45
|
+
afterEach(() => {
|
|
46
|
+
delete globalThis.__astryxProjectCoreLoads;
|
|
47
|
+
for (const dir of tmpDirs.splice(0))
|
|
48
|
+
fs.rmSync(dir, {recursive: true, force: true});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Install a Core in `dir` that is the built one, re-exported, and records each
|
|
53
|
+
* entry as it is imported. Its `exports` map lists `source` first, as Core's
|
|
54
|
+
* own does, so the build must skip that condition to find the runtime file.
|
|
55
|
+
* @param {string} dir
|
|
56
|
+
* @param {{broken?: boolean}} [options]
|
|
57
|
+
*/
|
|
58
|
+
function installCore(dir, {broken = false} = {}) {
|
|
59
|
+
const core = path.join(dir, 'node_modules', '@astryxdesign', 'core');
|
|
60
|
+
fs.mkdirSync(core, {recursive: true});
|
|
61
|
+
fs.writeFileSync(
|
|
62
|
+
path.join(core, 'package.json'),
|
|
63
|
+
JSON.stringify({
|
|
64
|
+
name: '@astryxdesign/core',
|
|
65
|
+
version: '0.6.5',
|
|
66
|
+
type: 'module',
|
|
67
|
+
exports: {
|
|
68
|
+
'.': {types: './root.d.ts', default: './root.mjs'},
|
|
69
|
+
'./theme': {
|
|
70
|
+
source: './src/theme/index.ts',
|
|
71
|
+
types: './theme.d.ts',
|
|
72
|
+
default: './theme.mjs',
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
}),
|
|
76
|
+
);
|
|
77
|
+
/** @param {string} entry @param {string} target */
|
|
78
|
+
const module = (entry, target) =>
|
|
79
|
+
broken
|
|
80
|
+
? "throw new Error('this Core is broken');\n"
|
|
81
|
+
: `export * from ${JSON.stringify(pathToFileURL(path.join(CORE_DIST, target)).href)};\n` +
|
|
82
|
+
`globalThis.__astryxProjectCoreLoads = [...(globalThis.__astryxProjectCoreLoads ?? []), '${entry}'];\n`;
|
|
83
|
+
fs.writeFileSync(
|
|
84
|
+
path.join(core, 'theme.mjs'),
|
|
85
|
+
module('theme', 'theme/index.js'),
|
|
86
|
+
);
|
|
87
|
+
fs.writeFileSync(path.join(core, 'root.mjs'), module('root', 'index.js'));
|
|
88
|
+
return core;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
describe('theme build generates with the Core the project installed', () => {
|
|
92
|
+
it('loads the app’s installed Core when it differs from the CLI’s own', async () => {
|
|
93
|
+
const app = makeDir();
|
|
94
|
+
installCore(app);
|
|
95
|
+
await themeBuild('ocean.mjs', {}, {cwd: app});
|
|
96
|
+
expect(globalThis.__astryxProjectCoreLoads).toEqual(['theme', 'root']);
|
|
97
|
+
// The same Core, re-exported, emits the same CSS as the CLI's own. The
|
|
98
|
+
// generated header differs only in the Core version it records.
|
|
99
|
+
const bare = makeDir();
|
|
100
|
+
await themeBuild('ocean.mjs', {}, {cwd: bare});
|
|
101
|
+
/** @param {string} dir */
|
|
102
|
+
const rules = dir =>
|
|
103
|
+
fs
|
|
104
|
+
.readFileSync(path.join(dir, 'ocean.css'), 'utf-8')
|
|
105
|
+
.replace(/^\/\*[\s\S]*?\*\/\n/, '');
|
|
106
|
+
expect(rules(app)).toBe(rules(bare));
|
|
107
|
+
expect(rules(app)).toContain('#0a0a0a');
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('keeps the CLI’s own Core when the project has none', async () => {
|
|
111
|
+
const bare = makeDir();
|
|
112
|
+
await themeBuild('ocean.mjs', {}, {cwd: bare});
|
|
113
|
+
expect(globalThis.__astryxProjectCoreLoads).toBeUndefined();
|
|
114
|
+
expect(fs.existsSync(path.join(bare, 'ocean.css'))).toBe(true);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('keeps the CLI’s own Core when the installed one does not load', async () => {
|
|
118
|
+
const app = makeDir();
|
|
119
|
+
installCore(app, {broken: true});
|
|
120
|
+
await themeBuild('ocean.mjs', {}, {cwd: app});
|
|
121
|
+
expect(fs.existsSync(path.join(app, 'ocean.css'))).toBe(true);
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
describe('the error when there is no Core to generate with', () => {
|
|
126
|
+
it('tells an app to install Core, not to build it', () => {
|
|
127
|
+
const app = makeDir();
|
|
128
|
+
const message = coreUnavailableMessage(
|
|
129
|
+
app,
|
|
130
|
+
new Error("Cannot find package '@astryxdesign/core'"),
|
|
131
|
+
);
|
|
132
|
+
expect(message).toContain(
|
|
133
|
+
'This project does not have @astryxdesign/core installed',
|
|
134
|
+
);
|
|
135
|
+
expect(message).toContain('`npm install @astryxdesign/core`');
|
|
136
|
+
expect(message).not.toContain('pnpm -F');
|
|
137
|
+
expect(message).toContain(
|
|
138
|
+
"Import error: Cannot find package '@astryxdesign/core'",
|
|
139
|
+
);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('names an installed Core that does not load', () => {
|
|
143
|
+
const app = makeDir();
|
|
144
|
+
installCore(app, {broken: true});
|
|
145
|
+
const message = coreUnavailableMessage(
|
|
146
|
+
app,
|
|
147
|
+
new Error('this Core is broken'),
|
|
148
|
+
);
|
|
149
|
+
expect(message).toContain(
|
|
150
|
+
`Could not load the @astryxdesign/core installed at ${path.join('node_modules', '@astryxdesign', 'core')}`,
|
|
151
|
+
);
|
|
152
|
+
expect(message).toContain('Reinstall it.');
|
|
153
|
+
expect(message).not.toContain('pnpm -F');
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('keeps the build instruction in the Astryx repository', () => {
|
|
157
|
+
const message = coreUnavailableMessage(
|
|
158
|
+
path.join(REPO_ROOT, 'packages', 'cli'),
|
|
159
|
+
undefined,
|
|
160
|
+
);
|
|
161
|
+
expect(message).toContain('Build @astryxdesign/core first');
|
|
162
|
+
expect(message).toContain('`pnpm -F @astryxdesign/core build`');
|
|
163
|
+
expect(message).not.toContain('Import error');
|
|
164
|
+
});
|
|
165
|
+
});
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* `theme build` writes its CSS, JS, and declarations as one transaction: when
|
|
5
|
+
* a later output fails to publish, every output it already replaced gets its
|
|
6
|
+
* previous bytes back and every output it created is removed. Publishing is
|
|
7
|
+
* forced to fail by wrapping the two calls that publish a staged file:
|
|
8
|
+
* `linkSync` for a new file and `renameSync` for a replacement.
|
|
9
|
+
*
|
|
10
|
+
* Separate file because vi.mock is hoisted and affects the whole module.
|
|
11
|
+
* `themeBuild` needs a built core; the `node` project's globalSetup builds it.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest';
|
|
15
|
+
import * as os from 'node:os';
|
|
16
|
+
import * as path from 'node:path';
|
|
17
|
+
|
|
18
|
+
const failures = vi.hoisted(() => ({
|
|
19
|
+
/** Fail the Nth staged-file publish (1-based); 0 disables. */
|
|
20
|
+
publish: 0,
|
|
21
|
+
count: 0,
|
|
22
|
+
}));
|
|
23
|
+
|
|
24
|
+
vi.mock('node:fs', async importOriginal => {
|
|
25
|
+
const actual = /** @type {typeof import('node:fs')} */ (
|
|
26
|
+
await importOriginal()
|
|
27
|
+
);
|
|
28
|
+
/** @param {string} source @param {string} op */
|
|
29
|
+
const maybeFail = (source, op) => {
|
|
30
|
+
if (!path.basename(String(source)).includes('.tmp')) return;
|
|
31
|
+
if (failures.publish === 0) return;
|
|
32
|
+
failures.count++;
|
|
33
|
+
if (failures.count === failures.publish) {
|
|
34
|
+
throw Object.assign(new Error(`EIO: forced ${op} failure`), {
|
|
35
|
+
code: 'EIO',
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
return {
|
|
40
|
+
...actual,
|
|
41
|
+
linkSync: vi.fn((source, destination) => {
|
|
42
|
+
maybeFail(source, 'link');
|
|
43
|
+
return actual.linkSync(source, destination);
|
|
44
|
+
}),
|
|
45
|
+
renameSync: vi.fn((source, destination) => {
|
|
46
|
+
maybeFail(source, 'rename');
|
|
47
|
+
return actual.renameSync(source, destination);
|
|
48
|
+
}),
|
|
49
|
+
};
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const fs = await import('node:fs');
|
|
53
|
+
const {themeBuild} = await import('./build.mjs');
|
|
54
|
+
|
|
55
|
+
vi.setConfig({testTimeout: 30000});
|
|
56
|
+
|
|
57
|
+
let tmpDir;
|
|
58
|
+
|
|
59
|
+
beforeEach(() => {
|
|
60
|
+
tmpDir = fs.mkdtempSync(
|
|
61
|
+
path.join(os.tmpdir(), 'astryx-theme-build-rollback-'),
|
|
62
|
+
);
|
|
63
|
+
failures.publish = 0;
|
|
64
|
+
failures.count = 0;
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
afterEach(() => {
|
|
68
|
+
failures.publish = 0;
|
|
69
|
+
fs.rmSync(tmpDir, {recursive: true, force: true});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Write a source for the theme named `rollbacktheme`. Each call uses a new
|
|
74
|
+
* file so the loader cannot serve an earlier version from its cache.
|
|
75
|
+
* @param {string} file @param {string} background
|
|
76
|
+
*/
|
|
77
|
+
function writeTheme(file, background) {
|
|
78
|
+
fs.writeFileSync(
|
|
79
|
+
path.join(tmpDir, file),
|
|
80
|
+
`export default { name: 'rollbacktheme', tokens: { '--color-bg': '${background}' } };\n`,
|
|
81
|
+
);
|
|
82
|
+
return file;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const OUTPUTS = ['rollbacktheme.css', 'rollbacktheme.js', 'rollbacktheme.d.ts'];
|
|
86
|
+
|
|
87
|
+
/** @returns {Map<string, Buffer | null>} */
|
|
88
|
+
function readOutputs() {
|
|
89
|
+
return new Map(
|
|
90
|
+
OUTPUTS.map(name => {
|
|
91
|
+
const file = path.join(tmpDir, name);
|
|
92
|
+
return [name, fs.existsSync(file) ? fs.readFileSync(file) : null];
|
|
93
|
+
}),
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function strays() {
|
|
98
|
+
return fs
|
|
99
|
+
.readdirSync(tmpDir)
|
|
100
|
+
.filter(name => name.includes('.tmp') || name.includes('.restore-'));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
describe('themeBuild rolls back a partial write', () => {
|
|
104
|
+
it('removes every created output when the second publish fails', async () => {
|
|
105
|
+
const source = writeTheme('first.mjs', '#0a0a0a');
|
|
106
|
+
failures.publish = 2;
|
|
107
|
+
|
|
108
|
+
await expect(themeBuild(source, {}, {cwd: tmpDir})).rejects.toMatchObject({
|
|
109
|
+
code: 'ERR_WRITE_FAILED',
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
for (const bytes of readOutputs().values()) expect(bytes).toBeNull();
|
|
113
|
+
expect(strays()).toEqual([]);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('restores every replaced output when the second publish fails', async () => {
|
|
117
|
+
const first = await themeBuild(
|
|
118
|
+
writeTheme('first.mjs', '#0a0a0a'),
|
|
119
|
+
{},
|
|
120
|
+
{cwd: tmpDir},
|
|
121
|
+
);
|
|
122
|
+
expect(first?.type).toBe('theme.build');
|
|
123
|
+
const before = readOutputs();
|
|
124
|
+
for (const bytes of before.values()) expect(bytes).not.toBeNull();
|
|
125
|
+
|
|
126
|
+
const next = writeTheme('next.mjs', '#fafafa');
|
|
127
|
+
failures.publish = 2;
|
|
128
|
+
await expect(themeBuild(next, {}, {cwd: tmpDir})).rejects.toMatchObject({
|
|
129
|
+
code: 'ERR_WRITE_FAILED',
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
const after = readOutputs();
|
|
133
|
+
for (const name of OUTPUTS) {
|
|
134
|
+
expect(
|
|
135
|
+
after.get(name)?.equals(/** @type {Buffer} */ (before.get(name))),
|
|
136
|
+
).toBe(true);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// The failed build really had different bytes to write.
|
|
140
|
+
failures.publish = 0;
|
|
141
|
+
await themeBuild(next, {}, {cwd: tmpDir});
|
|
142
|
+
const css = readOutputs().get('rollbacktheme.css');
|
|
143
|
+
expect(
|
|
144
|
+
css?.equals(/** @type {Buffer} */ (before.get('rollbacktheme.css'))),
|
|
145
|
+
).toBe(false);
|
|
146
|
+
expect(strays()).toEqual([]);
|
|
147
|
+
});
|
|
148
|
+
});
|
|
@@ -29,7 +29,7 @@ export const doc = {
|
|
|
29
29
|
name: 'input',
|
|
30
30
|
type: 'TonalPaletteGenerationInput',
|
|
31
31
|
description:
|
|
32
|
-
'Families and seeds plus optional modes, shared stops, anchors, vibrancy from 0 to 100 (default 50), and neutral profile.
|
|
32
|
+
'Families and seeds plus optional modes, shared stops, anchors, vibrancy from 0 to 100 (default 50), and neutral profile.',
|
|
33
33
|
required: true,
|
|
34
34
|
},
|
|
35
35
|
],
|
|
@@ -72,6 +72,5 @@ export const doc = {
|
|
|
72
72
|
code: "generateTonalPalette({stops: [12.5, 50], families: [{id: 'blue', seed: '#0074e2'}]});",
|
|
73
73
|
},
|
|
74
74
|
],
|
|
75
|
-
command: 'theme palette generate',
|
|
76
75
|
related: ['themePaletteGenerate'],
|
|
77
76
|
};
|
|
@@ -13,7 +13,7 @@ export const doc = {
|
|
|
13
13
|
displayName: 'listThemes()',
|
|
14
14
|
summary: 'Read the CLI bundled-theme descriptors.',
|
|
15
15
|
description:
|
|
16
|
-
|
|
16
|
+
"Synchronously returns the themes bundled with the CLI, including each one's entry file, export name and file list. Bundled themes only; use themeListAvailable() to include themes from installed integrations.",
|
|
17
17
|
importPath: '@astryxdesign/cli/api',
|
|
18
18
|
signature: 'listThemes(): BundledTheme[]',
|
|
19
19
|
keywords: ['theme', 'themes', 'descriptor', 'bundled', 'adapter', 'list'],
|