@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
|
@@ -0,0 +1,1643 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file Every module in the CLI package that runs anything but its static
|
|
5
|
+
* imports of other CLI code (a doc file, a module chosen at run time, a code
|
|
6
|
+
* string, a thread, or a process) is listed here, site by site, with what it
|
|
7
|
+
* runs. A doc read that goes around the doc compiler (./read.mjs) is such a
|
|
8
|
+
* site, so it fails this test until it is listed; so does any other new
|
|
9
|
+
* site, and a listed one that is gone.
|
|
10
|
+
*
|
|
11
|
+
* What it claims: every ordinary way a module runs code — the shapes code
|
|
12
|
+
* review sees, whatever the local names. What it does not claim: that the
|
|
13
|
+
* rule cannot be evaded on purpose. Deliberate obfuscation (a method name
|
|
14
|
+
* built at run time, a runner stashed behind a computed key or a getter) is
|
|
15
|
+
* out of scope; this test records what the CLI does, it does not sandbox it.
|
|
16
|
+
* It checks where code runs, not what a computed site loads: each entry
|
|
17
|
+
* says that.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import {describe, expect, it} from 'vitest';
|
|
21
|
+
import * as fs from 'node:fs';
|
|
22
|
+
import * as os from 'node:os';
|
|
23
|
+
import * as path from 'node:path';
|
|
24
|
+
import {isBuiltin} from 'node:module';
|
|
25
|
+
import {parse} from '@babel/parser';
|
|
26
|
+
import babelTypes from '@babel/types';
|
|
27
|
+
import {CLI_ROOT} from '../fs/paths.mjs';
|
|
28
|
+
|
|
29
|
+
const {VISITOR_KEYS, isReferenced, isTSType} = babelTypes;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Command modules build their help text from the raw exports of their own
|
|
33
|
+
* self-docs, imported statically: those reads skip the compiler.
|
|
34
|
+
*/
|
|
35
|
+
const SELF_DOCS = 'its self-docs, statically: raw exports build its help text';
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Every module that runs anything but its static imports of other CLI code:
|
|
39
|
+
* what it runs, and each site the scan finds in it (`×n` for a repeated one).
|
|
40
|
+
* @type {Record<string, {runs: string, sites: string[]}>}
|
|
41
|
+
*/
|
|
42
|
+
const RUNNERS = {
|
|
43
|
+
'api/doctor/doctor.mjs': {
|
|
44
|
+
runs: "the project's astryx.config, for the config check",
|
|
45
|
+
sites: ['import(<computed>)'],
|
|
46
|
+
},
|
|
47
|
+
'api/gap-report/gap-report-worker.mjs': {
|
|
48
|
+
runs: "an integration's gap-report handler, in the worker thread",
|
|
49
|
+
sites: ['importUserModule'],
|
|
50
|
+
},
|
|
51
|
+
'api/gap-report/gap-report.mjs': {
|
|
52
|
+
runs: 'the gap-report worker thread, and `gh issue create` as the GitHub fallback',
|
|
53
|
+
sites: ['import(node:child_process)', 'worker_threads.Worker'],
|
|
54
|
+
},
|
|
55
|
+
'api/integration/pack-check.mjs': {
|
|
56
|
+
runs: '`npm pack`, `tar`, and a Node script that resolves the packed specifiers, to check a package as published, and the package\'s doc modules, to see which need a newer CLI',
|
|
57
|
+
sites: ['child_process.spawnSync ×3', 'loadTopicModule'],
|
|
58
|
+
},
|
|
59
|
+
'api/swizzle/_github.mjs': {
|
|
60
|
+
runs: '`gh auth status`, to check the GitHub CLI is signed in',
|
|
61
|
+
sites: ['child_process.execFileSync'],
|
|
62
|
+
},
|
|
63
|
+
'api/theme/build/build.mjs': {
|
|
64
|
+
runs: 'theme source modules, through jiti; eval for legacy theme object literals',
|
|
65
|
+
sites: [
|
|
66
|
+
'.evalModule() ×2',
|
|
67
|
+
'.import()',
|
|
68
|
+
'createJiti ×4',
|
|
69
|
+
'eval ×2',
|
|
70
|
+
'import(<computed>)',
|
|
71
|
+
],
|
|
72
|
+
},
|
|
73
|
+
'api/theme/build/core-interception.mjs': {
|
|
74
|
+
runs: "@astryxdesign/core as a theme's CommonJS dependencies require it, to wrap defineTheme",
|
|
75
|
+
sites: ['require(<computed>)'],
|
|
76
|
+
},
|
|
77
|
+
'api/upgrade/_adapter.mjs': {
|
|
78
|
+
runs: 'the commands a codemod declares as its post-codemod hooks',
|
|
79
|
+
sites: ['child_process.execFile'],
|
|
80
|
+
},
|
|
81
|
+
'assets/codemods/ensure-jscodeshift.mjs': {
|
|
82
|
+
runs: 'the package manager, to install jscodeshift',
|
|
83
|
+
sites: ['child_process.execSync'],
|
|
84
|
+
},
|
|
85
|
+
'assets/codemods/integration-discovery.mjs': {
|
|
86
|
+
runs: "each integration's codemods, through the codemod parser, to plan an upgrade",
|
|
87
|
+
sites: ['loadModuleWithParser(..., parseCodemod)'],
|
|
88
|
+
},
|
|
89
|
+
'authoring/shadcn/source-variants.mjs': {
|
|
90
|
+
runs: "@babel/plugin-transform-typescript (CommonJS), to reproduce the ShadCN client's transform",
|
|
91
|
+
sites: ['require(@babel/plugin-transform-typescript)'],
|
|
92
|
+
},
|
|
93
|
+
'clients/cli/bin/astryx.mjs': {
|
|
94
|
+
runs: "the CLI's own modules, by real path, so a linked bin finds them",
|
|
95
|
+
sites: ['import(<computed>)'],
|
|
96
|
+
},
|
|
97
|
+
'clients/cli/commands/blog.mjs': {
|
|
98
|
+
runs: SELF_DOCS,
|
|
99
|
+
sites: ['import ../../../api/blog/blog.doc.mjs', 'import ./blog.doc.mjs'],
|
|
100
|
+
},
|
|
101
|
+
'clients/cli/commands/build-theme.mjs': {
|
|
102
|
+
runs: `${SELF_DOCS}; and \`astryx theme build\`, a child process per theme`,
|
|
103
|
+
sites: [
|
|
104
|
+
'child_process.spawn',
|
|
105
|
+
'import ../../../api/theme/themeAdd.doc.mjs',
|
|
106
|
+
'import ../../../api/theme/themeBuild.doc.mjs',
|
|
107
|
+
'import ../../../api/theme/themeListAvailable.doc.mjs',
|
|
108
|
+
'import ../../../api/theme/themePaletteGenerate.doc.mjs',
|
|
109
|
+
'import ../../../api/theme/themeTargets.doc.mjs',
|
|
110
|
+
'import ../../../api/theme/themeTemplate.doc.mjs',
|
|
111
|
+
'import ./theme-add.doc.mjs',
|
|
112
|
+
'import ./theme-build.doc.mjs',
|
|
113
|
+
'import ./theme-list.doc.mjs',
|
|
114
|
+
'import ./theme-palette-generate.doc.mjs',
|
|
115
|
+
'import ./theme-palette.doc.mjs',
|
|
116
|
+
'import ./theme-targets.doc.mjs',
|
|
117
|
+
'import ./theme-template.doc.mjs',
|
|
118
|
+
'import ./theme.doc.mjs',
|
|
119
|
+
],
|
|
120
|
+
},
|
|
121
|
+
'clients/cli/commands/build.mjs': {
|
|
122
|
+
runs: SELF_DOCS,
|
|
123
|
+
sites: [
|
|
124
|
+
'import ../../../api/build/build.doc.mjs',
|
|
125
|
+
'import ./build.doc.mjs',
|
|
126
|
+
],
|
|
127
|
+
},
|
|
128
|
+
'clients/cli/commands/component/index.mjs': {
|
|
129
|
+
runs: SELF_DOCS,
|
|
130
|
+
sites: [
|
|
131
|
+
'import ../../../../api/component/component.doc.mjs',
|
|
132
|
+
'import ../component.doc.mjs',
|
|
133
|
+
],
|
|
134
|
+
},
|
|
135
|
+
'clients/cli/commands/discover.mjs': {
|
|
136
|
+
runs: SELF_DOCS,
|
|
137
|
+
sites: [
|
|
138
|
+
'import ../../../api/discover/discover.doc.mjs',
|
|
139
|
+
'import ./discover.doc.mjs',
|
|
140
|
+
],
|
|
141
|
+
},
|
|
142
|
+
'clients/cli/commands/docs.mjs': {
|
|
143
|
+
runs: SELF_DOCS,
|
|
144
|
+
sites: ['import ../../../api/docs/docs.doc.mjs', 'import ./docs.doc.mjs'],
|
|
145
|
+
},
|
|
146
|
+
'clients/cli/commands/doctor.mjs': {
|
|
147
|
+
runs: SELF_DOCS,
|
|
148
|
+
sites: [
|
|
149
|
+
'import ../../../api/doctor/doctor.doc.mjs',
|
|
150
|
+
'import ../../../api/integration/integrationComponentConflicts.doc.mjs',
|
|
151
|
+
'import ../../../api/integration/integrationDocConflicts.doc.mjs',
|
|
152
|
+
'import ../../../api/integration/integrationTemplateConflicts.doc.mjs',
|
|
153
|
+
'import ../../../api/integration/validateIntegration.doc.mjs',
|
|
154
|
+
'import ./doctor-integration-components.doc.mjs',
|
|
155
|
+
'import ./doctor-integration-docs.doc.mjs',
|
|
156
|
+
'import ./doctor-integration-templates.doc.mjs',
|
|
157
|
+
'import ./doctor-integration-validate.doc.mjs',
|
|
158
|
+
'import ./doctor-integration.doc.mjs',
|
|
159
|
+
'import ./doctor.doc.mjs',
|
|
160
|
+
],
|
|
161
|
+
},
|
|
162
|
+
'clients/cli/commands/ensure-core-built.mjs': {
|
|
163
|
+
runs: '`pnpm -F @astryxdesign/core build`: a test helper that builds core once for the theme-build suites',
|
|
164
|
+
sites: ['child_process.execFileSync'],
|
|
165
|
+
},
|
|
166
|
+
'clients/cli/commands/gap-report.mjs': {
|
|
167
|
+
runs: SELF_DOCS,
|
|
168
|
+
sites: [
|
|
169
|
+
'import ../../../api/gap-report/gap-report.doc.mjs',
|
|
170
|
+
'import ./gap-report.doc.mjs',
|
|
171
|
+
],
|
|
172
|
+
},
|
|
173
|
+
'clients/cli/commands/hook/index.mjs': {
|
|
174
|
+
runs: SELF_DOCS,
|
|
175
|
+
sites: [
|
|
176
|
+
'import ../../../../api/hook/hook.doc.mjs',
|
|
177
|
+
'import ../hook.doc.mjs',
|
|
178
|
+
],
|
|
179
|
+
},
|
|
180
|
+
'clients/cli/commands/init.mjs': {
|
|
181
|
+
runs: SELF_DOCS,
|
|
182
|
+
sites: ['import ../../../api/init/init.doc.mjs', 'import ./init.doc.mjs'],
|
|
183
|
+
},
|
|
184
|
+
'clients/cli/commands/integration.mjs': {
|
|
185
|
+
runs: SELF_DOCS,
|
|
186
|
+
sites: [
|
|
187
|
+
'import ../../../api/integration/integrationAdd.doc.mjs',
|
|
188
|
+
'import ../../../api/integration/integrationPackCheck.doc.mjs',
|
|
189
|
+
'import ./integration-add.doc.mjs',
|
|
190
|
+
'import ./integration-pack.doc.mjs',
|
|
191
|
+
'import ./integration-verify.doc.mjs',
|
|
192
|
+
'import ./integration.doc.mjs',
|
|
193
|
+
],
|
|
194
|
+
},
|
|
195
|
+
'clients/cli/commands/layout.mjs': {
|
|
196
|
+
runs: SELF_DOCS,
|
|
197
|
+
sites: [
|
|
198
|
+
'import ../../../api/layout/layoutCheck.doc.mjs',
|
|
199
|
+
'import ../../../api/layout/layoutExpand.doc.mjs',
|
|
200
|
+
'import ../../../api/layout/layoutGrammar.doc.mjs',
|
|
201
|
+
'import ./layout-check.doc.mjs',
|
|
202
|
+
'import ./layout-expand.doc.mjs',
|
|
203
|
+
'import ./layout-grammar.doc.mjs',
|
|
204
|
+
'import ./layout.doc.mjs',
|
|
205
|
+
],
|
|
206
|
+
},
|
|
207
|
+
'clients/cli/commands/search.mjs': {
|
|
208
|
+
runs: SELF_DOCS,
|
|
209
|
+
sites: [
|
|
210
|
+
'import ../../../api/search/search.doc.mjs',
|
|
211
|
+
'import ./search.doc.mjs',
|
|
212
|
+
],
|
|
213
|
+
},
|
|
214
|
+
'clients/cli/commands/swizzle.mjs': {
|
|
215
|
+
runs: SELF_DOCS,
|
|
216
|
+
sites: [
|
|
217
|
+
'import ../../../api/swizzle/swizzle.doc.mjs',
|
|
218
|
+
'import ./swizzle.doc.mjs',
|
|
219
|
+
],
|
|
220
|
+
},
|
|
221
|
+
'clients/cli/commands/template.mjs': {
|
|
222
|
+
runs: SELF_DOCS,
|
|
223
|
+
sites: [
|
|
224
|
+
'import ../../../api/template/template.doc.mjs',
|
|
225
|
+
'import ./template.doc.mjs',
|
|
226
|
+
],
|
|
227
|
+
},
|
|
228
|
+
'clients/cli/commands/upgrade.mjs': {
|
|
229
|
+
runs: SELF_DOCS,
|
|
230
|
+
sites: [
|
|
231
|
+
'import ../../../api/upgrade/upgrade.doc.mjs',
|
|
232
|
+
'import ./upgrade.doc.mjs',
|
|
233
|
+
],
|
|
234
|
+
},
|
|
235
|
+
'clients/cli/index.mjs': {
|
|
236
|
+
runs: 'the command modules, to register each command, and the manifest command doc',
|
|
237
|
+
sites: ['import ./commands/manifest.doc.mjs', 'import(<computed>)'],
|
|
238
|
+
},
|
|
239
|
+
'clients/cli/lib/manifest.mjs': {
|
|
240
|
+
runs: 'the manifest command doc, for the exit codes the manifest reports',
|
|
241
|
+
sites: ['import ../commands/manifest.doc.mjs'],
|
|
242
|
+
},
|
|
243
|
+
'clients/cli/lib/resolve-theme.mjs': {
|
|
244
|
+
runs: 'the configured theme (ASTRYX_THEME or package.json), by path or package name',
|
|
245
|
+
sites: ['require(<computed>) ×2'],
|
|
246
|
+
},
|
|
247
|
+
'foundation/config/project.mjs': {
|
|
248
|
+
runs: 'astryx.config, through the config parser',
|
|
249
|
+
sites: ['loadModuleWithParser(..., parseConfig)'],
|
|
250
|
+
},
|
|
251
|
+
'foundation/discovery/docs-discovery.mjs': {
|
|
252
|
+
runs: 'a contributed topic, for its catalog fields: name, title, description, category, and relationships',
|
|
253
|
+
sites: ['importDocModule'],
|
|
254
|
+
},
|
|
255
|
+
'foundation/discovery/template-adapter.mjs': {
|
|
256
|
+
runs: '@babel/parser, to find the fixture references in template source; and a template doc that does not validate, to read the replacement it declares',
|
|
257
|
+
sites: ['importDocModule', 'require(@babel/parser)'],
|
|
258
|
+
},
|
|
259
|
+
'foundation/discovery/theme-discovery.mjs': {
|
|
260
|
+
runs: '@babel/parser and jscodeshift (CommonJS), each on first use, so listing bundled themes never loads jscodeshift',
|
|
261
|
+
sites: ['require(@babel/parser)', 'require(jscodeshift)'],
|
|
262
|
+
},
|
|
263
|
+
'foundation/doc-compiler/import.mjs': {
|
|
264
|
+
runs: 'authored doc files, for the doc reader and discovery',
|
|
265
|
+
sites: [
|
|
266
|
+
'.import()',
|
|
267
|
+
'createJiti',
|
|
268
|
+
'import(<computed>) ×2',
|
|
269
|
+
'importUserModule',
|
|
270
|
+
],
|
|
271
|
+
},
|
|
272
|
+
'foundation/doc-compiler/read.mjs': {
|
|
273
|
+
runs: 'authored doc files, each one handed to the compiler, with the loader its reader has always used',
|
|
274
|
+
sites: ['importDocModule', 'importNativeModule ×3', 'importTemplateModule'],
|
|
275
|
+
},
|
|
276
|
+
'foundation/fs/module-loader.mjs': {
|
|
277
|
+
runs: 'user modules (config, manifests, handlers, codemods): natively, through jiti, or by require for a fresh CommonJS read',
|
|
278
|
+
sites: [
|
|
279
|
+
'.import() ×2',
|
|
280
|
+
'createJiti ×2',
|
|
281
|
+
'import(<computed>)',
|
|
282
|
+
'require(<computed>)',
|
|
283
|
+
'require.cache',
|
|
284
|
+
],
|
|
285
|
+
},
|
|
286
|
+
'foundation/fs/paths.mjs': {
|
|
287
|
+
runs: "Yarn's pnpapi, under Plug'n'Play, to find an installed package",
|
|
288
|
+
sites: ['require(pnpapi)'],
|
|
289
|
+
},
|
|
290
|
+
'foundation/integrations/integrations.mjs': {
|
|
291
|
+
runs: "each installed integration's manifest",
|
|
292
|
+
sites: ['importUserModule'],
|
|
293
|
+
},
|
|
294
|
+
'scripts/generate-cli-readme.mjs': {
|
|
295
|
+
runs: '`astryx manifest` and the error-code and response-type docs, to write the README tables (build time, not shipped)',
|
|
296
|
+
sites: ['child_process.spawnSync', 'import(<computed>)'],
|
|
297
|
+
},
|
|
298
|
+
'scripts/sync-api-types.mjs': {
|
|
299
|
+
runs: '`tsc`, to emit the API declarations (build time, not shipped)',
|
|
300
|
+
sites: ['child_process.execFileSync'],
|
|
301
|
+
},
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Modules that hand back whatever module or doc they are pointed at. Every
|
|
306
|
+
* export is a loader except those named here, which hand back no module.
|
|
307
|
+
* @type {Record<string, string[]>}
|
|
308
|
+
*/
|
|
309
|
+
const LOADER_MODULES = {
|
|
310
|
+
'foundation/doc-compiler/import.mjs': [],
|
|
311
|
+
'foundation/fs/module-loader.mjs': ['findPresentFiles'],
|
|
312
|
+
// loadTopicModule hands back a topic's raw doc.
|
|
313
|
+
'foundation/discovery/docs-discovery.mjs': [
|
|
314
|
+
'BUILTIN_DOCS_PACKAGE',
|
|
315
|
+
'DocsCatalog',
|
|
316
|
+
'discoverBuiltinTopics',
|
|
317
|
+
'discoverIntegrationDocs',
|
|
318
|
+
'GRAPH_BLOCK_TYPES',
|
|
319
|
+
'GRAPH_ONLY_FIELDS',
|
|
320
|
+
'mergeTopic',
|
|
321
|
+
'problemsInTopic',
|
|
322
|
+
'withSourceTitle',
|
|
323
|
+
],
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Built-ins that run code. Every export counts except those named here.
|
|
328
|
+
* @type {Record<string, string[]>}
|
|
329
|
+
*/
|
|
330
|
+
const RUNNER_BUILTINS = {
|
|
331
|
+
child_process: [],
|
|
332
|
+
cluster: [],
|
|
333
|
+
inspector: [],
|
|
334
|
+
'inspector/promises': [],
|
|
335
|
+
module: [
|
|
336
|
+
'builtinModules',
|
|
337
|
+
'constants',
|
|
338
|
+
'enableCompileCache',
|
|
339
|
+
'findPackageJSON',
|
|
340
|
+
'findSourceMap',
|
|
341
|
+
'flushCompileCache',
|
|
342
|
+
'getCompileCacheDir',
|
|
343
|
+
'isBuiltin',
|
|
344
|
+
'SourceMap',
|
|
345
|
+
'stripTypeScriptTypes',
|
|
346
|
+
],
|
|
347
|
+
repl: [],
|
|
348
|
+
test: [],
|
|
349
|
+
vm: [],
|
|
350
|
+
worker_threads: [
|
|
351
|
+
'BroadcastChannel',
|
|
352
|
+
'isMainThread',
|
|
353
|
+
'MessageChannel',
|
|
354
|
+
'MessagePort',
|
|
355
|
+
'parentPort',
|
|
356
|
+
'threadId',
|
|
357
|
+
'workerData',
|
|
358
|
+
],
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
/** Packages whose every export runs modules. */
|
|
362
|
+
const RUNNER_PACKAGES = new Set(['jiti']);
|
|
363
|
+
|
|
364
|
+
/** Globals that run code, and the objects they can be read from. */
|
|
365
|
+
const RUNNER_GLOBALS = new Set([
|
|
366
|
+
'eval',
|
|
367
|
+
'Function',
|
|
368
|
+
'ShadowRealm',
|
|
369
|
+
'SharedWorker',
|
|
370
|
+
'Worker',
|
|
371
|
+
]);
|
|
372
|
+
const GLOBAL_OBJECTS = new Set(['globalThis', 'global', 'self', 'window']);
|
|
373
|
+
|
|
374
|
+
/** Members of `process` that run code. */
|
|
375
|
+
const PROCESS_RUNNERS = new Set([
|
|
376
|
+
'binding',
|
|
377
|
+
'dlopen',
|
|
378
|
+
'execve',
|
|
379
|
+
'getBuiltinModule',
|
|
380
|
+
]);
|
|
381
|
+
|
|
382
|
+
/** Members of `WebAssembly` that run code. */
|
|
383
|
+
const WASM_RUNNERS = new Set([
|
|
384
|
+
'compile',
|
|
385
|
+
'compileStreaming',
|
|
386
|
+
'Instance',
|
|
387
|
+
'instantiate',
|
|
388
|
+
'instantiateStreaming',
|
|
389
|
+
'Module',
|
|
390
|
+
]);
|
|
391
|
+
|
|
392
|
+
/** URL schemes that carry code rather than name a file in this package. */
|
|
393
|
+
const CODE_URL = /^(?:data|blob|https?):/;
|
|
394
|
+
|
|
395
|
+
/** Methods that run code whatever they are called on: jiti's, vm's, a realm's. */
|
|
396
|
+
const RUNNER_METHODS = new Set([
|
|
397
|
+
'compileFunction',
|
|
398
|
+
'evalModule',
|
|
399
|
+
'import',
|
|
400
|
+
'importValue',
|
|
401
|
+
'runInContext',
|
|
402
|
+
'runInNewContext',
|
|
403
|
+
'runInThisContext',
|
|
404
|
+
]);
|
|
405
|
+
|
|
406
|
+
/** A doc file specifier: `x.doc.mjs`, `x.doc.ts`, `x.doc.zh.mjs`, `x.doc`. */
|
|
407
|
+
const DOC_FILE = /\.doc(?:\.[\w-]+)?(?:\.[cm]?[jt]sx?)?$/;
|
|
408
|
+
|
|
409
|
+
/** Syntax that only types; it never runs. */
|
|
410
|
+
const TYPE_ONLY = new Set([
|
|
411
|
+
'TSDeclareFunction',
|
|
412
|
+
'TSDeclareMethod',
|
|
413
|
+
'TSInterfaceDeclaration',
|
|
414
|
+
'TSTypeAliasDeclaration',
|
|
415
|
+
'TSTypeAnnotation',
|
|
416
|
+
'TSTypeParameterDeclaration',
|
|
417
|
+
'TSTypeParameterInstantiation',
|
|
418
|
+
]);
|
|
419
|
+
|
|
420
|
+
/** Values whose `.constructor` reaches Function in a step or two. */
|
|
421
|
+
const LITERALS = new Set([
|
|
422
|
+
'ArrayExpression',
|
|
423
|
+
'ArrowFunctionExpression',
|
|
424
|
+
'ClassExpression',
|
|
425
|
+
'FunctionExpression',
|
|
426
|
+
'NumericLiteral',
|
|
427
|
+
'ObjectExpression',
|
|
428
|
+
'RegExpLiteral',
|
|
429
|
+
'StringLiteral',
|
|
430
|
+
'TemplateLiteral',
|
|
431
|
+
]);
|
|
432
|
+
|
|
433
|
+
/** A code file Node or jiti can run. */
|
|
434
|
+
const CODE_FILE = /\.(?:[cm]?[jt]s|[jt]sx)$/;
|
|
435
|
+
/** Directories that hold no code the CLI runs as itself, by name... */
|
|
436
|
+
const UNSCANNED_NAMES = new Set(['node_modules', '__fixtures__', '__tests__']);
|
|
437
|
+
/**
|
|
438
|
+
* ...and the temporary folders tests create with mkdtemp (`__<prefix>_XXXXXX`)
|
|
439
|
+
* and delete while this scan runs. Any other `__*` folder is scanned.
|
|
440
|
+
*/
|
|
441
|
+
const TRANSIENT_DIR = /^__[a-z][a-z_]*_[A-Za-z0-9]{6}$/;
|
|
442
|
+
/** ...and by path: tests, and the authored docs and templates. */
|
|
443
|
+
const UNSCANNED_PATHS = new Set([
|
|
444
|
+
'assets/docs',
|
|
445
|
+
'assets/templates',
|
|
446
|
+
'test',
|
|
447
|
+
'test-utils',
|
|
448
|
+
]);
|
|
449
|
+
|
|
450
|
+
/** @param {string} full @returns {string} */
|
|
451
|
+
const relOf = full => path.relative(CLI_ROOT, full).split(path.sep).join('/');
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* Every code file under `dir` the CLI can run as itself: not a test, fixture,
|
|
455
|
+
* dependency, generated `.d.mts`, or file in a hidden (transient) directory.
|
|
456
|
+
* @param {string} dir
|
|
457
|
+
* @returns {string[]}
|
|
458
|
+
*/
|
|
459
|
+
function sources(dir) {
|
|
460
|
+
return fs.readdirSync(dir, {withFileTypes: true}).flatMap(entry => {
|
|
461
|
+
const full = path.join(dir, entry.name);
|
|
462
|
+
if (entry.isDirectory()) {
|
|
463
|
+
const skip =
|
|
464
|
+
entry.name.startsWith('.') ||
|
|
465
|
+
TRANSIENT_DIR.test(entry.name) ||
|
|
466
|
+
UNSCANNED_NAMES.has(entry.name) ||
|
|
467
|
+
UNSCANNED_PATHS.has(relOf(full));
|
|
468
|
+
return skip ? [] : sources(full);
|
|
469
|
+
}
|
|
470
|
+
const code =
|
|
471
|
+
CODE_FILE.test(entry.name) &&
|
|
472
|
+
!/\.test\.[^.]+$/.test(entry.name) &&
|
|
473
|
+
!entry.name.endsWith('.d.mts');
|
|
474
|
+
return code ? [full] : [];
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* @param {string} source
|
|
480
|
+
* @param {string} rel
|
|
481
|
+
*/
|
|
482
|
+
function parseModule(source, rel) {
|
|
483
|
+
const typescript = /\.[cm]?tsx?$/.test(rel);
|
|
484
|
+
/** @type {import('@babel/parser').ParserPlugin[]} */
|
|
485
|
+
const plugins = typescript
|
|
486
|
+
? [['typescript', {dts: /\.d\.[cm]?ts$/.test(rel)}]]
|
|
487
|
+
: [];
|
|
488
|
+
if (!typescript || rel.endsWith('x')) plugins.push('jsx');
|
|
489
|
+
return parse(source, {
|
|
490
|
+
sourceType: rel.endsWith('.cjs') ? 'script' : 'module',
|
|
491
|
+
allowReturnOutsideFunction: rel.endsWith('.cjs'),
|
|
492
|
+
plugins,
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* Visit every node that runs, depth first, with its ancestors.
|
|
498
|
+
* @param {any} node
|
|
499
|
+
* @param {any[]} ancestors
|
|
500
|
+
* @param {(node: any, ancestors: any[]) => void} visit
|
|
501
|
+
*/
|
|
502
|
+
function walk(node, ancestors, visit) {
|
|
503
|
+
if (typeof node?.type !== 'string') return;
|
|
504
|
+
if (TYPE_ONLY.has(node.type) || isTSType(node) || node.declare) return;
|
|
505
|
+
if (node.importKind === 'type' || node.exportKind === 'type') return;
|
|
506
|
+
visit(node, ancestors);
|
|
507
|
+
ancestors.push(node);
|
|
508
|
+
for (const key of VISITOR_KEYS[node.type] ?? []) {
|
|
509
|
+
for (const child of [node[key]].flat()) walk(child, ancestors, visit);
|
|
510
|
+
}
|
|
511
|
+
ancestors.pop();
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/** @param {any} node @returns {string | undefined} */
|
|
515
|
+
function stringValue(node) {
|
|
516
|
+
if (node?.type === 'StringLiteral') return node.value;
|
|
517
|
+
if (node?.type === 'TemplateLiteral' && node.expressions.length === 0) {
|
|
518
|
+
return node.quasis[0].value.cooked;
|
|
519
|
+
}
|
|
520
|
+
return undefined;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
/** @param {any} node @returns {string} */
|
|
524
|
+
const nameOf = node => (node.type === 'Identifier' ? node.name : node.value);
|
|
525
|
+
|
|
526
|
+
/** @param {any} node @returns {boolean} */
|
|
527
|
+
const isMember = node =>
|
|
528
|
+
node?.type === 'MemberExpression' ||
|
|
529
|
+
node?.type === 'OptionalMemberExpression';
|
|
530
|
+
|
|
531
|
+
/** @param {any} node @returns {boolean} */
|
|
532
|
+
const isCall = node =>
|
|
533
|
+
node?.type === 'CallExpression' ||
|
|
534
|
+
node?.type === 'OptionalCallExpression' ||
|
|
535
|
+
node?.type === 'NewExpression';
|
|
536
|
+
|
|
537
|
+
/** @param {any} member @returns {string | undefined} */
|
|
538
|
+
const memberName = member =>
|
|
539
|
+
member.computed ? stringValue(member.property) : member.property.name;
|
|
540
|
+
|
|
541
|
+
/** @param {string} specifier */
|
|
542
|
+
const withoutQuery = specifier => specifier.replace(/[?#].*$/, '');
|
|
543
|
+
|
|
544
|
+
/**
|
|
545
|
+
* @typedef {object} Target
|
|
546
|
+
* @property {'doc' | 'url' | 'unscanned' | 'loader' | 'builtin' | 'package'}
|
|
547
|
+
* kind — `url`: code carried by the specifier or fetched, not a file in this
|
|
548
|
+
* package; `unscanned`: a file in a part of the package this test does not
|
|
549
|
+
* scan
|
|
550
|
+
* @property {string} key
|
|
551
|
+
*/
|
|
552
|
+
|
|
553
|
+
/**
|
|
554
|
+
* What an import specifier names, seen from `rel`, when it matters here.
|
|
555
|
+
* @param {string} specifier
|
|
556
|
+
* @param {string} rel
|
|
557
|
+
* @returns {Target | null}
|
|
558
|
+
*/
|
|
559
|
+
function targetOf(specifier, rel) {
|
|
560
|
+
if (CODE_URL.test(specifier)) return {kind: 'url', key: specifier};
|
|
561
|
+
let file = withoutQuery(specifier);
|
|
562
|
+
const local = /^(?:\.|\/|file:)/.test(file);
|
|
563
|
+
if (local) {
|
|
564
|
+
try {
|
|
565
|
+
file = decodeURIComponent(file);
|
|
566
|
+
} catch {
|
|
567
|
+
// A malformed escape names no file; compare it as written.
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
if (DOC_FILE.test(file)) return {kind: 'doc', key: specifier};
|
|
571
|
+
if (isBuiltin(file)) {
|
|
572
|
+
const name = file.replace(/^node:/, '');
|
|
573
|
+
return Object.hasOwn(RUNNER_BUILTINS, name)
|
|
574
|
+
? {kind: 'builtin', key: name}
|
|
575
|
+
: null;
|
|
576
|
+
}
|
|
577
|
+
if (local) {
|
|
578
|
+
const resolved = file.startsWith('.')
|
|
579
|
+
? path.posix.join(path.posix.dirname(rel), file)
|
|
580
|
+
: file.replace(/^file:\/\//, '');
|
|
581
|
+
// Extension-free and case-free: TypeScript and case-insensitive disks
|
|
582
|
+
// resolve both.
|
|
583
|
+
/** @param {string} p */
|
|
584
|
+
const stem = p =>
|
|
585
|
+
p.toLowerCase().replace(/(?:\/index)?\.[cm]?[jt]sx?$/, '');
|
|
586
|
+
const loader = Object.keys(LOADER_MODULES).find(
|
|
587
|
+
mod =>
|
|
588
|
+
stem(resolved) === stem(mod) ||
|
|
589
|
+
stem(resolved).endsWith(`/${stem(mod)}`),
|
|
590
|
+
);
|
|
591
|
+
if (loader) return {kind: 'loader', key: loader};
|
|
592
|
+
const unscanned =
|
|
593
|
+
[...UNSCANNED_PATHS].some(dir => resolved.startsWith(`${dir}/`)) ||
|
|
594
|
+
resolved
|
|
595
|
+
.split('/')
|
|
596
|
+
.slice(0, -1)
|
|
597
|
+
.some(
|
|
598
|
+
part =>
|
|
599
|
+
UNSCANNED_NAMES.has(part) ||
|
|
600
|
+
TRANSIENT_DIR.test(part) ||
|
|
601
|
+
(part.startsWith('.') && part !== '..'),
|
|
602
|
+
) ||
|
|
603
|
+
/\.test\.[^./]+$/.test(resolved);
|
|
604
|
+
return unscanned ? {kind: 'unscanned', key: specifier} : null;
|
|
605
|
+
}
|
|
606
|
+
const pkg = file
|
|
607
|
+
.split('/')
|
|
608
|
+
.slice(0, file.startsWith('@') ? 2 : 1)
|
|
609
|
+
.join('/');
|
|
610
|
+
return RUNNER_PACKAGES.has(pkg) ? {kind: 'package', key: pkg} : null;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
/** Targets whose every export is code this test does not read. */
|
|
614
|
+
const UNREAD = new Set(['doc', 'unscanned', 'url']);
|
|
615
|
+
|
|
616
|
+
/** @param {Target} target @returns {string[]} */
|
|
617
|
+
function inertExports(target) {
|
|
618
|
+
if (target.kind === 'loader') return LOADER_MODULES[target.key];
|
|
619
|
+
if (target.kind === 'builtin') return RUNNER_BUILTINS[target.key];
|
|
620
|
+
return [];
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
/**
|
|
624
|
+
* Every ordinary place `source` runs code outside its static imports, as
|
|
625
|
+
* labels (deliberate obfuscation is out of scope, as the file header says):
|
|
626
|
+
*
|
|
627
|
+
* - a static import or re-export of a doc file, or of a file this test does
|
|
628
|
+
* not scan;
|
|
629
|
+
* - a use of a loader module's export under any local name: called, passed,
|
|
630
|
+
* re-exported, or read off a namespace;
|
|
631
|
+
* - `import()` of a computed specifier, a doc file, a loader module, or a
|
|
632
|
+
* built-in or package that runs code;
|
|
633
|
+
* - a require function, CommonJS or from createRequire, called or passed on
|
|
634
|
+
* (resolving is not running);
|
|
635
|
+
* - an export of jiti, or a code-running export of child_process, cluster,
|
|
636
|
+
* inspector, module, repl, test, vm, or worker_threads, imported or
|
|
637
|
+
* fetched with getBuiltinModule;
|
|
638
|
+
* - eval, Function (also as any called `.constructor`), ShadowRealm, a
|
|
639
|
+
* global Worker, `module.require`; a jiti, vm, or realm method on anything;
|
|
640
|
+
* - a doc file resolved as a module.
|
|
641
|
+
*
|
|
642
|
+
* Names are matched without scope analysis, so a shadowing local cannot hide
|
|
643
|
+
* a loader; it can only add a site.
|
|
644
|
+
* @param {string} source
|
|
645
|
+
* @param {string} rel path under packages/cli
|
|
646
|
+
* @returns {{sites: string[], problems: string[]}}
|
|
647
|
+
*/
|
|
648
|
+
function scanModule(source, rel) {
|
|
649
|
+
const program = parseModule(source, rel).program;
|
|
650
|
+
/** @type {string[]} */
|
|
651
|
+
const sites = [];
|
|
652
|
+
/** @type {string[]} */
|
|
653
|
+
const problems = [];
|
|
654
|
+
/** @type {Map<string, {target: Target, imported: string | null}>} */
|
|
655
|
+
const bindings = new Map();
|
|
656
|
+
/** Local names holding a require function; CommonJS has `require`. */
|
|
657
|
+
const requires = new Set(['require']);
|
|
658
|
+
/** Local names holding the global object. */
|
|
659
|
+
const globals = new Set(GLOBAL_OBJECTS);
|
|
660
|
+
|
|
661
|
+
walk(program, [], node => {
|
|
662
|
+
if (node.type === 'ImportDeclaration') {
|
|
663
|
+
const target = targetOf(node.source.value, rel);
|
|
664
|
+
if (!target || UNREAD.has(target.kind)) return;
|
|
665
|
+
for (const specifier of node.specifiers) {
|
|
666
|
+
if (specifier.importKind === 'type') continue;
|
|
667
|
+
const imported =
|
|
668
|
+
specifier.type === 'ImportSpecifier'
|
|
669
|
+
? nameOf(specifier.imported)
|
|
670
|
+
: null;
|
|
671
|
+
bindings.set(specifier.local.name, {
|
|
672
|
+
target,
|
|
673
|
+
imported: imported === 'default' ? null : imported,
|
|
674
|
+
});
|
|
675
|
+
}
|
|
676
|
+
} else if (
|
|
677
|
+
node.type === 'TSImportEqualsDeclaration' &&
|
|
678
|
+
node.moduleReference.type === 'TSExternalModuleReference'
|
|
679
|
+
) {
|
|
680
|
+
const target = targetOf(node.moduleReference.expression.value, rel);
|
|
681
|
+
if (target && !UNREAD.has(target.kind)) {
|
|
682
|
+
bindings.set(node.id.name, {target, imported: null});
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
});
|
|
686
|
+
|
|
687
|
+
/** @param {any} expr `createRequire` from `module`, under any name */
|
|
688
|
+
const isCreateRequire = expr => {
|
|
689
|
+
const id = isMember(expr) ? expr.object : expr;
|
|
690
|
+
const binding = id?.type === 'Identifier' ? bindings.get(id.name) : null;
|
|
691
|
+
if (binding?.target.kind !== 'builtin' || binding.target.key !== 'module') {
|
|
692
|
+
return false;
|
|
693
|
+
}
|
|
694
|
+
return isMember(expr)
|
|
695
|
+
? binding.imported === null && memberName(expr) === 'createRequire'
|
|
696
|
+
: binding.imported === 'createRequire';
|
|
697
|
+
};
|
|
698
|
+
// A name bound to a require function or to the global object stands in for
|
|
699
|
+
// it, and is bound before it is used.
|
|
700
|
+
walk(program, [], node => {
|
|
701
|
+
const [id, value] =
|
|
702
|
+
node.type === 'VariableDeclarator'
|
|
703
|
+
? [node.id, node.init]
|
|
704
|
+
: node.type === 'AssignmentExpression'
|
|
705
|
+
? [node.left, node.right]
|
|
706
|
+
: [];
|
|
707
|
+
if (id?.type !== 'Identifier') return;
|
|
708
|
+
if (isCall(value) && isCreateRequire(value.callee)) requires.add(id.name);
|
|
709
|
+
if (value?.type === 'Identifier' && globals.has(value.name)) {
|
|
710
|
+
globals.add(id.name);
|
|
711
|
+
}
|
|
712
|
+
});
|
|
713
|
+
|
|
714
|
+
/**
|
|
715
|
+
* `const {eval: e} = globalThis`: taking the member is the site, whatever
|
|
716
|
+
* the local name.
|
|
717
|
+
* @param {any} pattern
|
|
718
|
+
* @param {ReadonlySet<string>} runners
|
|
719
|
+
* @param {string} owner
|
|
720
|
+
*/
|
|
721
|
+
const destructured = (pattern, runners, owner) => {
|
|
722
|
+
if (pattern?.type !== 'ObjectPattern') return;
|
|
723
|
+
for (const property of pattern.properties) {
|
|
724
|
+
const name =
|
|
725
|
+
property.type === 'ObjectProperty'
|
|
726
|
+
? property.computed
|
|
727
|
+
? stringValue(property.key)
|
|
728
|
+
: nameOf(property.key)
|
|
729
|
+
: undefined;
|
|
730
|
+
if (name === undefined) sites.push(`${owner}[<computed>]`);
|
|
731
|
+
else if (runners.has(name)) sites.push(name);
|
|
732
|
+
}
|
|
733
|
+
};
|
|
734
|
+
|
|
735
|
+
/** @param {any} node */
|
|
736
|
+
const docResolve = node => {
|
|
737
|
+
const specifier = stringValue(node);
|
|
738
|
+
if (specifier !== undefined && DOC_FILE.test(withoutQuery(specifier))) {
|
|
739
|
+
sites.push(`resolve(${specifier})`);
|
|
740
|
+
}
|
|
741
|
+
};
|
|
742
|
+
|
|
743
|
+
/** @param {any} call */
|
|
744
|
+
const requireCall = call => {
|
|
745
|
+
const specifier = stringValue(call.arguments[0]);
|
|
746
|
+
if (
|
|
747
|
+
specifier !== undefined &&
|
|
748
|
+
isBuiltin(specifier) &&
|
|
749
|
+
!targetOf(specifier, rel)
|
|
750
|
+
) {
|
|
751
|
+
return;
|
|
752
|
+
}
|
|
753
|
+
sites.push(`require(${specifier ?? '<computed>'})`);
|
|
754
|
+
};
|
|
755
|
+
|
|
756
|
+
/**
|
|
757
|
+
* `process.getBuiltinModule`: a built-in without an import.
|
|
758
|
+
* @param {any} call
|
|
759
|
+
*/
|
|
760
|
+
const builtinGetter = call => {
|
|
761
|
+
const specifier = stringValue(call.arguments[0]);
|
|
762
|
+
if (specifier === undefined) sites.push('getBuiltinModule(<computed>)');
|
|
763
|
+
else if (targetOf(specifier, rel)) {
|
|
764
|
+
sites.push(`getBuiltinModule(${specifier})`);
|
|
765
|
+
}
|
|
766
|
+
};
|
|
767
|
+
|
|
768
|
+
/**
|
|
769
|
+
* `createRequire`: a site unless the require function it makes is kept by
|
|
770
|
+
* name (tracked above), only resolves, or runs right here.
|
|
771
|
+
* @param {any} ref
|
|
772
|
+
* @param {any[]} ancestors
|
|
773
|
+
*/
|
|
774
|
+
const createRequireSite = (ref, ancestors) => {
|
|
775
|
+
const call = ancestors.at(-1);
|
|
776
|
+
const up = ancestors.at(-2);
|
|
777
|
+
const kept =
|
|
778
|
+
(up?.type === 'VariableDeclarator' && up.init === call) ||
|
|
779
|
+
(up?.type === 'AssignmentExpression' && up.right === call);
|
|
780
|
+
if (!isCall(call) || call.callee !== ref) sites.push('createRequire');
|
|
781
|
+
else if (isCall(up) && up.callee === call) requireCall(up);
|
|
782
|
+
else if (isMember(up) && up.object === call) {
|
|
783
|
+
if (memberName(up) !== 'resolve') sites.push('createRequire');
|
|
784
|
+
} else if (!kept || (up.id ?? up.left).type !== 'Identifier') {
|
|
785
|
+
sites.push('createRequire');
|
|
786
|
+
}
|
|
787
|
+
};
|
|
788
|
+
|
|
789
|
+
/**
|
|
790
|
+
* `loadModuleWithParser`: the parser it is handed says what it loads.
|
|
791
|
+
* @param {any} ref
|
|
792
|
+
* @param {any[]} ancestors
|
|
793
|
+
*/
|
|
794
|
+
const parserSite = (ref, ancestors) => {
|
|
795
|
+
const call = ancestors.at(-1);
|
|
796
|
+
if (!isCall(call) || call.callee !== ref) {
|
|
797
|
+
sites.push('loadModuleWithParser');
|
|
798
|
+
return;
|
|
799
|
+
}
|
|
800
|
+
const parser = call.arguments[1];
|
|
801
|
+
const text = parser
|
|
802
|
+
? source.slice(parser.start, parser.end).replace(/\s+/g, ' ')
|
|
803
|
+
: '';
|
|
804
|
+
sites.push(`loadModuleWithParser(..., ${text})`);
|
|
805
|
+
let docParser = false;
|
|
806
|
+
walk(parser, [], node => {
|
|
807
|
+
const name = node.type === 'Identifier' ? node.name : stringValue(node);
|
|
808
|
+
docParser ||= name !== undefined && DOC_PARSERS.has(name);
|
|
809
|
+
});
|
|
810
|
+
if (docParser) {
|
|
811
|
+
problems.push(
|
|
812
|
+
`${rel}: loadModuleWithParser(..., ${text}) loads a doc outside the compiler`,
|
|
813
|
+
);
|
|
814
|
+
}
|
|
815
|
+
};
|
|
816
|
+
|
|
817
|
+
/**
|
|
818
|
+
* @param {Target} target
|
|
819
|
+
* @param {string} name the export used
|
|
820
|
+
* @param {any} ref the expression that reads it
|
|
821
|
+
* @param {any[]} ancestors
|
|
822
|
+
*/
|
|
823
|
+
const exportSite = (target, name, ref, ancestors) => {
|
|
824
|
+
if (inertExports(target).includes(name)) return;
|
|
825
|
+
if (target.key === 'module' && name === 'createRequire') {
|
|
826
|
+
createRequireSite(ref, ancestors);
|
|
827
|
+
} else if (target.kind === 'loader' && name === 'loadModuleWithParser') {
|
|
828
|
+
parserSite(ref, ancestors);
|
|
829
|
+
} else {
|
|
830
|
+
sites.push(target.kind === 'builtin' ? `${target.key}.${name}` : name);
|
|
831
|
+
}
|
|
832
|
+
};
|
|
833
|
+
|
|
834
|
+
/**
|
|
835
|
+
* @param {any} id an identifier that is read
|
|
836
|
+
* @param {any[]} ancestors
|
|
837
|
+
*/
|
|
838
|
+
const reference = (id, ancestors) => {
|
|
839
|
+
const parent = ancestors.at(-1);
|
|
840
|
+
const binding = bindings.get(id.name);
|
|
841
|
+
if (binding) {
|
|
842
|
+
const {target, imported} = binding;
|
|
843
|
+
if (imported !== null) {
|
|
844
|
+
exportSite(target, imported, id, ancestors);
|
|
845
|
+
} else if (isMember(parent) && parent.object === id) {
|
|
846
|
+
const name = memberName(parent);
|
|
847
|
+
if (name === undefined) sites.push(`${target.key}[<computed>]`);
|
|
848
|
+
else exportSite(target, name, parent, ancestors.slice(0, -1));
|
|
849
|
+
} else {
|
|
850
|
+
sites.push(target.key);
|
|
851
|
+
}
|
|
852
|
+
} else if (requires.has(id.name)) {
|
|
853
|
+
if (isCall(parent) && parent.callee === id) requireCall(parent);
|
|
854
|
+
else if (!isMember(parent) || parent.object !== id) sites.push('require');
|
|
855
|
+
else if (memberName(parent) !== 'resolve') {
|
|
856
|
+
sites.push(`require.${memberName(parent) ?? '<computed>'}`);
|
|
857
|
+
}
|
|
858
|
+
} else if (id.name === 'getBuiltinModule') {
|
|
859
|
+
if (isCall(parent) && parent.callee === id) builtinGetter(parent);
|
|
860
|
+
else sites.push('getBuiltinModule');
|
|
861
|
+
} else if (
|
|
862
|
+
RUNNER_GLOBALS.has(id.name) &&
|
|
863
|
+
!(parent.operator === 'instanceof' && parent.right === id)
|
|
864
|
+
) {
|
|
865
|
+
sites.push(id.name);
|
|
866
|
+
}
|
|
867
|
+
};
|
|
868
|
+
|
|
869
|
+
walk(program, [], (node, ancestors) => {
|
|
870
|
+
switch (node.type) {
|
|
871
|
+
case 'VariableDeclarator':
|
|
872
|
+
case 'AssignmentExpression': {
|
|
873
|
+
const [id, value] =
|
|
874
|
+
node.type === 'VariableDeclarator'
|
|
875
|
+
? [node.id, node.init]
|
|
876
|
+
: [node.left, node.right];
|
|
877
|
+
if (value?.type !== 'Identifier') return;
|
|
878
|
+
if (globals.has(value.name))
|
|
879
|
+
destructured(id, RUNNER_GLOBALS, value.name);
|
|
880
|
+
else if (value.name === 'process') {
|
|
881
|
+
destructured(id, PROCESS_RUNNERS, 'process');
|
|
882
|
+
}
|
|
883
|
+
return;
|
|
884
|
+
}
|
|
885
|
+
case 'ImportDeclaration':
|
|
886
|
+
case 'ExportAllDeclaration':
|
|
887
|
+
case 'ExportNamedDeclaration': {
|
|
888
|
+
if (!node.source) return;
|
|
889
|
+
const specifier = node.source.value;
|
|
890
|
+
const target = targetOf(specifier, rel);
|
|
891
|
+
if (!target) return;
|
|
892
|
+
if (UNREAD.has(target.kind)) {
|
|
893
|
+
const how =
|
|
894
|
+
node.type === 'ImportDeclaration' ? 'import' : 'export from';
|
|
895
|
+
sites.push(`${how} ${specifier}`);
|
|
896
|
+
} else if (node.type === 'ExportAllDeclaration') {
|
|
897
|
+
sites.push(`export * from ${specifier}`);
|
|
898
|
+
} else if (node.type === 'ExportNamedDeclaration') {
|
|
899
|
+
for (const s of node.specifiers) {
|
|
900
|
+
if (s.exportKind === 'type') continue;
|
|
901
|
+
const name = s.type === 'ExportSpecifier' ? nameOf(s.local) : '*';
|
|
902
|
+
if (!inertExports(target).includes(name)) {
|
|
903
|
+
sites.push(`export ${name} from ${specifier}`);
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
return;
|
|
908
|
+
}
|
|
909
|
+
case 'TSImportEqualsDeclaration': {
|
|
910
|
+
const ref = node.moduleReference;
|
|
911
|
+
if (ref.type !== 'TSExternalModuleReference') return;
|
|
912
|
+
const specifier = ref.expression.value;
|
|
913
|
+
const target = targetOf(specifier, rel);
|
|
914
|
+
if (target && UNREAD.has(target.kind)) {
|
|
915
|
+
sites.push(`import ${specifier}`);
|
|
916
|
+
} else if (target && node.isExport) {
|
|
917
|
+
sites.push(`export * from ${specifier}`);
|
|
918
|
+
}
|
|
919
|
+
return;
|
|
920
|
+
}
|
|
921
|
+
case 'ImportExpression':
|
|
922
|
+
case 'CallExpression':
|
|
923
|
+
case 'OptionalCallExpression': {
|
|
924
|
+
if (node.type === 'ImportExpression' || node.callee.type === 'Import') {
|
|
925
|
+
const specifier = stringValue(
|
|
926
|
+
node.type === 'ImportExpression' ? node.source : node.arguments[0],
|
|
927
|
+
);
|
|
928
|
+
if (specifier === undefined) sites.push('import(<computed>)');
|
|
929
|
+
else if (targetOf(specifier, rel)) sites.push(`import(${specifier})`);
|
|
930
|
+
return;
|
|
931
|
+
}
|
|
932
|
+
const callee = node.callee;
|
|
933
|
+
if (!isMember(callee)) return;
|
|
934
|
+
const name = memberName(callee);
|
|
935
|
+
const object = callee.object;
|
|
936
|
+
if (name === 'constructor') sites.push('.constructor()');
|
|
937
|
+
if (name === 'getBuiltinModule') builtinGetter(node);
|
|
938
|
+
if (
|
|
939
|
+
name === 'get' &&
|
|
940
|
+
object.type === 'Identifier' &&
|
|
941
|
+
object.name === 'Reflect'
|
|
942
|
+
) {
|
|
943
|
+
const key = stringValue(node.arguments[1]);
|
|
944
|
+
if (
|
|
945
|
+
key !== undefined &&
|
|
946
|
+
(RUNNER_GLOBALS.has(key) || PROCESS_RUNNERS.has(key))
|
|
947
|
+
) {
|
|
948
|
+
sites.push(key);
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
const resolver =
|
|
952
|
+
object.type === 'MetaProperty' ||
|
|
953
|
+
(object.type === 'Identifier' && requires.has(object.name)) ||
|
|
954
|
+
(isCall(object) && isCreateRequire(object.callee));
|
|
955
|
+
if (name === 'resolve' && resolver) docResolve(node.arguments[0]);
|
|
956
|
+
if (
|
|
957
|
+
name !== undefined &&
|
|
958
|
+
RUNNER_METHODS.has(name) &&
|
|
959
|
+
!(object.type === 'Identifier' && bindings.has(object.name))
|
|
960
|
+
) {
|
|
961
|
+
sites.push(`.${name}()`);
|
|
962
|
+
}
|
|
963
|
+
return;
|
|
964
|
+
}
|
|
965
|
+
case 'NewExpression':
|
|
966
|
+
if (node.callee.type === 'Identifier' && node.callee.name === 'URL') {
|
|
967
|
+
docResolve(node.arguments[0]);
|
|
968
|
+
}
|
|
969
|
+
return;
|
|
970
|
+
case 'MemberExpression':
|
|
971
|
+
case 'OptionalMemberExpression': {
|
|
972
|
+
const object = node.object;
|
|
973
|
+
const name = memberName(node);
|
|
974
|
+
const global =
|
|
975
|
+
(object.type === 'Identifier' && globals.has(object.name)) ||
|
|
976
|
+
(isMember(object) &&
|
|
977
|
+
object.object.type === 'Identifier' &&
|
|
978
|
+
globals.has(object.object.name));
|
|
979
|
+
if (name === 'constructor' && LITERALS.has(object.type)) {
|
|
980
|
+
sites.push('.constructor');
|
|
981
|
+
} else if (
|
|
982
|
+
WASM_RUNNERS.has(name ?? '') &&
|
|
983
|
+
((object.type === 'Identifier' && object.name === 'WebAssembly') ||
|
|
984
|
+
(isMember(object) && memberName(object) === 'WebAssembly'))
|
|
985
|
+
) {
|
|
986
|
+
sites.push(`WebAssembly.${name}`);
|
|
987
|
+
} else if (isMember(object)) {
|
|
988
|
+
return;
|
|
989
|
+
} else if (object.type !== 'Identifier' || bindings.has(object.name)) {
|
|
990
|
+
return;
|
|
991
|
+
} else if (object.name === 'module') {
|
|
992
|
+
if (name === 'require' || name === 'constructor') {
|
|
993
|
+
sites.push(`module.${name}`);
|
|
994
|
+
}
|
|
995
|
+
} else if (object.name === 'process') {
|
|
996
|
+
if (name !== 'getBuiltinModule' && PROCESS_RUNNERS.has(name ?? '')) {
|
|
997
|
+
sites.push(`process.${name}`);
|
|
998
|
+
}
|
|
999
|
+
} else if (global) {
|
|
1000
|
+
if (name === undefined) sites.push(`${object.name}[<computed>]`);
|
|
1001
|
+
else if (RUNNER_GLOBALS.has(name)) sites.push(name);
|
|
1002
|
+
}
|
|
1003
|
+
return;
|
|
1004
|
+
}
|
|
1005
|
+
case 'Identifier': {
|
|
1006
|
+
const parent = ancestors.at(-1);
|
|
1007
|
+
if (parent?.type === 'TSImportEqualsDeclaration') return;
|
|
1008
|
+
if (parent && isReferenced(node, parent, ancestors.at(-2))) {
|
|
1009
|
+
reference(node, ancestors);
|
|
1010
|
+
}
|
|
1011
|
+
return;
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
});
|
|
1015
|
+
|
|
1016
|
+
return {sites, problems};
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
/**
|
|
1020
|
+
* The names a module exports.
|
|
1021
|
+
* @param {string} source
|
|
1022
|
+
* @param {string} rel
|
|
1023
|
+
* @returns {string[]}
|
|
1024
|
+
*/
|
|
1025
|
+
function exportNames(source, rel) {
|
|
1026
|
+
return parseModule(source, rel).program.body.flatMap(
|
|
1027
|
+
(/** @type {any} */ node) => {
|
|
1028
|
+
if (node.type === 'ExportDefaultDeclaration') return ['default'];
|
|
1029
|
+
if (node.type !== 'ExportNamedDeclaration') return [];
|
|
1030
|
+
const declaration = node.declaration;
|
|
1031
|
+
return [
|
|
1032
|
+
...(declaration?.id ? [declaration.id.name] : []),
|
|
1033
|
+
...(declaration?.declarations ?? []).map(
|
|
1034
|
+
(/** @type {any} */ d) => d.id.name,
|
|
1035
|
+
),
|
|
1036
|
+
...node.specifiers.map((/** @type {any} */ s) => nameOf(s.exported)),
|
|
1037
|
+
];
|
|
1038
|
+
},
|
|
1039
|
+
);
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
/** @param {string} rel */
|
|
1043
|
+
const read = rel => fs.readFileSync(path.join(CLI_ROOT, rel), 'utf8');
|
|
1044
|
+
|
|
1045
|
+
/** Every doc kind's parser: the `parse*` exports under authoring/doctypes. */
|
|
1046
|
+
const DOC_PARSERS = new Set(
|
|
1047
|
+
sources(path.join(CLI_ROOT, 'authoring/doctypes')).flatMap(full =>
|
|
1048
|
+
exportNames(read(relOf(full)), relOf(full)).filter(name =>
|
|
1049
|
+
/^parse[A-Z]/.test(name),
|
|
1050
|
+
),
|
|
1051
|
+
),
|
|
1052
|
+
);
|
|
1053
|
+
|
|
1054
|
+
/**
|
|
1055
|
+
* A module's sites as the list writes them: sorted, repeats counted.
|
|
1056
|
+
* @param {string[]} sites
|
|
1057
|
+
* @returns {string[]}
|
|
1058
|
+
*/
|
|
1059
|
+
function tally(sites) {
|
|
1060
|
+
/** @type {Map<string, number>} */
|
|
1061
|
+
const counts = new Map();
|
|
1062
|
+
for (const site of sites) counts.set(site, (counts.get(site) ?? 0) + 1);
|
|
1063
|
+
return [...counts]
|
|
1064
|
+
.map(([site, n]) => (n === 1 ? site : `${site} ×${n}`))
|
|
1065
|
+
.sort();
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
/**
|
|
1069
|
+
* How one module breaks the list: sites other than its entry's, or a doc
|
|
1070
|
+
* parser handed to a generic loader, which no entry can allow.
|
|
1071
|
+
* @param {string} rel
|
|
1072
|
+
* @param {string} source
|
|
1073
|
+
* @param {readonly string[]} [entry] the listed sites
|
|
1074
|
+
* @returns {string[]}
|
|
1075
|
+
*/
|
|
1076
|
+
function violations(rel, source, entry = RUNNERS[rel]?.sites ?? []) {
|
|
1077
|
+
const {sites, problems} = scanModule(source, rel);
|
|
1078
|
+
const found = tally(sites);
|
|
1079
|
+
const listed = [...entry].sort();
|
|
1080
|
+
const same = found.join('\n') === listed.join('\n');
|
|
1081
|
+
return same
|
|
1082
|
+
? problems
|
|
1083
|
+
: [
|
|
1084
|
+
...problems,
|
|
1085
|
+
`${rel} runs [${found.join(', ')}], listed [${listed.join(', ')}]`,
|
|
1086
|
+
];
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
describe('every module that runs code outside its static imports is listed', () => {
|
|
1090
|
+
const scanned = sources(CLI_ROOT).map(relOf);
|
|
1091
|
+
|
|
1092
|
+
it('lists each one with exactly the sites it has', () => {
|
|
1093
|
+
const found = scanned.flatMap(rel => {
|
|
1094
|
+
try {
|
|
1095
|
+
return violations(rel, read(rel));
|
|
1096
|
+
} catch (error) {
|
|
1097
|
+
return [
|
|
1098
|
+
`${rel} does not parse: ${/** @type {Error} */ (error).message}`,
|
|
1099
|
+
];
|
|
1100
|
+
}
|
|
1101
|
+
});
|
|
1102
|
+
expect(found).toEqual([]);
|
|
1103
|
+
});
|
|
1104
|
+
|
|
1105
|
+
it('lists no module that is gone', () => {
|
|
1106
|
+
expect(Object.keys(RUNNERS).filter(rel => !scanned.includes(rel))).toEqual(
|
|
1107
|
+
[],
|
|
1108
|
+
);
|
|
1109
|
+
});
|
|
1110
|
+
|
|
1111
|
+
it('names only exports the loader modules still have', () => {
|
|
1112
|
+
const stale = Object.entries(LOADER_MODULES).flatMap(([rel, inert]) => {
|
|
1113
|
+
const names = exportNames(read(rel), rel);
|
|
1114
|
+
return inert
|
|
1115
|
+
.filter(name => !names.includes(name))
|
|
1116
|
+
.map(n => `${rel}#${n}`);
|
|
1117
|
+
});
|
|
1118
|
+
expect(stale).toEqual([]);
|
|
1119
|
+
});
|
|
1120
|
+
});
|
|
1121
|
+
|
|
1122
|
+
/** A module the list does not name. */
|
|
1123
|
+
const LEAF = 'api/leaf/leaf.mjs';
|
|
1124
|
+
const MODULE_LOADER = "'../../foundation/fs/module-loader.mjs'";
|
|
1125
|
+
const IMPORTERS = "'../../foundation/doc-compiler/import.mjs'";
|
|
1126
|
+
|
|
1127
|
+
/**
|
|
1128
|
+
* A listed module's real source, edited.
|
|
1129
|
+
* @param {string} rel
|
|
1130
|
+
* @param {(source: string) => string} edit
|
|
1131
|
+
*/
|
|
1132
|
+
const edited = (rel, edit) => {
|
|
1133
|
+
const code = edit(read(rel));
|
|
1134
|
+
if (code === read(rel)) throw new Error(`the edit to ${rel} changed nothing`);
|
|
1135
|
+
return {file: rel, code};
|
|
1136
|
+
};
|
|
1137
|
+
|
|
1138
|
+
/**
|
|
1139
|
+
* Ways to run a doc file (or any module) that the list must catch.
|
|
1140
|
+
* @type {Array<{name: string, file: string, code: string}>}
|
|
1141
|
+
*/
|
|
1142
|
+
const BYPASSES = [
|
|
1143
|
+
// The 23 cases from review of the first scan.
|
|
1144
|
+
['computed import(file)', 'const m = await import(file);'],
|
|
1145
|
+
[
|
|
1146
|
+
'importUserModule(docPath)',
|
|
1147
|
+
`import {importUserModule} from ${MODULE_LOADER};\nawait importUserModule(docPath);`,
|
|
1148
|
+
],
|
|
1149
|
+
[
|
|
1150
|
+
'a static import of a doc file',
|
|
1151
|
+
"import {doc} from './Button.doc.mjs';\nconsole.log(doc);",
|
|
1152
|
+
],
|
|
1153
|
+
[
|
|
1154
|
+
'a static default import of a doc file',
|
|
1155
|
+
"import doc from '../../foundation/x/Button.doc.mjs';",
|
|
1156
|
+
],
|
|
1157
|
+
[
|
|
1158
|
+
'a literal dynamic import of a doc file',
|
|
1159
|
+
"await import('/abs/node_modules/@acme/kit/components/Foo.doc.mjs');",
|
|
1160
|
+
],
|
|
1161
|
+
[
|
|
1162
|
+
'createRequire(...)(docPath)',
|
|
1163
|
+
"import {createRequire} from 'node:module';\ncreateRequire(import.meta.url)(docPath);",
|
|
1164
|
+
],
|
|
1165
|
+
[
|
|
1166
|
+
'require = createRequire(...); require(docPath)',
|
|
1167
|
+
"import {createRequire} from 'node:module';\nconst require = createRequire(import.meta.url);\nrequire(docPath);",
|
|
1168
|
+
],
|
|
1169
|
+
["eval('import(docPath)')", "await eval('import(docPath)');"],
|
|
1170
|
+
[
|
|
1171
|
+
"new Function('p', 'return import(p)')",
|
|
1172
|
+
"await new Function('p', 'return import(p)')(docPath);",
|
|
1173
|
+
],
|
|
1174
|
+
[
|
|
1175
|
+
"jiti['import'](docPath)",
|
|
1176
|
+
"import {createJiti} from 'jiti';\nawait createJiti(import.meta.url)['import'](docPath);",
|
|
1177
|
+
],
|
|
1178
|
+
[
|
|
1179
|
+
'a jiti instance called as require',
|
|
1180
|
+
"import {createJiti} from 'jiti';\nconst jiti = createJiti(import.meta.url);\njiti(docPath);",
|
|
1181
|
+
],
|
|
1182
|
+
[
|
|
1183
|
+
'loader.importUserModule(docPath) through a namespace import',
|
|
1184
|
+
`import * as loader from ${MODULE_LOADER};\nawait loader.importUserModule(docPath);`,
|
|
1185
|
+
],
|
|
1186
|
+
[
|
|
1187
|
+
'a renamed import of a loader',
|
|
1188
|
+
`import {importUserModule as load} from ${MODULE_LOADER};\nawait load(docPath);`,
|
|
1189
|
+
],
|
|
1190
|
+
[
|
|
1191
|
+
'files.map(importUserModule): a loader passed as a value',
|
|
1192
|
+
`import {importUserModule} from ${MODULE_LOADER};\nawait Promise.all(files.map(importUserModule));`,
|
|
1193
|
+
],
|
|
1194
|
+
[
|
|
1195
|
+
'importUserModule.call(null, docPath)',
|
|
1196
|
+
`import {importUserModule} from ${MODULE_LOADER};\nawait importUserModule.call(null, docPath);`,
|
|
1197
|
+
],
|
|
1198
|
+
[
|
|
1199
|
+
'a re-export of the doc importers',
|
|
1200
|
+
`export {importDocModule} from ${IMPORTERS};`,
|
|
1201
|
+
],
|
|
1202
|
+
[
|
|
1203
|
+
'a literal dynamic import of the doc importers',
|
|
1204
|
+
`const {importDocModule} = await import(${IMPORTERS});\nawait importDocModule(docPath);`,
|
|
1205
|
+
],
|
|
1206
|
+
[
|
|
1207
|
+
'loadModuleWithParser(file, parsers.parseDoc)',
|
|
1208
|
+
`import {loadModuleWithParser} from ${MODULE_LOADER};\nawait loadModuleWithParser(file, parsers.parseDoc);`,
|
|
1209
|
+
],
|
|
1210
|
+
[
|
|
1211
|
+
'loadModuleWithParser(file, x => parseDoc(x))',
|
|
1212
|
+
`import {loadModuleWithParser} from ${MODULE_LOADER};\nawait loadModuleWithParser(file, x => parseDoc(x));`,
|
|
1213
|
+
],
|
|
1214
|
+
[
|
|
1215
|
+
'new Worker(docPath)',
|
|
1216
|
+
"import {Worker} from 'node:worker_threads';\nnew Worker(docPath);",
|
|
1217
|
+
],
|
|
1218
|
+
[
|
|
1219
|
+
'child_process fork(docPath)',
|
|
1220
|
+
"import {fork} from 'node:child_process';\nfork(docPath);",
|
|
1221
|
+
],
|
|
1222
|
+
[
|
|
1223
|
+
'vm.runInThisContext(readFileSync(docPath))',
|
|
1224
|
+
"import vm from 'node:vm';\nimport fs from 'node:fs';\nvm.runInThisContext(fs.readFileSync(docPath, 'utf8'));",
|
|
1225
|
+
],
|
|
1226
|
+
[
|
|
1227
|
+
'import.meta.resolve of a doc file',
|
|
1228
|
+
"const url = import.meta.resolve('./Button.doc.mjs');",
|
|
1229
|
+
],
|
|
1230
|
+
// The same, reached another way.
|
|
1231
|
+
[
|
|
1232
|
+
'importUserModule.apply(null, [docPath])',
|
|
1233
|
+
`import {importUserModule} from ${MODULE_LOADER};\nawait importUserModule.apply(null, [docPath]);`,
|
|
1234
|
+
],
|
|
1235
|
+
[
|
|
1236
|
+
'a dynamic import of import.mjs, then m.importDocModule(docPath)',
|
|
1237
|
+
`const m = await import(${IMPORTERS});\nawait m.importDocModule(docPath);`,
|
|
1238
|
+
],
|
|
1239
|
+
['export * from import.mjs', `export * from ${IMPORTERS};`],
|
|
1240
|
+
[
|
|
1241
|
+
'a namespace of import.mjs passed on',
|
|
1242
|
+
`import * as importers from ${IMPORTERS};\nexport default importers;`,
|
|
1243
|
+
],
|
|
1244
|
+
[
|
|
1245
|
+
'a computed member of a loader namespace',
|
|
1246
|
+
`import * as loader from ${MODULE_LOADER};\nloader[name](docPath);`,
|
|
1247
|
+
],
|
|
1248
|
+
[
|
|
1249
|
+
'a loader re-exported by a local export',
|
|
1250
|
+
`import {importUserModule} from ${MODULE_LOADER};\nexport {importUserModule};`,
|
|
1251
|
+
],
|
|
1252
|
+
[
|
|
1253
|
+
'a loader in an object shorthand',
|
|
1254
|
+
`import {importUserModule} from ${MODULE_LOADER};\nexport const api = {importUserModule};`,
|
|
1255
|
+
],
|
|
1256
|
+
[
|
|
1257
|
+
'a loader behind a block-scoped shadow',
|
|
1258
|
+
`import {importUserModule as load} from ${MODULE_LOADER};\nexport function f(p) {\n { const load = () => {}; }\n return load(p);\n}`,
|
|
1259
|
+
],
|
|
1260
|
+
[
|
|
1261
|
+
'a loader import with a query string',
|
|
1262
|
+
"import {importDocModule} from '../../foundation/doc-compiler/import.mjs?v=1';\nimportDocModule(docPath);",
|
|
1263
|
+
],
|
|
1264
|
+
[
|
|
1265
|
+
'a loader import with an escaped character',
|
|
1266
|
+
"import {importDocModule} from '../../foundation/doc-compiler/%69mport.mjs';\nimportDocModule(docPath);",
|
|
1267
|
+
],
|
|
1268
|
+
[
|
|
1269
|
+
'a raw topic loader from discovery',
|
|
1270
|
+
"import {loadTopicModule} from '../../foundation/discovery/docs-discovery.mjs';\nawait loadTopicModule(docPath);",
|
|
1271
|
+
],
|
|
1272
|
+
['a side-effect import of a doc file', "import './Button.doc.mjs';"],
|
|
1273
|
+
[
|
|
1274
|
+
'a static import of a doc overlay',
|
|
1275
|
+
"import {docsZh} from './Button.doc.zh.mjs';",
|
|
1276
|
+
],
|
|
1277
|
+
['a re-export of a doc file', "export {doc} from './Button.doc.mjs';"],
|
|
1278
|
+
[
|
|
1279
|
+
'Module.createRequire(...)(docPath) through a default import',
|
|
1280
|
+
"import Module from 'node:module';\nModule.createRequire(import.meta.url)(docPath);",
|
|
1281
|
+
],
|
|
1282
|
+
[
|
|
1283
|
+
'a require function passed on',
|
|
1284
|
+
"import {createRequire} from 'node:module';\nconst r = createRequire(import.meta.url);\nconst q = r;\nq(docPath);",
|
|
1285
|
+
],
|
|
1286
|
+
[
|
|
1287
|
+
'module.register',
|
|
1288
|
+
"import {register} from 'node:module';\nregister('./hooks.mjs', import.meta.url);",
|
|
1289
|
+
],
|
|
1290
|
+
['(0, eval)(source)', '(0, eval)(source);'],
|
|
1291
|
+
['globalThis.eval(source)', 'globalThis.eval(source);'],
|
|
1292
|
+
["globalThis['ev' + 'al'](source)", "globalThis['ev' + 'al'](source);"],
|
|
1293
|
+
[
|
|
1294
|
+
"Function('return import(p)') without new",
|
|
1295
|
+
"Function('return import(p)')();",
|
|
1296
|
+
],
|
|
1297
|
+
[
|
|
1298
|
+
"import('node:child_process')",
|
|
1299
|
+
"const {fork} = await import('node:child_process');",
|
|
1300
|
+
],
|
|
1301
|
+
[
|
|
1302
|
+
"require('worker_threads').Worker",
|
|
1303
|
+
"new (require('worker_threads').Worker)(docPath);",
|
|
1304
|
+
],
|
|
1305
|
+
[
|
|
1306
|
+
'a default import of child_process',
|
|
1307
|
+
"import cp from 'child_process';\ncp.fork(docPath);",
|
|
1308
|
+
],
|
|
1309
|
+
['jiti.evalModule(source)', 'jiti.evalModule(source);'],
|
|
1310
|
+
[
|
|
1311
|
+
'process.getBuiltinModule, with no import',
|
|
1312
|
+
"process.getBuiltinModule('node:child_process').fork(docPath);",
|
|
1313
|
+
],
|
|
1314
|
+
[
|
|
1315
|
+
'getBuiltinModule taken off process',
|
|
1316
|
+
"const {getBuiltinModule} = process;\ngetBuiltinModule('node:vm');",
|
|
1317
|
+
],
|
|
1318
|
+
['a ShadowRealm', 'const realm = new ShadowRealm();'],
|
|
1319
|
+
["a realm's importValue", "await realm.importValue(docPath, 'doc');"],
|
|
1320
|
+
[
|
|
1321
|
+
"an async function's constructor",
|
|
1322
|
+
"await (async () => {}).constructor('return import(p)')();",
|
|
1323
|
+
],
|
|
1324
|
+
[
|
|
1325
|
+
'node:test running files',
|
|
1326
|
+
"import {run} from 'node:test';\nrun({files: [docPath]});",
|
|
1327
|
+
],
|
|
1328
|
+
[
|
|
1329
|
+
'a helper from a directory the scan skips',
|
|
1330
|
+
"import {runCli} from '../../test-utils/run-cli.mjs';\nrunCli(docPath);",
|
|
1331
|
+
],
|
|
1332
|
+
[
|
|
1333
|
+
'a static import of a data: URL',
|
|
1334
|
+
"import one from 'data:text/javascript,export default 1';\nexport {one};",
|
|
1335
|
+
],
|
|
1336
|
+
[
|
|
1337
|
+
'a dynamic import of a data: URL',
|
|
1338
|
+
"await import('data:text/javascript,export default 1');",
|
|
1339
|
+
],
|
|
1340
|
+
[
|
|
1341
|
+
'a dynamic import over https',
|
|
1342
|
+
"await import('https://example.test/x.mjs');",
|
|
1343
|
+
],
|
|
1344
|
+
['process.dlopen', 'process.dlopen({exports: {}}, file);'],
|
|
1345
|
+
['process.execve', 'process.execve(file, args);'],
|
|
1346
|
+
[
|
|
1347
|
+
'dlopen destructured off process',
|
|
1348
|
+
'const {dlopen} = process;\ndlopen({exports: {}}, file);',
|
|
1349
|
+
],
|
|
1350
|
+
['WebAssembly.instantiate', 'await WebAssembly.instantiate(bytes);'],
|
|
1351
|
+
[
|
|
1352
|
+
'WebAssembly.compile through globalThis',
|
|
1353
|
+
'await globalThis.WebAssembly.compile(bytes);',
|
|
1354
|
+
],
|
|
1355
|
+
['new WebAssembly.Module', 'new WebAssembly.Module(bytes);'],
|
|
1356
|
+
['Object.constructor(source)', 'Object.constructor(source)();'],
|
|
1357
|
+
[
|
|
1358
|
+
"a function literal's constructor, taken and kept",
|
|
1359
|
+
'const F = (() => {}).constructor;\nexport {F};',
|
|
1360
|
+
],
|
|
1361
|
+
[
|
|
1362
|
+
'[].constructor.constructor(source)',
|
|
1363
|
+
'[].constructor.constructor(source)();',
|
|
1364
|
+
],
|
|
1365
|
+
[
|
|
1366
|
+
"''.constructor.constructor(source)",
|
|
1367
|
+
"''.constructor.constructor(source)();",
|
|
1368
|
+
],
|
|
1369
|
+
[
|
|
1370
|
+
"a prototype's constructor",
|
|
1371
|
+
'Object.getPrototypeOf(async function () {}).constructor(source)();',
|
|
1372
|
+
],
|
|
1373
|
+
[
|
|
1374
|
+
'eval destructured off globalThis',
|
|
1375
|
+
'const {eval: e} = globalThis;\ne(source);',
|
|
1376
|
+
],
|
|
1377
|
+
[
|
|
1378
|
+
'Function destructured off globalThis',
|
|
1379
|
+
'const {Function: F} = globalThis;\nF(source)();',
|
|
1380
|
+
],
|
|
1381
|
+
[
|
|
1382
|
+
'eval through an alias of globalThis',
|
|
1383
|
+
'const g = globalThis;\ng.eval(source);',
|
|
1384
|
+
],
|
|
1385
|
+
[
|
|
1386
|
+
'eval through a chain of aliases',
|
|
1387
|
+
'const g = globalThis;\nconst h = g;\nh.eval(source);',
|
|
1388
|
+
],
|
|
1389
|
+
[
|
|
1390
|
+
'a computed destructuring off globalThis',
|
|
1391
|
+
"const {['ev' + 'al']: e} = globalThis;\ne(source);",
|
|
1392
|
+
],
|
|
1393
|
+
[
|
|
1394
|
+
"Reflect.get(globalThis, 'eval')",
|
|
1395
|
+
"Reflect.get(globalThis, 'eval')(source);",
|
|
1396
|
+
],
|
|
1397
|
+
[
|
|
1398
|
+
'a module under the authored templates',
|
|
1399
|
+
"import helper from '../../assets/templates/x/helper.mjs';",
|
|
1400
|
+
],
|
|
1401
|
+
[
|
|
1402
|
+
'a module in a hidden directory',
|
|
1403
|
+
"import {load} from './.cache/load.mjs';\nload(docPath);",
|
|
1404
|
+
],
|
|
1405
|
+
[
|
|
1406
|
+
'a module in a fixtures directory',
|
|
1407
|
+
"import {load} from './__fixtures__/load.mjs';\nload(docPath);",
|
|
1408
|
+
],
|
|
1409
|
+
['a test module', "import {load} from './load.test.mjs';\nload(docPath);"],
|
|
1410
|
+
[
|
|
1411
|
+
'the doc importers by absolute file URL',
|
|
1412
|
+
"import {importDocModule} from 'file:///repo/packages/cli/foundation/doc-compiler/import.mjs';\nimportDocModule(docPath);",
|
|
1413
|
+
],
|
|
1414
|
+
[
|
|
1415
|
+
'child_process imported as {default as cp}',
|
|
1416
|
+
"import {default as cp} from 'node:child_process';\ncp.fork(docPath);",
|
|
1417
|
+
],
|
|
1418
|
+
[
|
|
1419
|
+
'createRequire passed on',
|
|
1420
|
+
"import {createRequire} from 'node:module';\nconst make = createRequire;\nmake(import.meta.url)(docPath);",
|
|
1421
|
+
],
|
|
1422
|
+
].map(([name, code]) => ({name, file: LEAF, code}));
|
|
1423
|
+
|
|
1424
|
+
BYPASSES.push(
|
|
1425
|
+
{
|
|
1426
|
+
name: 'a require of a doc file, in CommonJS',
|
|
1427
|
+
file: 'api/leaf/leaf.cjs',
|
|
1428
|
+
code: "const {doc} = require('./Button.doc.cjs');",
|
|
1429
|
+
},
|
|
1430
|
+
{
|
|
1431
|
+
name: 'an import-equals of a doc file, in TypeScript',
|
|
1432
|
+
file: 'api/leaf/leaf.ts',
|
|
1433
|
+
code: "import doc = require('./Button.doc.mjs');",
|
|
1434
|
+
},
|
|
1435
|
+
{
|
|
1436
|
+
name: 'module.require, in CommonJS',
|
|
1437
|
+
file: 'api/leaf/leaf.cjs',
|
|
1438
|
+
code: 'module.require(docPath);',
|
|
1439
|
+
},
|
|
1440
|
+
{
|
|
1441
|
+
name: 'a loader module re-exported by import-equals, in TypeScript',
|
|
1442
|
+
file: 'api/leaf/leaf.ts',
|
|
1443
|
+
code: `export import loader = require(${MODULE_LOADER});`,
|
|
1444
|
+
},
|
|
1445
|
+
{
|
|
1446
|
+
name: 'an extension-free import of the doc importers, in TypeScript',
|
|
1447
|
+
file: 'api/leaf/leaf.ts',
|
|
1448
|
+
code: "import {importDocModule} from '../../foundation/doc-compiler/import';\nimportDocModule(docPath);",
|
|
1449
|
+
},
|
|
1450
|
+
{
|
|
1451
|
+
name: 'import(docPath) in a listed module',
|
|
1452
|
+
...edited(
|
|
1453
|
+
'foundation/config/project.mjs',
|
|
1454
|
+
s => `${s}\nawait import(docPath);`,
|
|
1455
|
+
),
|
|
1456
|
+
},
|
|
1457
|
+
{
|
|
1458
|
+
name: 'a doc parser swapped into a listed loadModuleWithParser call',
|
|
1459
|
+
...edited('foundation/config/project.mjs', s =>
|
|
1460
|
+
s.replace(
|
|
1461
|
+
'loadModuleWithParser(configPath, parseConfig',
|
|
1462
|
+
'loadModuleWithParser(configPath, parseDoc',
|
|
1463
|
+
),
|
|
1464
|
+
),
|
|
1465
|
+
},
|
|
1466
|
+
{
|
|
1467
|
+
name: 'a second loader call in a listed module',
|
|
1468
|
+
...edited(
|
|
1469
|
+
'foundation/integrations/integrations.mjs',
|
|
1470
|
+
s => `${s}\nawait importUserModule(docPath);`,
|
|
1471
|
+
),
|
|
1472
|
+
},
|
|
1473
|
+
);
|
|
1474
|
+
|
|
1475
|
+
/**
|
|
1476
|
+
* Code that runs nothing outside its static imports.
|
|
1477
|
+
* @type {Array<{name: string, file: string, code: string}>}
|
|
1478
|
+
*/
|
|
1479
|
+
const INNOCENT = [
|
|
1480
|
+
['a literal import of a built-in', "await import('node:fs');"],
|
|
1481
|
+
['a literal lazy import', "const m = await import('./other.mjs');"],
|
|
1482
|
+
[
|
|
1483
|
+
'an inert loader-module export',
|
|
1484
|
+
`import {findPresentFiles} from ${MODULE_LOADER};\nfindPresentFiles(dir, names);`,
|
|
1485
|
+
],
|
|
1486
|
+
[
|
|
1487
|
+
'an inert export read off a loader namespace',
|
|
1488
|
+
`import * as loader from ${MODULE_LOADER};\nloader.findPresentFiles(dir, names);`,
|
|
1489
|
+
],
|
|
1490
|
+
[
|
|
1491
|
+
'a require function that only resolves',
|
|
1492
|
+
"import {createRequire} from 'node:module';\nconst require = createRequire(import.meta.url);\nrequire.resolve('pkg/package.json');",
|
|
1493
|
+
],
|
|
1494
|
+
[
|
|
1495
|
+
'createRequire(...).resolve',
|
|
1496
|
+
"import {createRequire} from 'node:module';\ncreateRequire(import.meta.url).resolve('pkg');",
|
|
1497
|
+
],
|
|
1498
|
+
[
|
|
1499
|
+
'a doc path as data',
|
|
1500
|
+
"const target = name + '.doc.mjs';\nif (file.endsWith('.doc.mjs')) files.push(target);",
|
|
1501
|
+
],
|
|
1502
|
+
[
|
|
1503
|
+
'an inert member of {default as x}',
|
|
1504
|
+
"import {default as threads} from 'node:worker_threads';\nthreads.parentPort.postMessage(1);",
|
|
1505
|
+
],
|
|
1506
|
+
[
|
|
1507
|
+
'the worker side of worker_threads',
|
|
1508
|
+
"import {parentPort, workerData} from 'node:worker_threads';\nparentPort.postMessage(workerData);",
|
|
1509
|
+
],
|
|
1510
|
+
['x instanceof Function', 'if (x instanceof Function) f();'],
|
|
1511
|
+
['j.Function, an AST node type', 'root.find(j.Function);'],
|
|
1512
|
+
[
|
|
1513
|
+
'a property that shares a loader name',
|
|
1514
|
+
'const o = {importUserModule: 1};\nconsole.log(o.importUserModule);',
|
|
1515
|
+
],
|
|
1516
|
+
[
|
|
1517
|
+
'a read through the compiler',
|
|
1518
|
+
"import {readDocView} from '../../foundation/doc-compiler/read.mjs';\nawait readDocView(file, {root: 'components'});",
|
|
1519
|
+
],
|
|
1520
|
+
[
|
|
1521
|
+
'the docs catalog',
|
|
1522
|
+
"import {DocsCatalog} from '../../foundation/discovery/docs-discovery.mjs';\nnew DocsCatalog();",
|
|
1523
|
+
],
|
|
1524
|
+
[
|
|
1525
|
+
'an inert export of module',
|
|
1526
|
+
"import {builtinModules} from 'node:module';\nbuiltinModules.includes(name);",
|
|
1527
|
+
],
|
|
1528
|
+
[
|
|
1529
|
+
'a URL of a module that is not a doc',
|
|
1530
|
+
"const worker = new URL('./worker.mjs', import.meta.url);",
|
|
1531
|
+
],
|
|
1532
|
+
['a string that mentions import()', "const text = 'await import(x)';"],
|
|
1533
|
+
[
|
|
1534
|
+
'getBuiltinModule of an inert built-in',
|
|
1535
|
+
"const fs = process.getBuiltinModule('node:fs');",
|
|
1536
|
+
],
|
|
1537
|
+
['an inert member of process', 'const dir = process.cwd();'],
|
|
1538
|
+
['an inert destructuring of process', 'const {env, argv} = process;'],
|
|
1539
|
+
['an inert member of globalThis', 'globalThis.crypto.randomUUID();'],
|
|
1540
|
+
['WebAssembly.validate', 'WebAssembly.validate(bytes);'],
|
|
1541
|
+
['a constructor read and passed on', 'patchPrototype(command.constructor);'],
|
|
1542
|
+
[
|
|
1543
|
+
"a constructor's name",
|
|
1544
|
+
"const kind = proto?.constructor?.name ?? 'non-plain object';",
|
|
1545
|
+
],
|
|
1546
|
+
['a property named constructor', 'const o = {constructor: 1};\nread(o);'],
|
|
1547
|
+
].map(([name, code]) => ({name, file: LEAF, code}));
|
|
1548
|
+
|
|
1549
|
+
INNOCENT.push(
|
|
1550
|
+
{
|
|
1551
|
+
name: 'a require of a built-in, in CommonJS',
|
|
1552
|
+
file: 'api/leaf/leaf.cjs',
|
|
1553
|
+
code: "const path = require('node:path');",
|
|
1554
|
+
},
|
|
1555
|
+
{
|
|
1556
|
+
name: 'a type-only import of a doc file, in TypeScript',
|
|
1557
|
+
file: 'api/leaf/leaf.ts',
|
|
1558
|
+
code: "import type {Doc} from './Button.doc.mjs';\nexport type D = Doc;",
|
|
1559
|
+
},
|
|
1560
|
+
{
|
|
1561
|
+
name: 'a loader named in a type, in TypeScript',
|
|
1562
|
+
file: 'api/leaf/leaf.ts',
|
|
1563
|
+
code: `import {importDocModule} from ${IMPORTERS};\nlet load: typeof importDocModule | undefined;`,
|
|
1564
|
+
},
|
|
1565
|
+
);
|
|
1566
|
+
|
|
1567
|
+
describe('the scan', () => {
|
|
1568
|
+
it('reads every code file but tests, fixtures, dependencies, and generated or hidden ones', () => {
|
|
1569
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'doc-loads-'));
|
|
1570
|
+
const code = [
|
|
1571
|
+
'a.cjs',
|
|
1572
|
+
'a.cts',
|
|
1573
|
+
'a.js',
|
|
1574
|
+
'a.jsx',
|
|
1575
|
+
'a.mjs',
|
|
1576
|
+
'a.mts',
|
|
1577
|
+
'a.ts',
|
|
1578
|
+
'a.tsx',
|
|
1579
|
+
];
|
|
1580
|
+
const skipped = [
|
|
1581
|
+
'.cache/a.mjs',
|
|
1582
|
+
'__fixtures__/a.mjs',
|
|
1583
|
+
'__tests__/a.mjs',
|
|
1584
|
+
'a.d.mts',
|
|
1585
|
+
'a.json',
|
|
1586
|
+
'a.test.mjs',
|
|
1587
|
+
'a.test.ts',
|
|
1588
|
+
'node_modules/a.mjs',
|
|
1589
|
+
];
|
|
1590
|
+
try {
|
|
1591
|
+
for (const file of [...code, ...skipped]) {
|
|
1592
|
+
fs.mkdirSync(path.dirname(path.join(dir, file)), {recursive: true});
|
|
1593
|
+
fs.writeFileSync(path.join(dir, file), '');
|
|
1594
|
+
}
|
|
1595
|
+
const found = sources(dir).map(full => path.relative(dir, full));
|
|
1596
|
+
expect(found.sort()).toEqual(code);
|
|
1597
|
+
} finally {
|
|
1598
|
+
fs.rmSync(dir, {recursive: true, force: true});
|
|
1599
|
+
}
|
|
1600
|
+
});
|
|
1601
|
+
|
|
1602
|
+
it.each(BYPASSES)('catches $name', ({file, code}) => {
|
|
1603
|
+
expect(violations(file, code)).not.toEqual([]);
|
|
1604
|
+
});
|
|
1605
|
+
|
|
1606
|
+
it.each(INNOCENT)('passes $name', ({file, code}) => {
|
|
1607
|
+
expect(scanModule(code, file)).toEqual({sites: [], problems: []});
|
|
1608
|
+
});
|
|
1609
|
+
|
|
1610
|
+
it('refuses a doc parser handed to a loader, even with the site listed', () => {
|
|
1611
|
+
for (const parser of ['parsers.parseDoc', 'x => parseFunction(x)']) {
|
|
1612
|
+
const code = `import {loadModuleWithParser} from ${MODULE_LOADER};\nloadModuleWithParser(file, ${parser});`;
|
|
1613
|
+
const listed = tally(scanModule(code, LEAF).sites);
|
|
1614
|
+
expect(violations(LEAF, code, listed)).toEqual([
|
|
1615
|
+
`${LEAF}: loadModuleWithParser(..., ${parser}) loads a doc outside the compiler`,
|
|
1616
|
+
]);
|
|
1617
|
+
}
|
|
1618
|
+
});
|
|
1619
|
+
});
|
|
1620
|
+
|
|
1621
|
+
describe('the scan walks every folder that can ship', () => {
|
|
1622
|
+
it('skips only test scaffolding and the temporary folders tests create', () => {
|
|
1623
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'doc-loads-walk-'));
|
|
1624
|
+
try {
|
|
1625
|
+
for (const dir of [
|
|
1626
|
+
'__generated__',
|
|
1627
|
+
'__tests__',
|
|
1628
|
+
'__fixtures__',
|
|
1629
|
+
'__import_hint_integration_Ab12Cd',
|
|
1630
|
+
'.hidden',
|
|
1631
|
+
'node_modules',
|
|
1632
|
+
]) {
|
|
1633
|
+
fs.mkdirSync(path.join(root, dir));
|
|
1634
|
+
fs.writeFileSync(path.join(root, dir, 'run.mjs'), "eval('1');\n");
|
|
1635
|
+
}
|
|
1636
|
+
expect(
|
|
1637
|
+
sources(root).map(file => path.basename(path.dirname(file))),
|
|
1638
|
+
).toEqual(['__generated__']);
|
|
1639
|
+
} finally {
|
|
1640
|
+
fs.rmSync(root, {recursive: true, force: true});
|
|
1641
|
+
}
|
|
1642
|
+
});
|
|
1643
|
+
});
|