@astryxdesign/cli 0.6.3-canary.ea2f048 → 0.6.3-canary.ee13359
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 +114 -76
- 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/_adapter.d.mts +6 -12
- package/api/component/_adapter.mjs +20 -10
- package/api/component/component.doc.mjs +13 -3
- package/api/component/component.mjs +91 -14
- 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 +40 -11
- 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 +230 -41
- package/api/docs/_adapter.mjs +734 -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/docs.d.mts +10 -3
- package/api/docs/docs.doc.mjs +54 -16
- package/api/docs/docs.mjs +53 -10
- package/api/docs/docs.test.mjs +164 -4
- package/api/docs/docs.type.d.mts +205 -5
- package/api/docs/docs.type.mjs +137 -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/integration-tree.test.mjs +547 -0
- package/api/docs/integrationDocs.test.mjs +114 -8
- 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/doctor/doctor.d.mts +99 -1
- package/api/doctor/doctor.doc.mjs +1 -0
- package/api/doctor/doctor.mjs +544 -1
- package/api/doctor/doctor.test.mjs +610 -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/index.d.mts +1 -1
- package/api/index.mjs +1 -0
- package/api/init/init.doc.mjs +6 -1
- 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-theme.mjs +34 -64
- package/api/integration/add-theme.test.mjs +105 -21
- package/api/integration/authoring-checks.mjs +108 -31
- package/api/integration/authoring-checks.test.mjs +171 -6
- package/api/integration/authoring-checks.type.d.mts +3 -1
- package/api/integration/authoring-checks.type.mjs +3 -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 +2 -1
- package/api/integration/integrationTemplateConflicts.doc.mjs +7 -5
- package/api/integration/pack-check.mjs +83 -7
- package/api/integration/pack-check.test.mjs +387 -47
- package/api/integration/pack-check.type.d.mts +26 -2
- package/api/integration/pack-check.type.mjs +14 -1
- 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 +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 +1 -0
- package/api/json/envelope-types.test.mjs +76 -0
- package/api/json/index.ts +2 -0
- package/api/json/isError.doc.mjs +1 -0
- 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 +29 -11
- package/api/search/search.doc.mjs +8 -2
- package/api/search/search.mjs +337 -41
- package/api/search/search.test.mjs +124 -1
- 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 +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/list/list.mjs +1 -0
- package/api/template/template-integration.test.mjs +1022 -3
- package/api/template/template-suffix.test.mjs +41 -21
- package/api/template/template.d.mts +1 -1
- package/api/template/template.doc.mjs +30 -8
- 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 +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 +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 +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 +6 -2
- 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/run.mjs +356 -59
- package/api/upgrade/status/status.mjs +2 -2
- package/api/upgrade/upgrade.doc.mjs +12 -5
- package/api/upgrade/upgrade.type.d.mts +43 -5
- package/api/upgrade/upgrade.type.mjs +27 -11
- package/assets/codemods/__tests__/runner.test.mjs +330 -8
- package/assets/codemods/integration-discovery.mjs +48 -4
- package/assets/codemods/integration-discovery.test.mjs +73 -0
- package/assets/codemods/integration-runner.mjs +56 -4
- package/assets/codemods/integration-runner.protection.test.mjs +153 -0
- package/assets/codemods/run-codemod.mjs +177 -34
- package/assets/codemods/runner.mjs +350 -102
- 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-native-picker-to-presentation.test.mjs +63 -0
- package/assets/codemods/transforms/next/__tests__/migrate-theme-catalog-to-descriptors.test.mjs +220 -0
- package/assets/codemods/transforms/next/index.mjs +19 -1
- package/assets/codemods/transforms/next/migrate-native-picker-to-presentation.mjs +148 -0
- 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/getting-started.doc.mjs +2 -2
- package/assets/docs/principles.doc.mjs +6 -6
- package/assets/docs/styling-libraries.doc.mjs +3 -3
- package/assets/docs/styling.doc.mjs +4 -4
- package/assets/docs/theme.doc.mjs +4 -4
- package/assets/docs/tokens.doc.mjs +1 -1
- package/assets/docs/tree/api.doc.mjs +30 -0
- package/assets/docs/tree/cli.doc.mjs +23 -0
- package/assets/docs/tree/commands.doc.mjs +25 -0
- package/assets/docs/{cli-integrations.doc.mjs → tree/integrations.doc.mjs} +124 -25
- package/assets/docs/tree/integrations.test.mjs +62 -0
- package/assets/docs/tree/writing-docs.doc.mjs +286 -0
- package/assets/docs/working-with-ai.doc.mjs +1 -1
- package/assets/templates/blocks/components/DateInput/DateInputDateRange.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/TimeInput/TimeInputConstrained.tsx +1 -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/codemod/type.ts +12 -0
- 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 +8 -8
- package/authoring/debug/parse.mjs +3 -3
- package/authoring/doctypes/_schema.d.mts +718 -23
- package/authoring/doctypes/_schema.mjs +519 -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 +41 -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 +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 +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 +208 -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 +20 -5
- package/authoring/doctypes/reference/type.ts +53 -5
- 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 +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 +2 -0
- package/authoring/index.d.ts +52 -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 +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 +5 -2
- package/clients/cli/commands/build.exit-codes-doc.test.mjs +50 -0
- package/clients/cli/commands/build.mjs +37 -49
- 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 +3 -8
- package/clients/cli/commands/component-ownership.test.mjs +3 -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 +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 +52 -5
- 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 +4 -4
- 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 +238 -24
- package/clients/cli/commands/docs.test.mjs +193 -1
- package/clients/cli/commands/doctor-integration-components.doc.mjs +1 -1
- package/clients/cli/commands/doctor-integration-docs.doc.mjs +3 -3
- 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 +83 -8
- package/clients/cli/commands/doctor.doc.mjs +1 -1
- package/clients/cli/commands/doctor.mjs +30 -25
- 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 +32 -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/integration.mjs +1 -0
- 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 +28 -9
- package/clients/cli/commands/search.test.mjs +75 -0
- package/clients/cli/commands/setup-nudge.test.mjs +6 -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 +52 -13
- 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 +719 -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.behavior.test.mjs +4 -3
- 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 +22 -10
- 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 +21 -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 +36 -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 +70 -3
- package/foundation/agent-docs/agent-docs.path-safety.test.mjs +266 -4
- package/foundation/agent-docs/agent-docs.test.mjs +10 -1
- 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 +75 -0
- package/foundation/discovery/authoring-self-docs.mjs +224 -0
- package/foundation/discovery/authoring-self-docs.test.mjs +165 -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 +490 -0
- package/foundation/discovery/cli-self-docs.test.mjs +375 -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 +116 -11
- package/foundation/discovery/docs-discovery.mjs +389 -108
- package/foundation/discovery/docs-discovery.test.mjs +325 -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/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 +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 +291 -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 +471 -0
- package/foundation/doc-compiler/lenses.d.mts +34 -0
- package/foundation/doc-compiler/lenses.mjs +128 -0
- package/foundation/doc-compiler/links.d.mts +135 -0
- package/foundation/doc-compiler/links.mjs +253 -0
- package/foundation/doc-compiler/links.test.mjs +139 -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 +288 -0
- package/foundation/doc-compiler/tree.mjs +876 -0
- package/foundation/doc-compiler/tree.test.mjs +598 -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 +45 -0
- package/foundation/integrations/cli-requirement.mjs +154 -0
- package/foundation/integrations/cli-requirement.test.mjs +109 -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 +27 -24
- 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 +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 +131 -29
- package/foundation/response/base.d.ts +8 -4
- 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 +13 -4
- package/foundation/response/error-codes.mjs +8 -2
- package/foundation/response/error-codes.test.mjs +137 -10
- 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 +26 -16
- 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 +4 -3
- package/foundation/xle/expand.test.mjs +54 -0
- package/foundation/xle/xle.test.mjs +13 -0
- package/package.json +10 -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
|
@@ -20,7 +20,12 @@
|
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
22
|
import {describe, it, expect} from 'vitest';
|
|
23
|
+
import {spawnSync} from 'node:child_process';
|
|
24
|
+
import {fileURLToPath} from 'node:url';
|
|
25
|
+
import {Command} from 'commander';
|
|
23
26
|
import {runCli} from '../../../test-utils/run-cli.mjs';
|
|
27
|
+
import {installJsonShim} from './json-shim.mjs';
|
|
28
|
+
import {setJsonMode} from '../../../foundation/response/json.mjs';
|
|
24
29
|
|
|
25
30
|
function parseJson(stdout) {
|
|
26
31
|
return JSON.parse(stdout);
|
|
@@ -207,3 +212,81 @@ describe('--json shim: stdout discipline under --json', () => {
|
|
|
207
212
|
}
|
|
208
213
|
});
|
|
209
214
|
});
|
|
215
|
+
|
|
216
|
+
describe('--json shim: help shown for a failed invocation is an error envelope', () => {
|
|
217
|
+
it('astryx help bogus --json emits ERR_UNKNOWN_COMMAND, exit 1', async () => {
|
|
218
|
+
const {status, stdout, stderr} = await runCli(['help', 'bogus', '--json']);
|
|
219
|
+
expect(status).toBe(1);
|
|
220
|
+
expect(stderr).toBe('');
|
|
221
|
+
const parsed = parseJson(stdout);
|
|
222
|
+
expect(parsed).not.toHaveProperty('type');
|
|
223
|
+
expect(parsed.apiVersion).toBe(1);
|
|
224
|
+
expect(parsed.code).toBe('ERR_UNKNOWN_COMMAND');
|
|
225
|
+
expect(parsed.error).toMatch(/bogus/);
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it('astryx layout --json (group, no subcommand) emits ERR_MISSING_ARGUMENT, exit 1', async () => {
|
|
229
|
+
const {status, stdout, stderr} = await runCli(['layout', '--json']);
|
|
230
|
+
expect(status).toBe(1);
|
|
231
|
+
expect(stderr).toBe('');
|
|
232
|
+
const parsed = parseJson(stdout);
|
|
233
|
+
expect(parsed).not.toHaveProperty('type');
|
|
234
|
+
expect(parsed.code).toBe('ERR_MISSING_ARGUMENT');
|
|
235
|
+
expect(parsed.suggestions.map((s) => s.name)).toContain('layout expand');
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it('astryx layout (no --json) still prints help to stderr, exit 1', async () => {
|
|
239
|
+
const {status, stdout, stderr} = await runCli(['layout']);
|
|
240
|
+
expect(status).toBe(1);
|
|
241
|
+
expect(stdout).toBe('');
|
|
242
|
+
expect(stderr).toMatch(/Usage: astryx layout/);
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
it('the real binary emits the same envelope for help bogus --json', () => {
|
|
246
|
+
// One program per process, so the root's own outputHelp patch is used here.
|
|
247
|
+
const bin = fileURLToPath(new URL('../bin/astryx.mjs', import.meta.url));
|
|
248
|
+
const res = spawnSync(process.execPath, [bin, 'help', 'bogus', '--json'], {
|
|
249
|
+
encoding: 'utf-8',
|
|
250
|
+
timeout: 20_000,
|
|
251
|
+
});
|
|
252
|
+
expect(res.status).toBe(1);
|
|
253
|
+
expect(res.stderr).toBe('');
|
|
254
|
+
const parsed = parseJson(res.stdout);
|
|
255
|
+
expect(parsed).not.toHaveProperty('type');
|
|
256
|
+
expect(parsed.code).toBe('ERR_UNKNOWN_COMMAND');
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it('a group added after install gets the same error envelope', () => {
|
|
260
|
+
const program = new Command('astryx');
|
|
261
|
+
installJsonShim(program);
|
|
262
|
+
// Added later, so only the prototype-level patch covers its outputHelp.
|
|
263
|
+
const late = new Command('late');
|
|
264
|
+
late.command('child');
|
|
265
|
+
program.addCommand(late);
|
|
266
|
+
/** @type {string[]} */
|
|
267
|
+
const writes = [];
|
|
268
|
+
const origWrite = process.stdout.write;
|
|
269
|
+
// @ts-expect-error - test-only capture
|
|
270
|
+
process.stdout.write = (chunk) => writes.push(String(chunk)) > 0;
|
|
271
|
+
setJsonMode(true);
|
|
272
|
+
delete process.__xdsJsonHandled;
|
|
273
|
+
try {
|
|
274
|
+
late.outputHelp({error: true});
|
|
275
|
+
} finally {
|
|
276
|
+
process.stdout.write = origWrite;
|
|
277
|
+
setJsonMode(false);
|
|
278
|
+
delete process.__xdsJsonHandled;
|
|
279
|
+
}
|
|
280
|
+
const parsed = parseJson(writes.join(''));
|
|
281
|
+
expect(parsed.code).toBe('ERR_MISSING_ARGUMENT');
|
|
282
|
+
expect(parsed.suggestions).toEqual([{name: 'late child', reason: 'available subcommand'}]);
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
it('astryx help layout --json still emits the help envelope, exit 0', async () => {
|
|
286
|
+
const {status, stdout} = await runCli(['help', 'layout', '--json']);
|
|
287
|
+
expect(status).toBe(0);
|
|
288
|
+
const parsed = parseJson(stdout);
|
|
289
|
+
expect(parsed.type).toBe('help');
|
|
290
|
+
expect(parsed.data.command).toBe('astryx layout');
|
|
291
|
+
});
|
|
292
|
+
});
|
|
@@ -48,6 +48,8 @@ export interface ManifestCommand {
|
|
|
48
48
|
responseTypes?: string[];
|
|
49
49
|
/** Example invocations. */
|
|
50
50
|
examples?: string[];
|
|
51
|
+
/** Documented exit codes: the process exit status and when it occurs. */
|
|
52
|
+
exitCodes?: {code: number; when: string}[];
|
|
51
53
|
/** Nested subcommands (e.g. `theme build` under `theme`). */
|
|
52
54
|
subcommands?: ManifestCommand[];
|
|
53
55
|
}
|
|
@@ -35,6 +35,8 @@
|
|
|
35
35
|
*/
|
|
36
36
|
|
|
37
37
|
import {API_VERSION} from '../../../foundation/response/json.mjs';
|
|
38
|
+
import {commandDocsOf} from './define-command.mjs';
|
|
39
|
+
import {doc as manifestDoc} from '../commands/manifest.doc.mjs';
|
|
38
40
|
|
|
39
41
|
/**
|
|
40
42
|
* Response `type` discriminators each fully-qualified command can emit in
|
|
@@ -55,7 +57,13 @@ export const RESPONSE_TYPES = {
|
|
|
55
57
|
'component.detail.showcase',
|
|
56
58
|
'component.detail.blocks',
|
|
57
59
|
],
|
|
58
|
-
docs: [
|
|
60
|
+
docs: [
|
|
61
|
+
'docs.list',
|
|
62
|
+
'docs.index',
|
|
63
|
+
'docs.detail',
|
|
64
|
+
'docs.detail.section',
|
|
65
|
+
'docs.node',
|
|
66
|
+
],
|
|
59
67
|
blog: ['blog.list', 'blog.detail'],
|
|
60
68
|
discover: [
|
|
61
69
|
'discover.list',
|
|
@@ -105,7 +113,13 @@ const EXAMPLES = {
|
|
|
105
113
|
'astryx component XDSButton',
|
|
106
114
|
'astryx component XDSButton --props --json',
|
|
107
115
|
],
|
|
108
|
-
docs: [
|
|
116
|
+
docs: [
|
|
117
|
+
'astryx docs',
|
|
118
|
+
'astryx docs spacing --json',
|
|
119
|
+
'astryx docs theme',
|
|
120
|
+
'astryx docs theme quick-start',
|
|
121
|
+
'astryx docs cli/integrations --full',
|
|
122
|
+
],
|
|
109
123
|
discover: ['astryx discover --json'],
|
|
110
124
|
search: [
|
|
111
125
|
'astryx search modal --json',
|
|
@@ -232,6 +246,18 @@ function fullName(cmd, root) {
|
|
|
232
246
|
return parts.join(' ');
|
|
233
247
|
}
|
|
234
248
|
|
|
249
|
+
/**
|
|
250
|
+
* The docs a command was built from. `manifest` is registered by hand in
|
|
251
|
+
* index.mjs, so its CommandDoc is read here.
|
|
252
|
+
* @param {import('commander').Command} cmd
|
|
253
|
+
* @param {string} name
|
|
254
|
+
*/
|
|
255
|
+
function docsOf(cmd, name) {
|
|
256
|
+
return (
|
|
257
|
+
commandDocsOf(cmd) ?? (name === 'manifest' ? {doc: manifestDoc} : undefined)
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
|
|
235
261
|
/**
|
|
236
262
|
* Recursively describe a Commander command and its subcommands.
|
|
237
263
|
*
|
|
@@ -247,6 +273,8 @@ function describeCommand(cmd, root, jsonSupported) {
|
|
|
247
273
|
// `_hidden` is a Commander internal not present on its public types.
|
|
248
274
|
if (!name || /** @type {any} */ (cmd)._hidden || name === 'help') return null;
|
|
249
275
|
|
|
276
|
+
const docs = docsOf(cmd, name);
|
|
277
|
+
|
|
250
278
|
const subcommands = /** @type {object[]} */ (
|
|
251
279
|
(cmd.commands || [])
|
|
252
280
|
.map(sub => describeCommand(sub, root, jsonSupported))
|
|
@@ -279,6 +307,12 @@ function describeCommand(cmd, root, jsonSupported) {
|
|
|
279
307
|
|
|
280
308
|
if (EXAMPLES[name]) entry.examples = [...EXAMPLES[name]];
|
|
281
309
|
|
|
310
|
+
const exitCodes = (docs?.doc.exitCodes ?? []).map(({code, when}) => ({
|
|
311
|
+
code,
|
|
312
|
+
when,
|
|
313
|
+
}));
|
|
314
|
+
if (exitCodes.length > 0) entry.exitCodes = exitCodes;
|
|
315
|
+
|
|
282
316
|
// Sort subcommands by name for a stable, agent-facing contract — the same
|
|
283
317
|
// guarantee the top-level command list makes. Otherwise Commander
|
|
284
318
|
// registration order leaks into the manifest and a pure reorder of
|
|
@@ -196,3 +196,20 @@ describe('manifest: e2e', () => {
|
|
|
196
196
|
.toContain('component.list');
|
|
197
197
|
});
|
|
198
198
|
});
|
|
199
|
+
|
|
200
|
+
describe('manifest: text projection', () => {
|
|
201
|
+
it('astryx manifest (text) uses the JSON entry keys as its field names', async () => {
|
|
202
|
+
const text = await runCli(['manifest']);
|
|
203
|
+
expect(text.status).toBe(0);
|
|
204
|
+
const json = JSON.parse((await runCli(['manifest', '--json'])).stdout);
|
|
205
|
+
const jsonKeys = new Set(json.data.commands.flatMap((c) => Object.keys(c)));
|
|
206
|
+
const textKeys = new Set(
|
|
207
|
+
text.stdout
|
|
208
|
+
.split('\n')
|
|
209
|
+
.map((line) => /^([A-Za-z]+):\s/.exec(line)?.[1])
|
|
210
|
+
.filter(Boolean),
|
|
211
|
+
);
|
|
212
|
+
expect(textKeys).toContain('name');
|
|
213
|
+
for (const key of textKeys) expect(jsonKeys).toContain(key);
|
|
214
|
+
});
|
|
215
|
+
});
|
|
@@ -182,6 +182,8 @@ export function removeXdsBlock(filePath: string, { deleteIfEmpty }?: {
|
|
|
182
182
|
/**
|
|
183
183
|
* Remove Astryx section from all known agent doc files.
|
|
184
184
|
* @param {string} targetDir
|
|
185
|
+
* @throws {PathSafetyError} `ERR_PATH_TRAVERSAL` when a file it would change
|
|
186
|
+
* resolves outside `targetDir`; nothing is changed.
|
|
185
187
|
*/
|
|
186
188
|
export function removeAgentDocs(targetDir: string): void;
|
|
187
189
|
/**
|
|
@@ -208,6 +210,8 @@ export function removeAgentDocs(targetDir: string): void;
|
|
|
208
210
|
* @param {string} [options.renderedBlock] - Fully rendered expected block. Init
|
|
209
211
|
* and upgrade pass one shared block to every target.
|
|
210
212
|
* @returns {string[]} List of files written
|
|
213
|
+
* @throws {import('../fs/path-safety.mjs').PathSafetyError} when a file it would
|
|
214
|
+
* write resolves outside `targetDir`; nothing is written.
|
|
211
215
|
*/
|
|
212
216
|
export function installAgentDocs(targetDir: string, { zh, lang, agent, paths, onlyReplace, topics, renderedBlock, }?: {
|
|
213
217
|
zh?: boolean | undefined;
|
|
@@ -22,11 +22,12 @@
|
|
|
22
22
|
import * as fs from 'node:fs';
|
|
23
23
|
import * as path from 'node:path';
|
|
24
24
|
import {findCoreDir, CLI_ROOT} from '../fs/paths.mjs';
|
|
25
|
-
import {assertWithin} from '../fs/path-safety.mjs';
|
|
25
|
+
import {assertWithin, PathSafetyError} from '../fs/path-safety.mjs';
|
|
26
26
|
import {getCliInvocation} from '../env/package-manager.mjs';
|
|
27
27
|
import {discoverComponents} from '../discovery/component-discovery.mjs';
|
|
28
28
|
import {Project} from '../config/project.mjs';
|
|
29
29
|
import {humanLog} from '../response/json.mjs';
|
|
30
|
+
import {ERROR_CODES} from '../response/error-codes.mjs';
|
|
30
31
|
import {
|
|
31
32
|
AGENTS_MD,
|
|
32
33
|
CLAUDE_MD,
|
|
@@ -485,6 +486,7 @@ export function generateCompressedIndex(
|
|
|
485
486
|
if (resolvedTopics.length > 0) {
|
|
486
487
|
lines.push(` docs <topic> ${resolvedTopics.join(', ')}`);
|
|
487
488
|
}
|
|
489
|
+
lines.push(' docs cli commands, API reference, integration authoring (one level at a time)');
|
|
488
490
|
lines.push(' swizzle <Name> eject component source for deep customization');
|
|
489
491
|
lines.push(' upgrade --apply run after any Astryx or integration dependency bump');
|
|
490
492
|
const appendCount = agentDocs.reduce(
|
|
@@ -705,12 +707,65 @@ export function removeXdsBlock(filePath, {deleteIfEmpty = false} = {}) {
|
|
|
705
707
|
return true;
|
|
706
708
|
}
|
|
707
709
|
|
|
710
|
+
/**
|
|
711
|
+
* Whether an agent-doc file already carries a managed-block marker.
|
|
712
|
+
* @param {string} filePath
|
|
713
|
+
* @returns {boolean}
|
|
714
|
+
*/
|
|
715
|
+
function hasManagedMarker(filePath) {
|
|
716
|
+
try {
|
|
717
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
718
|
+
return content.includes(MARKER_START) || content.includes(LEGACY_MARKER_START);
|
|
719
|
+
} catch {
|
|
720
|
+
return false;
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
/**
|
|
725
|
+
* Whether `removeXdsBlock` would change `filePath`: it holds one well-formed
|
|
726
|
+
* managed block.
|
|
727
|
+
* @param {string} filePath
|
|
728
|
+
* @returns {boolean}
|
|
729
|
+
*/
|
|
730
|
+
function hasRemovableBlock(filePath) {
|
|
731
|
+
try {
|
|
732
|
+
return findManagedBlock(fs.readFileSync(filePath, 'utf-8')) != null;
|
|
733
|
+
} catch {
|
|
734
|
+
return false;
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
/**
|
|
739
|
+
* Every file a run writes must resolve inside `targetDir`, symlinks
|
|
740
|
+
* included. Checked for the whole write set before the first write, so an
|
|
741
|
+
* escape writes nothing.
|
|
742
|
+
* @param {string} targetDir
|
|
743
|
+
* @param {Iterable<string>} relPaths
|
|
744
|
+
*/
|
|
745
|
+
function assertTargetsWithin(targetDir, relPaths) {
|
|
746
|
+
for (const p of relPaths) {
|
|
747
|
+
assertWithin(p, targetDir, {label: 'agent docs path'});
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
|
|
708
751
|
/**
|
|
709
752
|
* Remove Astryx section from all known agent doc files.
|
|
710
753
|
* @param {string} targetDir
|
|
754
|
+
* @throws {PathSafetyError} `ERR_PATH_TRAVERSAL` when a file it would change
|
|
755
|
+
* resolves outside `targetDir`; nothing is changed.
|
|
711
756
|
*/
|
|
712
757
|
export function removeAgentDocs(targetDir) {
|
|
713
758
|
const allPaths = discoverAgentDocs(targetDir);
|
|
759
|
+
try {
|
|
760
|
+
assertTargetsWithin(
|
|
761
|
+
targetDir,
|
|
762
|
+
allPaths.filter(p => hasRemovableBlock(path.join(targetDir, p))),
|
|
763
|
+
);
|
|
764
|
+
} catch (err) {
|
|
765
|
+
// The code reaches the error envelope as is, so it must be registered.
|
|
766
|
+
if (!(err instanceof PathSafetyError)) throw err;
|
|
767
|
+
throw new PathSafetyError(err.message, ERROR_CODES.ERR_PATH_TRAVERSAL);
|
|
768
|
+
}
|
|
714
769
|
|
|
715
770
|
for (const p of allPaths) {
|
|
716
771
|
const filePath = path.join(targetDir, p);
|
|
@@ -718,9 +773,9 @@ export function removeAgentDocs(targetDir) {
|
|
|
718
773
|
const deleteIfEmpty = p === AGENTS_MD || p === CLAUDE_DIR_MD;
|
|
719
774
|
if (removeXdsBlock(filePath, {deleteIfEmpty})) {
|
|
720
775
|
if (!fs.existsSync(filePath)) {
|
|
721
|
-
humanLog(
|
|
776
|
+
humanLog(`[ok] Removed empty ${p}`);
|
|
722
777
|
} else {
|
|
723
|
-
humanLog(
|
|
778
|
+
humanLog(`[ok] Removed design system section from ${p}`);
|
|
724
779
|
}
|
|
725
780
|
}
|
|
726
781
|
}
|
|
@@ -750,6 +805,8 @@ export function removeAgentDocs(targetDir) {
|
|
|
750
805
|
* @param {string} [options.renderedBlock] - Fully rendered expected block. Init
|
|
751
806
|
* and upgrade pass one shared block to every target.
|
|
752
807
|
* @returns {string[]} List of files written
|
|
808
|
+
* @throws {import('../fs/path-safety.mjs').PathSafetyError} when a file it would
|
|
809
|
+
* write resolves outside `targetDir`; nothing is written.
|
|
753
810
|
*/
|
|
754
811
|
export function installAgentDocs(
|
|
755
812
|
targetDir,
|
|
@@ -806,6 +863,7 @@ export function installAgentDocs(
|
|
|
806
863
|
// Agent preset
|
|
807
864
|
if (agent) {
|
|
808
865
|
const {inject, create} = resolveAgentPaths(targetDir, agent);
|
|
866
|
+
assertTargetsWithin(targetDir, [...inject, ...create]);
|
|
809
867
|
for (const p of inject) {
|
|
810
868
|
injectXdsBlock(path.join(targetDir, p), compressedIndex);
|
|
811
869
|
written.push(p);
|
|
@@ -833,6 +891,14 @@ export function installAgentDocs(
|
|
|
833
891
|
if (existing.length > 0) {
|
|
834
892
|
const wrappers = discoverAgentDocWrappers(targetDir, existing);
|
|
835
893
|
const targets = existing.filter(p => !wrappers.has(p));
|
|
894
|
+
// A refresh skips unmarked files and a wrapper is only written when it
|
|
895
|
+
// carries a block, so only the files this run writes are checked.
|
|
896
|
+
/** @param {string} p */
|
|
897
|
+
const marked = p => hasManagedMarker(path.join(targetDir, p));
|
|
898
|
+
assertTargetsWithin(targetDir, [
|
|
899
|
+
...targets.filter(p => !onlyReplace || marked(p)),
|
|
900
|
+
...[...wrappers].filter(marked),
|
|
901
|
+
]);
|
|
836
902
|
|
|
837
903
|
for (const p of targets) {
|
|
838
904
|
const didWrite = injectXdsBlock(path.join(targetDir, p), compressedIndex, {onlyReplace});
|
|
@@ -860,6 +926,7 @@ export function installAgentDocs(
|
|
|
860
926
|
if (onlyReplace) return written;
|
|
861
927
|
|
|
862
928
|
const defaultPath = AGENTS_MD;
|
|
929
|
+
assertTargetsWithin(targetDir, [defaultPath]);
|
|
863
930
|
injectXdsBlock(path.join(targetDir, defaultPath), compressedIndex, {
|
|
864
931
|
createIfMissing: true,
|
|
865
932
|
header: `# AGENTS.md\n\nProject-specific guidance for AI coding agents.`,
|
|
@@ -1,22 +1,31 @@
|
|
|
1
1
|
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* @file Path-safety regression tests for
|
|
4
|
+
* @file Path-safety regression tests for agent-doc writes.
|
|
5
5
|
*
|
|
6
6
|
* `installAgentDocs(targetDir, {paths})` previously joined each path
|
|
7
7
|
* onto `targetDir` with no validation, so:
|
|
8
8
|
* - `../OUT.md` wrote outside `targetDir`
|
|
9
9
|
* - `/tmp/x.md` was silently re-rooted under `targetDir`
|
|
10
10
|
*
|
|
11
|
-
*
|
|
11
|
+
* Auto-detected and preset files were never checked at all, so a `CLAUDE.md`
|
|
12
|
+
* or `.claude/` symlinked out of the project had the block written through
|
|
13
|
+
* it. Every case now throws PathSafetyError before any file is touched.
|
|
14
|
+
*
|
|
15
|
+
* `removeAgentDocs` (`init --remove-agents`) stripped or deleted a block
|
|
16
|
+
* through the same kind of link and reported success. It now refuses with
|
|
17
|
+
* `ERR_PATH_TRAVERSAL` before any file is touched.
|
|
12
18
|
*/
|
|
13
19
|
|
|
14
|
-
import {describe, it, expect, beforeEach, afterEach} from 'vitest';
|
|
20
|
+
import {describe, it, expect, beforeEach, afterEach, vi} from 'vitest';
|
|
15
21
|
import * as fs from 'node:fs';
|
|
16
22
|
import * as path from 'node:path';
|
|
17
23
|
import * as os from 'node:os';
|
|
18
|
-
import {installAgentDocs} from './agent-docs.mjs';
|
|
24
|
+
import {installAgentDocs, removeAgentDocs} from './agent-docs.mjs';
|
|
19
25
|
import {PathSafetyError} from '../fs/path-safety.mjs';
|
|
26
|
+
import {runCli} from '../../test-utils/run-cli.mjs';
|
|
27
|
+
|
|
28
|
+
const BLOCK = '<!-- ASTRYX:START -->\nmanaged\n<!-- ASTRYX:END -->';
|
|
20
29
|
|
|
21
30
|
let tmpDir;
|
|
22
31
|
beforeEach(() => {
|
|
@@ -26,6 +35,28 @@ afterEach(() => {
|
|
|
26
35
|
fs.rmSync(tmpDir, {recursive: true, force: true});
|
|
27
36
|
});
|
|
28
37
|
|
|
38
|
+
/** A project dir plus a sibling dir outside it. */
|
|
39
|
+
function layout() {
|
|
40
|
+
const project = path.join(tmpDir, 'project');
|
|
41
|
+
const outside = path.join(tmpDir, 'outside');
|
|
42
|
+
fs.mkdirSync(project, {recursive: true});
|
|
43
|
+
fs.mkdirSync(outside, {recursive: true});
|
|
44
|
+
return {project, outside};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @param {() => unknown} fn
|
|
49
|
+
* @returns {unknown} what `fn` threw
|
|
50
|
+
*/
|
|
51
|
+
function thrownBy(fn) {
|
|
52
|
+
try {
|
|
53
|
+
fn();
|
|
54
|
+
} catch (err) {
|
|
55
|
+
return err;
|
|
56
|
+
}
|
|
57
|
+
throw new Error('expected a throw');
|
|
58
|
+
}
|
|
59
|
+
|
|
29
60
|
describe('installAgentDocs path safety', () => {
|
|
30
61
|
it('rejects --agent-docs-path that escapes the target directory', () => {
|
|
31
62
|
const target = path.join(tmpDir, 'project');
|
|
@@ -64,3 +95,234 @@ describe('installAgentDocs path safety', () => {
|
|
|
64
95
|
expect(fs.existsSync(path.join(tmpDir, 'escaped.md'))).toBe(false);
|
|
65
96
|
});
|
|
66
97
|
});
|
|
98
|
+
|
|
99
|
+
describe('installAgentDocs never writes through a symlink out of the project', () => {
|
|
100
|
+
it('refuses an auto-detected file that links outside', () => {
|
|
101
|
+
const {project, outside} = layout();
|
|
102
|
+
const shared = path.join(outside, 'CLAUDE.md');
|
|
103
|
+
fs.writeFileSync(shared, '# Shared\n');
|
|
104
|
+
fs.symlinkSync(shared, path.join(project, 'CLAUDE.md'));
|
|
105
|
+
|
|
106
|
+
expect(() => installAgentDocs(project, {renderedBlock: BLOCK})).toThrow(
|
|
107
|
+
/outside the project root/,
|
|
108
|
+
);
|
|
109
|
+
expect(fs.readFileSync(shared, 'utf-8')).toBe('# Shared\n');
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('refuses a preset file reached through a symlinked directory', () => {
|
|
113
|
+
const {project, outside} = layout();
|
|
114
|
+
fs.symlinkSync(outside, path.join(project, '.claude'));
|
|
115
|
+
|
|
116
|
+
expect(() =>
|
|
117
|
+
installAgentDocs(project, {agent: 'claude', renderedBlock: BLOCK}),
|
|
118
|
+
).toThrow(PathSafetyError);
|
|
119
|
+
expect(fs.readdirSync(outside)).toEqual([]);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('refuses an existing preset file that links outside', () => {
|
|
123
|
+
const {project, outside} = layout();
|
|
124
|
+
const shared = path.join(outside, 'rules');
|
|
125
|
+
fs.writeFileSync(shared, 'rules\n');
|
|
126
|
+
fs.symlinkSync(shared, path.join(project, '.cursorrules'));
|
|
127
|
+
|
|
128
|
+
expect(() =>
|
|
129
|
+
installAgentDocs(project, {agent: 'cursor', renderedBlock: BLOCK}),
|
|
130
|
+
).toThrow(PathSafetyError);
|
|
131
|
+
expect(fs.readFileSync(shared, 'utf-8')).toBe('rules\n');
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('writes nothing when any file of the set escapes', () => {
|
|
135
|
+
const {project, outside} = layout();
|
|
136
|
+
fs.writeFileSync(path.join(project, 'AGENTS.md'), '# Agents\n');
|
|
137
|
+
const shared = path.join(outside, 'CLAUDE.md');
|
|
138
|
+
fs.writeFileSync(shared, '# Shared\n');
|
|
139
|
+
fs.symlinkSync(shared, path.join(project, 'CLAUDE.md'));
|
|
140
|
+
|
|
141
|
+
expect(() => installAgentDocs(project, {renderedBlock: BLOCK})).toThrow(
|
|
142
|
+
PathSafetyError,
|
|
143
|
+
);
|
|
144
|
+
expect(fs.readFileSync(path.join(project, 'AGENTS.md'), 'utf-8')).toBe(
|
|
145
|
+
'# Agents\n',
|
|
146
|
+
);
|
|
147
|
+
expect(fs.readFileSync(shared, 'utf-8')).toBe('# Shared\n');
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('refuses to refresh a managed block that lives outside', () => {
|
|
151
|
+
const {project, outside} = layout();
|
|
152
|
+
const shared = path.join(outside, 'CLAUDE.md');
|
|
153
|
+
fs.writeFileSync(shared, `# Shared\n\n${BLOCK.replace('managed', 'old')}\n`);
|
|
154
|
+
const before = fs.readFileSync(shared, 'utf-8');
|
|
155
|
+
fs.symlinkSync(shared, path.join(project, 'CLAUDE.md'));
|
|
156
|
+
|
|
157
|
+
expect(() =>
|
|
158
|
+
installAgentDocs(project, {onlyReplace: true, renderedBlock: BLOCK}),
|
|
159
|
+
).toThrow(PathSafetyError);
|
|
160
|
+
expect(fs.readFileSync(shared, 'utf-8')).toBe(before);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('refuses to strip a duplicate block from a wrapper that links outside', () => {
|
|
164
|
+
const {project, outside} = layout();
|
|
165
|
+
fs.writeFileSync(path.join(project, 'AGENTS.md'), `# Agents\n\n${BLOCK}\n`);
|
|
166
|
+
const shared = path.join(outside, 'CLAUDE.md');
|
|
167
|
+
fs.writeFileSync(shared, `@AGENTS.md\n\n${BLOCK}\n`);
|
|
168
|
+
fs.symlinkSync(shared, path.join(project, 'CLAUDE.md'));
|
|
169
|
+
|
|
170
|
+
expect(() => installAgentDocs(project, {renderedBlock: BLOCK})).toThrow(
|
|
171
|
+
PathSafetyError,
|
|
172
|
+
);
|
|
173
|
+
expect(fs.readFileSync(shared, 'utf-8')).toBe(`@AGENTS.md\n\n${BLOCK}\n`);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it('still refreshes in-project blocks beside an unmarked outside link it skips', () => {
|
|
177
|
+
const {project, outside} = layout();
|
|
178
|
+
fs.writeFileSync(
|
|
179
|
+
path.join(project, 'AGENTS.md'),
|
|
180
|
+
`# Agents\n\n${BLOCK.replace('managed', 'old')}\n`,
|
|
181
|
+
);
|
|
182
|
+
const shared = path.join(outside, 'rules');
|
|
183
|
+
fs.writeFileSync(shared, 'rules\n');
|
|
184
|
+
fs.symlinkSync(shared, path.join(project, '.cursorrules'));
|
|
185
|
+
|
|
186
|
+
expect(
|
|
187
|
+
installAgentDocs(project, {onlyReplace: true, renderedBlock: BLOCK}),
|
|
188
|
+
).toEqual(['AGENTS.md']);
|
|
189
|
+
expect(fs.readFileSync(path.join(project, 'AGENTS.md'), 'utf-8')).toContain(
|
|
190
|
+
'managed',
|
|
191
|
+
);
|
|
192
|
+
expect(fs.readFileSync(shared, 'utf-8')).toBe('rules\n');
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it('writes through a symlink that stays inside the project', () => {
|
|
196
|
+
const {project} = layout();
|
|
197
|
+
fs.writeFileSync(path.join(project, 'AGENTS.md'), '# Agents\n');
|
|
198
|
+
fs.symlinkSync('AGENTS.md', path.join(project, 'CLAUDE.md'));
|
|
199
|
+
|
|
200
|
+
expect(installAgentDocs(project, {renderedBlock: BLOCK})).toEqual([
|
|
201
|
+
'AGENTS.md',
|
|
202
|
+
'CLAUDE.md',
|
|
203
|
+
]);
|
|
204
|
+
const content = fs.readFileSync(path.join(project, 'AGENTS.md'), 'utf-8');
|
|
205
|
+
expect(content.split('<!-- ASTRYX:START -->')).toHaveLength(2);
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
describe('init --features agents with an agent doc linked outside the project', () => {
|
|
210
|
+
it('writes nothing outside, reports path-safety, and exits 1 in both modes', async () => {
|
|
211
|
+
const project = path.join(tmpDir, 'project');
|
|
212
|
+
const outside = path.join(tmpDir, 'outside');
|
|
213
|
+
fs.mkdirSync(project, {recursive: true});
|
|
214
|
+
fs.mkdirSync(outside, {recursive: true});
|
|
215
|
+
fs.writeFileSync(path.join(project, 'package.json'), '{"name":"app"}\n');
|
|
216
|
+
const shared = path.join(outside, 'CLAUDE.md');
|
|
217
|
+
fs.writeFileSync(shared, '# Shared\n');
|
|
218
|
+
fs.symlinkSync(shared, path.join(project, 'CLAUDE.md'));
|
|
219
|
+
|
|
220
|
+
const json = await runCli(['init', '--features', 'agents', '--json'], project);
|
|
221
|
+
expect(json.status).toBe(1);
|
|
222
|
+
const env = JSON.parse(json.stdout);
|
|
223
|
+
expect(env.type).toBe('init.run');
|
|
224
|
+
expect(env.data.docsError).toMatchObject({kind: 'path-safety'});
|
|
225
|
+
|
|
226
|
+
const text = await runCli(['init', '--features', 'agents'], project);
|
|
227
|
+
expect(text.status).toBe(1);
|
|
228
|
+
|
|
229
|
+
expect(fs.readFileSync(shared, 'utf-8')).toBe('# Shared\n');
|
|
230
|
+
}, 30_000);
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
describe('removeAgentDocs never changes a file out of the project', () => {
|
|
234
|
+
beforeEach(() => {
|
|
235
|
+
vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
236
|
+
});
|
|
237
|
+
afterEach(() => {
|
|
238
|
+
vi.restoreAllMocks();
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it('refuses to strip a block from a file that links outside', () => {
|
|
242
|
+
const {project, outside} = layout();
|
|
243
|
+
const shared = path.join(outside, 'CLAUDE.md');
|
|
244
|
+
fs.writeFileSync(shared, `# Shared\n\n${BLOCK}\n`);
|
|
245
|
+
fs.symlinkSync(shared, path.join(project, 'CLAUDE.md'));
|
|
246
|
+
|
|
247
|
+
const err = thrownBy(() => removeAgentDocs(project));
|
|
248
|
+
expect(err).toBeInstanceOf(PathSafetyError);
|
|
249
|
+
expect(err).toMatchObject({code: 'ERR_PATH_TRAVERSAL'});
|
|
250
|
+
expect(fs.readFileSync(shared, 'utf-8')).toBe(`# Shared\n\n${BLOCK}\n`);
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it('refuses to delete a file reached through a symlinked directory', () => {
|
|
254
|
+
const {project, outside} = layout();
|
|
255
|
+
const shared = path.join(outside, 'CLAUDE.md');
|
|
256
|
+
const content = `# CLAUDE\n\nProject-specific guidance for AI coding agents.\n\n${BLOCK}\n`;
|
|
257
|
+
fs.writeFileSync(shared, content);
|
|
258
|
+
fs.symlinkSync(outside, path.join(project, '.claude'));
|
|
259
|
+
|
|
260
|
+
expect(thrownBy(() => removeAgentDocs(project))).toMatchObject({
|
|
261
|
+
code: 'ERR_PATH_TRAVERSAL',
|
|
262
|
+
});
|
|
263
|
+
expect(fs.readFileSync(shared, 'utf-8')).toBe(content);
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
it('changes nothing when any file of the set escapes', () => {
|
|
267
|
+
const {project, outside} = layout();
|
|
268
|
+
const agents = `# Agents\n\nkeep\n\n${BLOCK}\n`;
|
|
269
|
+
fs.writeFileSync(path.join(project, 'AGENTS.md'), agents);
|
|
270
|
+
const shared = path.join(outside, 'CLAUDE.md');
|
|
271
|
+
fs.writeFileSync(shared, `# Shared\n\n${BLOCK}\n`);
|
|
272
|
+
fs.symlinkSync(shared, path.join(project, 'CLAUDE.md'));
|
|
273
|
+
|
|
274
|
+
expect(thrownBy(() => removeAgentDocs(project))).toBeInstanceOf(PathSafetyError);
|
|
275
|
+
expect(fs.readFileSync(path.join(project, 'AGENTS.md'), 'utf-8')).toBe(agents);
|
|
276
|
+
expect(fs.readFileSync(shared, 'utf-8')).toBe(`# Shared\n\n${BLOCK}\n`);
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
it('still strips in-project blocks beside an unmarked outside link it skips', () => {
|
|
280
|
+
const {project, outside} = layout();
|
|
281
|
+
fs.writeFileSync(path.join(project, 'AGENTS.md'), `# Agents\n\nkeep\n\n${BLOCK}\n`);
|
|
282
|
+
const shared = path.join(outside, 'rules');
|
|
283
|
+
fs.writeFileSync(shared, 'rules\n');
|
|
284
|
+
fs.symlinkSync(shared, path.join(project, '.cursorrules'));
|
|
285
|
+
|
|
286
|
+
removeAgentDocs(project);
|
|
287
|
+
expect(fs.readFileSync(path.join(project, 'AGENTS.md'), 'utf-8')).toBe(
|
|
288
|
+
'# Agents\n\nkeep\n',
|
|
289
|
+
);
|
|
290
|
+
expect(fs.readFileSync(shared, 'utf-8')).toBe('rules\n');
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
it('strips through a symlink that stays inside the project', () => {
|
|
294
|
+
const {project} = layout();
|
|
295
|
+
fs.writeFileSync(path.join(project, 'AGENTS.md'), `# Agents\n\nkeep\n\n${BLOCK}\n`);
|
|
296
|
+
fs.symlinkSync('AGENTS.md', path.join(project, 'CLAUDE.md'));
|
|
297
|
+
|
|
298
|
+
removeAgentDocs(project);
|
|
299
|
+
expect(fs.readFileSync(path.join(project, 'AGENTS.md'), 'utf-8')).toBe(
|
|
300
|
+
'# Agents\n\nkeep\n',
|
|
301
|
+
);
|
|
302
|
+
});
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
describe('init --remove-agents with an agent doc linked outside the project', () => {
|
|
306
|
+
it('changes nothing outside, fails with ERR_PATH_TRAVERSAL, and exits 1 in both modes', async () => {
|
|
307
|
+
const {project, outside} = layout();
|
|
308
|
+
fs.writeFileSync(path.join(project, 'package.json'), '{"name":"app"}\n');
|
|
309
|
+
const shared = path.join(outside, 'CLAUDE.md');
|
|
310
|
+
const content = `# Shared\n\n${BLOCK}\n`;
|
|
311
|
+
fs.writeFileSync(shared, content);
|
|
312
|
+
fs.symlinkSync(shared, path.join(project, 'CLAUDE.md'));
|
|
313
|
+
|
|
314
|
+
const json = await runCli(['init', '--remove-agents', '--json'], project);
|
|
315
|
+
expect(json.status).toBe(1);
|
|
316
|
+
const env = JSON.parse(json.stdout);
|
|
317
|
+
expect(env).toMatchObject({apiVersion: 1, code: 'ERR_PATH_TRAVERSAL'});
|
|
318
|
+
expect(env.error).toMatch(/"CLAUDE\.md": resolves outside the project root/);
|
|
319
|
+
expect(env).not.toHaveProperty('data');
|
|
320
|
+
|
|
321
|
+
const text = await runCli(['init', '--remove-agents'], project);
|
|
322
|
+
expect(text.status).toBe(1);
|
|
323
|
+
expect(text.stderr).toMatch(/^Error: .*"CLAUDE\.md": resolves outside the project root/m);
|
|
324
|
+
expect(text.stdout).not.toMatch(/removed/i);
|
|
325
|
+
|
|
326
|
+
expect(fs.readFileSync(shared, 'utf-8')).toBe(content);
|
|
327
|
+
}, 30_000);
|
|
328
|
+
});
|
|
@@ -134,7 +134,6 @@ describe('generateCompressedIndex', () => {
|
|
|
134
134
|
const line = topicLine(generateCompressedIndex('1.0.0'));
|
|
135
135
|
for (const topic of [
|
|
136
136
|
'getting-started',
|
|
137
|
-
'cli-integrations',
|
|
138
137
|
'browser-support',
|
|
139
138
|
'styling-libraries',
|
|
140
139
|
'working-with-ai',
|
|
@@ -143,6 +142,16 @@ describe('generateCompressedIndex', () => {
|
|
|
143
142
|
}
|
|
144
143
|
});
|
|
145
144
|
|
|
145
|
+
it('points to the CLI docs tree on a line of its own', () => {
|
|
146
|
+
// The integration guide lives in the docs tree now (cli/integrations), so
|
|
147
|
+
// the topic line no longer names it; the tree's entry point does.
|
|
148
|
+
const block = generateCompressedIndex('1.0.0');
|
|
149
|
+
expect(block).toContain(
|
|
150
|
+
' docs cli commands, API reference, integration authoring (one level at a time)',
|
|
151
|
+
);
|
|
152
|
+
expect(topicLine(block)).not.toContain('cli-integrations');
|
|
153
|
+
});
|
|
154
|
+
|
|
146
155
|
it('lists the topics it is given, so an integration’s reach the agent', () => {
|
|
147
156
|
const line = topicLine(
|
|
148
157
|
generateCompressedIndex('1.0.0', {topics: ['tokens', 'deploying']}),
|