@astryxdesign/cli 0.6.3 → 0.6.4-canary.078fd25
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 +194 -0
- package/README.md +152 -107
- package/api/blog/blog.doc.mjs +1 -0
- package/api/build/_adapter.d.mts +50 -0
- package/api/build/_adapter.mjs +60 -0
- package/api/build/build.doc.mjs +22 -10
- package/api/build/build.test.mjs +219 -8
- package/api/build/build.type.d.mts +91 -2
- package/api/build/build.type.mjs +52 -8
- package/api/build/help/help.d.mts +12 -5
- package/api/build/help/help.mjs +69 -6
- package/api/build/kit/kit.d.mts +4 -1
- package/api/build/kit/kit.mjs +208 -53
- package/api/build/kit/rank.d.mts +44 -0
- package/api/build/kit/rank.mjs +432 -0
- package/api/build/kit/rank.test.mjs +196 -0
- package/api/component/_adapter.d.mts +31 -12
- package/api/component/_adapter.mjs +79 -15
- package/api/component/component.d.mts +6 -3
- package/api/component/component.doc.mjs +49 -19
- package/api/component/component.mjs +339 -22
- package/api/component/component.test.mjs +38 -0
- package/api/component/component.type.d.mts +47 -11
- package/api/component/component.type.mjs +76 -24
- package/api/component/detail/blocks/blocks.d.mts +2 -1
- package/api/component/detail/blocks/blocks.mjs +4 -3
- package/api/component/list/list.d.mts +0 -5
- package/api/component/list/list.mjs +40 -11
- 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 +62 -18
- package/api/discover/discover.mjs +220 -36
- package/api/discover/discover.test.mjs +11 -2
- package/api/discover/discover.type.d.mts +150 -11
- package/api/discover/discover.type.mjs +107 -17
- 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 +277 -41
- package/api/docs/_adapter.mjs +993 -108
- package/api/docs/compiled-topics.test.mjs +78 -0
- package/api/docs/detail/detail.mjs +22 -63
- package/api/docs/detail/section/section.d.mts +1 -1
- package/api/docs/detail/section/section.mjs +54 -19
- package/api/docs/detail/section/section.test.mjs +50 -0
- package/api/docs/docOverlays.test.mjs +27 -1
- package/api/docs/docs.d.mts +10 -3
- package/api/docs/docs.doc.mjs +55 -16
- package/api/docs/docs.mjs +53 -10
- package/api/docs/docs.type.d.mts +221 -5
- package/api/docs/docs.type.mjs +153 -11
- package/api/docs/index/index.d.mts +18 -0
- package/api/docs/index/index.mjs +40 -0
- package/api/docs/index/index.test.mjs +62 -0
- package/api/docs/list/list.mjs +28 -12
- package/api/docs/node/node.d.mts +43 -0
- package/api/docs/node/node.mjs +192 -0
- package/api/docs/reference-blocks.test.mjs +406 -0
- package/api/doctor/doctor.d.mts +104 -1
- package/api/doctor/doctor.doc.mjs +18 -8
- package/api/doctor/doctor.mjs +635 -7
- package/api/doctor/doctor.test.mjs +732 -11
- package/api/doctor/doctor.type.d.mts +1 -1
- package/api/doctor/doctor.type.mjs +1 -1
- package/api/gap-report/gap-report.doc.mjs +27 -14
- package/api/hook/_adapter.mjs +19 -5
- package/api/hook/hook.doc.mjs +7 -3
- package/api/hook/hook.type.d.mts +3 -3
- package/api/hook/hook.type.mjs +11 -11
- package/api/hook/list/list.d.mts +2 -2
- package/api/hook/list/list.mjs +69 -17
- package/api/index.d.mts +2 -1
- package/api/index.mjs +6 -3
- package/api/init/init.doc.mjs +22 -12
- package/api/init/init.test.mjs +41 -1
- package/api/init/remove/remove.mjs +1 -1
- package/api/init/run/run.mjs +20 -10
- package/api/integration/add-contribution.component-names.test.mjs +120 -0
- package/api/integration/add-contribution.d.mts +2 -1
- package/api/integration/add-contribution.mjs +130 -15
- package/api/integration/add-contribution.test.mjs +258 -7
- package/api/integration/add-helpers.d.mts +5 -2
- package/api/integration/add-helpers.mjs +36 -9
- package/api/integration/add-theme.mjs +56 -65
- package/api/integration/add-theme.test.mjs +139 -21
- package/api/integration/authoring-checks.mjs +138 -28
- package/api/integration/authoring-checks.test.mjs +179 -7
- package/api/integration/authoring-checks.type.mjs +6 -1
- package/api/integration/integration-authoring.type.d.mts +3 -1
- package/api/integration/integration-authoring.type.mjs +2 -0
- package/api/integration/integration-block-exports.test.mjs +10 -6
- package/api/integration/integrationAdd.doc.mjs +14 -4
- package/api/integration/integrationAddAgentDoc.doc.mjs +1 -0
- package/api/integration/integrationAddCodemod.doc.mjs +1 -0
- package/api/integration/integrationAddComponent.doc.mjs +2 -1
- package/api/integration/integrationAddDoc.doc.mjs +8 -1
- package/api/integration/integrationAddTemplate.doc.mjs +1 -0
- package/api/integration/integrationAddTheme.doc.mjs +6 -5
- package/api/integration/integrationComponentConflicts.doc.mjs +1 -0
- package/api/integration/integrationDocConflicts.doc.mjs +2 -1
- package/api/integration/integrationPackCheck.doc.mjs +5 -4
- package/api/integration/integrationTemplateConflicts.doc.d.mts +3 -0
- package/api/integration/integrationTemplateConflicts.doc.mjs +4 -0
- package/api/integration/pack-check.lifecycle-output.test.mjs +107 -0
- package/api/integration/pack-check.mjs +160 -11
- package/api/integration/pack-check.test.mjs +477 -47
- package/api/integration/pack-check.type.d.mts +26 -2
- package/api/integration/pack-check.type.mjs +15 -2
- package/api/integration/summarizeIssues.doc.mjs +1 -0
- package/api/integration/template-conflict-compatibility.test.mjs +73 -0
- package/api/integration/validate-integration-fixes.test.mjs +1389 -0
- package/api/integration/validate-integration.mjs +52 -102
- package/api/integration/validate-integration.test.mjs +179 -26
- package/api/integration/validate-unread-theme-folders.test.mjs +110 -0
- package/api/integration/validateIntegration.doc.mjs +3 -2
- package/api/json/assertResponse.doc.mjs +2 -1
- package/api/json/envelope-types.test.mjs +76 -0
- package/api/json/index.ts +2 -0
- package/api/json/isError.doc.mjs +2 -1
- package/api/json/parseResponse.doc.mjs +3 -2
- package/api/layout/_adapter.mjs +20 -5
- package/api/layout/expand/expand.mjs +7 -5
- package/api/layout/expand/expand.path-safety.test.mjs +53 -0
- package/api/layout/grammar/grammar.mjs +2 -1
- package/api/layout/layoutCheck.doc.mjs +1 -0
- package/api/layout/layoutExpand.doc.mjs +2 -1
- package/api/layout/layoutGrammar.doc.mjs +1 -0
- package/api/search/search-return-type.test.mjs +54 -0
- package/api/search/search.d.mts +89 -12
- package/api/search/search.doc.mjs +8 -2
- package/api/search/search.mjs +697 -97
- package/api/search/search.type.d.mts +15 -3
- package/api/search/search.type.mjs +5 -2
- package/api/swizzle/copy/copy.mjs +28 -11
- package/api/swizzle/swizzle.doc.mjs +8 -5
- package/api/swizzle/swizzle.type.d.mts +2 -2
- package/api/swizzle/swizzle.type.mjs +2 -2
- package/api/template/copy/copy.mjs +18 -24
- package/api/template/copy/copy.test.mjs +26 -0
- package/api/template/list/list.mjs +1 -0
- package/api/template/table-floating-bulk-actions.test.mjs +66 -0
- package/api/template/template-integration.test.mjs +1072 -3
- package/api/template/template-suffix.test.mjs +41 -21
- package/api/template/template.d.mts +1 -1
- package/api/template/template.doc.mjs +32 -9
- package/api/template/template.mjs +45 -8
- package/api/template/template.type.d.mts +12 -14
- package/api/template/template.type.mjs +15 -14
- package/api/theme/_adapter.d.mts +2 -3
- package/api/theme/_adapter.mjs +4 -5
- package/api/theme/add/add.binary.test.mjs +84 -0
- package/api/theme/add/add.mjs +31 -22
- package/api/theme/add/add.rollback.test.mjs +158 -0
- package/api/theme/add/add.staging.test.mjs +83 -0
- package/api/theme/add/add.test.mjs +14 -1
- package/api/theme/build/build.family.test.mjs +7 -12
- package/api/theme/build/build.mjs +140 -59
- package/api/theme/build/build.public-component-vars.test.mjs +1 -1
- package/api/theme/build/build.receipt-doc.test.mjs +111 -0
- package/api/theme/build/build.rollback.test.mjs +148 -0
- package/api/theme/build/build.test.mjs +127 -0
- package/api/theme/build/font-warning.mjs +3 -3
- package/api/theme/build/font-warning.test.mjs +5 -2
- package/api/theme/generateTonalPalette.doc.mjs +2 -2
- package/api/theme/integration-themes.test.mjs +39 -28
- package/api/theme/list/list.test.mjs +19 -20
- package/api/theme/listThemes.doc.mjs +6 -5
- package/api/theme/palette/generate/generate.mjs +8 -3
- package/api/theme/palette/generate/generate.test.mjs +96 -0
- package/api/theme/palette/generate/generator.d.mts +10 -13
- package/api/theme/palette/generate/generator.mjs +15 -4
- package/api/theme/palette/generate/generator.test.mjs +10 -0
- package/api/theme/template/template.mjs +11 -2
- package/api/theme/template/template.test.mjs +20 -0
- package/api/theme/theme.type.d.mts +170 -11
- package/api/theme/theme.type.mjs +94 -27
- package/api/theme/themeAdd.doc.mjs +12 -12
- package/api/theme/themeBuild.doc.mjs +21 -17
- package/api/theme/themeList.doc.mjs +6 -3
- package/api/theme/themeListAvailable.doc.mjs +6 -3
- package/api/theme/themePaletteGenerate.doc.mjs +16 -8
- package/api/theme/themeTargets.doc.mjs +4 -2
- package/api/theme/themeTemplate.doc.mjs +8 -3
- package/api/upgrade/_adapter.d.mts +32 -5
- package/api/upgrade/_adapter.mjs +139 -22
- package/api/upgrade/list/list.mjs +2 -1
- package/api/upgrade/list/list.test.mjs +73 -0
- package/api/upgrade/project-context.test.mjs +272 -0
- package/api/upgrade/provider-agreement.test.mjs +152 -0
- package/api/upgrade/run/files-changed.test.mjs +111 -0
- package/api/upgrade/run/run.mjs +358 -59
- package/api/upgrade/status/status.mjs +2 -2
- package/api/upgrade/upgrade.doc.mjs +32 -23
- package/api/upgrade/upgrade.type.d.mts +43 -5
- package/api/upgrade/upgrade.type.mjs +29 -13
- package/assets/codemods/__tests__/registry.test.mjs +1 -0
- package/assets/codemods/__tests__/runner.test.mjs +332 -8
- package/assets/codemods/file-count.test.mjs +163 -0
- package/assets/codemods/integration-discovery.mjs +48 -4
- package/assets/codemods/integration-discovery.test.mjs +73 -0
- package/assets/codemods/integration-runner.mjs +59 -7
- package/assets/codemods/integration-runner.protection.test.mjs +153 -0
- package/assets/codemods/registry.mjs +1 -0
- package/assets/codemods/run-codemod.mjs +177 -34
- package/assets/codemods/runner.mjs +353 -104
- package/assets/codemods/term-log.mjs +32 -8
- package/assets/codemods/term-log.test.mjs +19 -1
- package/assets/codemods/transform-prop.mjs +109 -0
- package/assets/codemods/transform-prop.test.mjs +95 -0
- package/assets/codemods/transforms/v0.0.14/__tests__/rename-status-variants.test.mjs +86 -165
- package/assets/codemods/transforms/v0.0.14/rename-status-variants.mjs +72 -210
- package/assets/codemods/transforms/v0.1.8/__tests__/rename-avatar-size-scale.test.mjs +83 -115
- package/assets/codemods/transforms/v0.1.8/rename-avatar-size-scale.mjs +57 -186
- package/assets/codemods/transforms/v0.3.0/__tests__/unwrap-authoring-factories.test.mjs +27 -5
- package/assets/codemods/transforms/v0.3.0/unwrap-authoring-factories.mjs +20 -5
- package/assets/codemods/transforms/v0.6.0/__tests__/next-codemods.test.mjs +47 -0
- package/assets/codemods/transforms/v0.6.0/rename-resizable-pixel-bounds.mjs +57 -10
- package/assets/codemods/transforms/v0.6.4/__tests__/migrate-native-picker-to-presentation.test.mjs +63 -0
- package/assets/codemods/transforms/v0.6.4/__tests__/migrate-theme-catalog-to-descriptors.test.mjs +220 -0
- package/assets/codemods/transforms/v0.6.4/index.mjs +31 -0
- package/assets/codemods/transforms/v0.6.4/migrate-native-picker-to-presentation.mjs +148 -0
- package/assets/codemods/transforms/v0.6.4/migrate-theme-catalog-to-descriptors.mjs +141 -0
- package/assets/docs/README.md +12 -1
- package/assets/docs/authoring.doc.mjs +14 -0
- 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 +6 -17
- package/assets/docs/icons.doc.mjs +2 -21
- package/assets/docs/illustrations.doc.mjs +7 -15
- package/assets/docs/internationalization.doc.mjs +7 -5
- package/assets/docs/layout.doc.dense.mjs +132 -84
- package/assets/docs/layout.doc.mjs +134 -78
- 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 +14 -6
- 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 +10 -6
- package/assets/docs/styling.doc.mjs +22 -26
- package/assets/docs/theme.doc.dense.mjs +58 -18
- package/assets/docs/theme.doc.mjs +60 -50
- package/assets/docs/theme.doc.zh.mjs +9 -8
- package/assets/docs/tokens.doc.dense.mjs +2 -2
- package/assets/docs/tokens.doc.mjs +390 -9
- 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/api.doc.mjs +30 -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/cli.doc.mjs +23 -0
- package/assets/docs/tree/codemods.doc.mjs +147 -0
- package/assets/docs/tree/commands.doc.mjs +25 -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 +40 -0
- 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 +109 -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 +149 -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 +161 -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 +34 -26
- package/assets/templates/blocks/components/CheckboxList/CheckboxListSelectAllPattern.doc.mjs +1 -1
- package/assets/templates/blocks/components/CheckboxList/CheckboxListSelectAllPattern.tsx +1 -3
- package/assets/templates/blocks/components/DateInput/DateInputDateRange.tsx +1 -1
- package/assets/templates/blocks/components/InternationalizationProvider/InternationalizationProvider01ShippedLocale.tsx +1 -1
- package/assets/templates/blocks/components/Item/ItemDocumentTabs.doc.mjs +14 -0
- package/assets/templates/blocks/components/Item/ItemDocumentTabs.tsx +100 -0
- package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaInlineRail.doc.mjs +14 -0
- package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaInlineRail.tsx +61 -0
- package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaOverscrollChaining.doc.mjs +14 -0
- package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaOverscrollChaining.tsx +126 -0
- package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaShowcase.doc.mjs +15 -0
- package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaShowcase.tsx +86 -0
- package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaStickyGroupHeaders.doc.mjs +14 -0
- package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaStickyGroupHeaders.tsx +99 -0
- package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaStickyPassthrough.doc.mjs +14 -0
- package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaStickyPassthrough.tsx +122 -0
- package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaTwoAxisBoard.doc.mjs +14 -0
- package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaTwoAxisBoard.tsx +95 -0
- package/assets/templates/blocks/components/Table/TableBulkActionsTable.doc.mjs +14 -0
- package/assets/templates/blocks/components/Table/TableBulkActionsTable.tsx +71 -0
- package/assets/templates/blocks/components/Table/TableFloatingBulkActionsTable.doc.mjs +19 -0
- package/assets/templates/blocks/components/Table/TableFloatingBulkActionsTable.tsx +139 -0
- package/assets/templates/blocks/components/TimeInput/TimeInputConstrained.tsx +1 -0
- package/assets/templates/blocks/components/Timer/TimerFormats.doc.mjs +14 -0
- package/assets/templates/blocks/components/Timer/TimerFormats.tsx +34 -0
- package/assets/templates/blocks/components/Timer/TimerInline.doc.mjs +14 -0
- package/assets/templates/blocks/components/Timer/TimerInline.tsx +14 -0
- package/assets/templates/blocks/components/Timer/TimerShowcase.doc.mjs +13 -0
- package/assets/templates/blocks/components/Timer/TimerShowcase.tsx +47 -0
- package/assets/templates/blocks/components/Timer/TimerTypography.doc.mjs +14 -0
- package/assets/templates/blocks/components/Timer/TimerTypography.tsx +31 -0
- package/assets/templates/blocks/components/Toolbar/ToolbarTableFilter.doc.mjs +19 -3
- package/assets/templates/blocks/components/Toolbar/ToolbarTableFilter.tsx +383 -65
- package/assets/templates/pages/table-tree/page.tsx +1704 -0
- package/assets/templates/pages/table-tree/template.doc.mjs +12 -0
- package/assets/templates/themes/butter/butterTheme.doc.mjs +11 -0
- package/assets/templates/themes/chocolate/chocolateTheme.doc.mjs +11 -0
- package/assets/templates/themes/gothic/gothicTheme.doc.mjs +11 -0
- package/assets/templates/themes/matcha/matchaTheme.doc.mjs +11 -0
- package/assets/templates/themes/neutral/neutralTheme.doc.mjs +11 -0
- package/assets/templates/themes/stone/stoneTheme.doc.mjs +11 -0
- package/assets/templates/themes/y2k/y2kTheme.doc.mjs +11 -0
- package/authoring/_shared/contract.ts +22 -0
- package/authoring/codemod/codemod.doc.mjs +7 -2
- package/authoring/codemod/parse.d.mts +8 -8
- package/authoring/codemod/parse.mjs +8 -6
- package/authoring/codemod/type.ts +12 -0
- package/authoring/config/config.doc.mjs +11 -3
- package/authoring/config/debug-composition.test.mjs +92 -0
- package/authoring/config/parse.d.mts +15 -13
- package/authoring/config/parse.mjs +27 -8
- package/authoring/config/parse.test.mjs +8 -0
- package/authoring/config/type.ts +29 -6
- package/authoring/debug/debug.doc.d.mts +11 -0
- package/authoring/debug/debug.doc.mjs +182 -0
- package/authoring/debug/parse.d.mts +8 -8
- package/authoring/debug/parse.mjs +3 -3
- 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 +792 -24
- package/authoring/doctypes/_schema.mjs +549 -39
- package/authoring/doctypes/base/graph-fields.doc.d.mts +9 -0
- package/authoring/doctypes/base/graph-fields.doc.mjs +64 -0
- package/authoring/doctypes/base/type.ts +43 -0
- package/authoring/doctypes/command/command.doc.mjs +4 -3
- package/authoring/doctypes/command/parse.d.mts +2 -2
- package/authoring/doctypes/command/parse.mjs +1 -1
- package/authoring/doctypes/command/type.ts +5 -4
- package/authoring/doctypes/component/component.doc.mjs +12 -3
- package/authoring/doctypes/component/parse.d.mts +2 -2
- package/authoring/doctypes/component/parse.mjs +1 -1
- package/authoring/doctypes/component/type.ts +14 -5
- package/authoring/doctypes/doctypes-new.test.mjs +48 -6
- package/authoring/doctypes/enum/enum.doc.mjs +1 -1
- package/authoring/doctypes/enum/parse.d.mts +2 -2
- package/authoring/doctypes/enum/parse.mjs +1 -1
- package/authoring/doctypes/enum/type.ts +4 -2
- package/authoring/doctypes/function/function.doc.mjs +7 -2
- package/authoring/doctypes/function/parse.d.mts +2 -2
- package/authoring/doctypes/function/parse.mjs +1 -1
- package/authoring/doctypes/function/type.ts +9 -4
- package/authoring/doctypes/hook/hook.doc.mjs +4 -0
- package/authoring/doctypes/hook/parse.d.mts +2 -2
- package/authoring/doctypes/hook/parse.mjs +1 -1
- package/authoring/doctypes/hook/type.ts +5 -4
- package/authoring/doctypes/legacy.d.mts +8 -6
- package/authoring/doctypes/legacy.mjs +5 -4
- package/authoring/doctypes/load-contract.test.mjs +233 -0
- package/authoring/doctypes/namespace/namespace.doc.d.mts +9 -0
- package/authoring/doctypes/namespace/namespace.doc.mjs +128 -0
- package/authoring/doctypes/namespace/parse.d.mts +12 -0
- package/authoring/doctypes/namespace/parse.mjs +25 -0
- package/authoring/doctypes/namespace/parse.test.mjs +163 -0
- package/authoring/doctypes/namespace/type.ts +74 -0
- package/authoring/doctypes/parse.d.mts +22 -18
- package/authoring/doctypes/parse.mjs +22 -11
- package/authoring/doctypes/parse.test.mjs +77 -3
- package/authoring/doctypes/reference/parse.d.mts +2 -2
- package/authoring/doctypes/reference/parse.mjs +8 -5
- package/authoring/doctypes/reference/reference.doc.mjs +55 -6
- package/authoring/doctypes/reference/type.ts +75 -7
- package/authoring/doctypes/schema/parse.d.mts +2 -2
- package/authoring/doctypes/schema/parse.mjs +1 -1
- package/authoring/doctypes/schema/schema.doc.mjs +2 -2
- package/authoring/doctypes/schema/type.ts +4 -4
- package/authoring/doctypes/template/parse.d.mts +94 -1
- package/authoring/doctypes/template/parse.mjs +40 -2
- package/authoring/doctypes/template/parse.test.mjs +26 -2
- package/authoring/doctypes/template/template.doc.mjs +13 -3
- package/authoring/doctypes/template/type.ts +13 -2
- package/authoring/doctypes/theme/parse.d.mts +35 -0
- package/authoring/doctypes/theme/parse.mjs +76 -0
- package/authoring/doctypes/theme/theme.doc.d.mts +9 -0
- package/authoring/doctypes/theme/theme.doc.mjs +79 -0
- package/authoring/doctypes/theme/type.ts +42 -0
- package/authoring/doctypes/types.ts +12 -10
- package/authoring/gap-report/gap-report.doc.d.mts +12 -0
- package/authoring/gap-report/gap-report.doc.mjs +183 -0
- package/authoring/gap-report/parse.d.mts +10 -10
- package/authoring/gap-report/parse.mjs +6 -6
- package/authoring/gap-report/type.ts +1 -1
- package/authoring/identity/identity.doc.d.mts +9 -0
- package/authoring/identity/identity.doc.mjs +61 -0
- package/authoring/identity/type.ts +132 -0
- package/authoring/index.d.mts +3 -0
- package/authoring/index.d.ts +62 -17
- package/authoring/index.mjs +4 -1
- package/authoring/integration/integration.doc.mjs +22 -13
- package/authoring/integration/parse.d.mts +2 -2
- package/authoring/integration/parse.mjs +1 -1
- package/authoring/integration/parse.test.mjs +10 -1
- package/authoring/integration/schema.d.mts +6 -4
- package/authoring/integration/schema.mjs +9 -3
- package/authoring/integration/type.ts +19 -8
- package/authoring/shadcn/receipt.d.mts +6 -6
- package/clients/cli/__tests__/cliManifest.test.ts +27 -29
- package/clients/cli/command-load-failure.test.mjs +83 -0
- package/clients/cli/commands/blog.doc.mjs +1 -1
- package/clients/cli/commands/blog.mjs +23 -8
- package/clients/cli/commands/blog.test.mjs +42 -1
- package/clients/cli/commands/build-theme.adaptations.test.mjs +100 -1
- package/clients/cli/commands/build-theme.ascii-output.test.mjs +161 -0
- package/clients/cli/commands/build-theme.flag-docs.test.mjs +110 -0
- package/clients/cli/commands/build-theme.mjs +16 -50
- package/clients/cli/commands/build-theme.path-safety.test.mjs +86 -1
- package/clients/cli/commands/build-theme.variants.test.mjs +76 -0
- package/clients/cli/commands/build.doc.mjs +16 -8
- package/clients/cli/commands/build.exit-codes-doc.test.mjs +50 -0
- package/clients/cli/commands/build.mjs +137 -114
- package/clients/cli/commands/build.playbook.test.mjs +75 -0
- package/clients/cli/commands/build.text-fields.test.mjs +81 -0
- package/clients/cli/commands/component/index.mjs +153 -61
- package/clients/cli/commands/component-batch.test.mjs +341 -0
- package/clients/cli/commands/component-ownership.test.mjs +92 -3
- package/clients/cli/commands/component-package.test.mjs +46 -0
- package/clients/cli/commands/component-resolution.test.mjs +21 -0
- package/clients/cli/commands/component.doc.mjs +28 -10
- package/clients/cli/commands/component.test.mjs +19 -0
- package/clients/cli/commands/detail-levels.test.mjs +2 -2
- package/clients/cli/commands/discover.broken-integration.test.mjs +52 -5
- package/clients/cli/commands/discover.components-flag.test.mjs +97 -0
- package/clients/cli/commands/discover.doc.mjs +55 -9
- package/clients/cli/commands/discover.mjs +393 -118
- package/clients/cli/commands/discover.sources.test.mjs +267 -0
- package/clients/cli/commands/discover.text-projection.test.mjs +103 -0
- package/clients/cli/commands/docs.doc.mjs +28 -6
- package/clients/cli/commands/docs.mjs +295 -38
- package/clients/cli/commands/doctor-integration-components.doc.mjs +1 -1
- package/clients/cli/commands/doctor-integration-docs.doc.mjs +6 -5
- package/clients/cli/commands/doctor-integration-templates.doc.mjs +15 -8
- package/clients/cli/commands/doctor-integration-validate.doc.mjs +1 -1
- package/clients/cli/commands/doctor-integration.doc.mjs +1 -1
- package/clients/cli/commands/doctor-integration.package-json.test.mjs +53 -0
- package/clients/cli/commands/doctor-integration.test.mjs +143 -8
- package/clients/cli/commands/doctor.doc.mjs +4 -2
- package/clients/cli/commands/doctor.mjs +108 -37
- package/clients/cli/commands/doctor.test.mjs +42 -0
- package/clients/cli/commands/gap-report.doc.mjs +27 -15
- package/clients/cli/commands/gap-report.test.mjs +72 -0
- package/clients/cli/commands/hook/index.mjs +7 -17
- package/clients/cli/commands/hook.doc.mjs +1 -1
- package/clients/cli/commands/hook.text-projection.test.mjs +45 -0
- package/clients/cli/commands/init.doc.mjs +24 -10
- package/clients/cli/commands/init.flag-help.test.mjs +153 -0
- package/clients/cli/commands/integration-add.controls.test.mjs +132 -0
- package/clients/cli/commands/integration-add.doc.mjs +39 -13
- package/clients/cli/commands/integration-authoring.test.mjs +74 -19
- package/clients/cli/commands/integration-pack.doc.mjs +6 -10
- package/clients/cli/commands/integration-real-world.test.mjs +4 -10
- package/clients/cli/commands/integration-verify.doc.mjs +22 -0
- package/clients/cli/commands/integration.doc.mjs +5 -5
- package/clients/cli/commands/integration.mjs +75 -43
- package/clients/cli/commands/interactive-guard.test.mjs +101 -24
- package/clients/cli/commands/json-contract.test.mjs +33 -0
- package/clients/cli/commands/layout-check.doc.mjs +15 -4
- package/clients/cli/commands/layout-expand.doc.mjs +22 -5
- package/clients/cli/commands/layout-grammar.doc.mjs +1 -1
- package/clients/cli/commands/layout.doc.mjs +3 -3
- package/clients/cli/commands/layout.mjs +21 -9
- package/clients/cli/commands/layout.path-help.test.mjs +33 -0
- package/clients/cli/commands/layout.stdin-cap.test.mjs +47 -0
- package/clients/cli/commands/layout.text-fields.test.mjs +39 -0
- package/clients/cli/commands/manifest.doc.mjs +2 -2
- package/clients/cli/commands/no-prompt-wording.test.mjs +94 -0
- package/clients/cli/commands/search.doc.mjs +16 -6
- package/clients/cli/commands/search.mjs +49 -11
- package/clients/cli/commands/search.test.mjs +92 -0
- package/clients/cli/commands/setup-nudge.test.mjs +6 -0
- package/clients/cli/commands/swizzle.doc.mjs +4 -3
- package/clients/cli/commands/swizzle.path-safety.test.mjs +42 -0
- package/clients/cli/commands/template.doc.mjs +53 -14
- package/clients/cli/commands/template.flag-help.test.mjs +117 -0
- package/clients/cli/commands/template.mjs +4 -91
- package/clients/cli/commands/template.path-help.test.mjs +40 -0
- package/clients/cli/commands/text-json-parity.test.mjs +725 -0
- package/clients/cli/commands/theme-add.doc.mjs +5 -4
- package/clients/cli/commands/theme-build.doc.mjs +8 -7
- package/clients/cli/commands/theme-list.doc.mjs +2 -2
- package/clients/cli/commands/theme-palette-generate.doc.mjs +12 -7
- package/clients/cli/commands/theme-palette-generate.test.mjs +19 -0
- package/clients/cli/commands/theme-palette.doc.mjs +2 -3
- package/clients/cli/commands/theme-targets.behavior.test.mjs +4 -3
- package/clients/cli/commands/theme-targets.doc.mjs +3 -3
- package/clients/cli/commands/theme-template.behavior.test.mjs +12 -0
- package/clients/cli/commands/theme-template.doc.mjs +2 -2
- package/clients/cli/commands/theme.doc.mjs +3 -2
- package/clients/cli/commands/upgrade.ascii-output.test.mjs +87 -0
- package/clients/cli/commands/upgrade.doc.mjs +83 -12
- package/clients/cli/commands/upgrade.file-protection.test.mjs +228 -0
- package/clients/cli/commands/upgrade.flag-help.test.mjs +188 -0
- package/clients/cli/commands/upgrade.hook-output.test.mjs +88 -0
- package/clients/cli/commands/upgrade.mjs +29 -7
- package/clients/cli/formatters/index.mjs +164 -1
- package/clients/cli/formatters/index.test.mjs +97 -0
- package/clients/cli/index.mjs +47 -34
- package/clients/cli/latest-version-env.test.mjs +50 -0
- package/clients/cli/lib/cli-error.test.mjs +7 -0
- package/clients/cli/lib/component-format.mjs +9 -9
- package/clients/cli/lib/component-format.test.mjs +1 -1
- package/clients/cli/lib/define-command.mjs +56 -6
- package/clients/cli/lib/define-command.test.mjs +54 -0
- package/clients/cli/lib/doc-text-ascii.test.mjs +82 -0
- package/clients/cli/lib/exit-codes.test.mjs +113 -0
- package/clients/cli/lib/hook-format.mjs +19 -10
- package/clients/cli/lib/json-shim.mjs +62 -16
- package/clients/cli/lib/json-shim.test.mjs +83 -0
- package/clients/cli/lib/manifest.d.ts +2 -0
- package/clients/cli/lib/manifest.mjs +53 -6
- package/clients/cli/lib/manifest.test.mjs +22 -2
- package/clients/cli/lib/parse-error-format.test.mjs +81 -0
- package/foundation/agent-docs/agent-docs.d.mts +7 -2
- package/foundation/agent-docs/agent-docs.mjs +83 -13
- package/foundation/agent-docs/agent-docs.path-safety.test.mjs +266 -4
- package/foundation/config/integration-debug.test.mjs +28 -3
- package/foundation/config/project-themes.test.mjs +11 -19
- package/foundation/config/project.d.mts +20 -11
- package/foundation/config/project.mjs +263 -91
- package/foundation/config/project.test.mjs +270 -21
- package/foundation/discovery/authoring-self-docs.d.mts +87 -0
- package/foundation/discovery/authoring-self-docs.mjs +237 -0
- package/foundation/discovery/authoring-self-docs.test.mjs +174 -0
- package/foundation/discovery/authoring-surface.d.mts +74 -0
- package/foundation/discovery/authoring-surface.mjs +525 -0
- package/foundation/discovery/authoring-surface.test.mjs +392 -0
- package/foundation/discovery/cli-self-docs.d.mts +119 -0
- package/foundation/discovery/cli-self-docs.mjs +504 -0
- package/foundation/discovery/cli-self-docs.test.mjs +395 -0
- package/foundation/discovery/component-discovery.d.mts +39 -1
- package/foundation/discovery/component-discovery.mjs +50 -1
- package/foundation/discovery/component-loader.d.mts +35 -38
- package/foundation/discovery/component-loader.mjs +53 -222
- package/foundation/discovery/docs-discovery.d.mts +119 -11
- package/foundation/discovery/docs-discovery.mjs +427 -108
- package/foundation/discovery/docs-discovery.test.mjs +386 -20
- package/foundation/discovery/docs-output-budget.d.mts +28 -0
- package/foundation/discovery/docs-output-budget.mjs +50 -0
- package/foundation/discovery/docs-section-key.d.mts +116 -0
- package/foundation/discovery/docs-section-key.mjs +322 -0
- package/foundation/discovery/docs-section-key.test.mjs +246 -0
- package/foundation/discovery/template-adapter.d.mts +113 -11
- package/foundation/discovery/template-adapter.fixture-refs.test.mjs +248 -0
- package/foundation/discovery/template-adapter.integration-isolation.test.mjs +94 -0
- package/foundation/discovery/template-adapter.mjs +774 -83
- package/foundation/discovery/template-adapter.test.mjs +57 -0
- package/foundation/discovery/template-conflict-release.d.mts +13 -0
- package/foundation/discovery/template-conflict-release.mjs +40 -0
- package/foundation/discovery/template-conflict-release.test.mjs +40 -0
- package/foundation/discovery/theme-discovery.d.mts +67 -7
- package/foundation/discovery/theme-discovery.mjs +916 -186
- package/foundation/discovery/theme-discovery.test.mjs +613 -219
- package/foundation/discovery/theming-targets.test.mjs +4 -0
- package/foundation/doc-compiler/bundle.d.mts +47 -0
- package/foundation/doc-compiler/bundle.mjs +278 -0
- package/foundation/doc-compiler/bundle.test.mjs +266 -0
- package/foundation/doc-compiler/compile.d.mts +343 -0
- package/foundation/doc-compiler/compile.mjs +558 -0
- package/foundation/doc-compiler/diagnostics.d.mts +126 -0
- package/foundation/doc-compiler/diagnostics.mjs +305 -0
- package/foundation/doc-compiler/doc-compiler.test.mjs +714 -0
- package/foundation/doc-compiler/doc-loads.test.mjs +1643 -0
- package/foundation/doc-compiler/import.d.mts +24 -0
- package/foundation/doc-compiler/import.mjs +59 -0
- package/foundation/doc-compiler/inputs.d.mts +102 -0
- package/foundation/doc-compiler/inputs.mjs +291 -0
- package/foundation/doc-compiler/inputs.test.mjs +298 -0
- package/foundation/doc-compiler/ir.d.mts +22 -0
- package/foundation/doc-compiler/ir.mjs +471 -0
- package/foundation/doc-compiler/lenses.d.mts +36 -0
- package/foundation/doc-compiler/lenses.mjs +173 -0
- package/foundation/doc-compiler/links.d.mts +162 -0
- package/foundation/doc-compiler/links.mjs +294 -0
- package/foundation/doc-compiler/links.test.mjs +192 -0
- package/foundation/doc-compiler/lower-doc.test.mjs +495 -0
- package/foundation/doc-compiler/overlays.d.mts +37 -0
- package/foundation/doc-compiler/overlays.mjs +206 -0
- package/foundation/doc-compiler/parse-readable.d.mts +9 -0
- package/foundation/doc-compiler/parse-readable.mjs +29 -0
- package/foundation/doc-compiler/read.d.mts +127 -0
- package/foundation/doc-compiler/read.mjs +325 -0
- package/foundation/doc-compiler/read.test.mjs +313 -0
- package/foundation/doc-compiler/source.d.mts +33 -0
- package/foundation/doc-compiler/source.mjs +128 -0
- package/foundation/doc-compiler/tree.d.mts +292 -0
- package/foundation/doc-compiler/tree.mjs +881 -0
- package/foundation/fs/file-protection.d.mts +33 -0
- package/foundation/fs/file-protection.mjs +825 -0
- package/foundation/fs/file-protection.test.mjs +250 -0
- package/foundation/fs/module-loader.d.mts +1 -0
- package/foundation/fs/module-loader.mjs +50 -1
- package/foundation/fs/module-loader.stdout.test.mjs +332 -0
- package/foundation/fs/path-safety.d.mts +3 -2
- package/foundation/fs/path-safety.mjs +49 -19
- package/foundation/fs/path-safety.test.mjs +50 -0
- package/foundation/fs/publish-file-hardlink-unavailable.test.mjs +129 -94
- package/foundation/identity/provider-identity.d.mts +90 -0
- package/foundation/identity/provider-identity.mjs +320 -0
- package/foundation/identity/provider-identity.test.mjs +254 -0
- package/foundation/identity/providers.d.mts +7 -0
- package/foundation/identity/providers.mjs +16 -0
- package/foundation/integrations/autolink.d.mts +58 -1
- package/foundation/integrations/autolink.mjs +143 -45
- package/foundation/integrations/autolink.test.mjs +1 -1
- package/foundation/integrations/cli-requirement.d.mts +65 -0
- package/foundation/integrations/cli-requirement.mjs +189 -0
- package/foundation/integrations/cli-requirement.test.mjs +89 -0
- package/foundation/integrations/contribution-fixes.d.mts +145 -0
- package/foundation/integrations/contribution-fixes.mjs +1284 -0
- package/foundation/integrations/contribution-inventory.d.mts +3 -2
- package/foundation/integrations/contribution-inventory.mjs +28 -25
- package/foundation/integrations/contribution-inventory.test.mjs +86 -27
- package/foundation/integrations/integration-warnings.d.mts +9 -2
- package/foundation/integrations/integration-warnings.mjs +52 -21
- package/foundation/integrations/integration-warnings.test.mjs +74 -1
- package/foundation/integrations/integrations.d.mts +63 -3
- package/foundation/integrations/integrations.mjs +122 -9
- package/foundation/integrations/integrations.test.mjs +415 -1
- package/foundation/integrations/provider-conflicts.test.mjs +125 -0
- package/foundation/integrations/provider-ledger.test.mjs +275 -0
- package/foundation/integrations/provider-resolution.d.mts +152 -0
- package/foundation/integrations/provider-resolution.mjs +576 -0
- package/foundation/integrations/provider-resolution.test.mjs +369 -0
- package/foundation/integrations/theme-descriptor.d.mts +8 -0
- package/foundation/integrations/theme-descriptor.mjs +44 -0
- package/foundation/integrations/validate-contributions.d.mts +2 -0
- package/foundation/integrations/validate-contributions.mjs +131 -29
- package/foundation/response/base.d.ts +8 -4
- package/foundation/response/batch.type.d.mts +33 -0
- package/foundation/response/batch.type.mjs +34 -0
- package/foundation/response/error-codes.d.mts +3 -1
- package/foundation/response/error-codes.d.ts +2 -0
- package/foundation/response/error-codes.doc.mjs +19 -12
- package/foundation/response/error-codes.mjs +8 -2
- package/foundation/response/error-codes.test.mjs +166 -14
- package/foundation/response/json-contract.test.mjs +57 -17
- package/foundation/response/json.d.mts +4 -2
- package/foundation/response/json.mjs +8 -10
- package/foundation/response/response-types.doc.d.mts +7 -2
- package/foundation/response/response-types.doc.mjs +69 -25
- package/foundation/response/response-types.doc.test.mjs +181 -0
- package/foundation/response/response.doc.mjs +12 -11
- package/foundation/text/string-utils.d.mts +8 -0
- package/foundation/text/string-utils.mjs +40 -10
- package/foundation/xle/expand.d.mts +2 -0
- package/foundation/xle/expand.mjs +4 -3
- package/foundation/xle/expand.test.mjs +54 -0
- package/foundation/xle/xle.test.mjs +13 -0
- package/package.json +10 -11
- package/api/docs/docs.test.mjs +0 -83
- package/api/docs/integrationDocs.test.mjs +0 -208
- package/api/search/search.test.mjs +0 -389
- package/assets/docs/cli-integrations.doc.mjs +0 -367
- package/assets/templates/themes/manifest.json +0 -95
- package/clients/cli/commands/docs.test.mjs +0 -102
- package/clients/cli/lib/update-check.mjs +0 -83
- package/clients/cli/lib/update-check.test.mjs +0 -137
- package/clients/cli/update-hint-commands.test.mjs +0 -54
- package/foundation/agent-docs/agent-docs.test.mjs +0 -1141
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* @file Theme
|
|
4
|
+
* @file Theme descriptor discovery shared by Project, theme list/add, and
|
|
5
5
|
* integration validation.
|
|
6
6
|
*
|
|
7
|
-
* A theme root
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
7
|
+
* A theme root contains one directory per lower-kebab slug. Each directory has
|
|
8
|
+
* a theme source and mandatory same-stem `.doc.mjs`; the directory is the
|
|
9
|
+
* complete copy and pack boundary. Descriptor metadata is parsed without
|
|
10
|
+
* executing theme source. A dot-folder, or a folder holding neither a
|
|
11
|
+
* descriptor nor a `<name>Theme` source, is not a theme. Dot entries and files
|
|
12
|
+
* npm never publishes belong to no theme.
|
|
11
13
|
*
|
|
12
14
|
* @input a bundled or integration-owned theme root
|
|
13
15
|
* @output validated source-theme records with package ownership
|
|
@@ -15,14 +17,42 @@
|
|
|
15
17
|
*/
|
|
16
18
|
|
|
17
19
|
import * as fs from 'node:fs';
|
|
20
|
+
import {createRequire} from 'node:module';
|
|
18
21
|
import * as path from 'node:path';
|
|
22
|
+
import {lowerDoc} from '../doc-compiler/compile.mjs';
|
|
23
|
+
import {packageSource} from '../doc-compiler/source.mjs';
|
|
19
24
|
import {CLI_ROOT} from '../fs/paths.mjs';
|
|
20
25
|
import {assertWithin, PathSafetyError} from '../fs/path-safety.mjs';
|
|
21
26
|
|
|
22
27
|
export const BUNDLED_THEME_PACKAGE = '@astryxdesign/cli';
|
|
23
28
|
export const THEMES_DIR = path.join(CLI_ROOT, 'assets', 'templates', 'themes');
|
|
24
|
-
export const
|
|
25
|
-
|
|
29
|
+
export const THEME_DOC_SUFFIX = '.doc.mjs';
|
|
30
|
+
|
|
31
|
+
const require = createRequire(import.meta.url);
|
|
32
|
+
|
|
33
|
+
/** @type {typeof import('@babel/parser') | undefined} */
|
|
34
|
+
let babelParser;
|
|
35
|
+
/** @type {any} */
|
|
36
|
+
let jscodeshiftApi;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The descriptor parser, loaded when the first descriptor is read.
|
|
40
|
+
* @returns {typeof import('@babel/parser')}
|
|
41
|
+
*/
|
|
42
|
+
function descriptorParser() {
|
|
43
|
+
babelParser ??= require('@babel/parser');
|
|
44
|
+
return /** @type {typeof import('@babel/parser')} */ (babelParser);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* The theme source parser, loaded when the first integration theme source is
|
|
49
|
+
* checked. Bundled themes never need it.
|
|
50
|
+
* @returns {any}
|
|
51
|
+
*/
|
|
52
|
+
function sourceParser() {
|
|
53
|
+
jscodeshiftApi ??= require('jscodeshift');
|
|
54
|
+
return jscodeshiftApi;
|
|
55
|
+
}
|
|
26
56
|
|
|
27
57
|
/**
|
|
28
58
|
* @typedef {object} DiscoveredTheme
|
|
@@ -32,22 +62,14 @@ export const MANIFEST_PATH = path.join(THEMES_DIR, THEME_MANIFEST_BASENAME);
|
|
|
32
62
|
* @property {boolean} maintained
|
|
33
63
|
* @property {string} entry
|
|
34
64
|
* @property {string} exportName
|
|
35
|
-
* @property {string[]} files
|
|
65
|
+
* @property {string[]} files what `theme add` copies, relative to sourceDir,
|
|
66
|
+
* entry first
|
|
36
67
|
* @property {string} package
|
|
37
68
|
* @property {string} sourceDir absolute directory holding this theme's files
|
|
38
69
|
* @property {boolean} bundled
|
|
70
|
+
* @property {string} docPath absolute descriptor path
|
|
39
71
|
*/
|
|
40
72
|
|
|
41
|
-
/** @param {unknown} value @param {string} field @param {string} owner */
|
|
42
|
-
function requiredString(value, field, owner) {
|
|
43
|
-
if (typeof value !== 'string' || value.trim().length === 0) {
|
|
44
|
-
throw new Error(
|
|
45
|
-
`Theme catalog for ${owner} has an invalid ${field}; expected a non-empty string.`,
|
|
46
|
-
);
|
|
47
|
-
}
|
|
48
|
-
return value;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
73
|
/**
|
|
52
74
|
* Resolve one authored relative path without allowing POSIX or Windows escape
|
|
53
75
|
* syntax, even when discovery runs on the other platform.
|
|
@@ -75,26 +97,45 @@ function resolveThemePath(value, root, label) {
|
|
|
75
97
|
}
|
|
76
98
|
}
|
|
77
99
|
|
|
78
|
-
const THEME_MODULE_EXTENSIONS = [
|
|
100
|
+
export const THEME_MODULE_EXTENSIONS = [
|
|
101
|
+
'.mjs',
|
|
102
|
+
'.js',
|
|
103
|
+
'.mts',
|
|
104
|
+
'.ts',
|
|
105
|
+
'.tsx',
|
|
106
|
+
'.jsx',
|
|
107
|
+
];
|
|
108
|
+
|
|
109
|
+
/** A theme directory's name: lower-kebab, starting with a letter. */
|
|
110
|
+
export const THEME_SLUG_RE = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/u;
|
|
79
111
|
|
|
80
112
|
/**
|
|
81
|
-
* Resolve a local theme module only when the target is a listed file confined
|
|
82
|
-
* to the theme directory.
|
|
83
113
|
* @param {unknown} specifier
|
|
84
114
|
* @param {string} fromFile
|
|
85
|
-
* @
|
|
86
|
-
* @param {Set<string>} allowedFiles
|
|
115
|
+
* @returns {string[]}
|
|
87
116
|
*/
|
|
88
|
-
function
|
|
89
|
-
if (typeof specifier !== 'string' || !specifier.startsWith('.')) return
|
|
117
|
+
function localThemeModuleCandidates(specifier, fromFile) {
|
|
118
|
+
if (typeof specifier !== 'string' || !specifier.startsWith('.')) return [];
|
|
90
119
|
const base = path.resolve(path.dirname(fromFile), specifier);
|
|
91
|
-
|
|
120
|
+
return [
|
|
92
121
|
base,
|
|
93
122
|
...THEME_MODULE_EXTENSIONS.map(extension => `${base}${extension}`),
|
|
94
123
|
...THEME_MODULE_EXTENSIONS.map(extension =>
|
|
95
124
|
path.join(base, `index${extension}`),
|
|
96
125
|
),
|
|
97
126
|
];
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Resolve a local theme module only when the target is a listed file confined
|
|
131
|
+
* to the theme directory.
|
|
132
|
+
* @param {unknown} specifier
|
|
133
|
+
* @param {string} fromFile
|
|
134
|
+
* @param {string} themeDir
|
|
135
|
+
* @param {Set<string>} allowedFiles
|
|
136
|
+
*/
|
|
137
|
+
function resolveLocalThemeModule(specifier, fromFile, themeDir, allowedFiles) {
|
|
138
|
+
const candidates = localThemeModuleCandidates(specifier, fromFile);
|
|
98
139
|
for (const candidate of candidates) {
|
|
99
140
|
try {
|
|
100
141
|
if (!fs.statSync(candidate).isFile()) continue;
|
|
@@ -111,30 +152,16 @@ function resolveLocalThemeModule(specifier, fromFile, themeDir, allowedFiles) {
|
|
|
111
152
|
}
|
|
112
153
|
|
|
113
154
|
class ThemeModuleReferenceError extends Error {}
|
|
155
|
+
class ThemeRuntimeExportError extends Error {}
|
|
114
156
|
|
|
115
157
|
/**
|
|
116
|
-
*
|
|
158
|
+
* Every string specifier a module imports, re-exports, or imports dynamically.
|
|
117
159
|
* @param {string} file
|
|
118
160
|
* @param {any} jscodeshift
|
|
119
|
-
* @
|
|
120
|
-
* @param {Set<string>} allowedFiles
|
|
121
|
-
* @param {string} owner
|
|
122
|
-
* @param {string} entry
|
|
123
|
-
* @param {Set<string>} [seen]
|
|
161
|
+
* @returns {string[]}
|
|
124
162
|
*/
|
|
125
|
-
function
|
|
126
|
-
file
|
|
127
|
-
jscodeshift,
|
|
128
|
-
themeDir,
|
|
129
|
-
allowedFiles,
|
|
130
|
-
owner,
|
|
131
|
-
entry,
|
|
132
|
-
seen = new Set(),
|
|
133
|
-
) {
|
|
134
|
-
if (seen.has(file)) return;
|
|
135
|
-
seen.add(file);
|
|
136
|
-
|
|
137
|
-
const parser = /\.(?:ts|tsx|mts)$/u.test(file) ? 'tsx' : 'babel';
|
|
163
|
+
function moduleSpecifiers(file, jscodeshift) {
|
|
164
|
+
const parser = /\.(?:ts|tsx|mts|cts)$/u.test(file) ? 'tsx' : 'babel';
|
|
138
165
|
const j = jscodeshift.withParser(parser);
|
|
139
166
|
const root = j(fs.readFileSync(file, 'utf-8'));
|
|
140
167
|
/** @type {string[]} */
|
|
@@ -170,7 +197,34 @@ function validateThemeModuleGraph(
|
|
|
170
197
|
}
|
|
171
198
|
});
|
|
172
199
|
|
|
173
|
-
|
|
200
|
+
return specifiers;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Validate that every local static dependency is copied with the theme.
|
|
205
|
+
* @param {string} file
|
|
206
|
+
* @param {any} jscodeshift
|
|
207
|
+
* @param {string} themeDir
|
|
208
|
+
* @param {Set<string>} allowedFiles
|
|
209
|
+
* @param {string} owner
|
|
210
|
+
* @param {string} entry
|
|
211
|
+
* @param {string} descriptorPath
|
|
212
|
+
* @param {Set<string>} [seen]
|
|
213
|
+
*/
|
|
214
|
+
function validateThemeModuleGraph(
|
|
215
|
+
file,
|
|
216
|
+
jscodeshift,
|
|
217
|
+
themeDir,
|
|
218
|
+
allowedFiles,
|
|
219
|
+
owner,
|
|
220
|
+
entry,
|
|
221
|
+
descriptorPath,
|
|
222
|
+
seen = new Set(),
|
|
223
|
+
) {
|
|
224
|
+
if (seen.has(file)) return;
|
|
225
|
+
seen.add(file);
|
|
226
|
+
|
|
227
|
+
for (const specifier of moduleSpecifiers(file, jscodeshift)) {
|
|
174
228
|
if (!specifier.startsWith('.')) continue;
|
|
175
229
|
const target = resolveLocalThemeModule(
|
|
176
230
|
specifier,
|
|
@@ -179,8 +233,19 @@ function validateThemeModuleGraph(
|
|
|
179
233
|
allowedFiles,
|
|
180
234
|
);
|
|
181
235
|
if (!target) {
|
|
236
|
+
const referencesDescriptor = localThemeModuleCandidates(
|
|
237
|
+
specifier,
|
|
238
|
+
file,
|
|
239
|
+
).some(
|
|
240
|
+
candidate => path.resolve(candidate) === path.resolve(descriptorPath),
|
|
241
|
+
);
|
|
242
|
+
if (referencesDescriptor) {
|
|
243
|
+
throw new ThemeModuleReferenceError(
|
|
244
|
+
`Theme entry "${entry}" from ${owner} must not import its descriptor "${path.basename(descriptorPath)}". Theme descriptors are authoring metadata, not runtime modules.`,
|
|
245
|
+
);
|
|
246
|
+
}
|
|
182
247
|
throw new ThemeModuleReferenceError(
|
|
183
|
-
`Theme
|
|
248
|
+
`Theme entry "${entry}" from ${owner} references local module "${specifier}" that must resolve to a file inside the theme directory.`,
|
|
184
249
|
);
|
|
185
250
|
}
|
|
186
251
|
if (THEME_MODULE_EXTENSIONS.includes(path.extname(target))) {
|
|
@@ -191,6 +256,7 @@ function validateThemeModuleGraph(
|
|
|
191
256
|
allowedFiles,
|
|
192
257
|
owner,
|
|
193
258
|
entry,
|
|
259
|
+
descriptorPath,
|
|
194
260
|
seen,
|
|
195
261
|
);
|
|
196
262
|
}
|
|
@@ -428,223 +494,887 @@ function moduleExportsName(
|
|
|
428
494
|
}
|
|
429
495
|
|
|
430
496
|
/**
|
|
431
|
-
*
|
|
432
|
-
* @param {
|
|
433
|
-
* @
|
|
434
|
-
* @param {{bundled?: boolean}} [options]
|
|
435
|
-
* @returns {DiscoveredTheme[]}
|
|
497
|
+
* An expression without the parentheses around it.
|
|
498
|
+
* @param {any} node
|
|
499
|
+
* @returns {any}
|
|
436
500
|
*/
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
);
|
|
501
|
+
function unparenthesized(node) {
|
|
502
|
+
let current = node;
|
|
503
|
+
while (current?.type === 'ParenthesizedExpression') {
|
|
504
|
+
current = current.expression;
|
|
442
505
|
}
|
|
506
|
+
return current;
|
|
507
|
+
}
|
|
443
508
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
509
|
+
/**
|
|
510
|
+
* Convert one static literal used by ThemeDoc. Theme descriptors intentionally
|
|
511
|
+
* contain data only so synchronous bundled-theme APIs stay synchronous.
|
|
512
|
+
* @param {any} node
|
|
513
|
+
* @param {string} label
|
|
514
|
+
* @returns {string | boolean}
|
|
515
|
+
*/
|
|
516
|
+
function staticThemeValue(node, label) {
|
|
517
|
+
if (node?.type === 'StringLiteral' || node?.type === 'BooleanLiteral') {
|
|
518
|
+
return node.value;
|
|
519
|
+
}
|
|
520
|
+
throw new Error(
|
|
521
|
+
`${label} must use static string and boolean values in its default export.`,
|
|
448
522
|
);
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
/**
|
|
526
|
+
* How messages about a theme descriptor name it.
|
|
527
|
+
* @param {string} docPath
|
|
528
|
+
* @param {string} owner
|
|
529
|
+
*/
|
|
530
|
+
export function themeDescriptorLabel(docPath, owner) {
|
|
531
|
+
return `Theme descriptor ${path.basename(docPath)} for ${owner}`;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
/**
|
|
535
|
+
* Read one strongly typed theme descriptor without executing it, and compile
|
|
536
|
+
* it.
|
|
537
|
+
* @param {string} docPath
|
|
538
|
+
* @param {string} owner
|
|
539
|
+
* @returns {import('../../authoring/doctypes/theme/type').ThemeDoc}
|
|
540
|
+
*/
|
|
541
|
+
function readThemeDoc(docPath, owner) {
|
|
542
|
+
const label = themeDescriptorLabel(docPath, owner);
|
|
543
|
+
const value = readThemeDescriptorValue(docPath, label);
|
|
544
|
+
// Read statically, never executed, then compiled like every other doc.
|
|
545
|
+
const {node, failed, failure} = lowerDoc({
|
|
546
|
+
id: `${owner}:themes:${path.basename(docPath)}`,
|
|
547
|
+
root: 'themes',
|
|
548
|
+
provider: owner,
|
|
549
|
+
source: packageSource(docPath),
|
|
550
|
+
lang: null,
|
|
551
|
+
file: {file: path.basename(docPath), doc: value},
|
|
552
|
+
label,
|
|
553
|
+
});
|
|
554
|
+
if (!node || failed) throw failure;
|
|
555
|
+
return node.doc;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
/**
|
|
559
|
+
* Descriptor reads in this process, by path and label, reused while the file
|
|
560
|
+
* keeps its size and mtime.
|
|
561
|
+
* @type {Map<string, {size: number, mtimeMs: number, read: {value: Record<string, string | boolean>} | {error: unknown}}>}
|
|
562
|
+
*/
|
|
563
|
+
const descriptorReads = new Map();
|
|
564
|
+
|
|
565
|
+
/**
|
|
566
|
+
* The static value a theme descriptor default-exports, read from its source
|
|
567
|
+
* without executing it. Throws when the file is not one static ThemeDoc
|
|
568
|
+
* object.
|
|
569
|
+
* @param {string} docPath
|
|
570
|
+
* @param {string} label
|
|
571
|
+
* @returns {Record<string, string | boolean>}
|
|
572
|
+
*/
|
|
573
|
+
export function readThemeDescriptorValue(docPath, label) {
|
|
574
|
+
const {size, mtimeMs} = fs.statSync(docPath);
|
|
575
|
+
const key = `${docPath}\0${label}`;
|
|
576
|
+
let cached = descriptorReads.get(key);
|
|
577
|
+
if (!cached || cached.size !== size || cached.mtimeMs !== mtimeMs) {
|
|
578
|
+
/** @type {{value: Record<string, string | boolean>} | {error: unknown}} */
|
|
579
|
+
let read;
|
|
580
|
+
try {
|
|
581
|
+
read = {
|
|
582
|
+
value: readDescriptorSource(fs.readFileSync(docPath, 'utf-8'), label),
|
|
583
|
+
};
|
|
584
|
+
} catch (error) {
|
|
585
|
+
read = {error};
|
|
586
|
+
}
|
|
587
|
+
cached = {size, mtimeMs, read};
|
|
588
|
+
descriptorReads.set(key, cached);
|
|
453
589
|
}
|
|
590
|
+
if ('error' in cached.read) throw cached.read.error;
|
|
591
|
+
return {...cached.read.value};
|
|
592
|
+
}
|
|
454
593
|
|
|
455
|
-
|
|
456
|
-
|
|
594
|
+
/**
|
|
595
|
+
* @param {string} source
|
|
596
|
+
* @param {string} label
|
|
597
|
+
* @returns {Record<string, string | boolean>}
|
|
598
|
+
*/
|
|
599
|
+
function readDescriptorSource(source, label) {
|
|
600
|
+
/** @type {any} */
|
|
601
|
+
let ast;
|
|
457
602
|
try {
|
|
458
|
-
|
|
603
|
+
ast = descriptorParser().parse(source, {
|
|
604
|
+
sourceType: 'module',
|
|
605
|
+
tokens: true,
|
|
606
|
+
createParenthesizedExpressions: true,
|
|
607
|
+
});
|
|
459
608
|
} catch (error) {
|
|
460
609
|
const message = error instanceof Error ? error.message : String(error);
|
|
461
|
-
throw new Error(
|
|
462
|
-
|
|
610
|
+
throw new Error(`${label} could not be parsed: ${message}`, {cause: error});
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
const statements = ast.program.body;
|
|
614
|
+
const defaults = statements.filter(
|
|
615
|
+
(/** @type {any} */ statement) =>
|
|
616
|
+
statement.type === 'ExportDefaultDeclaration',
|
|
617
|
+
);
|
|
618
|
+
const unsupported = statements.filter(
|
|
619
|
+
(/** @type {any} */ statement) =>
|
|
620
|
+
statement.type !== 'ExportDefaultDeclaration' &&
|
|
621
|
+
statement.type !== 'EmptyStatement',
|
|
622
|
+
);
|
|
623
|
+
if (unsupported.length > 0 || ast.program.directives.length > 0) {
|
|
624
|
+
throw new Error(
|
|
625
|
+
`${label} must contain only its static default-exported ThemeDoc object.`,
|
|
626
|
+
);
|
|
627
|
+
}
|
|
628
|
+
const object =
|
|
629
|
+
defaults.length === 1 && unparenthesized(defaults[0].declaration);
|
|
630
|
+
if (object?.type !== 'ObjectExpression') {
|
|
631
|
+
throw new Error(`${label} must default-export one static ThemeDoc object.`);
|
|
632
|
+
}
|
|
633
|
+
if (!declaresThemeDoc(ast, defaults[0])) {
|
|
634
|
+
throw new Error(
|
|
635
|
+
`${label} must declare its public ThemeDoc type from @astryxdesign/cli/authoring.`,
|
|
636
|
+
);
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
/** @type {Record<string, string | boolean>} */
|
|
640
|
+
const value = {};
|
|
641
|
+
for (const property of object.properties) {
|
|
642
|
+
if (property.type !== 'ObjectProperty' || property.computed) {
|
|
643
|
+
throw new Error(`${label} must contain only static object properties.`);
|
|
644
|
+
}
|
|
645
|
+
const key =
|
|
646
|
+
property.key.type === 'Identifier'
|
|
647
|
+
? property.key.name
|
|
648
|
+
: property.key.type === 'StringLiteral'
|
|
649
|
+
? property.key.value
|
|
650
|
+
: null;
|
|
651
|
+
if (key === null) {
|
|
652
|
+
throw new Error(`${label} has an invalid property name.`);
|
|
653
|
+
}
|
|
654
|
+
if (Object.hasOwn(value, key)) {
|
|
655
|
+
throw new Error(`${label} declares "${key}" more than once.`);
|
|
656
|
+
}
|
|
657
|
+
// Defined, not assigned, so `__proto__` stays a key the parser rejects.
|
|
658
|
+
Object.defineProperty(value, key, {
|
|
659
|
+
value: staticThemeValue(unparenthesized(property.value), label),
|
|
660
|
+
enumerable: true,
|
|
661
|
+
writable: true,
|
|
662
|
+
configurable: true,
|
|
463
663
|
});
|
|
464
664
|
}
|
|
665
|
+
return value;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
/** `import('@astryxdesign/cli/authoring').ThemeDoc`, without whitespace. */
|
|
669
|
+
const THEME_DOC_TYPE =
|
|
670
|
+
/^import\((['"])@astryxdesign\/cli\/authoring\1\)\.ThemeDoc$/u;
|
|
671
|
+
|
|
672
|
+
/** @param {any} comment */
|
|
673
|
+
function isJsdoc(comment) {
|
|
674
|
+
return comment.type === 'CommentBlock' && comment.value.startsWith('*');
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
/** @param {string} ch */
|
|
678
|
+
const isSpace = ch => ch === ' ' || ch === '\t';
|
|
679
|
+
|
|
680
|
+
/**
|
|
681
|
+
* The tags of one JSDoc comment, found the way TypeScript's JSDoc scanner
|
|
682
|
+
* finds them: an `@` starts a tag at the start of a line (after the margin
|
|
683
|
+
* `*`), or after a space and before a non-space, never inside a backtick span
|
|
684
|
+
* of a tag's text. Each tag's text runs to the next tag, margins removed.
|
|
685
|
+
* @param {string} value the comment's body, as Babel gives it
|
|
686
|
+
* @returns {{name: string, text: string}[]}
|
|
687
|
+
*/
|
|
688
|
+
function jsdocTags(value) {
|
|
689
|
+
const body = value.slice(1);
|
|
690
|
+
/** @type {number[]} */
|
|
691
|
+
const starts = [];
|
|
692
|
+
let lineStart = true;
|
|
693
|
+
let sawAsterisk = true;
|
|
694
|
+
let inTag = false;
|
|
695
|
+
let backticks = false;
|
|
696
|
+
for (let index = 0; index < body.length; index++) {
|
|
697
|
+
const ch = body[index];
|
|
698
|
+
if (ch === '\n' || ch === '\r') {
|
|
699
|
+
lineStart = true;
|
|
700
|
+
sawAsterisk = false;
|
|
701
|
+
backticks = false;
|
|
702
|
+
continue;
|
|
703
|
+
}
|
|
704
|
+
if (lineStart) {
|
|
705
|
+
if (isSpace(ch)) continue;
|
|
706
|
+
if (ch === '*' && !sawAsterisk) {
|
|
707
|
+
sawAsterisk = true;
|
|
708
|
+
continue;
|
|
709
|
+
}
|
|
710
|
+
lineStart = false;
|
|
711
|
+
if (ch === '@') {
|
|
712
|
+
starts.push(index);
|
|
713
|
+
inTag = true;
|
|
714
|
+
continue;
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
if (ch === '`' && inTag) {
|
|
718
|
+
backticks = !backticks;
|
|
719
|
+
} else if (
|
|
720
|
+
ch === '@' &&
|
|
721
|
+
!backticks &&
|
|
722
|
+
isSpace(body[index - 1] ?? '') &&
|
|
723
|
+
!/\s/u.test(body[index + 1] ?? ' ')
|
|
724
|
+
) {
|
|
725
|
+
starts.push(index);
|
|
726
|
+
inTag = true;
|
|
727
|
+
backticks = false;
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
return starts.map((start, position) => {
|
|
731
|
+
const raw = body.slice(start + 1, starts[position + 1] ?? body.length);
|
|
732
|
+
const name = /^[\w$]*/u.exec(raw)?.[0] ?? '';
|
|
733
|
+
const text = raw.slice(name.length).replace(/(\r?\n)[ \t]*\*?/gu, '$1');
|
|
734
|
+
return {name, text};
|
|
735
|
+
});
|
|
736
|
+
}
|
|
465
737
|
|
|
466
|
-
|
|
467
|
-
|
|
738
|
+
/**
|
|
739
|
+
* The braced type at the start of a tag's text, and what follows it.
|
|
740
|
+
* @param {string} text
|
|
741
|
+
* @returns {{type: string, rest: string} | null}
|
|
742
|
+
*/
|
|
743
|
+
function bracedType(text) {
|
|
744
|
+
const open = text.search(/\S/u);
|
|
745
|
+
if (open === -1 || text[open] !== '{') return null;
|
|
746
|
+
let depth = 0;
|
|
747
|
+
/** @type {string | null} */
|
|
748
|
+
let quote = null;
|
|
749
|
+
for (let index = open; index < text.length; index++) {
|
|
750
|
+
const ch = text[index];
|
|
751
|
+
if (quote) {
|
|
752
|
+
if (ch === quote) quote = null;
|
|
753
|
+
} else if (ch === "'" || ch === '"' || ch === '`') {
|
|
754
|
+
quote = ch;
|
|
755
|
+
} else if (ch === '{') {
|
|
756
|
+
depth++;
|
|
757
|
+
} else if (ch === '}' && --depth === 0) {
|
|
758
|
+
return {
|
|
759
|
+
type: text.slice(open + 1, index).replace(/\s+/gu, ''),
|
|
760
|
+
rest: text.slice(index + 1),
|
|
761
|
+
};
|
|
762
|
+
}
|
|
468
763
|
}
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
764
|
+
return null;
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
/**
|
|
768
|
+
* Split a type at top-level occurrences of one operator.
|
|
769
|
+
* @param {string} type
|
|
770
|
+
* @param {string} operator
|
|
771
|
+
*/
|
|
772
|
+
function splitType(type, operator) {
|
|
773
|
+
/** @type {string[]} */
|
|
774
|
+
const parts = [];
|
|
775
|
+
let depth = 0;
|
|
776
|
+
/** @type {string | null} */
|
|
777
|
+
let quote = null;
|
|
778
|
+
let last = 0;
|
|
779
|
+
for (let index = 0; index < type.length; index++) {
|
|
780
|
+
const ch = type[index];
|
|
781
|
+
if (quote) {
|
|
782
|
+
if (ch === quote) quote = null;
|
|
783
|
+
} else if (ch === "'" || ch === '"' || ch === '`') {
|
|
784
|
+
quote = ch;
|
|
785
|
+
} else if ('([{<'.includes(ch)) {
|
|
786
|
+
depth++;
|
|
787
|
+
} else if (')]}>'.includes(ch)) {
|
|
788
|
+
depth--;
|
|
789
|
+
} else if (ch === operator && depth === 0) {
|
|
790
|
+
parts.push(type.slice(last, index));
|
|
791
|
+
last = index + 1;
|
|
792
|
+
}
|
|
472
793
|
}
|
|
473
|
-
|
|
474
|
-
|
|
794
|
+
parts.push(type.slice(last));
|
|
795
|
+
return parts;
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
/** @param {string} type */
|
|
799
|
+
function wrappedInParens(type) {
|
|
800
|
+
if (!type.startsWith('(') || !type.endsWith(')')) return false;
|
|
801
|
+
let depth = 0;
|
|
802
|
+
for (let index = 0; index < type.length; index++) {
|
|
803
|
+
if (type[index] === '(') depth++;
|
|
804
|
+
else if (type[index] === ')' && --depth === 0) {
|
|
805
|
+
return index === type.length - 1;
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
return false;
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
/**
|
|
812
|
+
* Whether a JSDoc type, whitespace removed, checks an object literal exactly
|
|
813
|
+
* as ThemeDoc does: ThemeDoc or one of its aliases, optionally parenthesized,
|
|
814
|
+
* marked `!`, `?` or `=`, joined with `null` or `undefined`, or intersected
|
|
815
|
+
* with `{}`.
|
|
816
|
+
* @param {string} type
|
|
817
|
+
* @param {Map<string, string>} aliases alias name to the type it names
|
|
818
|
+
* @param {Set<string>} [seen]
|
|
819
|
+
* @returns {boolean}
|
|
820
|
+
*/
|
|
821
|
+
function isThemeDocType(type, aliases, seen = new Set()) {
|
|
822
|
+
let current = type;
|
|
823
|
+
for (;;) {
|
|
824
|
+
if (/^[!?]/u.test(current)) current = current.slice(1);
|
|
825
|
+
else if (current.endsWith('=')) current = current.slice(0, -1);
|
|
826
|
+
else if (wrappedInParens(current)) current = current.slice(1, -1);
|
|
827
|
+
else break;
|
|
828
|
+
}
|
|
829
|
+
const members = splitType(current, '|').filter(
|
|
830
|
+
member => member !== 'null' && member !== 'undefined',
|
|
831
|
+
);
|
|
832
|
+
if (members.length !== 1) return false;
|
|
833
|
+
if (members[0] !== current) return isThemeDocType(members[0], aliases, seen);
|
|
834
|
+
const parts = splitType(current, '&').filter(part => part !== '{}');
|
|
835
|
+
if (parts.length !== 1) return false;
|
|
836
|
+
if (parts[0] !== current) return isThemeDocType(parts[0], aliases, seen);
|
|
837
|
+
if (THEME_DOC_TYPE.test(current)) return true;
|
|
838
|
+
const named = aliases.get(current);
|
|
839
|
+
if (named === undefined || seen.has(current)) return false;
|
|
840
|
+
seen.add(current);
|
|
841
|
+
return isThemeDocType(named, aliases, seen);
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
const AUTHORING = String.raw`(['"])@astryxdesign\/cli\/authoring\2`;
|
|
845
|
+
|
|
846
|
+
/**
|
|
847
|
+
* The type names the file's JSDoc binds with `@typedef` or `@import`, from any
|
|
848
|
+
* JSDoc comment in the module, as TypeScript reads them.
|
|
849
|
+
* @param {any[]} comments
|
|
850
|
+
* @returns {Map<string, string>}
|
|
851
|
+
*/
|
|
852
|
+
function jsdocAliases(comments) {
|
|
853
|
+
/** @type {Map<string, string>} */
|
|
854
|
+
const aliases = new Map();
|
|
855
|
+
const theme = "import('@astryxdesign/cli/authoring').ThemeDoc";
|
|
856
|
+
for (const comment of comments.filter(isJsdoc)) {
|
|
857
|
+
for (const tag of jsdocTags(comment.value)) {
|
|
858
|
+
if (tag.name === 'typedef') {
|
|
859
|
+
const typed = bracedType(tag.text);
|
|
860
|
+
const name = typed && /^\s*([$A-Z_a-z][$\w]*)/u.exec(typed.rest)?.[1];
|
|
861
|
+
if (typed && name) aliases.set(name, typed.type);
|
|
862
|
+
} else if (tag.name === 'import') {
|
|
863
|
+
const named = new RegExp(
|
|
864
|
+
String.raw`^\s*\{([^{}]*)\}\s*from\s*${AUTHORING}`,
|
|
865
|
+
'u',
|
|
866
|
+
).exec(tag.text);
|
|
867
|
+
for (const specifier of named?.[1].split(',') ?? []) {
|
|
868
|
+
const match =
|
|
869
|
+
/^\s*(?:type\s+)?ThemeDoc(?:\s+as\s+([$A-Z_a-z][$\w]*))?\s*$/u.exec(
|
|
870
|
+
specifier,
|
|
871
|
+
);
|
|
872
|
+
if (match) aliases.set(match[1] ?? 'ThemeDoc', theme);
|
|
873
|
+
}
|
|
874
|
+
const namespace = new RegExp(
|
|
875
|
+
String.raw`^\s*\*\s*as\s+([$A-Z_a-z][$\w]*)\s+from\s*${AUTHORING}`,
|
|
876
|
+
'u',
|
|
877
|
+
).exec(tag.text)?.[1];
|
|
878
|
+
if (namespace) aliases.set(`${namespace}.ThemeDoc`, theme);
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
return aliases;
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
/**
|
|
886
|
+
* Whether the JSDoc TypeScript reads for the default export types it as the
|
|
887
|
+
* public ThemeDoc: `@type` in the last JSDoc comment between the previous
|
|
888
|
+
* token and `export`, or `@type` or `@satisfies` in the last one between the
|
|
889
|
+
* previous token and the opening parenthesis of a JSDoc cast of the object.
|
|
890
|
+
* Line comments, strings, code spans, and JSDoc anywhere else never type it.
|
|
891
|
+
* @param {any} ast parsed with tokens and parenthesized expressions
|
|
892
|
+
* @param {any} exportDefault
|
|
893
|
+
*/
|
|
894
|
+
function declaresThemeDoc(ast, exportDefault) {
|
|
895
|
+
const tokens = ast.tokens.filter(
|
|
896
|
+
(/** @type {any} */ token) => typeof token.type !== 'string',
|
|
897
|
+
);
|
|
898
|
+
const comments = ast.comments ?? [];
|
|
899
|
+
/** @param {number} start */
|
|
900
|
+
const jsdocBefore = start => {
|
|
901
|
+
const after =
|
|
902
|
+
tokens.filter((/** @type {any} */ token) => token.end <= start).at(-1)
|
|
903
|
+
?.end ?? 0;
|
|
904
|
+
return comments
|
|
905
|
+
.filter(
|
|
906
|
+
(/** @type {any} */ comment) =>
|
|
907
|
+
isJsdoc(comment) && comment.start >= after && comment.end <= start,
|
|
908
|
+
)
|
|
909
|
+
.at(-1);
|
|
910
|
+
};
|
|
911
|
+
/** @type {Array<{comment: any, tags: string[]}>} */
|
|
912
|
+
const attached = [
|
|
913
|
+
{comment: jsdocBefore(exportDefault.start), tags: ['type']},
|
|
914
|
+
];
|
|
915
|
+
for (
|
|
916
|
+
let node = exportDefault.declaration;
|
|
917
|
+
node.type === 'ParenthesizedExpression';
|
|
918
|
+
node = node.expression
|
|
919
|
+
) {
|
|
920
|
+
attached.push({
|
|
921
|
+
comment: jsdocBefore(node.start),
|
|
922
|
+
tags: ['type', 'satisfies'],
|
|
923
|
+
});
|
|
924
|
+
}
|
|
925
|
+
const aliases = jsdocAliases(comments);
|
|
926
|
+
return attached.some(({comment, tags}) => {
|
|
927
|
+
if (!comment) return false;
|
|
928
|
+
const found = jsdocTags(comment.value);
|
|
929
|
+
return tags.some(name => {
|
|
930
|
+
const tag = found.find(candidate => candidate.name === name);
|
|
931
|
+
const typed = tag && bracedType(tag.text);
|
|
932
|
+
return typed != null && isThemeDocType(typed.type, aliases);
|
|
933
|
+
});
|
|
934
|
+
});
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
/** A module named for the theme it exports, such as `oceanTheme.ts`. */
|
|
938
|
+
const THEME_SOURCE_RE = /^[$A-Z_a-z][$\w]*Theme\.(?:mjs|js|mts|ts|tsx|jsx)$/u;
|
|
939
|
+
|
|
940
|
+
/**
|
|
941
|
+
* Whether an entry at any depth of a themes root is outside every theme: a dot
|
|
942
|
+
* entry, or a name npm never publishes (npm-packlist's defaults).
|
|
943
|
+
* @param {string} name
|
|
944
|
+
*/
|
|
945
|
+
export function isIgnoredThemeEntry(name) {
|
|
946
|
+
return (
|
|
947
|
+
name.startsWith('.') ||
|
|
948
|
+
name === 'node_modules' ||
|
|
949
|
+
name === 'CVS' ||
|
|
950
|
+
name === 'npm-debug.log' ||
|
|
951
|
+
name.endsWith('.orig')
|
|
952
|
+
);
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
/**
|
|
956
|
+
* Every entry below one folder of a theme root, without following symlinks
|
|
957
|
+
* and without {@link isIgnoredThemeEntry} entries.
|
|
958
|
+
* @param {string} folder
|
|
959
|
+
* @returns {{files: string[], symlinks: string[]}} sorted POSIX paths
|
|
960
|
+
* relative to the folder
|
|
961
|
+
*/
|
|
962
|
+
function listThemeFolder(folder) {
|
|
963
|
+
/** @type {string[]} */
|
|
964
|
+
const files = [];
|
|
965
|
+
/** @type {string[]} */
|
|
966
|
+
const symlinks = [];
|
|
967
|
+
/** @param {string} directory */
|
|
968
|
+
function walk(directory) {
|
|
969
|
+
for (const entry of fs.readdirSync(directory, {withFileTypes: true})) {
|
|
970
|
+
if (isIgnoredThemeEntry(entry.name)) continue;
|
|
971
|
+
const full = path.join(directory, entry.name);
|
|
972
|
+
const relative = path.relative(folder, full).split(path.sep).join('/');
|
|
973
|
+
if (entry.isSymbolicLink()) symlinks.push(relative);
|
|
974
|
+
else if (entry.isDirectory()) walk(full);
|
|
975
|
+
else if (entry.isFile()) files.push(relative);
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
walk(folder);
|
|
979
|
+
return {files: files.sort(), symlinks: symlinks.sort()};
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
/**
|
|
983
|
+
* The first entry that makes a folder a theme: a `.doc.mjs` descriptor or a
|
|
984
|
+
* `<name>Theme` source, at any depth, linked or not.
|
|
985
|
+
* @param {{files: string[], symlinks: string[]}} listing
|
|
986
|
+
* @returns {string | undefined}
|
|
987
|
+
*/
|
|
988
|
+
function themeEvidence({files, symlinks}) {
|
|
989
|
+
return [...files, ...symlinks].sort().find(file => {
|
|
990
|
+
const name = path.posix.basename(file);
|
|
991
|
+
return name.endsWith(THEME_DOC_SUFFIX) || THEME_SOURCE_RE.test(name);
|
|
992
|
+
});
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
/**
|
|
996
|
+
* Whether discovery reads a folder under a theme root as a theme. Dot-folders
|
|
997
|
+
* and folders with neither a descriptor nor a `<name>Theme` source are not.
|
|
998
|
+
* @param {string} folder absolute path
|
|
999
|
+
*/
|
|
1000
|
+
export function isThemeFolder(folder) {
|
|
1001
|
+
return (
|
|
1002
|
+
!isIgnoredThemeEntry(path.basename(folder)) &&
|
|
1003
|
+
themeEvidence(listThemeFolder(folder)) !== undefined
|
|
1004
|
+
);
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
/**
|
|
1008
|
+
* The files one theme folder ships: every regular file below it except
|
|
1009
|
+
* {@link isIgnoredThemeEntry} entries. What pack-check requires and what
|
|
1010
|
+
* `theme add` copies.
|
|
1011
|
+
* @param {string} folder absolute path
|
|
1012
|
+
* @returns {string[]} sorted POSIX paths relative to the folder
|
|
1013
|
+
*/
|
|
1014
|
+
export function listThemeFiles(folder) {
|
|
1015
|
+
return listThemeFolder(folder).files;
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
/** @param {string[]} files */
|
|
1019
|
+
function quoted(files) {
|
|
1020
|
+
return files.map(file => `"${file}"`).join(', ');
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
/**
|
|
1024
|
+
* The files `theme add` has always copied after a bundled theme's entry, in
|
|
1025
|
+
* copy order. SYNC: scripts/generate-cli-themes.mjs bundles these.
|
|
1026
|
+
* @param {string} id the theme's export name without `Theme`
|
|
1027
|
+
*/
|
|
1028
|
+
function bundledThemeArtifacts(id) {
|
|
1029
|
+
return [
|
|
1030
|
+
'icons.tsx',
|
|
1031
|
+
`${id}Palettes.ts`,
|
|
1032
|
+
`${id}Palettes.generated.ts`,
|
|
1033
|
+
`${id}PaletteRefs.generated.ts`,
|
|
1034
|
+
`${id}Palettes.generated.receipt.json`,
|
|
1035
|
+
'palette.config.json',
|
|
1036
|
+
];
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
/**
|
|
1040
|
+
* The files `theme add` copies, entry first. A bundled theme's descriptor is
|
|
1041
|
+
* the CLI's own metadata and stays behind; an integration theme copies its
|
|
1042
|
+
* whole directory.
|
|
1043
|
+
* @param {string[]} files every regular file in the theme directory, sorted
|
|
1044
|
+
* @param {string} entry
|
|
1045
|
+
* @param {string} descriptor
|
|
1046
|
+
* @param {string} exportName
|
|
1047
|
+
* @param {boolean} bundled
|
|
1048
|
+
*/
|
|
1049
|
+
function copiedThemeFiles(files, entry, descriptor, exportName, bundled) {
|
|
1050
|
+
const rest = files.filter(
|
|
1051
|
+
file => file !== entry && !(bundled && file === descriptor),
|
|
1052
|
+
);
|
|
1053
|
+
if (!bundled) return [entry, ...rest];
|
|
1054
|
+
const order = bundledThemeArtifacts(exportName.replace(/Theme$/u, ''));
|
|
1055
|
+
return [
|
|
1056
|
+
entry,
|
|
1057
|
+
...order.filter(file => rest.includes(file)),
|
|
1058
|
+
...rest.filter(file => !order.includes(file)),
|
|
1059
|
+
];
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
/**
|
|
1063
|
+
* Discover and validate one theme root.
|
|
1064
|
+
* @param {string} themeRoot absolute root containing one directory per slug
|
|
1065
|
+
* @param {string} owner package that owns the root
|
|
1066
|
+
* @param {{bundled?: boolean}} [options] a bundled root is the CLI's own; its
|
|
1067
|
+
* sources are checked by the CLI's tests rather than on every read
|
|
1068
|
+
* @returns {DiscoveredTheme[]}
|
|
1069
|
+
*/
|
|
1070
|
+
export function discoverThemeDirectory(
|
|
1071
|
+
themeRoot,
|
|
1072
|
+
owner,
|
|
1073
|
+
{bundled = false} = {},
|
|
1074
|
+
) {
|
|
1075
|
+
if (!fs.existsSync(themeRoot) || !fs.statSync(themeRoot).isDirectory()) {
|
|
1076
|
+
throw new Error(
|
|
1077
|
+
`Declared themes root does not exist on disk: ${themeRoot}`,
|
|
1078
|
+
);
|
|
1079
|
+
}
|
|
1080
|
+
if (fs.existsSync(path.join(themeRoot, 'manifest.json'))) {
|
|
1081
|
+
throw new Error(
|
|
1082
|
+
`Theme root for ${owner} contains manifest.json, the theme catalog Astryx 0.6 wrote; each theme now carries a strongly typed same-stem .doc.mjs descriptor instead. To convert it, run \`astryx upgrade --from 0.6.3 --path . --apply\` in the package, or add each theme's descriptor and delete manifest.json.`,
|
|
1083
|
+
);
|
|
475
1084
|
}
|
|
476
1085
|
|
|
477
1086
|
/** @type {DiscoveredTheme[]} */
|
|
478
1087
|
const themes = [];
|
|
479
1088
|
const slugs = new Set();
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
1089
|
+
const rootEntries = fs
|
|
1090
|
+
.readdirSync(themeRoot, {withFileTypes: true})
|
|
1091
|
+
.filter(entry => !isIgnoredThemeEntry(entry.name));
|
|
1092
|
+
const rootDescriptor = rootEntries.find(
|
|
1093
|
+
entry => entry.isFile() && entry.name.endsWith(THEME_DOC_SUFFIX),
|
|
1094
|
+
);
|
|
1095
|
+
if (rootDescriptor) {
|
|
1096
|
+
throw new Error(
|
|
1097
|
+
`Theme descriptor "${rootDescriptor.name}" for ${owner} must be inside a lower-kebab theme directory.`,
|
|
1098
|
+
);
|
|
1099
|
+
}
|
|
1100
|
+
const rootSymlink = rootEntries.find(entry => entry.isSymbolicLink());
|
|
1101
|
+
if (rootSymlink) {
|
|
1102
|
+
throw new Error(
|
|
1103
|
+
`Theme root for ${owner} contains symlink "${rootSymlink.name}"; theme directories must stay inside the declared root.`,
|
|
1104
|
+
);
|
|
1105
|
+
}
|
|
1106
|
+
const directories = rootEntries
|
|
1107
|
+
.filter(entry => entry.isDirectory())
|
|
1108
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
1109
|
+
|
|
1110
|
+
for (const directory of directories) {
|
|
1111
|
+
const slug = directory.name;
|
|
1112
|
+
const listing = listThemeFolder(path.join(themeRoot, slug));
|
|
1113
|
+
const evidence = themeEvidence(listing);
|
|
1114
|
+
if (evidence === undefined) continue;
|
|
1115
|
+
if (!THEME_SLUG_RE.test(slug)) {
|
|
489
1116
|
throw new Error(
|
|
490
|
-
`Theme
|
|
1117
|
+
`Theme root for ${owner} has invalid directory "${slug}"; use lowercase kebab-case starting with a letter.`,
|
|
491
1118
|
);
|
|
492
1119
|
}
|
|
493
1120
|
const normalizedSlug = slug.toLowerCase();
|
|
494
1121
|
if (slugs.has(normalizedSlug)) {
|
|
495
1122
|
throw new Error(
|
|
496
|
-
`Theme
|
|
1123
|
+
`Theme root for ${owner} declares duplicate slug "${slug}".`,
|
|
497
1124
|
);
|
|
498
1125
|
}
|
|
499
1126
|
slugs.add(normalizedSlug);
|
|
500
1127
|
|
|
501
|
-
const
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
1128
|
+
const sourceDir = resolveThemePath(
|
|
1129
|
+
slug,
|
|
1130
|
+
themeRoot,
|
|
1131
|
+
`theme "${slug}" directory`,
|
|
505
1132
|
);
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
`Theme catalog for ${owner} has an invalid description for "${slug}".`,
|
|
509
|
-
);
|
|
510
|
-
}
|
|
511
|
-
if (typeof entry.maintained !== 'boolean') {
|
|
1133
|
+
const theme = `Theme "${slug}" for ${owner}`;
|
|
1134
|
+
if (listing.symlinks.length > 0) {
|
|
512
1135
|
throw new Error(
|
|
513
|
-
|
|
1136
|
+
`${theme} contains symlink "${listing.symlinks[0]}"; theme files must be regular files inside the theme directory.`,
|
|
514
1137
|
);
|
|
515
1138
|
}
|
|
516
|
-
const
|
|
517
|
-
|
|
518
|
-
`theme "${slug}" entry`,
|
|
519
|
-
owner,
|
|
520
|
-
);
|
|
521
|
-
const exportName = requiredString(
|
|
522
|
-
entry.exportName,
|
|
523
|
-
`theme "${slug}" exportName`,
|
|
524
|
-
owner,
|
|
1139
|
+
const descriptors = listing.files.filter(file =>
|
|
1140
|
+
file.endsWith(THEME_DOC_SUFFIX),
|
|
525
1141
|
);
|
|
526
|
-
|
|
1142
|
+
const docs = descriptors.filter(file => !file.includes('/'));
|
|
1143
|
+
if (docs.length !== 1) {
|
|
1144
|
+
const detail =
|
|
1145
|
+
docs.length > 1
|
|
1146
|
+
? `: ${quoted(docs)}`
|
|
1147
|
+
: evidence.includes('/')
|
|
1148
|
+
? `; "${evidence}" is in a subfolder`
|
|
1149
|
+
: ` beside "${evidence}"`;
|
|
527
1150
|
throw new Error(
|
|
528
|
-
|
|
1151
|
+
`${theme} must contain exactly one same-stem .doc.mjs descriptor; found ${docs.length}${detail}.`,
|
|
529
1152
|
);
|
|
530
1153
|
}
|
|
531
|
-
|
|
1154
|
+
const nested = descriptors.find(file => file.includes('/'));
|
|
1155
|
+
if (nested) {
|
|
532
1156
|
throw new Error(
|
|
533
|
-
|
|
1157
|
+
`${theme} contains more than one .doc.mjs descriptor: "${docs[0]}" and "${nested}".`,
|
|
534
1158
|
);
|
|
535
1159
|
}
|
|
536
1160
|
|
|
537
|
-
const
|
|
538
|
-
|
|
1161
|
+
const docPath = resolveThemePath(
|
|
1162
|
+
docs[0],
|
|
1163
|
+
sourceDir,
|
|
1164
|
+
`theme "${slug}" descriptor`,
|
|
539
1165
|
);
|
|
540
|
-
|
|
1166
|
+
const label = themeDescriptorLabel(docPath, owner);
|
|
1167
|
+
const exportName = docs[0].slice(0, -THEME_DOC_SUFFIX.length);
|
|
1168
|
+
if (!/^[$A-Z_a-z][$\w]*$/u.test(exportName)) {
|
|
541
1169
|
throw new Error(
|
|
542
|
-
|
|
1170
|
+
`${label} has stem "${exportName}", which is not a valid runtime export name.`,
|
|
543
1171
|
);
|
|
544
1172
|
}
|
|
545
|
-
|
|
1173
|
+
const sources = THEME_MODULE_EXTENSIONS.map(
|
|
1174
|
+
extension => `${exportName}${extension}`,
|
|
1175
|
+
).filter(file => listing.files.includes(file));
|
|
1176
|
+
if (sources.length !== 1) {
|
|
546
1177
|
throw new Error(
|
|
547
|
-
|
|
1178
|
+
`${theme} must contain exactly one same-stem source for ${docs[0]}; found ${sources.length}${sources.length > 1 ? `: ${quoted(sources)}` : ''}.`,
|
|
548
1179
|
);
|
|
549
1180
|
}
|
|
550
|
-
|
|
1181
|
+
|
|
1182
|
+
const doc = readThemeDoc(docPath, owner);
|
|
1183
|
+
if (doc.name !== slug) {
|
|
551
1184
|
throw new Error(
|
|
552
|
-
|
|
1185
|
+
`${label} names "${doc.name}" but its directory is "${slug}".`,
|
|
553
1186
|
);
|
|
554
1187
|
}
|
|
555
|
-
|
|
556
|
-
const
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
`theme "${slug}"
|
|
1188
|
+
const entry = sources[0];
|
|
1189
|
+
const entryPath = resolveThemePath(
|
|
1190
|
+
entry,
|
|
1191
|
+
sourceDir,
|
|
1192
|
+
`theme "${slug}" entry`,
|
|
560
1193
|
);
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
1194
|
+
|
|
1195
|
+
if (!bundled) {
|
|
1196
|
+
const allowedFiles = new Set(
|
|
1197
|
+
listing.files
|
|
1198
|
+
.map(file =>
|
|
1199
|
+
resolveThemePath(file, sourceDir, `theme "${slug}" file`),
|
|
1200
|
+
)
|
|
1201
|
+
.filter(file => file !== docPath),
|
|
564
1202
|
);
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
1203
|
+
const jscodeshift = sourceParser();
|
|
1204
|
+
try {
|
|
1205
|
+
validateThemeModuleGraph(
|
|
1206
|
+
entryPath,
|
|
1207
|
+
jscodeshift,
|
|
1208
|
+
sourceDir,
|
|
1209
|
+
allowedFiles,
|
|
1210
|
+
owner,
|
|
1211
|
+
entry,
|
|
1212
|
+
docPath,
|
|
1213
|
+
);
|
|
1214
|
+
if (
|
|
1215
|
+
!moduleExportsName(
|
|
1216
|
+
entryPath,
|
|
1217
|
+
exportName,
|
|
1218
|
+
jscodeshift,
|
|
1219
|
+
sourceDir,
|
|
1220
|
+
allowedFiles,
|
|
1221
|
+
)
|
|
1222
|
+
) {
|
|
1223
|
+
throw new ThemeRuntimeExportError(
|
|
1224
|
+
`Theme "${slug}" for ${owner} entry "${entry}" does not export "${exportName}".`,
|
|
1225
|
+
);
|
|
1226
|
+
}
|
|
1227
|
+
} catch (error) {
|
|
1228
|
+
if (
|
|
1229
|
+
error instanceof ThemeModuleReferenceError ||
|
|
1230
|
+
error instanceof ThemeRuntimeExportError
|
|
1231
|
+
) {
|
|
1232
|
+
throw error;
|
|
1233
|
+
}
|
|
1234
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
569
1235
|
throw new Error(
|
|
570
|
-
`Theme
|
|
1236
|
+
`Theme "${slug}" for ${owner} entry "${entry}" could not be parsed: ${message}`,
|
|
1237
|
+
{cause: error},
|
|
571
1238
|
);
|
|
572
1239
|
}
|
|
573
1240
|
}
|
|
574
1241
|
|
|
575
1242
|
themes.push({
|
|
576
1243
|
slug,
|
|
577
|
-
displayName,
|
|
578
|
-
description:
|
|
579
|
-
maintained:
|
|
580
|
-
entry
|
|
1244
|
+
displayName: doc.displayName,
|
|
1245
|
+
description: doc.description,
|
|
1246
|
+
maintained: doc.maintained,
|
|
1247
|
+
entry,
|
|
581
1248
|
exportName,
|
|
582
|
-
files
|
|
1249
|
+
files: copiedThemeFiles(
|
|
1250
|
+
listing.files,
|
|
1251
|
+
entry,
|
|
1252
|
+
docs[0],
|
|
1253
|
+
exportName,
|
|
1254
|
+
bundled,
|
|
1255
|
+
),
|
|
583
1256
|
package: owner,
|
|
584
1257
|
sourceDir,
|
|
585
1258
|
bundled,
|
|
1259
|
+
docPath,
|
|
586
1260
|
});
|
|
587
1261
|
}
|
|
588
1262
|
|
|
589
1263
|
return themes;
|
|
590
1264
|
}
|
|
591
1265
|
|
|
592
|
-
/**
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
1266
|
+
/** A JavaScript or TypeScript module. */
|
|
1267
|
+
const MODULE_FILE_RE = /\.(?:[cm]?[jt]s|[jt]sx)$/u;
|
|
1268
|
+
|
|
1269
|
+
/**
|
|
1270
|
+
* Whether a folder's files look like an attempt at a theme: a doc file of any
|
|
1271
|
+
* suffix, a file named for a theme, an index module, or a module named for
|
|
1272
|
+
* the folder.
|
|
1273
|
+
* @param {string} folder
|
|
1274
|
+
* @param {string[]} files
|
|
1275
|
+
*/
|
|
1276
|
+
function looksLikeTheme(folder, files) {
|
|
1277
|
+
const own = path.basename(folder).toLowerCase();
|
|
1278
|
+
return files.some(file => {
|
|
1279
|
+
const name = path.posix.basename(file).toLowerCase();
|
|
1280
|
+
const stem = name.replace(/\.[^.]+$/u, '');
|
|
1281
|
+
return (
|
|
1282
|
+
/\.doc\.[^.]+$/u.test(name) ||
|
|
1283
|
+
/theme/u.test(name) ||
|
|
1284
|
+
(MODULE_FILE_RE.test(name) && (stem === 'index' || stem === own))
|
|
1285
|
+
);
|
|
596
1286
|
});
|
|
597
1287
|
}
|
|
598
1288
|
|
|
599
1289
|
/**
|
|
600
|
-
*
|
|
601
|
-
*
|
|
1290
|
+
* Folders under a themes root that look like themes (see looksLikeTheme)
|
|
1291
|
+
* but that discovery does not read as themes, and that no module elsewhere
|
|
1292
|
+
* under the root imports, by a relative path or through the package's own
|
|
1293
|
+
* name. Discovery skips them silently, as the released catalog skipped
|
|
1294
|
+
* unlisted folders; doctor warns.
|
|
1295
|
+
* @param {string} themeRoot
|
|
1296
|
+
* @param {{packageDir?: string, packageName?: string}} [owner]
|
|
1297
|
+
* @returns {string[]} absolute folder paths, sorted
|
|
602
1298
|
*/
|
|
603
|
-
export
|
|
604
|
-
if (!
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
1299
|
+
export function unreadThemeFolders(themeRoot, {packageDir, packageName} = {}) {
|
|
1300
|
+
if (!fs.existsSync(themeRoot) || !fs.statSync(themeRoot).isDirectory()) {
|
|
1301
|
+
return [];
|
|
1302
|
+
}
|
|
1303
|
+
const folders = fs
|
|
1304
|
+
.readdirSync(themeRoot, {withFileTypes: true})
|
|
1305
|
+
.filter(entry => entry.isDirectory() && !isIgnoredThemeEntry(entry.name))
|
|
1306
|
+
.map(entry => path.join(themeRoot, entry.name))
|
|
1307
|
+
.sort();
|
|
1308
|
+
const unread = folders.filter(folder => {
|
|
1309
|
+
if (isThemeFolder(folder)) return false;
|
|
1310
|
+
const {files} = listThemeFolder(folder);
|
|
1311
|
+
return (
|
|
1312
|
+
files.some(file => MODULE_FILE_RE.test(file)) &&
|
|
1313
|
+
looksLikeTheme(folder, files)
|
|
617
1314
|
);
|
|
618
|
-
|
|
1315
|
+
});
|
|
1316
|
+
if (unread.length === 0) return [];
|
|
1317
|
+
|
|
1318
|
+
const modules = [
|
|
1319
|
+
...fs
|
|
1320
|
+
.readdirSync(themeRoot, {withFileTypes: true})
|
|
1321
|
+
.filter(
|
|
1322
|
+
entry =>
|
|
1323
|
+
entry.isFile() &&
|
|
1324
|
+
!isIgnoredThemeEntry(entry.name) &&
|
|
1325
|
+
MODULE_FILE_RE.test(entry.name),
|
|
1326
|
+
)
|
|
1327
|
+
.map(entry => path.join(themeRoot, entry.name)),
|
|
1328
|
+
...folders.flatMap(folder =>
|
|
1329
|
+
listThemeFolder(folder)
|
|
1330
|
+
.files.filter(file => MODULE_FILE_RE.test(file))
|
|
1331
|
+
.map(file => path.join(folder, file)),
|
|
1332
|
+
),
|
|
1333
|
+
];
|
|
1334
|
+
const self = packageName ? `${packageName}/` : null;
|
|
1335
|
+
/** @type {string[]} */
|
|
1336
|
+
const imported = [];
|
|
1337
|
+
for (const file of modules) {
|
|
1338
|
+
let specifiers;
|
|
619
1339
|
try {
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
theme.sourceDir,
|
|
624
|
-
allowedFiles,
|
|
625
|
-
theme.package,
|
|
626
|
-
theme.entry,
|
|
627
|
-
);
|
|
628
|
-
exportsName = moduleExportsName(
|
|
629
|
-
entryPath,
|
|
630
|
-
theme.exportName,
|
|
631
|
-
jscodeshift,
|
|
632
|
-
theme.sourceDir,
|
|
633
|
-
allowedFiles,
|
|
634
|
-
);
|
|
635
|
-
} catch (error) {
|
|
636
|
-
if (error instanceof ThemeModuleReferenceError) throw error;
|
|
637
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
638
|
-
throw new Error(
|
|
639
|
-
`Theme catalog for ${theme.package} entry "${theme.entry}" could not be parsed: ${message}`,
|
|
640
|
-
{cause: error},
|
|
641
|
-
);
|
|
1340
|
+
specifiers = moduleSpecifiers(file, sourceParser());
|
|
1341
|
+
} catch {
|
|
1342
|
+
continue;
|
|
642
1343
|
}
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
)
|
|
1344
|
+
for (const specifier of specifiers) {
|
|
1345
|
+
if (specifier.startsWith('.')) {
|
|
1346
|
+
imported.push(path.resolve(path.dirname(file), specifier));
|
|
1347
|
+
} else if (self && packageDir && specifier.startsWith(self)) {
|
|
1348
|
+
imported.push(path.resolve(packageDir, specifier.slice(self.length)));
|
|
1349
|
+
}
|
|
647
1350
|
}
|
|
648
1351
|
}
|
|
649
|
-
return
|
|
1352
|
+
return unread.filter(
|
|
1353
|
+
folder =>
|
|
1354
|
+
!imported.some(
|
|
1355
|
+
target => target === folder || target.startsWith(folder + path.sep),
|
|
1356
|
+
),
|
|
1357
|
+
);
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
/** @type {DiscoveredTheme[] | null} */
|
|
1361
|
+
let bundledThemeCache = null;
|
|
1362
|
+
|
|
1363
|
+
/** @returns {DiscoveredTheme[]} */
|
|
1364
|
+
export function discoverBundledThemes() {
|
|
1365
|
+
bundledThemeCache ??= discoverThemeDirectory(
|
|
1366
|
+
THEMES_DIR,
|
|
1367
|
+
BUNDLED_THEME_PACKAGE,
|
|
1368
|
+
{bundled: true},
|
|
1369
|
+
);
|
|
1370
|
+
return bundledThemeCache.map(theme => ({...theme, files: [...theme.files]}));
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1373
|
+
/**
|
|
1374
|
+
* @param {import('../integrations/integrations.mjs').LoadedIntegration} integration
|
|
1375
|
+
* @returns {Promise<DiscoveredTheme[]>}
|
|
1376
|
+
*/
|
|
1377
|
+
export async function discoverIntegrationThemes(integration) {
|
|
1378
|
+
if (!integration.themes) return [];
|
|
1379
|
+
return discoverThemeDirectory(integration.themes, integration.name);
|
|
650
1380
|
}
|