@astryxdesign/cli 0.6.3-canary.ea2f048 → 0.6.3-canary.f04b501
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/README.md +106 -72
- package/api/blog/blog.doc.mjs +1 -0
- package/api/build/build.doc.mjs +4 -3
- package/api/build/build.test.mjs +14 -3
- package/api/build/build.type.d.mts +36 -2
- package/api/build/build.type.mjs +25 -4
- package/api/build/help/help.d.mts +8 -5
- package/api/build/help/help.mjs +56 -6
- package/api/component/component.doc.mjs +13 -3
- package/api/component/component.mjs +31 -2
- package/api/component/component.test.mjs +38 -0
- package/api/component/component.type.d.mts +22 -11
- package/api/component/component.type.mjs +32 -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 +37 -9
- package/api/discover/discover.doc.mjs +2 -1
- package/api/discover/discover.type.d.mts +4 -4
- package/api/discover/discover.type.mjs +10 -10
- package/api/docs/_adapter.d.mts +32 -24
- package/api/docs/_adapter.mjs +81 -87
- package/api/docs/compiled-topics.test.mjs +78 -0
- package/api/docs/detail/detail.mjs +14 -63
- package/api/docs/detail/section/section.d.mts +1 -1
- package/api/docs/detail/section/section.mjs +44 -20
- package/api/docs/detail/section/section.test.mjs +41 -0
- package/api/docs/docs.d.mts +7 -2
- package/api/docs/docs.doc.mjs +28 -10
- package/api/docs/docs.mjs +16 -9
- package/api/docs/docs.test.mjs +6 -0
- package/api/docs/docs.type.d.mts +40 -3
- package/api/docs/docs.type.mjs +36 -8
- package/api/docs/index/index.d.mts +18 -0
- package/api/docs/index/index.mjs +32 -0
- package/api/docs/index/index.test.mjs +62 -0
- package/api/docs/integrationDocs.test.mjs +106 -0
- package/api/docs/list/list.mjs +3 -7
- package/api/doctor/doctor.d.mts +70 -0
- package/api/doctor/doctor.doc.mjs +1 -0
- package/api/doctor/doctor.mjs +404 -0
- package/api/doctor/doctor.test.mjs +522 -1
- package/api/gap-report/gap-report.doc.mjs +8 -4
- package/api/hook/_adapter.mjs +19 -5
- package/api/hook/hook.doc.mjs +1 -0
- 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/init/init.doc.mjs +5 -0
- 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.mjs +23 -10
- package/api/integration/add-contribution.test.mjs +121 -7
- package/api/integration/add-theme.mjs +34 -64
- package/api/integration/add-theme.test.mjs +105 -21
- package/api/integration/authoring-checks.test.mjs +8 -4
- package/api/integration/integration-authoring.type.d.mts +1 -1
- package/api/integration/integration-block-exports.test.mjs +10 -6
- package/api/integration/integrationAdd.doc.mjs +8 -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 +1 -0
- 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 +1 -0
- package/api/integration/integrationPackCheck.doc.mjs +1 -0
- package/api/integration/integrationTemplateConflicts.doc.mjs +1 -0
- package/api/integration/pack-check.mjs +49 -7
- package/api/integration/pack-check.test.mjs +266 -47
- package/api/integration/summarizeIssues.doc.mjs +1 -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 +176 -23
- package/api/integration/validate-unread-theme-folders.test.mjs +110 -0
- package/api/integration/validateIntegration.doc.mjs +2 -1
- package/api/json/assertResponse.doc.mjs +1 -0
- package/api/json/envelope-types.test.mjs +76 -0
- package/api/json/isError.doc.mjs +1 -0
- package/api/json/parseResponse.doc.mjs +3 -2
- 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 +3 -10
- package/api/search/search.doc.mjs +6 -1
- package/api/search/search.mjs +31 -14
- package/api/search/search.test.mjs +34 -0
- package/api/search/search.type.d.mts +2 -2
- package/api/search/search.type.mjs +1 -1
- package/api/swizzle/copy/copy.mjs +28 -11
- package/api/swizzle/swizzle.doc.mjs +2 -1
- package/api/swizzle/swizzle.type.d.mts +2 -2
- package/api/swizzle/swizzle.type.mjs +2 -2
- package/api/template/copy/copy.mjs +17 -23
- package/api/template/copy/copy.test.mjs +17 -0
- package/api/template/template-suffix.test.mjs +41 -21
- package/api/template/template.d.mts +1 -1
- package/api/template/template.doc.mjs +3 -1
- package/api/template/template.type.d.mts +6 -6
- package/api/template/template.type.mjs +12 -12
- 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 +20 -3
- package/api/theme/add/add.staging.test.mjs +66 -0
- package/api/theme/add/add.test.mjs +14 -1
- package/api/theme/build/build.mjs +133 -42
- 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.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 +1 -0
- 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 +15 -0
- package/api/theme/theme.type.d.mts +170 -11
- package/api/theme/theme.type.mjs +94 -27
- package/api/theme/themeAdd.doc.mjs +4 -3
- package/api/theme/themeBuild.doc.mjs +8 -4
- package/api/theme/themeList.doc.mjs +6 -3
- package/api/theme/themeListAvailable.doc.mjs +5 -3
- package/api/theme/themePaletteGenerate.doc.mjs +1 -0
- package/api/theme/themeTargets.doc.mjs +1 -0
- package/api/theme/themeTemplate.doc.mjs +5 -1
- package/api/upgrade/_adapter.d.mts +12 -3
- package/api/upgrade/_adapter.mjs +123 -19
- 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/run.mjs +1 -0
- package/api/upgrade/status/status.mjs +2 -2
- package/api/upgrade/upgrade.doc.mjs +7 -4
- package/api/upgrade/upgrade.type.d.mts +9 -5
- package/api/upgrade/upgrade.type.mjs +12 -11
- package/assets/codemods/integration-discovery.mjs +48 -4
- package/assets/codemods/integration-discovery.test.mjs +73 -0
- package/assets/codemods/runner.mjs +115 -5
- 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/next/__tests__/migrate-theme-catalog-to-descriptors.test.mjs +216 -0
- package/assets/codemods/transforms/next/index.mjs +11 -1
- package/assets/codemods/transforms/next/migrate-theme-catalog-to-descriptors.mjs +141 -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/docs/README.md +9 -0
- package/assets/docs/authoring.doc.mjs +14 -0
- package/assets/docs/cli-integrations.doc.mjs +78 -20
- package/assets/docs/cli.doc.mjs +15 -0
- package/assets/docs/styling-libraries.doc.mjs +1 -1
- package/assets/docs/theme.doc.mjs +1 -1
- package/assets/docs/working-with-ai.doc.mjs +1 -1
- 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/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/config/config.doc.mjs +2 -2
- package/authoring/config/debug-composition.test.mjs +92 -0
- package/authoring/config/parse.d.mts +13 -13
- package/authoring/config/parse.mjs +8 -8
- package/authoring/config/type.ts +18 -6
- package/authoring/debug/debug.doc.d.mts +11 -0
- package/authoring/debug/debug.doc.mjs +182 -0
- package/authoring/debug/parse.d.mts +5 -5
- package/authoring/debug/parse.mjs +3 -3
- package/authoring/doctypes/_schema.d.mts +788 -23
- package/authoring/doctypes/_schema.mjs +492 -39
- package/authoring/doctypes/base/graph-fields.doc.d.mts +9 -0
- package/authoring/doctypes/base/graph-fields.doc.mjs +62 -0
- package/authoring/doctypes/base/type.ts +40 -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 +4 -3
- package/authoring/doctypes/component/component.doc.mjs +6 -3
- package/authoring/doctypes/component/parse.d.mts +2 -2
- package/authoring/doctypes/component/parse.mjs +1 -1
- package/authoring/doctypes/component/type.ts +6 -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 +5 -1
- package/authoring/doctypes/function/parse.d.mts +2 -2
- package/authoring/doctypes/function/parse.mjs +1 -1
- package/authoring/doctypes/function/type.ts +7 -3
- 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 +208 -0
- package/authoring/doctypes/namespace/namespace.doc.d.mts +9 -0
- package/authoring/doctypes/namespace/namespace.doc.mjs +132 -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 +165 -0
- package/authoring/doctypes/namespace/type.ts +71 -0
- package/authoring/doctypes/parse.d.mts +22 -18
- package/authoring/doctypes/parse.mjs +22 -12
- 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 +18 -5
- package/authoring/doctypes/reference/type.ts +53 -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 +1 -1
- package/authoring/doctypes/schema/type.ts +4 -4
- package/authoring/doctypes/template/parse.d.mts +92 -1
- package/authoring/doctypes/template/parse.mjs +36 -2
- package/authoring/doctypes/template/parse.test.mjs +8 -2
- package/authoring/doctypes/template/template.doc.mjs +7 -3
- package/authoring/doctypes/template/type.ts +5 -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 +2 -0
- package/authoring/index.d.ts +51 -17
- package/authoring/index.mjs +3 -1
- package/authoring/integration/integration.doc.mjs +15 -8
- 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 +18 -7
- 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.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 +5 -5
- 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 +5 -2
- package/clients/cli/commands/build.exit-codes-doc.test.mjs +50 -0
- package/clients/cli/commands/build.mjs +32 -47
- package/clients/cli/commands/build.playbook.test.mjs +75 -0
- package/clients/cli/commands/build.text-fields.test.mjs +40 -0
- package/clients/cli/commands/component/index.mjs +2 -7
- 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 +1 -1
- 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 +48 -0
- package/clients/cli/commands/discover.components-flag.test.mjs +97 -0
- package/clients/cli/commands/discover.doc.mjs +5 -3
- package/clients/cli/commands/discover.mjs +3 -3
- package/clients/cli/commands/discover.text-projection.test.mjs +103 -0
- package/clients/cli/commands/docs.doc.mjs +14 -4
- package/clients/cli/commands/docs.mjs +121 -21
- package/clients/cli/commands/docs.test.mjs +88 -0
- package/clients/cli/commands/doctor-integration-components.doc.mjs +1 -1
- package/clients/cli/commands/doctor-integration-docs.doc.mjs +1 -1
- package/clients/cli/commands/doctor-integration-templates.doc.mjs +1 -1
- 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 +15 -7
- package/clients/cli/commands/doctor.doc.mjs +1 -1
- package/clients/cli/commands/doctor.mjs +6 -16
- package/clients/cli/commands/doctor.test.mjs +42 -0
- package/clients/cli/commands/gap-report.doc.mjs +17 -6
- 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 +20 -9
- 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 +22 -6
- package/clients/cli/commands/integration-authoring.test.mjs +13 -9
- package/clients/cli/commands/integration-pack.doc.mjs +1 -1
- package/clients/cli/commands/integration-real-world.test.mjs +3 -9
- package/clients/cli/commands/integration.doc.mjs +1 -1
- 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 +1 -1
- package/clients/cli/commands/no-prompt-wording.test.mjs +94 -0
- package/clients/cli/commands/search.doc.mjs +7 -4
- package/clients/cli/commands/search.mjs +17 -7
- package/clients/cli/commands/search.test.mjs +75 -0
- package/clients/cli/commands/swizzle.doc.mjs +3 -2
- package/clients/cli/commands/swizzle.path-safety.test.mjs +42 -0
- package/clients/cli/commands/template.doc.mjs +29 -7
- package/clients/cli/commands/template.flag-help.test.mjs +117 -0
- package/clients/cli/commands/template.path-help.test.mjs +40 -0
- package/clients/cli/commands/theme-add.doc.mjs +4 -3
- 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 +9 -5
- package/clients/cli/commands/theme-palette-generate.test.mjs +19 -0
- package/clients/cli/commands/theme-palette.doc.mjs +1 -1
- package/clients/cli/commands/theme-targets.doc.mjs +1 -1
- 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 +1 -1
- package/clients/cli/commands/upgrade.ascii-output.test.mjs +87 -0
- package/clients/cli/commands/upgrade.doc.mjs +20 -9
- 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/formatters/index.mjs +162 -1
- package/clients/cli/formatters/index.test.mjs +91 -0
- package/clients/cli/index.mjs +13 -30
- 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 +32 -6
- package/clients/cli/lib/doc-text-ascii.test.mjs +82 -0
- package/clients/cli/lib/exit-codes.test.mjs +97 -0
- package/clients/cli/lib/hook-format.mjs +19 -10
- package/clients/cli/lib/json-shim.mjs +38 -2
- 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 +29 -2
- package/clients/cli/lib/manifest.test.mjs +17 -0
- package/foundation/agent-docs/agent-docs.d.mts +4 -0
- package/foundation/agent-docs/agent-docs.mjs +69 -3
- 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 +8 -0
- package/foundation/config/project.mjs +190 -32
- package/foundation/config/project.test.mjs +129 -16
- package/foundation/discovery/authoring-self-docs.d.mts +75 -0
- package/foundation/discovery/authoring-self-docs.mjs +224 -0
- package/foundation/discovery/authoring-self-docs.test.mjs +154 -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 +113 -0
- package/foundation/discovery/cli-self-docs.mjs +514 -0
- package/foundation/discovery/cli-self-docs.test.mjs +437 -0
- package/foundation/discovery/component-discovery.d.mts +1 -1
- package/foundation/discovery/component-discovery.mjs +2 -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 +12 -4
- package/foundation/discovery/docs-discovery.mjs +214 -98
- package/foundation/discovery/docs-discovery.test.mjs +284 -15
- 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 +98 -0
- package/foundation/discovery/docs-section-key.mjs +221 -0
- package/foundation/discovery/docs-section-key.test.mjs +224 -0
- package/foundation/discovery/template-adapter.d.mts +21 -5
- 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 +321 -66
- package/foundation/discovery/template-adapter.test.mjs +15 -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 +218 -0
- package/foundation/doc-compiler/bundle.test.mjs +255 -0
- package/foundation/doc-compiler/compile.d.mts +362 -0
- package/foundation/doc-compiler/compile.mjs +511 -0
- package/foundation/doc-compiler/diagnostics.d.mts +126 -0
- package/foundation/doc-compiler/diagnostics.mjs +277 -0
- package/foundation/doc-compiler/doc-compiler.test.mjs +714 -0
- package/foundation/doc-compiler/doc-loads.test.mjs +1642 -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 +286 -0
- package/foundation/doc-compiler/inputs.test.mjs +299 -0
- package/foundation/doc-compiler/ir.d.mts +22 -0
- package/foundation/doc-compiler/ir.mjs +464 -0
- package/foundation/doc-compiler/lenses.d.mts +33 -0
- package/foundation/doc-compiler/lenses.mjs +127 -0
- package/foundation/doc-compiler/lower-doc.test.mjs +492 -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 +126 -0
- package/foundation/doc-compiler/read.mjs +320 -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/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/contribution-fixes.d.mts +145 -0
- package/foundation/integrations/contribution-fixes.mjs +1284 -0
- package/foundation/integrations/contribution-inventory.d.mts +1 -1
- package/foundation/integrations/contribution-inventory.mjs +17 -20
- package/foundation/integrations/contribution-inventory.test.mjs +67 -27
- package/foundation/integrations/integration-warnings.mjs +6 -0
- package/foundation/integrations/integrations.d.mts +49 -2
- package/foundation/integrations/integrations.mjs +81 -8
- package/foundation/integrations/integrations.test.mjs +384 -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 +114 -10
- package/foundation/response/base.d.ts +8 -4
- package/foundation/response/error-codes.doc.mjs +3 -4
- package/foundation/response/error-codes.mjs +2 -2
- package/foundation/response/error-codes.test.mjs +83 -0
- 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.mjs +16 -11
- package/foundation/response/response-types.doc.test.mjs +158 -0
- package/foundation/response/response.doc.mjs +1 -1
- package/foundation/text/string-utils.mjs +22 -10
- package/foundation/xle/expand.d.mts +2 -0
- package/foundation/xle/expand.mjs +3 -2
- package/foundation/xle/expand.test.mjs +54 -0
- package/package.json +9 -11
- package/assets/templates/themes/manifest.json +0 -95
- 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
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file A project module that prints while it loads must not reach stdout.
|
|
5
|
+
* Every `astryx.config.*` and integration manifest runs through
|
|
6
|
+
* `importUserModule`, so one stray `console.log` there used to corrupt every
|
|
7
|
+
* `--json` envelope. The loader cases run in a real Node process (vitest
|
|
8
|
+
* replaces `console`); the CLI cases spawn the real binary.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import {afterEach, beforeEach, describe, expect, it} from 'vitest';
|
|
12
|
+
import {spawnSync} from 'node:child_process';
|
|
13
|
+
import * as fs from 'node:fs';
|
|
14
|
+
import * as os from 'node:os';
|
|
15
|
+
import * as path from 'node:path';
|
|
16
|
+
import {fileURLToPath, pathToFileURL} from 'node:url';
|
|
17
|
+
|
|
18
|
+
const HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
19
|
+
const LOADER_URL = pathToFileURL(path.join(HERE, 'module-loader.mjs')).href;
|
|
20
|
+
const CLI = path.resolve(HERE, '..', '..', 'clients', 'cli', 'bin', 'astryx.mjs');
|
|
21
|
+
|
|
22
|
+
const PRINTS =
|
|
23
|
+
"console.log('module log');\nprocess.stdout.write('module write\\n');\n";
|
|
24
|
+
|
|
25
|
+
let tmpDir;
|
|
26
|
+
|
|
27
|
+
beforeEach(() => {
|
|
28
|
+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'astryx-module-stdout-'));
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
afterEach(() => {
|
|
32
|
+
fs.rmSync(tmpDir, {recursive: true, force: true});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
/** @param {string} rel @param {string} body @returns {string} */
|
|
36
|
+
function write(rel, body) {
|
|
37
|
+
const file = path.join(tmpDir, rel);
|
|
38
|
+
fs.mkdirSync(path.dirname(file), {recursive: true});
|
|
39
|
+
fs.writeFileSync(file, body);
|
|
40
|
+
return file;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Run `body` as an ES module in a real Node process with `importUserModule`
|
|
45
|
+
* in scope.
|
|
46
|
+
* @param {string} body
|
|
47
|
+
*/
|
|
48
|
+
function runWithLoader(body) {
|
|
49
|
+
return spawnSync(
|
|
50
|
+
process.execPath,
|
|
51
|
+
[
|
|
52
|
+
'--input-type=module',
|
|
53
|
+
'-e',
|
|
54
|
+
`import {importUserModule} from ${JSON.stringify(LOADER_URL)};\n${body}`,
|
|
55
|
+
],
|
|
56
|
+
{encoding: 'utf8', timeout: 60_000, stdio: ['ignore', 'pipe', 'pipe']},
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
describe('importUserModule keeps module output off stdout', () => {
|
|
61
|
+
it.each([
|
|
62
|
+
['.mjs', 'mod.mjs', false, `${PRINTS}export default {answer: 42};\n`],
|
|
63
|
+
['ESM .js', 'esm/mod.js', false, `${PRINTS}export default {answer: 42};\n`],
|
|
64
|
+
[
|
|
65
|
+
'.ts',
|
|
66
|
+
'mod.ts',
|
|
67
|
+
false,
|
|
68
|
+
`${PRINTS}const answer: number = 42;\nexport default {answer};\n`,
|
|
69
|
+
],
|
|
70
|
+
['fresh .mjs', 'mod.mjs', true, `${PRINTS}export default {answer: 42};\n`],
|
|
71
|
+
[
|
|
72
|
+
'fresh .ts',
|
|
73
|
+
'mod.ts',
|
|
74
|
+
true,
|
|
75
|
+
`${PRINTS}const answer: number = 42;\nexport default {answer};\n`,
|
|
76
|
+
],
|
|
77
|
+
[
|
|
78
|
+
'fresh CommonJS .js',
|
|
79
|
+
'cjs/mod.js',
|
|
80
|
+
true,
|
|
81
|
+
`${PRINTS}module.exports = {answer: 42};\n`,
|
|
82
|
+
],
|
|
83
|
+
])('sends a printing %s module to stderr', (_label, rel, fresh, source) => {
|
|
84
|
+
write('esm/package.json', JSON.stringify({type: 'module'}));
|
|
85
|
+
write('cjs/package.json', JSON.stringify({type: 'commonjs'}));
|
|
86
|
+
const file = write(rel, source);
|
|
87
|
+
|
|
88
|
+
const r = runWithLoader(
|
|
89
|
+
`const before = process.stdout.write;
|
|
90
|
+
const mod = await importUserModule(${JSON.stringify(file)}, {fresh: ${fresh}});
|
|
91
|
+
process.stdout.write(JSON.stringify({answer: mod.default?.answer, restored: process.stdout.write === before}));`,
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
expect(r.status, r.stderr).toBe(0);
|
|
95
|
+
expect(JSON.parse(r.stdout)).toEqual({answer: 42, restored: true});
|
|
96
|
+
expect(r.stderr).toContain('module log');
|
|
97
|
+
expect(r.stderr).toContain('module write');
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('keeps stdout redirected until the last overlapping load settles', () => {
|
|
101
|
+
const first = write(
|
|
102
|
+
'first.mjs',
|
|
103
|
+
"await new Promise(resolve => setTimeout(resolve, 20));\nprocess.stdout.write('first write\\n');\nexport default 'first';\n",
|
|
104
|
+
);
|
|
105
|
+
const second = write(
|
|
106
|
+
'second.mjs',
|
|
107
|
+
"await new Promise(resolve => setTimeout(resolve, 150));\nprocess.stdout.write('second write\\n');\nexport default 'second';\n",
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
const r = runWithLoader(
|
|
111
|
+
`const before = process.stdout.write;
|
|
112
|
+
const mods = await Promise.all([
|
|
113
|
+
importUserModule(${JSON.stringify(first)}),
|
|
114
|
+
importUserModule(${JSON.stringify(second)}),
|
|
115
|
+
]);
|
|
116
|
+
process.stdout.write(JSON.stringify({loaded: mods.map(mod => mod.default), restored: process.stdout.write === before}));`,
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
expect(r.status, r.stderr).toBe(0);
|
|
120
|
+
expect(JSON.parse(r.stdout)).toEqual({
|
|
121
|
+
loaded: ['first', 'second'],
|
|
122
|
+
restored: true,
|
|
123
|
+
});
|
|
124
|
+
expect(r.stderr).toContain('first write');
|
|
125
|
+
expect(r.stderr).toContain('second write');
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('leaves the caller on stdout while an abandoned load is still in flight', () => {
|
|
129
|
+
const slow = write(
|
|
130
|
+
'slow.mjs',
|
|
131
|
+
"await new Promise(resolve => setTimeout(resolve, 150));\nprocess.stdout.write('slow write\\n');\nexport default 'slow';\n",
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
const r = runWithLoader(
|
|
135
|
+
`const before = process.stdout.write;
|
|
136
|
+
const slow = importUserModule(${JSON.stringify(slow)});
|
|
137
|
+
const sibling = new Promise((_, reject) => setTimeout(() => reject(new Error('sibling failed')), 10));
|
|
138
|
+
try {
|
|
139
|
+
await Promise.all([slow, sibling]);
|
|
140
|
+
} catch (error) {
|
|
141
|
+
process.stdout.write('caught: ' + error.message + '\\n');
|
|
142
|
+
}
|
|
143
|
+
await slow;
|
|
144
|
+
process.stdout.write('restored: ' + (process.stdout.write === before) + '\\n');`,
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
expect(r.status, r.stderr).toBe(0);
|
|
148
|
+
expect(r.stdout).toBe('caught: sibling failed\nrestored: true\n');
|
|
149
|
+
expect(r.stderr).toContain('slow write');
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('restores stdout when the module throws', () => {
|
|
153
|
+
const file = write(
|
|
154
|
+
'boom.mjs',
|
|
155
|
+
"process.stdout.write('boom write\\n');\nthrow new Error('boom');\n",
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
const r = runWithLoader(
|
|
159
|
+
`const before = process.stdout.write;
|
|
160
|
+
let message = null;
|
|
161
|
+
try {
|
|
162
|
+
await importUserModule(${JSON.stringify(file)});
|
|
163
|
+
} catch (error) {
|
|
164
|
+
message = error.message;
|
|
165
|
+
}
|
|
166
|
+
process.stdout.write(JSON.stringify({message, restored: process.stdout.write === before}));`,
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
expect(r.status, r.stderr).toBe(0);
|
|
170
|
+
expect(JSON.parse(r.stdout)).toEqual({message: 'boom', restored: true});
|
|
171
|
+
expect(r.stderr).toContain('boom write');
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
/** @param {string[]} args */
|
|
176
|
+
function runCli(args) {
|
|
177
|
+
return spawnSync(process.execPath, [CLI, ...args], {
|
|
178
|
+
cwd: tmpDir,
|
|
179
|
+
encoding: 'utf8',
|
|
180
|
+
timeout: 60_000,
|
|
181
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
182
|
+
env: {...process.env, FORCE_COLOR: '0', CI: ''},
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Stdout must be exactly one envelope and the module's output must be on
|
|
188
|
+
* stderr.
|
|
189
|
+
* @param {ReturnType<typeof runCli>} r
|
|
190
|
+
* @param {string} printed
|
|
191
|
+
*/
|
|
192
|
+
function expectOneEnvelope(r, printed) {
|
|
193
|
+
expect(r.signal).toBeNull();
|
|
194
|
+
const envelope = JSON.parse(r.stdout);
|
|
195
|
+
expect(envelope.apiVersion).toBe(1);
|
|
196
|
+
expect(r.stdout).not.toContain(printed);
|
|
197
|
+
expect(r.stderr).toContain(printed);
|
|
198
|
+
return envelope;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/** A declared dependency that ships a printing manifest and one component. */
|
|
202
|
+
function installNoisyIntegration() {
|
|
203
|
+
write(
|
|
204
|
+
'package.json',
|
|
205
|
+
JSON.stringify({
|
|
206
|
+
name: 'consumer',
|
|
207
|
+
private: true,
|
|
208
|
+
type: 'module',
|
|
209
|
+
dependencies: {'@acme/noisy': '1.0.0'},
|
|
210
|
+
}),
|
|
211
|
+
);
|
|
212
|
+
write(
|
|
213
|
+
'node_modules/@acme/noisy/package.json',
|
|
214
|
+
JSON.stringify({name: '@acme/noisy', version: '1.0.0'}),
|
|
215
|
+
);
|
|
216
|
+
write(
|
|
217
|
+
'node_modules/@acme/noisy/astryx.integration.mjs',
|
|
218
|
+
"console.log('manifest log');\nexport default {components: './components'};\n",
|
|
219
|
+
);
|
|
220
|
+
write(
|
|
221
|
+
'node_modules/@acme/noisy/components/Widget.doc.mjs',
|
|
222
|
+
"export const docs = {name: 'Widget', usage: {description: 'A widget'}};\n",
|
|
223
|
+
);
|
|
224
|
+
write(
|
|
225
|
+
'node_modules/@acme/noisy/components/Widget.tsx',
|
|
226
|
+
'export function Widget() { return null; }\n',
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
describe('--json stdout stays one envelope when project modules print', () => {
|
|
231
|
+
it.each([
|
|
232
|
+
[['--json', 'docs'], 'docs.list'],
|
|
233
|
+
[['--json', 'template', '--list'], 'template.list'],
|
|
234
|
+
[['--json', 'doctor'], 'doctor'],
|
|
235
|
+
])('astryx.config.mjs: %j', (args, type) => {
|
|
236
|
+
write(
|
|
237
|
+
'package.json',
|
|
238
|
+
JSON.stringify({name: 'consumer', private: true, type: 'module'}),
|
|
239
|
+
);
|
|
240
|
+
write(
|
|
241
|
+
'astryx.config.mjs',
|
|
242
|
+
"console.log('config log');\nprocess.stdout.write('config write\\n');\nexport default {integrations: []};\n",
|
|
243
|
+
);
|
|
244
|
+
|
|
245
|
+
const r = runCli(args);
|
|
246
|
+
|
|
247
|
+
expect(expectOneEnvelope(r, 'config log').type).toBe(type);
|
|
248
|
+
expect(r.stderr).toContain('config write');
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it('astryx.config.ts through jiti', () => {
|
|
252
|
+
write(
|
|
253
|
+
'package.json',
|
|
254
|
+
JSON.stringify({name: 'consumer', private: true, type: 'module'}),
|
|
255
|
+
);
|
|
256
|
+
write(
|
|
257
|
+
'astryx.config.ts',
|
|
258
|
+
"console.log('config log');\nconst config: {integrations: string[]} = {integrations: []};\nexport default config;\n",
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
const r = runCli(['--json', 'template', '--list']);
|
|
262
|
+
|
|
263
|
+
expect(r.status, r.stderr).toBe(0);
|
|
264
|
+
expect(expectOneEnvelope(r, 'config log').type).toBe('template.list');
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
it('CommonJS astryx.config.js reloaded fresh by upgrade', () => {
|
|
268
|
+
write('package.json', JSON.stringify({name: 'consumer', private: true}));
|
|
269
|
+
write(
|
|
270
|
+
'node_modules/@astryxdesign/core/package.json',
|
|
271
|
+
JSON.stringify({name: '@astryxdesign/core', version: '0.6.3'}),
|
|
272
|
+
);
|
|
273
|
+
write(
|
|
274
|
+
'astryx.config.js',
|
|
275
|
+
"console.log('config log');\nmodule.exports = {integrations: []};\n",
|
|
276
|
+
);
|
|
277
|
+
|
|
278
|
+
const r = runCli(['--json', 'upgrade', '--from', '0.5.0']);
|
|
279
|
+
|
|
280
|
+
expect(r.status, r.stderr).toBe(0);
|
|
281
|
+
expect(expectOneEnvelope(r, 'config log').type).toBe('upgrade.run');
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
it('an error envelope while sibling project loads are still in flight', () => {
|
|
285
|
+
// search rejects on a Core without sources before its parallel loads settle.
|
|
286
|
+
write(
|
|
287
|
+
'package.json',
|
|
288
|
+
JSON.stringify({name: 'consumer', private: true, type: 'module'}),
|
|
289
|
+
);
|
|
290
|
+
write(
|
|
291
|
+
'node_modules/@astryxdesign/core/package.json',
|
|
292
|
+
JSON.stringify({name: '@astryxdesign/core', version: '0.6.3'}),
|
|
293
|
+
);
|
|
294
|
+
write(
|
|
295
|
+
'astryx.config.mjs',
|
|
296
|
+
"console.log('config log');\nexport default {integrations: []};\n",
|
|
297
|
+
);
|
|
298
|
+
|
|
299
|
+
const r = runCli(['--json', 'search', 'button']);
|
|
300
|
+
|
|
301
|
+
expect(r.status).toBe(1);
|
|
302
|
+
expect(typeof expectOneEnvelope(r, 'config log').code).toBe('string');
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
it('an autolinked integration manifest', () => {
|
|
306
|
+
installNoisyIntegration();
|
|
307
|
+
|
|
308
|
+
const r = runCli(['--json', 'docs']);
|
|
309
|
+
|
|
310
|
+
expect(r.status, r.stderr).toBe(0);
|
|
311
|
+
expect(expectOneEnvelope(r, 'manifest log').type).toBe('docs.list');
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
it.each([
|
|
315
|
+
[['--json', 'integration', 'add', 'doc', 'deploying'], 'integration.add'],
|
|
316
|
+
[['--json', 'doctor', 'integration', 'validate'], 'integration.validate'],
|
|
317
|
+
])('the local package manifest: %j', (args, type) => {
|
|
318
|
+
write(
|
|
319
|
+
'package.json',
|
|
320
|
+
JSON.stringify({name: 'acme-kit', version: '1.0.0', type: 'module'}),
|
|
321
|
+
);
|
|
322
|
+
write(
|
|
323
|
+
'astryx.integration.mjs',
|
|
324
|
+
"console.log('manifest log');\nexport default {};\n",
|
|
325
|
+
);
|
|
326
|
+
|
|
327
|
+
const r = runCli(args);
|
|
328
|
+
|
|
329
|
+
expect(r.status, r.stderr).toBe(0);
|
|
330
|
+
expect(expectOneEnvelope(r, 'manifest log').type).toBe(type);
|
|
331
|
+
});
|
|
332
|
+
});
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*
|
|
8
8
|
* - Absolute paths (e.g. `/tmp/x`) — re-rooted by `path.join` is silent;
|
|
9
9
|
* better to fail loudly. Pass `{allowAbsolute: true}` to opt in.
|
|
10
|
-
* - Paths that escape via `..` segments.
|
|
10
|
+
* - Paths that escape via `..` segments or a symlink, dangling or not.
|
|
11
11
|
*
|
|
12
12
|
* Returns the resolved absolute path on success.
|
|
13
13
|
*
|
|
@@ -19,7 +19,8 @@
|
|
|
19
19
|
* @param {string} [options.label='path'] - Human-readable name of the arg
|
|
20
20
|
* being checked (used in error messages, e.g. 'output directory').
|
|
21
21
|
* @returns {string} Absolute resolved path inside `rootDir`.
|
|
22
|
-
* @throws {PathSafetyError}
|
|
22
|
+
* @throws {PathSafetyError} An escape carries the registered
|
|
23
|
+
* `ERR_PATH_TRAVERSAL`, so it reaches an envelope correctly even uncaught.
|
|
23
24
|
*/
|
|
24
25
|
export function assertWithin(targetPath: string, rootDir: string, options?: {
|
|
25
26
|
allowAbsolute?: boolean | undefined;
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
import * as path from 'node:path';
|
|
16
16
|
import * as fs from 'node:fs';
|
|
17
|
+
import {ERROR_CODES} from '../response/error-codes.mjs';
|
|
17
18
|
|
|
18
19
|
/**
|
|
19
20
|
* Error thrown by path-safety guards. Carries a stable `code`
|
|
@@ -38,7 +39,7 @@ export class PathSafetyError extends Error {
|
|
|
38
39
|
*
|
|
39
40
|
* - Absolute paths (e.g. `/tmp/x`) — re-rooted by `path.join` is silent;
|
|
40
41
|
* better to fail loudly. Pass `{allowAbsolute: true}` to opt in.
|
|
41
|
-
* - Paths that escape via `..` segments.
|
|
42
|
+
* - Paths that escape via `..` segments or a symlink, dangling or not.
|
|
42
43
|
*
|
|
43
44
|
* Returns the resolved absolute path on success.
|
|
44
45
|
*
|
|
@@ -50,7 +51,8 @@ export class PathSafetyError extends Error {
|
|
|
50
51
|
* @param {string} [options.label='path'] - Human-readable name of the arg
|
|
51
52
|
* being checked (used in error messages, e.g. 'output directory').
|
|
52
53
|
* @returns {string} Absolute resolved path inside `rootDir`.
|
|
53
|
-
* @throws {PathSafetyError}
|
|
54
|
+
* @throws {PathSafetyError} An escape carries the registered
|
|
55
|
+
* `ERR_PATH_TRAVERSAL`, so it reaches an envelope correctly even uncaught.
|
|
54
56
|
*/
|
|
55
57
|
export function assertWithin(targetPath, rootDir, options = {}) {
|
|
56
58
|
const {allowAbsolute = false, label = 'path'} = options;
|
|
@@ -83,42 +85,70 @@ export function assertWithin(targetPath, rootDir, options = {}) {
|
|
|
83
85
|
throw new PathSafetyError(
|
|
84
86
|
`Invalid ${label} "${targetPath}": resolves outside the project root ` +
|
|
85
87
|
`(${absRoot}). Path traversal is not allowed.`,
|
|
86
|
-
|
|
88
|
+
ERROR_CODES.ERR_PATH_TRAVERSAL,
|
|
87
89
|
);
|
|
88
90
|
}
|
|
89
91
|
|
|
90
92
|
// path.resolve is purely lexical — it does NOT follow symlinks, so a symlink
|
|
91
93
|
// INSIDE the root that points outside would pass the check above while the
|
|
92
|
-
// real write lands outside root. Canonicalize
|
|
93
|
-
//
|
|
94
|
-
//
|
|
95
|
-
// writes through this guard.
|
|
94
|
+
// real write lands outside root. Canonicalize both paths through every
|
|
95
|
+
// symlink, dangling ones included (a write through a dangling link creates
|
|
96
|
+
// its target), and re-check.
|
|
96
97
|
try {
|
|
97
|
-
const realRoot =
|
|
98
|
-
|
|
99
|
-
while (!fs.existsSync(existing)) {
|
|
100
|
-
const parent = path.dirname(existing);
|
|
101
|
-
if (parent === existing) break;
|
|
102
|
-
existing = parent;
|
|
103
|
-
}
|
|
104
|
-
const realExisting = fs.realpathSync(existing);
|
|
98
|
+
const realRoot = canonicalPath(absRoot);
|
|
99
|
+
const realTarget = canonicalPath(resolved);
|
|
105
100
|
const realRootWithSep = realRoot.endsWith(path.sep) ? realRoot : realRoot + path.sep;
|
|
106
|
-
if (
|
|
101
|
+
if (realTarget !== realRoot && !realTarget.startsWith(realRootWithSep)) {
|
|
107
102
|
throw new PathSafetyError(
|
|
108
103
|
`Invalid ${label} "${targetPath}": resolves outside the project root ` +
|
|
109
104
|
`(${absRoot}) via a symlink. Path traversal is not allowed.`,
|
|
110
|
-
|
|
105
|
+
ERROR_CODES.ERR_PATH_TRAVERSAL,
|
|
111
106
|
);
|
|
112
107
|
}
|
|
113
108
|
} catch (err) {
|
|
114
109
|
if (err instanceof PathSafetyError) throw err;
|
|
115
|
-
//
|
|
116
|
-
//
|
|
110
|
+
// A symlink loop, a permission error, or a race; fall back to the lexical
|
|
111
|
+
// result already validated above rather than crash.
|
|
117
112
|
}
|
|
118
113
|
|
|
119
114
|
return resolved;
|
|
120
115
|
}
|
|
121
116
|
|
|
117
|
+
/** Linux's MAXSYMLINKS; a longer chain is treated as a loop. */
|
|
118
|
+
const MAX_SYMLINK_HOPS = 40;
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* `target` with every symlink resolved, including a dangling one, which
|
|
122
|
+
* `fs.realpathSync` refuses. Components below the deepest existing entry are
|
|
123
|
+
* appended as given.
|
|
124
|
+
*
|
|
125
|
+
* @param {string} target absolute path
|
|
126
|
+
* @param {number} [hops] symlinks followed so far
|
|
127
|
+
* @returns {string}
|
|
128
|
+
* @throws when the chain exceeds {@link MAX_SYMLINK_HOPS} or `lstat` fails
|
|
129
|
+
* for a reason other than a missing entry
|
|
130
|
+
*/
|
|
131
|
+
function canonicalPath(target, hops = 0) {
|
|
132
|
+
if (hops > MAX_SYMLINK_HOPS) throw new Error(`Too many symlinks: ${target}`);
|
|
133
|
+
let stat;
|
|
134
|
+
try {
|
|
135
|
+
stat = fs.lstatSync(target);
|
|
136
|
+
} catch (err) {
|
|
137
|
+
const code = /** @type {NodeJS.ErrnoException} */ (err).code;
|
|
138
|
+
if (code !== 'ENOENT' && code !== 'ENOTDIR') throw err;
|
|
139
|
+
const parent = path.dirname(target);
|
|
140
|
+
if (parent === target) return target;
|
|
141
|
+
return path.join(canonicalPath(parent, hops), path.basename(target));
|
|
142
|
+
}
|
|
143
|
+
try {
|
|
144
|
+
return fs.realpathSync(target);
|
|
145
|
+
} catch (err) {
|
|
146
|
+
if (!stat.isSymbolicLink()) throw err;
|
|
147
|
+
}
|
|
148
|
+
const link = fs.readlinkSync(target);
|
|
149
|
+
return canonicalPath(path.resolve(fs.realpathSync(path.dirname(target)), link), hops + 1);
|
|
150
|
+
}
|
|
151
|
+
|
|
122
152
|
/**
|
|
123
153
|
* Validate a "name" argument (e.g. theme name, component name) that will
|
|
124
154
|
* be embedded in a generated filename. Rejects names containing path
|
|
@@ -19,6 +19,19 @@ afterEach(() => {
|
|
|
19
19
|
fs.rmSync(tmpDir, {recursive: true, force: true});
|
|
20
20
|
});
|
|
21
21
|
|
|
22
|
+
/**
|
|
23
|
+
* The code a guard rejected with, or 'accepted'.
|
|
24
|
+
* @param {() => unknown} fn
|
|
25
|
+
*/
|
|
26
|
+
function rejectionCode(fn) {
|
|
27
|
+
try {
|
|
28
|
+
fn();
|
|
29
|
+
} catch (err) {
|
|
30
|
+
return err instanceof PathSafetyError ? err.code : err;
|
|
31
|
+
}
|
|
32
|
+
return 'accepted';
|
|
33
|
+
}
|
|
34
|
+
|
|
22
35
|
describe('assertWithin', () => {
|
|
23
36
|
it('accepts a relative path that stays inside root', () => {
|
|
24
37
|
const result = assertWithin('subdir/file.txt', tmpDir);
|
|
@@ -38,6 +51,14 @@ describe('assertWithin', () => {
|
|
|
38
51
|
expect(() => assertWithin('a/b/../../../c', tmpDir)).toThrow(PathSafetyError);
|
|
39
52
|
});
|
|
40
53
|
|
|
54
|
+
it('reports an escape with the registered ERR_PATH_TRAVERSAL code', () => {
|
|
55
|
+
// An uncaught guard error reaches the JSON envelope with this code as-is.
|
|
56
|
+
expect(rejectionCode(() => assertWithin('../escaped', tmpDir))).toBe('ERR_PATH_TRAVERSAL');
|
|
57
|
+
expect(
|
|
58
|
+
rejectionCode(() => assertWithin('/etc/passwd', tmpDir, {allowAbsolute: true})),
|
|
59
|
+
).toBe('ERR_PATH_TRAVERSAL');
|
|
60
|
+
});
|
|
61
|
+
|
|
41
62
|
it('rejects absolute paths by default', () => {
|
|
42
63
|
expect(() => assertWithin('/tmp/elsewhere', tmpDir)).toThrow(/absolute paths are not allowed/i);
|
|
43
64
|
});
|
|
@@ -160,6 +181,7 @@ describe('assertWithin — symlink escape (realpath guard)', () => {
|
|
|
160
181
|
// path.resolve is lexical and would pass this; the realpath check must catch it.
|
|
161
182
|
expect(() => assertWithin('link/evil.txt', root)).toThrow(PathSafetyError);
|
|
162
183
|
expect(() => assertWithin('link/evil.txt', root)).toThrow(/outside|symlink|traversal/i);
|
|
184
|
+
expect(rejectionCode(() => assertWithin('link/evil.txt', root))).toBe('ERR_PATH_TRAVERSAL');
|
|
163
185
|
});
|
|
164
186
|
|
|
165
187
|
it('still accepts a legit non-existent nested path inside root', () => {
|
|
@@ -169,4 +191,32 @@ describe('assertWithin — symlink escape (realpath guard)', () => {
|
|
|
169
191
|
it('rejects a NUL byte in the path', () => {
|
|
170
192
|
expect(() => assertWithin('a\u0000b', root)).toThrow(PathSafetyError);
|
|
171
193
|
});
|
|
194
|
+
|
|
195
|
+
it('rejects a dangling symlink whose target is outside root', () => {
|
|
196
|
+
// A write through the link would create the missing target outside root.
|
|
197
|
+
fs.symlinkSync(path.join(outside, 'created.txt'), path.join(root, 'leaf'));
|
|
198
|
+
expect(rejectionCode(() => assertWithin('leaf', root))).toBe('ERR_PATH_TRAVERSAL');
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it('rejects a path through a dangling directory symlink that points outside root', () => {
|
|
202
|
+
fs.symlinkSync(path.join(outside, 'missing-dir'), path.join(root, 'dir'));
|
|
203
|
+
expect(rejectionCode(() => assertWithin('dir/evil.txt', root))).toBe('ERR_PATH_TRAVERSAL');
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it('follows a chain of links that ends in a dangling target outside root', () => {
|
|
207
|
+
fs.symlinkSync(path.join(outside, 'missing.txt'), path.join(root, 'second'));
|
|
208
|
+
fs.symlinkSync('second', path.join(root, 'first'));
|
|
209
|
+
expect(rejectionCode(() => assertWithin('first', root))).toBe('ERR_PATH_TRAVERSAL');
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it('accepts a dangling symlink whose target stays inside root', () => {
|
|
213
|
+
fs.symlinkSync(path.join('sub', 'new.txt'), path.join(root, 'leaf'));
|
|
214
|
+
expect(assertWithin('leaf', root)).toBe(path.join(root, 'leaf'));
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it('terminates on a symlink loop', () => {
|
|
218
|
+
fs.symlinkSync('loop', path.join(root, 'loop'));
|
|
219
|
+
const result = rejectionCode(() => assertWithin('loop', root));
|
|
220
|
+
expect(['accepted', 'ERR_PATH_TRAVERSAL']).toContain(result);
|
|
221
|
+
});
|
|
172
222
|
});
|