@astryxdesign/cli 0.7.2-canary.fdd384f → 0.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +119 -0
- package/README.md +169 -149
- package/api/build/_adapter.d.mts +8 -0
- package/api/build/_adapter.mjs +12 -1
- package/api/build/build.doc.mjs +4 -0
- package/api/build/kit/kit.mjs +3 -11
- package/api/component/_adapter.d.mts +31 -0
- package/api/component/_adapter.mjs +119 -11
- package/api/component/component.doc.mjs +6 -1
- package/api/component/component.mjs +60 -10
- package/api/component/doc-links.test.mjs +386 -0
- package/api/discover/_adapter.d.mts +132 -12
- package/api/discover/_adapter.mjs +488 -27
- package/api/discover/_adapter.test.mjs +599 -17
- package/api/discover/_catalog-view.d.mts +16 -5
- package/api/discover/_catalog-view.mjs +32 -8
- package/api/discover/detail/doc/doc.mjs +9 -1
- package/api/discover/discover.d.mts +11 -0
- package/api/discover/discover.doc.mjs +9 -7
- package/api/discover/discover.mjs +121 -19
- package/api/discover/discover.type.d.mts +20 -7
- package/api/discover/discover.type.mjs +17 -7
- package/api/discover/list/list.d.mts +4 -3
- package/api/discover/list/list.mjs +17 -10
- package/api/docs/_adapter.d.mts +63 -0
- package/api/docs/_adapter.mjs +312 -18
- package/api/docs/docs.doc.mjs +2 -1
- package/api/docs/docs.mjs +2 -1
- package/api/docs/docs.test.mjs +5 -2
- package/api/docs/integration-tree.test.mjs +22 -0
- package/api/docs/list/list.mjs +12 -2
- package/api/docs/reference-blocks.test.mjs +1 -3
- package/api/doctor/_adapter.d.mts +108 -0
- package/api/doctor/_adapter.mjs +223 -4
- package/api/doctor/doctor.d.mts +199 -0
- package/api/doctor/doctor.doc.mjs +3 -2
- package/api/doctor/doctor.mjs +530 -34
- package/api/doctor/doctor.test.mjs +180 -14
- package/api/doctor/release-checks.test.mjs +156 -0
- package/api/doctor/theme-checks.mjs +16 -3
- package/api/doctor/theme-checks.test.mjs +223 -7
- package/api/error.d.mts +10 -0
- package/api/error.mjs +25 -0
- package/api/gap-report/gap-report.d.mts +3 -0
- package/api/gap-report/gap-report.doc.mjs +11 -3
- package/api/gap-report/gap-report.mjs +152 -48
- package/api/gap-report/gap-report.test.mjs +233 -0
- package/api/gap-report/gap-report.type.d.mts +41 -0
- package/api/gap-report/gap-report.type.mjs +18 -0
- package/api/hook/_adapter.mjs +3 -11
- package/api/hook/hook.doc.mjs +4 -0
- package/api/hook/list/list.d.mts +1 -1
- package/api/init/_adapter.d.mts +22 -0
- package/api/init/_adapter.mjs +59 -0
- package/api/init/init.d.mts +7 -5
- package/api/init/init.doc.mjs +28 -7
- package/api/init/init.mjs +28 -4
- package/api/init/init.type.d.mts +48 -3
- package/api/init/init.type.mjs +37 -3
- package/api/init/run/run.d.mts +1 -1
- package/api/init/run/run.mjs +15 -4
- package/api/init/user/user.d.mts +14 -0
- package/api/init/user/user.mjs +87 -0
- package/api/init/user/user.test.mjs +232 -0
- package/api/integration/_adapter.d.mts +33 -0
- package/api/integration/_adapter.mjs +58 -0
- package/api/integration/authoring-checks.mjs +100 -5
- package/api/integration/authoring-checks.test.mjs +769 -0
- package/api/integration/authoring-checks.type.d.mts +134 -0
- package/api/integration/authoring-checks.type.mjs +60 -1
- package/api/integration/integrationCheck.doc.mjs +1 -1
- package/api/integration/integrationComponentConflicts.doc.mjs +11 -3
- package/api/integration/integrationDocConflicts.doc.mjs +22 -5
- package/api/integration/pack-check.mjs +303 -13
- package/api/integration/pack-check.test.mjs +371 -10
- package/api/integration/validate-integration-fixes.test.mjs +67 -0
- package/api/integration/validate-integration.d.mts +8 -0
- package/api/integration/validate-integration.mjs +55 -9
- package/api/integration/validate-integration.test.mjs +77 -8
- package/api/integration/validate-integration.type.d.mts +8 -0
- package/api/integration/validate-integration.type.mjs +3 -1
- package/api/integration/validate-unread-theme-folders.test.mjs +3 -2
- package/api/integration/validateIntegration.doc.mjs +4 -2
- package/api/search/_adapter.d.mts +11 -0
- package/api/search/_adapter.mjs +25 -0
- package/api/search/search.d.mts +73 -13
- package/api/search/search.doc.mjs +8 -3
- package/api/search/search.domain-priority.test.mjs +108 -18
- package/api/search/search.mjs +541 -52
- package/api/search/search.test.mjs +143 -1
- package/api/search/search.type.d.mts +14 -1
- package/api/search/search.type.mjs +6 -1
- package/api/swizzle/_adapter.mjs +2 -2
- package/api/swizzle/copy/copy.mjs +1 -1
- package/api/template/template-integration.test.mjs +1 -1
- package/api/theme/_adapter.d.mts +16 -0
- package/api/theme/_adapter.mjs +32 -5
- package/api/theme/build/build.d.mts +2 -1
- package/api/theme/build/build.mjs +70 -14
- package/api/theme/build/build.packed-old-core.test.mjs +20 -3
- package/api/theme/build/font-warning.mjs +5 -3
- package/api/theme/build/font-warning.test.mjs +2 -1
- package/api/theme/remove/remove.test.mjs +2 -2
- package/api/theme/targets/targets.mjs +3 -10
- package/api/theme/themeTargets.doc.mjs +4 -0
- package/api/theme/use/use.test.mjs +1 -1
- package/api/upgrade/_adapter.d.mts +40 -2
- package/api/upgrade/_adapter.mjs +118 -5
- package/api/upgrade/run/cli-behind-core.test.mjs +438 -0
- package/api/upgrade/run/run.mjs +35 -2
- package/api/upgrade/status/status.d.mts +21 -5
- package/api/upgrade/status/status.mjs +45 -8
- package/api/upgrade/upgrade.doc.mjs +6 -3
- package/api/upgrade/upgrade.mjs +5 -1
- package/api/upgrade/upgrade.type.d.mts +23 -0
- package/api/upgrade/upgrade.type.mjs +16 -1
- package/assets/codemods/__tests__/registry.test.mjs +1 -0
- package/assets/codemods/registry.mjs +1 -0
- package/assets/codemods/runner.mjs +7 -1
- package/assets/codemods/transforms/next/index.mjs +1 -11
- package/assets/codemods/transforms/v0.7.2/index.mjs +20 -0
- package/assets/discover/official-packages.json +367 -0
- package/assets/docs/author-a-theme.doc.mjs +2 -2
- package/assets/docs/browser-support.doc.mjs +1 -1
- package/assets/docs/getting-started.doc.mjs +23 -3
- package/assets/docs/styling-overview.doc.mjs +1 -1
- package/assets/docs/theme.doc.mjs +1 -1
- package/assets/docs/tree/add-a-component.doc.mjs +12 -2
- package/assets/docs/tree/add-a-theme.doc.mjs +13 -3
- package/assets/docs/tree/add-a-topic.doc.mjs +54 -9
- package/assets/docs/tree/agent-guidance.doc.mjs +19 -5
- package/assets/docs/tree/block-template.doc.mjs +6 -18
- package/assets/docs/tree/build-the-template.doc.mjs +1 -1
- package/assets/docs/tree/building-blocks.doc.mjs +4 -0
- package/assets/docs/tree/check-the-props.doc.mjs +8 -3
- package/assets/docs/tree/check-your-docs.doc.mjs +30 -13
- package/assets/docs/tree/checks.doc.mjs +17 -17
- package/assets/docs/tree/ci-and-containers.doc.mjs +88 -0
- package/assets/docs/tree/cli-command.doc.mjs +142 -0
- package/assets/docs/tree/cli.doc.mjs +6 -0
- package/assets/docs/tree/codemods.doc.mjs +17 -7
- package/assets/docs/tree/commands.doc.mjs +7 -1
- package/assets/docs/tree/component-family.doc.mjs +3 -5
- package/assets/docs/tree/component-imports.doc.mjs +13 -3
- package/assets/docs/tree/components.doc.mjs +7 -1
- package/assets/docs/tree/computer-agent-guide.doc.mjs +160 -0
- package/assets/docs/tree/computer-integrations.doc.mjs +93 -0
- package/assets/docs/tree/configuration.doc.mjs +7 -3
- package/assets/docs/tree/debug-and-gap-reports.doc.mjs +41 -22
- package/assets/docs/tree/define-the-theme.doc.mjs +6 -5
- package/assets/docs/tree/describe-the-component.doc.mjs +2 -2
- package/assets/docs/tree/docs.doc.mjs +9 -3
- package/assets/docs/tree/document-the-template.doc.mjs +1 -1
- package/assets/docs/tree/document-the-theme.doc.mjs +3 -3
- package/assets/docs/tree/export-template-assets.doc.mjs +1 -1
- package/assets/docs/tree/extend-or-replace.doc.mjs +19 -9
- package/assets/docs/tree/fonts-and-assets.doc.mjs +2 -2
- package/assets/docs/tree/generate-a-palette.doc.mjs +7 -5
- package/assets/docs/tree/global-integrations.doc.mjs +124 -0
- package/assets/docs/tree/global.doc.mjs +131 -0
- package/assets/docs/tree/grade-template-with-agent.doc.mjs +5 -5
- package/assets/docs/tree/install-once.doc.mjs +105 -0
- package/assets/docs/tree/integrations.doc.mjs +13 -1
- package/assets/docs/tree/layout-spacing.doc.mjs +2 -3
- package/assets/docs/tree/layout.doc.mjs +6 -6
- package/assets/docs/tree/links.doc.mjs +6 -5
- package/assets/docs/tree/migration-setup.doc.mjs +3 -2
- package/assets/docs/tree/migration-verification.doc.mjs +0 -1
- package/assets/docs/tree/migration.doc.mjs +0 -1
- package/assets/docs/tree/monorepos.doc.mjs +84 -0
- package/assets/docs/tree/package-and-test.doc.mjs +1 -1
- package/assets/docs/tree/page-template.doc.mjs +3 -7
- package/assets/docs/tree/problems.doc.mjs +205 -0
- package/assets/docs/tree/project-global-or-both.doc.mjs +131 -0
- package/assets/docs/tree/publishing.doc.mjs +6 -6
- package/assets/docs/tree/quick-start.doc.mjs +52 -26
- package/assets/docs/tree/reference.doc.mjs +192 -0
- package/assets/docs/tree/replace-a-core-component.doc.mjs +15 -7
- package/assets/docs/tree/replace-a-core-template.doc.mjs +9 -11
- package/assets/docs/tree/scaffold.doc.mjs +1 -1
- package/assets/docs/tree/sections-and-placement.doc.mjs +9 -11
- package/assets/docs/tree/see-it-in-an-app.doc.mjs +17 -5
- package/assets/docs/tree/short-and-findable.doc.mjs +9 -8
- package/assets/docs/tree/single-component.doc.mjs +14 -6
- package/assets/docs/tree/start-a-template.doc.mjs +18 -8
- package/assets/docs/tree/subcomponent.doc.mjs +6 -6
- package/assets/docs/tree/template-assets.doc.mjs +2 -2
- package/assets/docs/tree/template-doc-overview.doc.mjs +6 -17
- package/assets/docs/tree/template-fonts.doc.mjs +1 -1
- package/assets/docs/tree/template-icons.doc.mjs +1 -1
- package/assets/docs/tree/template-images-media.doc.mjs +1 -1
- package/assets/docs/tree/template-styles.doc.mjs +2 -2
- package/assets/docs/tree/templates.doc.mjs +7 -1
- package/assets/docs/tree/test-in-an-app.doc.mjs +7 -7
- package/assets/docs/tree/test-template-in-app.doc.mjs +5 -5
- package/assets/docs/tree/themes.doc.mjs +4 -0
- package/assets/docs/tree/tokens-and-setup.doc.mjs +19 -15
- package/assets/docs/tree/troubleshooting.doc.mjs +22 -14
- package/assets/docs/tree/uninstall.doc.mjs +109 -0
- package/assets/docs/tree/updates.doc.mjs +92 -0
- package/assets/docs/tree/upgrading.doc.mjs +6 -6
- package/assets/docs/tree/use-a-theme-in-an-app.doc.mjs +14 -2
- package/assets/docs/tree/verify-packed-template.doc.mjs +3 -3
- package/assets/docs/tree/versioning.doc.mjs +9 -9
- package/assets/docs/tree/who-answers.doc.mjs +119 -0
- package/assets/docs/tree/write-the-template-file.doc.mjs +5 -5
- package/assets/docs/working-with-ai.doc.mjs +23 -9
- package/authoring/codemod/codemod.doc.mjs +7 -3
- package/authoring/config/config.doc.mjs +9 -6
- package/authoring/config/type.ts +4 -2
- package/authoring/debug/parse.d.mts +5 -5
- package/authoring/discover/discover.doc.mjs +7 -1
- package/authoring/doctypes/_schema.d.mts +8 -3
- package/authoring/doctypes/_schema.mjs +1 -0
- package/authoring/doctypes/base/graph-fields.doc.mjs +6 -4
- package/authoring/doctypes/command/command.doc.mjs +20 -12
- package/authoring/doctypes/command/type.ts +14 -0
- package/authoring/doctypes/component/component.doc.mjs +29 -26
- package/authoring/doctypes/component/type.ts +15 -5
- package/authoring/doctypes/enum/enum.doc.mjs +7 -4
- package/authoring/doctypes/function/function.doc.mjs +9 -8
- package/authoring/doctypes/hook/hook.doc.mjs +5 -4
- package/authoring/doctypes/hook/type.ts +4 -0
- package/authoring/doctypes/namespace/namespace.doc.mjs +7 -6
- package/authoring/doctypes/reference/reference.doc.mjs +12 -11
- package/authoring/doctypes/schema/schema.doc.mjs +9 -9
- package/authoring/doctypes/template/template.doc.mjs +2 -2
- package/authoring/doctypes/theme/theme.doc.mjs +2 -2
- package/authoring/gap-report/gap-report.doc.mjs +2 -2
- package/authoring/identity/identity.doc.mjs +3 -3
- package/authoring/index.d.ts +1 -0
- package/authoring/integration/integration.doc.mjs +22 -5
- package/authoring/integration/parse.test.mjs +11 -0
- package/authoring/integration/schema.d.mts +14 -3
- package/authoring/integration/schema.mjs +33 -2
- package/authoring/integration/type.ts +8 -0
- package/clients/cli/bin/astryx.mjs +20 -3
- package/clients/cli/cli-command.test.mjs +166 -0
- package/clients/cli/commands/blog.doc.mjs +1 -0
- package/clients/cli/commands/build-theme.batch-report.test.mjs +14 -2
- package/clients/cli/commands/build-theme.font-warning.test.mjs +3 -3
- package/clients/cli/commands/build-theme.mjs +25 -12
- package/clients/cli/commands/build.doc.mjs +1 -0
- package/clients/cli/commands/build.exit-codes-doc.test.mjs +4 -0
- package/clients/cli/commands/cli-postinstall.test.mjs +49 -1
- package/clients/cli/commands/component.doc.mjs +1 -0
- package/clients/cli/commands/discover.broken-integration.test.mjs +11 -7
- package/clients/cli/commands/discover.doc.mjs +9 -1
- package/clients/cli/commands/discover.mjs +144 -93
- package/clients/cli/commands/discover.no-source.test.mjs +448 -33
- package/clients/cli/commands/discover.sources.test.mjs +20 -1
- package/clients/cli/commands/docs.doc.mjs +1 -0
- package/clients/cli/commands/docs.mjs +49 -19
- package/clients/cli/commands/docs.test.mjs +2 -2
- package/clients/cli/commands/doctor-integration-components.doc.mjs +7 -3
- package/clients/cli/commands/doctor-integration-docs.doc.mjs +20 -5
- package/clients/cli/commands/doctor-integration-templates.doc.mjs +1 -0
- package/clients/cli/commands/doctor-integration-validate.doc.mjs +13 -2
- package/clients/cli/commands/doctor-integration.test.mjs +52 -4
- package/clients/cli/commands/doctor.doc.mjs +12 -2
- package/clients/cli/commands/doctor.mjs +102 -7
- package/clients/cli/commands/doctor.test.mjs +10 -4
- package/clients/cli/commands/gap-report.doc.mjs +15 -2
- package/clients/cli/commands/gap-report.mjs +24 -2
- package/clients/cli/commands/gap-report.test.mjs +79 -0
- package/clients/cli/commands/hook.doc.mjs +1 -0
- package/clients/cli/commands/init.behavior.test.mjs +5 -0
- package/clients/cli/commands/init.doc.mjs +29 -3
- package/clients/cli/commands/integration-add.doc.mjs +7 -0
- package/clients/cli/commands/integration-authoring.test.mjs +49 -3
- package/clients/cli/commands/integration-check.doc.mjs +10 -2
- package/clients/cli/commands/integration-check.test.mjs +175 -1
- package/clients/cli/commands/integration-pack.doc.mjs +8 -1
- package/clients/cli/commands/integration-verify.doc.mjs +8 -1
- package/clients/cli/commands/integration.mjs +15 -9
- package/clients/cli/commands/json-contract.test.mjs +11 -2
- package/clients/cli/commands/manifest.doc.mjs +1 -0
- package/clients/cli/commands/outside-project.test.mjs +774 -0
- package/clients/cli/commands/search.doc.mjs +1 -0
- package/clients/cli/commands/search.mjs +16 -11
- package/clients/cli/commands/search.test.mjs +429 -296
- package/clients/cli/commands/setup-nudge.test.mjs +102 -2
- package/clients/cli/commands/swizzle.doc.mjs +7 -0
- package/clients/cli/commands/swizzle.mjs +5 -3
- package/clients/cli/commands/swizzle.routing.test.mjs +19 -2
- package/clients/cli/commands/template.doc.mjs +1 -0
- package/clients/cli/commands/theme-add-lifecycle.test.mjs +3 -3
- package/clients/cli/commands/theme-add.doc.mjs +8 -1
- package/clients/cli/commands/theme-build.doc.mjs +1 -0
- package/clients/cli/commands/theme-eject.doc.mjs +1 -0
- package/clients/cli/commands/theme-list.behavior.test.mjs +6 -0
- package/clients/cli/commands/theme-list.doc.mjs +1 -0
- package/clients/cli/commands/theme-palette-generate.doc.mjs +1 -0
- package/clients/cli/commands/theme-remove.doc.mjs +7 -0
- package/clients/cli/commands/theme-targets.doc.mjs +1 -0
- package/clients/cli/commands/theme-template.doc.mjs +1 -0
- package/clients/cli/commands/theme-use.doc.mjs +7 -0
- package/clients/cli/commands/upgrade.config-ordering.test.mjs +2 -1
- package/clients/cli/commands/upgrade.doc.mjs +22 -1
- package/clients/cli/commands/upgrade.file-protection.test.mjs +3 -2
- package/clients/cli/commands/upgrade.hook-output.test.mjs +2 -1
- package/clients/cli/commands/upgrade.older-cli.test.mjs +160 -0
- package/clients/cli/index.mjs +111 -16
- package/clients/cli/lib/cli-error.mjs +4 -1
- package/clients/cli/lib/component-format.test.mjs +1 -1
- package/clients/cli/lib/define-command.mjs +2 -2
- package/clients/cli/lib/json-shim.mjs +3 -1
- package/clients/cli/lib/manifest.d.ts +9 -0
- package/clients/cli/lib/manifest.mjs +17 -1
- package/clients/cli/lib/manifest.test.mjs +25 -0
- package/foundation/agent-docs/agent-doc-state.d.mts +35 -6
- package/foundation/agent-docs/agent-doc-state.mjs +66 -6
- package/foundation/agent-docs/agent-docs.d.mts +4 -3
- package/foundation/agent-docs/agent-docs.mjs +11 -9
- package/foundation/agent-docs/computer-agent-text.d.mts +133 -0
- package/foundation/agent-docs/computer-agent-text.mjs +623 -0
- package/foundation/agent-docs/computer-agent-text.test.mjs +602 -0
- package/foundation/config/project.d.mts +35 -3
- package/foundation/config/project.mjs +145 -8
- package/foundation/config/project.test.mjs +66 -0
- package/foundation/config/theme-state.mjs +5 -4
- package/foundation/discovery/authoring-self-docs.mjs +1 -1
- package/foundation/discovery/cli-self-docs.mjs +20 -3
- package/foundation/discovery/cli-self-docs.test.mjs +3 -2
- package/foundation/discovery/component-replacement.mjs +2 -1
- package/foundation/discovery/copied-passages.d.mts +216 -0
- package/foundation/discovery/copied-passages.mjs +720 -0
- package/foundation/discovery/copied-passages.test.mjs +124 -0
- package/foundation/discovery/doc-consistency.d.mts +34 -0
- package/foundation/discovery/doc-consistency.mjs +513 -0
- package/foundation/discovery/doc-consistency.test.mjs +347 -0
- package/foundation/discovery/doc-fluff.d.mts +112 -0
- package/foundation/discovery/doc-fluff.mjs +328 -0
- package/foundation/discovery/doc-fluff.test.mjs +142 -0
- package/foundation/discovery/doc-types.d.mts +1 -1
- package/foundation/discovery/doc-types.mjs +15 -8
- package/foundation/discovery/doc-types.test.mjs +94 -2
- package/foundation/discovery/docs-discovery.d.mts +7 -0
- package/foundation/discovery/docs-discovery.mjs +26 -2
- package/foundation/discovery/project-typescript.d.mts +11 -2
- package/foundation/discovery/project-typescript.mjs +32 -4
- package/foundation/discovery/prop-drift.d.mts +26 -7
- package/foundation/discovery/prop-drift.mjs +173 -8
- package/foundation/discovery/prop-drift.test.mjs +120 -2
- package/foundation/discovery/runner-allowlist.d.mts +40 -0
- package/foundation/discovery/runner-allowlist.mjs +53 -0
- package/foundation/discovery/stray-pointers.d.mts +52 -0
- package/foundation/discovery/stray-pointers.mjs +293 -0
- package/foundation/discovery/stray-pointers.test.mjs +86 -0
- package/foundation/discovery/template-needs.mjs +5 -2
- package/foundation/discovery/theme-imports.mjs +8 -4
- package/foundation/discovery/theme-imports.test.mjs +1 -1
- package/foundation/discovery/type-text.mjs +1 -1
- package/foundation/discovery/type-text.test.mjs +1 -1
- package/foundation/doc-compiler/doc-loads.test.mjs +42 -2
- package/foundation/doc-compiler/inputs.mjs +4 -1
- package/foundation/doc-compiler/links.d.mts +32 -0
- package/foundation/doc-compiler/links.mjs +163 -26
- package/foundation/doc-compiler/links.test.mjs +97 -4
- package/foundation/doc-compiler/read.d.mts +1 -1
- package/foundation/doc-compiler/tree.mjs +30 -1
- package/foundation/doc-compiler/tree.test.mjs +8 -1
- package/foundation/env/cli-command.d.mts +83 -0
- package/foundation/env/cli-command.mjs +152 -0
- package/foundation/env/cli-command.test.mjs +129 -0
- package/foundation/env/node-version.test.mjs +15 -3
- package/foundation/env/package-manager.d.mts +93 -9
- package/foundation/env/package-manager.global.test.mjs +66 -0
- package/foundation/env/package-manager.mjs +184 -13
- package/foundation/env/package-manager.test.mjs +341 -15
- package/foundation/env/project-scope.d.mts +9 -0
- package/foundation/env/project-scope.mjs +33 -0
- package/foundation/env/search-path.d.mts +42 -0
- package/foundation/env/search-path.mjs +205 -0
- package/foundation/env/search-path.test.mjs +122 -0
- package/foundation/env/user-dirs.d.mts +39 -0
- package/foundation/env/user-dirs.mjs +93 -0
- package/foundation/env/user-dirs.test.mjs +72 -0
- package/foundation/env/user-settings.d.mts +46 -0
- package/foundation/env/user-settings.mjs +106 -0
- package/foundation/env/user-settings.test.mjs +64 -0
- package/foundation/fs/file-protection.mjs +15 -1
- package/foundation/fs/module-loader.d.mts +2 -1
- package/foundation/fs/module-loader.mjs +60 -4
- package/foundation/fs/module-loader.stdout.test.mjs +3 -2
- package/foundation/fs/module-loader.test.mjs +194 -0
- package/foundation/fs/path-compare.d.mts +33 -0
- package/foundation/fs/path-compare.mjs +73 -0
- package/foundation/fs/paths.d.mts +57 -1
- package/foundation/fs/paths.mjs +131 -3
- package/foundation/fs/paths.test.mjs +10 -1
- package/foundation/integrations/computer-integrations.d.mts +78 -0
- package/foundation/integrations/computer-integrations.mjs +316 -0
- package/foundation/integrations/computer-integrations.test.mjs +292 -0
- package/foundation/integrations/contribution-inventory.d.mts +11 -0
- package/foundation/integrations/contribution-inventory.mjs +50 -2
- package/foundation/integrations/integration-warnings.mjs +3 -2
- package/foundation/integrations/integration-warnings.test.mjs +8 -9
- package/foundation/integrations/integrations.d.mts +25 -1
- package/foundation/integrations/integrations.mjs +46 -3
- package/foundation/integrations/issue.ts +6 -0
- package/foundation/integrations/validate-contributions.d.mts +2 -0
- package/foundation/integrations/validate-contributions.mjs +41 -1
- package/foundation/release/agent-text-worker.d.mts +4 -0
- package/foundation/release/agent-text-worker.mjs +52 -0
- package/foundation/release/cache.d.mts +310 -0
- package/foundation/release/cache.mjs +1216 -0
- package/foundation/release/cache.test.mjs +920 -0
- package/foundation/release/choose.d.mts +207 -0
- package/foundation/release/choose.mjs +677 -0
- package/foundation/release/choose.test.mjs +427 -0
- package/foundation/release/core-worker.d.mts +4 -0
- package/foundation/release/core-worker.mjs +38 -0
- package/foundation/release/global-state.d.mts +259 -0
- package/foundation/release/global-state.mjs +472 -0
- package/foundation/release/global-state.test.mjs +381 -0
- package/foundation/release/handoff.d.mts +60 -0
- package/foundation/release/handoff.e2e.test.mjs +278 -0
- package/foundation/release/handoff.mjs +357 -0
- package/foundation/release/lock-lease.d.mts +23 -0
- package/foundation/release/lock-lease.mjs +80 -0
- package/foundation/release/notices.d.mts +253 -0
- package/foundation/release/notices.mjs +596 -0
- package/foundation/release/notices.test.mjs +565 -0
- package/foundation/release/npm-guard.d.mts +4 -0
- package/foundation/release/npm-guard.mjs +126 -0
- package/foundation/release/project.d.mts +141 -0
- package/foundation/release/project.mjs +466 -0
- package/foundation/release/project.test.mjs +288 -0
- package/foundation/release/refresh-worker.d.mts +4 -0
- package/foundation/release/refresh-worker.mjs +27 -0
- package/foundation/release/run-context.d.mts +47 -0
- package/foundation/release/run-context.mjs +149 -0
- package/foundation/release/update-notice-env.d.mts +28 -0
- package/foundation/release/update-notice-env.mjs +39 -0
- package/foundation/response/base.d.ts +26 -3
- package/foundation/response/error-codes.d.mts +3 -1
- package/foundation/response/error-codes.doc.mjs +12 -0
- package/foundation/response/error-codes.mjs +17 -0
- package/foundation/response/error-codes.test.mjs +2 -0
- package/foundation/response/json-contract.test.mjs +7 -2
- package/foundation/response/json.mjs +4 -1
- package/foundation/response/response-types.doc.mjs +17 -11
- package/foundation/response/response-types.doc.test.mjs +46 -0
- package/foundation/response/response.doc.mjs +3 -3
- package/foundation/xle/browser-imports.test.mjs +74 -0
- package/package.json +11 -11
- package/scripts/postinstall.mjs +24 -5
- /package/assets/codemods/transforms/{next → v0.7.2}/__tests__/migrate-list-edge-compensation-to-edge.test.mjs +0 -0
- /package/assets/codemods/transforms/{next → v0.7.2}/migrate-list-edge-compensation-to-edge.mjs +0 -0
|
@@ -18,6 +18,11 @@ import {discoverCoreTemplates} from '../../foundation/discovery/template-adapter
|
|
|
18
18
|
import {discoverOwnedComponents} from '../../foundation/discovery/component-discovery.mjs';
|
|
19
19
|
import {discoverBuiltinTopics} from '../../foundation/discovery/docs-discovery.mjs';
|
|
20
20
|
import {findCoreDir} from '../../foundation/fs/paths.mjs';
|
|
21
|
+
import {
|
|
22
|
+
builtinGuideFiles,
|
|
23
|
+
loadTopicModule,
|
|
24
|
+
} from '../../foundation/discovery/docs-discovery.mjs';
|
|
25
|
+
import {proseWords} from '../../foundation/discovery/copied-passages.mjs';
|
|
21
26
|
import {
|
|
22
27
|
integrationComponentConflicts,
|
|
23
28
|
integrationDocConflicts,
|
|
@@ -99,6 +104,44 @@ function writeDocIntegration({name, replaces, extendsTopic}) {
|
|
|
99
104
|
);
|
|
100
105
|
}
|
|
101
106
|
|
|
107
|
+
/**
|
|
108
|
+
* A docs-only package with one generic topic per entry: its prose, and
|
|
109
|
+
* optionally a code block and the Core topic it replaces.
|
|
110
|
+
* @param {{name: string, text: string, code?: string, replaces?: string}[]} topics
|
|
111
|
+
*/
|
|
112
|
+
function writeTopics(topics) {
|
|
113
|
+
fs.writeFileSync(
|
|
114
|
+
path.join(tmpDir, 'package.json'),
|
|
115
|
+
JSON.stringify({name: '@acme/widgets', version: '1.2.3'}),
|
|
116
|
+
);
|
|
117
|
+
fs.writeFileSync(
|
|
118
|
+
path.join(tmpDir, 'astryx.integration.mjs'),
|
|
119
|
+
`export default {docs: './docs'};\n`,
|
|
120
|
+
);
|
|
121
|
+
fs.mkdirSync(path.join(tmpDir, 'docs'), {recursive: true});
|
|
122
|
+
for (const {name, text, code, replaces, extendsTopic} of topics) {
|
|
123
|
+
const blocks = [{type: 'prose', text}];
|
|
124
|
+
if (code != null) blocks.push({type: 'code', lang: 'text', code});
|
|
125
|
+
const doc = {
|
|
126
|
+
type: 'generic',
|
|
127
|
+
name,
|
|
128
|
+
title: 'Fixture',
|
|
129
|
+
description: 'Fixture topic.',
|
|
130
|
+
...(replaces == null ? {} : {replaces}),
|
|
131
|
+
...(extendsTopic == null ? {} : {extends: extendsTopic}),
|
|
132
|
+
sections: [{title: 'Overview', content: blocks}],
|
|
133
|
+
};
|
|
134
|
+
fs.writeFileSync(
|
|
135
|
+
path.join(tmpDir, 'docs', `${name}.doc.mjs`),
|
|
136
|
+
`export default ${JSON.stringify(doc)};\n`,
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const PASSAGE =
|
|
142
|
+
'Themes cannot style the layer that holds a menu or a popover, so a menu ' +
|
|
143
|
+
'keeps the default surface color until the next release adds a token for it.';
|
|
144
|
+
|
|
102
145
|
beforeEach(() => {
|
|
103
146
|
tmpDir = fs.mkdtempSync(
|
|
104
147
|
path.join(process.cwd(), '.astryx-template-conflicts-'),
|
|
@@ -562,3 +605,729 @@ describe('integrationDocConflicts', () => {
|
|
|
562
605
|
]);
|
|
563
606
|
});
|
|
564
607
|
});
|
|
608
|
+
|
|
609
|
+
describe('integrationDocConflicts copied passages', () => {
|
|
610
|
+
const copied = (/** @type {any} */ result) => result.data.copiedPassages;
|
|
611
|
+
|
|
612
|
+
it('lists a passage two of the package topics repeat, and warns about it', async () => {
|
|
613
|
+
writeTopics([
|
|
614
|
+
{name: 'acme-setup', text: `Read this first. ${PASSAGE}`},
|
|
615
|
+
{name: 'acme-menus', text: `${PASSAGE} See the theme guide.`},
|
|
616
|
+
]);
|
|
617
|
+
|
|
618
|
+
const result = await integrationDocConflicts(undefined, {cwd: tmpDir});
|
|
619
|
+
const [issue, ...rest] = copied(result);
|
|
620
|
+
expect(result.data.issues).toEqual([
|
|
621
|
+
{code: 'copied_passage', severity: 'warning', message: issue.message},
|
|
622
|
+
]);
|
|
623
|
+
|
|
624
|
+
expect(rest).toEqual([]);
|
|
625
|
+
// The passage is about menus, so "acme-menus" keeps it.
|
|
626
|
+
expect(issue).toMatchObject({
|
|
627
|
+
topic: 'acme-setup',
|
|
628
|
+
otherTopic: 'acme-menus',
|
|
629
|
+
otherOwner: 'package',
|
|
630
|
+
owner: 'acme-menus',
|
|
631
|
+
section: 'Overview',
|
|
632
|
+
line: 1,
|
|
633
|
+
words: PASSAGE.split(' ').length,
|
|
634
|
+
passages: 1,
|
|
635
|
+
});
|
|
636
|
+
expect(issue.message).toContain(`${PASSAGE.split(' ').length} words`);
|
|
637
|
+
expect(issue.text).toBe(PASSAGE);
|
|
638
|
+
expect(issue.message).toContain(`"${PASSAGE}"`);
|
|
639
|
+
expect(issue.message).toContain(
|
|
640
|
+
'"acme-menus" keeps it: its name, title or section fits the passage.',
|
|
641
|
+
);
|
|
642
|
+
expect(issue.message).toContain(
|
|
643
|
+
'In "acme-setup", replace that whole passage, from its first word to its last, code ticks included, with {@link generic:acme-menus}.',
|
|
644
|
+
);
|
|
645
|
+
});
|
|
646
|
+
|
|
647
|
+
it('reports a passage copied from a Core topic, except into the topic that replaces it', async () => {
|
|
648
|
+
const [file] = builtinGuideFiles();
|
|
649
|
+
const coreDoc = /** @type {any} */ (await loadTopicModule(file));
|
|
650
|
+
const coreText = proseWords(coreDoc)
|
|
651
|
+
.slice(0, 30)
|
|
652
|
+
.map(each => each.word)
|
|
653
|
+
.join(' ');
|
|
654
|
+
|
|
655
|
+
writeTopics([{name: 'acme-copy', text: coreText}]);
|
|
656
|
+
const [issue] = copied(
|
|
657
|
+
await integrationDocConflicts(undefined, {cwd: tmpDir}),
|
|
658
|
+
);
|
|
659
|
+
expect(issue.otherOwner).toBe('core');
|
|
660
|
+
expect(issue.message).toContain(`the Core topic "${coreDoc.name}"`);
|
|
661
|
+
expect(issue.owner).toBe(coreDoc.name);
|
|
662
|
+
expect(issue.message).toContain(
|
|
663
|
+
`code ticks included, with {@link @astryxdesign/cli:generic:${coreDoc.name}}.`,
|
|
664
|
+
);
|
|
665
|
+
|
|
666
|
+
fs.rmSync(path.join(tmpDir, 'docs'), {recursive: true, force: true});
|
|
667
|
+
writeTopics([{name: 'acme-own', text: coreText, replaces: coreDoc.name}]);
|
|
668
|
+
expect(
|
|
669
|
+
copied(await integrationDocConflicts(undefined, {cwd: tmpDir})).filter(
|
|
670
|
+
(/** @type {any} */ each) => each.message.includes(`"${coreDoc.name}"`),
|
|
671
|
+
),
|
|
672
|
+
).toEqual([]);
|
|
673
|
+
});
|
|
674
|
+
|
|
675
|
+
it('leaves out boilerplate that three topics share', async () => {
|
|
676
|
+
writeTopics([
|
|
677
|
+
{name: 'acme-fonts', text: PASSAGE},
|
|
678
|
+
{name: 'acme-icons', text: PASSAGE},
|
|
679
|
+
{name: 'acme-media', text: PASSAGE},
|
|
680
|
+
]);
|
|
681
|
+
|
|
682
|
+
expect(
|
|
683
|
+
copied(await integrationDocConflicts(undefined, {cwd: tmpDir})),
|
|
684
|
+
).toEqual([]);
|
|
685
|
+
});
|
|
686
|
+
|
|
687
|
+
it('leaves out a run of function words, which states no fact', async () => {
|
|
688
|
+
const glue = 'and it is in the way that you can do it for them';
|
|
689
|
+
writeTopics([
|
|
690
|
+
{name: 'acme-a', text: `Start here. ${glue} first.`},
|
|
691
|
+
{name: 'acme-b', text: `Then ${glue} again.`},
|
|
692
|
+
]);
|
|
693
|
+
|
|
694
|
+
expect(
|
|
695
|
+
copied(await integrationDocConflicts(undefined, {cwd: tmpDir})),
|
|
696
|
+
).toEqual([]);
|
|
697
|
+
});
|
|
698
|
+
|
|
699
|
+
it('leaves out a shared phrase under 10 words, and code', async () => {
|
|
700
|
+
const short = PASSAGE.split(' ').slice(0, 9).join(' ');
|
|
701
|
+
writeTopics([
|
|
702
|
+
{name: 'acme-a', text: PASSAGE},
|
|
703
|
+
{name: 'acme-b', text: short},
|
|
704
|
+
{name: 'acme-c', text: 'Run it.', code: PASSAGE},
|
|
705
|
+
]);
|
|
706
|
+
|
|
707
|
+
expect(
|
|
708
|
+
copied(await integrationDocConflicts(undefined, {cwd: tmpDir})),
|
|
709
|
+
).toEqual([]);
|
|
710
|
+
});
|
|
711
|
+
});
|
|
712
|
+
|
|
713
|
+
describe('integrationDocConflicts copied passages and extensions', () => {
|
|
714
|
+
const copied = (/** @type {any} */ result) => result.data.copiedPassages;
|
|
715
|
+
|
|
716
|
+
it('links to the page an extension shows on, not to the extension', async () => {
|
|
717
|
+
writeTopics([
|
|
718
|
+
{name: 'acme-menus', text: PASSAGE, extendsTopic: 'theme'},
|
|
719
|
+
{name: 'acme-setup', text: `${PASSAGE} Then run setup.`},
|
|
720
|
+
]);
|
|
721
|
+
|
|
722
|
+
const [issue] = copied(
|
|
723
|
+
await integrationDocConflicts(undefined, {cwd: tmpDir}),
|
|
724
|
+
);
|
|
725
|
+
|
|
726
|
+
expect(issue).toMatchObject({topic: 'acme-setup', owner: 'acme-menus'});
|
|
727
|
+
expect(issue.message).toContain(
|
|
728
|
+
'with {@link @astryxdesign/cli:generic:theme}.',
|
|
729
|
+
);
|
|
730
|
+
});
|
|
731
|
+
|
|
732
|
+
it('says to delete a copy in a topic that extends its owner', async () => {
|
|
733
|
+
writeTopics([
|
|
734
|
+
{name: 'acme-base', text: PASSAGE},
|
|
735
|
+
{name: 'acme-more', text: PASSAGE, extendsTopic: 'acme-base'},
|
|
736
|
+
]);
|
|
737
|
+
|
|
738
|
+
const [issue] = copied(
|
|
739
|
+
await integrationDocConflicts(undefined, {cwd: tmpDir}),
|
|
740
|
+
);
|
|
741
|
+
|
|
742
|
+
expect(issue).toMatchObject({topic: 'acme-more', owner: 'acme-base'});
|
|
743
|
+
expect(issue.message).toContain(
|
|
744
|
+
'"acme-more" and "acme-base" show on the same page ("acme-base"): delete the passage from "acme-more"',
|
|
745
|
+
);
|
|
746
|
+
expect(issue.message).not.toContain('{@link');
|
|
747
|
+
});
|
|
748
|
+
|
|
749
|
+
it('compares pages without regard to case, and for two extensions of one page', async () => {
|
|
750
|
+
writeTopics([
|
|
751
|
+
{name: 'acme-colors', text: PASSAGE, extendsTopic: 'Theme'},
|
|
752
|
+
{name: 'acme-menus', text: PASSAGE, extendsTopic: 'theme'},
|
|
753
|
+
]);
|
|
754
|
+
|
|
755
|
+
const [issue] = copied(
|
|
756
|
+
await integrationDocConflicts(undefined, {cwd: tmpDir}),
|
|
757
|
+
);
|
|
758
|
+
|
|
759
|
+
expect(issue.message).toContain('show on the same page ("theme")');
|
|
760
|
+
expect(issue.message).not.toContain('{@link');
|
|
761
|
+
});
|
|
762
|
+
|
|
763
|
+
it('finds the line of the string that holds the copy, whatever it opens with', async () => {
|
|
764
|
+
writeTopics([{name: 'acme-menus', text: PASSAGE}]);
|
|
765
|
+
fs.writeFileSync(
|
|
766
|
+
path.join(tmpDir, 'docs', 'acme-setup.doc.mjs'),
|
|
767
|
+
[
|
|
768
|
+
'export default {',
|
|
769
|
+
" type: 'generic',",
|
|
770
|
+
" name: 'acme-setup',",
|
|
771
|
+
" title: 'Fixture',",
|
|
772
|
+
" description: 'Fixture topic.',",
|
|
773
|
+
" sections: [{title: 'Overview', content: [",
|
|
774
|
+
" {type: 'prose', text: '`acme` (the CLI) opens here, as {@link generic:acme-menus} says.'},",
|
|
775
|
+
` {type: 'prose', text: '\`acme\` (the CLI) opens here: ${PASSAGE}'},`,
|
|
776
|
+
' ]}],',
|
|
777
|
+
'};',
|
|
778
|
+
'',
|
|
779
|
+
].join('\n'),
|
|
780
|
+
);
|
|
781
|
+
|
|
782
|
+
const [issue] = copied(
|
|
783
|
+
await integrationDocConflicts(undefined, {cwd: tmpDir}),
|
|
784
|
+
);
|
|
785
|
+
|
|
786
|
+
expect(issue).toMatchObject({topic: 'acme-setup', line: 8});
|
|
787
|
+
});
|
|
788
|
+
|
|
789
|
+
it('does not list a 9-word sentence that ends in a link', async () => {
|
|
790
|
+
const sentence =
|
|
791
|
+
'Theme tokens control every surface color, as explained in {@link generic:acme-theme}.';
|
|
792
|
+
writeTopics([
|
|
793
|
+
{name: 'acme-colors', text: `Colors first. ${sentence}`},
|
|
794
|
+
{name: 'acme-menus', text: `Menus next. ${sentence}`},
|
|
795
|
+
]);
|
|
796
|
+
|
|
797
|
+
expect(
|
|
798
|
+
copied(await integrationDocConflicts(undefined, {cwd: tmpDir})),
|
|
799
|
+
).toEqual([]);
|
|
800
|
+
});
|
|
801
|
+
|
|
802
|
+
it('counts no function word toward which topic a passage fits', async () => {
|
|
803
|
+
writeTopics([
|
|
804
|
+
{name: 'acme-getting-started', text: `${PASSAGE} Then run setup.`},
|
|
805
|
+
{name: 'acme-menus', text: PASSAGE},
|
|
806
|
+
]);
|
|
807
|
+
// Titles that share only function words with the passage.
|
|
808
|
+
for (const [name, title] of [
|
|
809
|
+
['acme-getting-started', 'Get started with the widgets'],
|
|
810
|
+
['acme-menus', 'Menus'],
|
|
811
|
+
]) {
|
|
812
|
+
const file = path.join(tmpDir, 'docs', `${name}.doc.mjs`);
|
|
813
|
+
fs.writeFileSync(
|
|
814
|
+
file,
|
|
815
|
+
fs
|
|
816
|
+
.readFileSync(file, 'utf8')
|
|
817
|
+
.replace('"title":"Fixture"', `"title":"${title}"`),
|
|
818
|
+
);
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
const [issue] = copied(
|
|
822
|
+
await integrationDocConflicts(undefined, {cwd: tmpDir}),
|
|
823
|
+
);
|
|
824
|
+
|
|
825
|
+
expect(issue).toMatchObject({
|
|
826
|
+
owner: 'acme-menus',
|
|
827
|
+
topic: 'acme-getting-started',
|
|
828
|
+
});
|
|
829
|
+
});
|
|
830
|
+
|
|
831
|
+
it('finds a copy after a lead-in three topics share', async () => {
|
|
832
|
+
const lead =
|
|
833
|
+
'Every package ships its integration docs as plain modules the CLI reads';
|
|
834
|
+
writeTopics([
|
|
835
|
+
{name: 'acme-menus', text: `${lead}. ${PASSAGE}`},
|
|
836
|
+
{name: 'acme-setup', text: `${lead}. ${PASSAGE} Then run setup.`},
|
|
837
|
+
{name: 'acme-other', text: `${lead}. Nothing else here is shared.`},
|
|
838
|
+
]);
|
|
839
|
+
|
|
840
|
+
const found = copied(
|
|
841
|
+
await integrationDocConflicts(undefined, {cwd: tmpDir}),
|
|
842
|
+
);
|
|
843
|
+
|
|
844
|
+
expect(found).toHaveLength(1);
|
|
845
|
+
expect(found[0].text).toBe(PASSAGE);
|
|
846
|
+
});
|
|
847
|
+
|
|
848
|
+
it("links to a package topic that replaces Core's by its own name", async () => {
|
|
849
|
+
writeTopics([
|
|
850
|
+
{name: 'theme', text: PASSAGE, replaces: 'theme'},
|
|
851
|
+
{name: 'acme-setup', text: `${PASSAGE} Then run setup.`},
|
|
852
|
+
]);
|
|
853
|
+
|
|
854
|
+
const found = copied(
|
|
855
|
+
await integrationDocConflicts(undefined, {cwd: tmpDir}),
|
|
856
|
+
);
|
|
857
|
+
|
|
858
|
+
for (const each of found) {
|
|
859
|
+
expect(each.message).not.toContain('@astryxdesign/cli:generic:theme');
|
|
860
|
+
}
|
|
861
|
+
expect(
|
|
862
|
+
found.some((/** @type {any} */ each) =>
|
|
863
|
+
each.message.includes('with {@link generic:theme}.'),
|
|
864
|
+
),
|
|
865
|
+
).toBe(true);
|
|
866
|
+
});
|
|
867
|
+
|
|
868
|
+
it('prints no link to a Core topic one of the package topics replaces', async () => {
|
|
869
|
+
const [file] = builtinGuideFiles();
|
|
870
|
+
const coreDoc = /** @type {any} */ (await loadTopicModule(file));
|
|
871
|
+
const coreText = proseWords(coreDoc)
|
|
872
|
+
.slice(0, 30)
|
|
873
|
+
.map(each => each.word)
|
|
874
|
+
.join(' ');
|
|
875
|
+
writeTopics([
|
|
876
|
+
{name: 'acme-own', text: 'Our version.', replaces: coreDoc.name},
|
|
877
|
+
{name: 'acme-copy', text: coreText},
|
|
878
|
+
]);
|
|
879
|
+
|
|
880
|
+
const found = copied(
|
|
881
|
+
await integrationDocConflicts(undefined, {cwd: tmpDir}),
|
|
882
|
+
).filter((/** @type {any} */ each) => each.owner === coreDoc.name);
|
|
883
|
+
|
|
884
|
+
expect(found).toHaveLength(1);
|
|
885
|
+
expect(found[0].message).toContain(
|
|
886
|
+
`"acme-own" replaces the Core topic "${coreDoc.name}"`,
|
|
887
|
+
);
|
|
888
|
+
expect(found[0].message).not.toContain('{@link');
|
|
889
|
+
});
|
|
890
|
+
});
|
|
891
|
+
|
|
892
|
+
describe('integrationDocConflicts fluff', () => {
|
|
893
|
+
const fluff = (/** @type {any} */ result) => result.data.fluff;
|
|
894
|
+
|
|
895
|
+
it('lists each padding phrase with its line and what to write instead', async () => {
|
|
896
|
+
writeTopics([
|
|
897
|
+
{
|
|
898
|
+
name: 'acme-setup',
|
|
899
|
+
text: 'In order to start, run the setup command once.',
|
|
900
|
+
},
|
|
901
|
+
]);
|
|
902
|
+
|
|
903
|
+
const result = await integrationDocConflicts(undefined, {cwd: tmpDir});
|
|
904
|
+
const [hit, ...rest] = fluff(result);
|
|
905
|
+
|
|
906
|
+
expect(rest).toEqual([]);
|
|
907
|
+
expect(result.data.issues).toEqual([
|
|
908
|
+
{code: 'doc_fluff', severity: 'warning', message: hit.message},
|
|
909
|
+
]);
|
|
910
|
+
expect(hit).toMatchObject({
|
|
911
|
+
topic: 'acme-setup',
|
|
912
|
+
kind: 'phrase',
|
|
913
|
+
phrase: 'in order to',
|
|
914
|
+
section: 'Overview',
|
|
915
|
+
file: 'docs/acme-setup.doc.mjs',
|
|
916
|
+
line: 1,
|
|
917
|
+
density: null,
|
|
918
|
+
});
|
|
919
|
+
expect(hit.message).toContain('Write "to" instead.');
|
|
920
|
+
});
|
|
921
|
+
|
|
922
|
+
it('finds each line within its own string, not in code or another field', async () => {
|
|
923
|
+
writeTopics([]);
|
|
924
|
+
fs.writeFileSync(
|
|
925
|
+
path.join(tmpDir, 'docs', 'acme-lines.doc.mjs'),
|
|
926
|
+
[
|
|
927
|
+
'export default {',
|
|
928
|
+
" type: 'generic',",
|
|
929
|
+
" name: 'acme-lines',",
|
|
930
|
+
" title: 'Fixture',",
|
|
931
|
+
" description: 'Build the package in order to publish it.',",
|
|
932
|
+
" sections: [{title: 'Overview', content: [",
|
|
933
|
+
" {type: 'code', lang: 'text', code: 'in order to build'},",
|
|
934
|
+
" {type: 'prose', text: 'Note that the build runs once per package and writes its output.'},",
|
|
935
|
+
" {type: 'prose', text: 'Run the build in order to refresh the generated files for the docs.'},",
|
|
936
|
+
' ]}],',
|
|
937
|
+
'};',
|
|
938
|
+
'',
|
|
939
|
+
].join('\n'),
|
|
940
|
+
);
|
|
941
|
+
|
|
942
|
+
const found = fluff(
|
|
943
|
+
await integrationDocConflicts(undefined, {cwd: tmpDir}),
|
|
944
|
+
).map((/** @type {any} */ each) => [each.phrase, each.line]);
|
|
945
|
+
|
|
946
|
+
expect(found).toEqual([
|
|
947
|
+
['in order to', 5],
|
|
948
|
+
['note that', 8],
|
|
949
|
+
['in order to', 9],
|
|
950
|
+
]);
|
|
951
|
+
});
|
|
952
|
+
|
|
953
|
+
it('gives each use its own line in multi-line and concatenated strings', async () => {
|
|
954
|
+
writeTopics([]);
|
|
955
|
+
fs.writeFileSync(
|
|
956
|
+
path.join(tmpDir, 'docs', 'acme-multi.doc.mjs'),
|
|
957
|
+
[
|
|
958
|
+
'export default {',
|
|
959
|
+
" type: 'generic',",
|
|
960
|
+
" name: 'acme-multi',",
|
|
961
|
+
" title: 'Fixture',",
|
|
962
|
+
" description: 'Fixture topic.',",
|
|
963
|
+
" sections: [{title: 'Overview', content: [",
|
|
964
|
+
" {type: 'prose', text: `Build the package first.",
|
|
965
|
+
'Run the build in order to refresh the files.',
|
|
966
|
+
'Run it again in order to check them.`},',
|
|
967
|
+
" {type: 'prose', text: 'Publishing reads the built files ' +",
|
|
968
|
+
" 'in order to ship them, so build before you publish.'},",
|
|
969
|
+
' ]}],',
|
|
970
|
+
'};',
|
|
971
|
+
'',
|
|
972
|
+
].join('\n'),
|
|
973
|
+
);
|
|
974
|
+
|
|
975
|
+
const found = fluff(
|
|
976
|
+
await integrationDocConflicts(undefined, {cwd: tmpDir}),
|
|
977
|
+
).map((/** @type {any} */ each) => each.line);
|
|
978
|
+
|
|
979
|
+
expect(found).toEqual([8, 9, 11]);
|
|
980
|
+
});
|
|
981
|
+
|
|
982
|
+
it('finds the line of strings that open or end with code, links, quotes or escapes', async () => {
|
|
983
|
+
writeTopics([]);
|
|
984
|
+
fs.writeFileSync(
|
|
985
|
+
path.join(tmpDir, 'docs', 'acme-shapes.doc.mjs'),
|
|
986
|
+
[
|
|
987
|
+
'export default {',
|
|
988
|
+
" type: 'generic',",
|
|
989
|
+
" name: 'acme-shapes',",
|
|
990
|
+
" title: 'Fixture',",
|
|
991
|
+
" description: 'Fixture topic.',",
|
|
992
|
+
" sections: [{title: 'Overview', content: [",
|
|
993
|
+
" {type: 'prose', text: '`build` runs first, in order to check it.'},",
|
|
994
|
+
" {type: 'prose', text: '{@link generic:acme-shapes} runs it in order to ship.'},",
|
|
995
|
+
" {type: 'prose', text: '\"Build\" is a step you run in order to test (and ship).'},",
|
|
996
|
+
" {type: 'prose', text: '(Build) runs it once in order to publish the app\\'s files.'},",
|
|
997
|
+
" {type: 'prose', text: 'Run the build in order to check the files.'},",
|
|
998
|
+
" {type: 'prose', text: 'Run the build in order to publish the files.'},",
|
|
999
|
+
' ]}],',
|
|
1000
|
+
'};',
|
|
1001
|
+
'',
|
|
1002
|
+
].join('\n'),
|
|
1003
|
+
);
|
|
1004
|
+
|
|
1005
|
+
const found = fluff(
|
|
1006
|
+
await integrationDocConflicts(undefined, {cwd: tmpDir}),
|
|
1007
|
+
).map((/** @type {any} */ each) => each.line);
|
|
1008
|
+
|
|
1009
|
+
expect(found).toEqual([7, 8, 9, 10, 11, 12]);
|
|
1010
|
+
});
|
|
1011
|
+
|
|
1012
|
+
it('lists a long topic that is mostly glue, and not a dense one', async () => {
|
|
1013
|
+
const glue = Array.from(
|
|
1014
|
+
{length: 12},
|
|
1015
|
+
() =>
|
|
1016
|
+
'So that is why it is the one that you would want to use if you can.',
|
|
1017
|
+
).join(' ');
|
|
1018
|
+
const dense = Array.from(
|
|
1019
|
+
{length: 8},
|
|
1020
|
+
(_, i) =>
|
|
1021
|
+
`Name codemod folder ${i} after the Core version whose upgrade runs it.`,
|
|
1022
|
+
).join(' ');
|
|
1023
|
+
writeTopics([
|
|
1024
|
+
{name: 'acme-glue', text: glue},
|
|
1025
|
+
{name: 'acme-dense', text: dense},
|
|
1026
|
+
]);
|
|
1027
|
+
|
|
1028
|
+
const found = fluff(
|
|
1029
|
+
await integrationDocConflicts(undefined, {cwd: tmpDir}),
|
|
1030
|
+
);
|
|
1031
|
+
|
|
1032
|
+
expect(found.map((/** @type {any} */ each) => each.topic)).toEqual([
|
|
1033
|
+
'acme-glue',
|
|
1034
|
+
]);
|
|
1035
|
+
expect(found[0]).toMatchObject({kind: 'density', phrase: null});
|
|
1036
|
+
expect(found[0].density).toBeLessThan(0.5);
|
|
1037
|
+
expect(found[0].message).toContain("Core's guides have a median of");
|
|
1038
|
+
expect(found[0].message).toContain('under the 50% floor');
|
|
1039
|
+
});
|
|
1040
|
+
});
|
|
1041
|
+
|
|
1042
|
+
describe('integrationDocConflicts stray doc pointers', () => {
|
|
1043
|
+
const stray = (/** @type {any} */ result) => result.data.strayPointers;
|
|
1044
|
+
|
|
1045
|
+
it('lists a pointer to a topic whose name holds an underscore or capitals', async () => {
|
|
1046
|
+
writeTopics([
|
|
1047
|
+
{name: 'acme_setup', text: 'Install it first.'},
|
|
1048
|
+
{name: 'AcmeGuide', text: 'Read on.'},
|
|
1049
|
+
{
|
|
1050
|
+
name: 'acme-menus',
|
|
1051
|
+
text: 'Set it up as `astryx docs acme_setup` says, then `astryx docs AcmeGuide`; flags such as `astryx docs -h` are not pointers.',
|
|
1052
|
+
},
|
|
1053
|
+
]);
|
|
1054
|
+
|
|
1055
|
+
const found = stray(
|
|
1056
|
+
await integrationDocConflicts(undefined, {cwd: tmpDir}),
|
|
1057
|
+
);
|
|
1058
|
+
|
|
1059
|
+
expect(found.map((/** @type {any} */ each) => each.link)).toEqual([
|
|
1060
|
+
'{@link generic:acme_setup}',
|
|
1061
|
+
'{@link generic:AcmeGuide}',
|
|
1062
|
+
]);
|
|
1063
|
+
});
|
|
1064
|
+
|
|
1065
|
+
it('links a pointer to an extension topic to the page it extends', async () => {
|
|
1066
|
+
writeTopics([
|
|
1067
|
+
{name: 'acme-theme', text: 'Acme colors.', extendsTopic: 'theme'},
|
|
1068
|
+
{name: 'acme-menus', text: 'Colors are in `astryx docs acme-theme`.'},
|
|
1069
|
+
]);
|
|
1070
|
+
|
|
1071
|
+
const [hit, ...rest] = stray(
|
|
1072
|
+
await integrationDocConflicts(undefined, {cwd: tmpDir}),
|
|
1073
|
+
);
|
|
1074
|
+
|
|
1075
|
+
expect(rest).toEqual([]);
|
|
1076
|
+
expect(hit).toMatchObject({
|
|
1077
|
+
topic: 'acme-menus',
|
|
1078
|
+
link: '{@link @astryxdesign/cli:generic:theme}',
|
|
1079
|
+
});
|
|
1080
|
+
});
|
|
1081
|
+
|
|
1082
|
+
it('places a pointer-only list item on its own line, not an earlier summary or code block', async () => {
|
|
1083
|
+
writeTopics([{name: 'acme-menus', text: 'Menus.'}]);
|
|
1084
|
+
fs.writeFileSync(
|
|
1085
|
+
path.join(tmpDir, 'docs', 'acme-setup.doc.mjs'),
|
|
1086
|
+
[
|
|
1087
|
+
'export default {',
|
|
1088
|
+
" type: 'generic',",
|
|
1089
|
+
" name: 'acme-setup',",
|
|
1090
|
+
" title: 'Fixture',",
|
|
1091
|
+
" summary: 'See `astryx docs acme-menus`.',",
|
|
1092
|
+
" description: 'Fixture topic.',",
|
|
1093
|
+
" sections: [{title: 'Overview', content: [",
|
|
1094
|
+
" {type: 'code', lang: 'bash', code: 'astryx docs acme-menus'},",
|
|
1095
|
+
" {type: 'list', style: 'unordered', items: ['`astryx docs acme-menus`']},",
|
|
1096
|
+
' ]}],',
|
|
1097
|
+
'};',
|
|
1098
|
+
'',
|
|
1099
|
+
].join('\n'),
|
|
1100
|
+
);
|
|
1101
|
+
|
|
1102
|
+
const found = stray(
|
|
1103
|
+
await integrationDocConflicts(undefined, {cwd: tmpDir}),
|
|
1104
|
+
).filter((/** @type {any} */ each) => each.topic === 'acme-setup');
|
|
1105
|
+
|
|
1106
|
+
// The summary's own pointer is listed too, at its line.
|
|
1107
|
+
expect(found.map((/** @type {any} */ each) => each.line)).toEqual([5, 9]);
|
|
1108
|
+
});
|
|
1109
|
+
|
|
1110
|
+
it('lists a written pointer to a doc with the link that replaces it, and warns', async () => {
|
|
1111
|
+
writeTopics([
|
|
1112
|
+
{name: 'acme-setup', text: 'Install it first.'},
|
|
1113
|
+
{
|
|
1114
|
+
name: 'acme-menus',
|
|
1115
|
+
text: 'Set it up as `astryx docs acme-setup` says; the theme is in `astryx docs theme`.',
|
|
1116
|
+
},
|
|
1117
|
+
]);
|
|
1118
|
+
|
|
1119
|
+
const result = await integrationDocConflicts(undefined, {cwd: tmpDir});
|
|
1120
|
+
const [own, core, ...rest] = stray(result);
|
|
1121
|
+
|
|
1122
|
+
expect(rest).toEqual([]);
|
|
1123
|
+
expect(own).toMatchObject({
|
|
1124
|
+
topic: 'acme-menus',
|
|
1125
|
+
section: 'Overview',
|
|
1126
|
+
file: 'docs/acme-menus.doc.mjs',
|
|
1127
|
+
line: 1,
|
|
1128
|
+
pointer: 'astryx docs acme-setup',
|
|
1129
|
+
link: '{@link generic:acme-setup}',
|
|
1130
|
+
});
|
|
1131
|
+
expect(own.message).toContain(
|
|
1132
|
+
'Replace `astryx docs acme-setup`, code ticks included, with {@link generic:acme-setup}',
|
|
1133
|
+
);
|
|
1134
|
+
expect(core.link).toBe('{@link @astryxdesign/cli:generic:theme}');
|
|
1135
|
+
expect(result.data.issues).toEqual([
|
|
1136
|
+
{code: 'stray_doc_pointer', severity: 'warning', message: own.message},
|
|
1137
|
+
{code: 'stray_doc_pointer', severity: 'warning', message: core.message},
|
|
1138
|
+
]);
|
|
1139
|
+
});
|
|
1140
|
+
|
|
1141
|
+
it('names a namespace doc by its kind', async () => {
|
|
1142
|
+
writeTopics([
|
|
1143
|
+
{name: 'acme-setup', text: 'Lay it out as `astryx docs layout` says.'},
|
|
1144
|
+
]);
|
|
1145
|
+
|
|
1146
|
+
const [hit] = stray(
|
|
1147
|
+
await integrationDocConflicts(undefined, {cwd: tmpDir}),
|
|
1148
|
+
);
|
|
1149
|
+
|
|
1150
|
+
expect(hit.link).toBe('{@link @astryxdesign/cli:namespace:layout}');
|
|
1151
|
+
});
|
|
1152
|
+
|
|
1153
|
+
it('finds each line in the string that holds the pointer, not in code', async () => {
|
|
1154
|
+
writeTopics([]);
|
|
1155
|
+
fs.writeFileSync(
|
|
1156
|
+
path.join(tmpDir, 'docs', 'acme-lines.doc.mjs'),
|
|
1157
|
+
[
|
|
1158
|
+
'export default {',
|
|
1159
|
+
" type: 'generic',",
|
|
1160
|
+
" name: 'acme-lines',",
|
|
1161
|
+
" title: 'Fixture',",
|
|
1162
|
+
" description: 'Read `astryx docs theme` first.',",
|
|
1163
|
+
" sections: [{title: 'Overview', content: [",
|
|
1164
|
+
" {type: 'code', lang: 'bash', code: 'astryx docs theme'},",
|
|
1165
|
+
" {type: 'prose', text: 'The colors your package sets show up in `astryx docs theme` for every app.'},",
|
|
1166
|
+
' ]}],',
|
|
1167
|
+
'};',
|
|
1168
|
+
'',
|
|
1169
|
+
].join('\n'),
|
|
1170
|
+
);
|
|
1171
|
+
|
|
1172
|
+
const found = stray(
|
|
1173
|
+
await integrationDocConflicts(undefined, {cwd: tmpDir}),
|
|
1174
|
+
);
|
|
1175
|
+
|
|
1176
|
+
// The description resolves links too, so its pointer is its own finding.
|
|
1177
|
+
expect(found.map((/** @type {any} */ each) => each.line)).toEqual([5, 8]);
|
|
1178
|
+
});
|
|
1179
|
+
|
|
1180
|
+
it('leaves a command for a doc that does not exist, and a walkthrough', async () => {
|
|
1181
|
+
writeTopics([
|
|
1182
|
+
{
|
|
1183
|
+
name: 'acme-setup',
|
|
1184
|
+
text: 'Run `astryx docs acme-nothing`, or `astryx docs acme-setup overview`.',
|
|
1185
|
+
},
|
|
1186
|
+
]);
|
|
1187
|
+
|
|
1188
|
+
expect(
|
|
1189
|
+
stray(await integrationDocConflicts(undefined, {cwd: tmpDir})),
|
|
1190
|
+
).toEqual([]);
|
|
1191
|
+
});
|
|
1192
|
+
});
|
|
1193
|
+
|
|
1194
|
+
/**
|
|
1195
|
+
* A package with one component doc and, optionally, one topic.
|
|
1196
|
+
* @param {{usage: string, topicDescription?: string}} fixture
|
|
1197
|
+
*/
|
|
1198
|
+
function writeLinkedComponent({usage, topicDescription}) {
|
|
1199
|
+
fs.writeFileSync(
|
|
1200
|
+
path.join(tmpDir, 'package.json'),
|
|
1201
|
+
JSON.stringify({name: '@acme/widgets', version: '1.2.3'}),
|
|
1202
|
+
);
|
|
1203
|
+
fs.writeFileSync(
|
|
1204
|
+
path.join(tmpDir, 'astryx.integration.mjs'),
|
|
1205
|
+
topicDescription == null
|
|
1206
|
+
? `export default {components: './components'};\n`
|
|
1207
|
+
: `export default {components: './components', docs: './docs'};\n`,
|
|
1208
|
+
);
|
|
1209
|
+
fs.mkdirSync(path.join(tmpDir, 'components'), {recursive: true});
|
|
1210
|
+
const doc = {
|
|
1211
|
+
type: 'component',
|
|
1212
|
+
name: 'AcmeButton',
|
|
1213
|
+
displayName: 'Acme Button',
|
|
1214
|
+
usage: {description: usage},
|
|
1215
|
+
props: [],
|
|
1216
|
+
};
|
|
1217
|
+
fs.writeFileSync(
|
|
1218
|
+
path.join(tmpDir, 'components', 'AcmeButton.doc.mjs'),
|
|
1219
|
+
`export default ${JSON.stringify(doc, null, 2)};\n`,
|
|
1220
|
+
);
|
|
1221
|
+
fs.writeFileSync(
|
|
1222
|
+
path.join(tmpDir, 'components', 'AcmeButton.tsx'),
|
|
1223
|
+
'export function AcmeButton() { return null; }\n',
|
|
1224
|
+
);
|
|
1225
|
+
if (topicDescription == null) return;
|
|
1226
|
+
fs.mkdirSync(path.join(tmpDir, 'docs'), {recursive: true});
|
|
1227
|
+
const topic = {
|
|
1228
|
+
type: 'generic',
|
|
1229
|
+
name: 'acme-setup',
|
|
1230
|
+
title: 'Fixture',
|
|
1231
|
+
description: topicDescription,
|
|
1232
|
+
sections: [
|
|
1233
|
+
{title: 'Overview', content: [{type: 'prose', text: 'Install it.'}]},
|
|
1234
|
+
],
|
|
1235
|
+
};
|
|
1236
|
+
fs.writeFileSync(
|
|
1237
|
+
path.join(tmpDir, 'docs', 'acme-setup.doc.mjs'),
|
|
1238
|
+
`export default ${JSON.stringify(topic, null, 2)};\n`,
|
|
1239
|
+
);
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
describe('links in component docs and topic descriptions', () => {
|
|
1243
|
+
it("warns on a component doc's link that names no doc, and passes one to its own topic", async () => {
|
|
1244
|
+
writeLinkedComponent({
|
|
1245
|
+
usage:
|
|
1246
|
+
'Set it up as {@link generic:acme-setup} says, theme it with {@link @astryxdesign/cli:generic:theme}, then {@link generic:acme-nothing}.',
|
|
1247
|
+
topicDescription: 'Install Acme.',
|
|
1248
|
+
});
|
|
1249
|
+
|
|
1250
|
+
const {data} = await integrationComponentConflicts(undefined, {
|
|
1251
|
+
cwd: tmpDir,
|
|
1252
|
+
});
|
|
1253
|
+
const links = data.issues.filter(
|
|
1254
|
+
(/** @type {any} */ issue) => issue.code === 'invalid_doc_graph',
|
|
1255
|
+
);
|
|
1256
|
+
expect(links).toEqual([
|
|
1257
|
+
{
|
|
1258
|
+
code: 'invalid_doc_graph',
|
|
1259
|
+
severity: 'warning',
|
|
1260
|
+
message: expect.stringMatching(
|
|
1261
|
+
/^component AcmeButton \(usage\.description\): "generic:acme-nothing" names no doc\./,
|
|
1262
|
+
),
|
|
1263
|
+
},
|
|
1264
|
+
]);
|
|
1265
|
+
});
|
|
1266
|
+
|
|
1267
|
+
it("warns on a topic description's link that names no doc", async () => {
|
|
1268
|
+
writeLinkedComponent({
|
|
1269
|
+
usage: 'A button.',
|
|
1270
|
+
topicDescription: 'Install Acme; see {@link generic:acme-nothing}.',
|
|
1271
|
+
});
|
|
1272
|
+
|
|
1273
|
+
const {data} = await integrationDocConflicts(undefined, {cwd: tmpDir});
|
|
1274
|
+
const links = data.issues.filter(
|
|
1275
|
+
(/** @type {any} */ issue) => issue.code === 'invalid_doc_graph',
|
|
1276
|
+
);
|
|
1277
|
+
expect(links).toEqual([
|
|
1278
|
+
{
|
|
1279
|
+
code: 'invalid_doc_graph',
|
|
1280
|
+
severity: 'warning',
|
|
1281
|
+
message: expect.stringMatching(
|
|
1282
|
+
/^acme-setup \(description\): "generic:acme-nothing" names no doc\./,
|
|
1283
|
+
),
|
|
1284
|
+
},
|
|
1285
|
+
]);
|
|
1286
|
+
});
|
|
1287
|
+
|
|
1288
|
+
it('lists a written pointer in a component doc, with the link that replaces it', async () => {
|
|
1289
|
+
writeLinkedComponent({usage: 'Theme it as `astryx docs theme` says.'});
|
|
1290
|
+
|
|
1291
|
+
const {data} = await integrationDocConflicts(undefined, {cwd: tmpDir});
|
|
1292
|
+
const [pointer, ...rest] = data.strayPointers;
|
|
1293
|
+
|
|
1294
|
+
expect(rest).toEqual([]);
|
|
1295
|
+
expect(pointer).toMatchObject({
|
|
1296
|
+
topic: 'AcmeButton',
|
|
1297
|
+
kind: 'component',
|
|
1298
|
+
section: null,
|
|
1299
|
+
field: 'usage.description',
|
|
1300
|
+
file: 'components/AcmeButton.doc.mjs',
|
|
1301
|
+
pointer: 'astryx docs theme',
|
|
1302
|
+
link: '{@link @astryxdesign/cli:generic:theme}',
|
|
1303
|
+
});
|
|
1304
|
+
expect(pointer.line).toEqual(expect.any(Number));
|
|
1305
|
+
expect(pointer.message).toBe(
|
|
1306
|
+
`component "AcmeButton" (field usage.description, line ${pointer.line}) points at a doc with the written command \`astryx docs theme\`. ` +
|
|
1307
|
+
'Replace `astryx docs theme`, code ticks included, with {@link @astryxdesign/cli:generic:theme}: the CLI prints the command that opens the doc, and doctor checks the link.',
|
|
1308
|
+
);
|
|
1309
|
+
expect(data.issues).toContainEqual({
|
|
1310
|
+
code: 'stray_doc_pointer',
|
|
1311
|
+
severity: 'warning',
|
|
1312
|
+
message: pointer.message,
|
|
1313
|
+
});
|
|
1314
|
+
});
|
|
1315
|
+
|
|
1316
|
+
it("lists a written pointer in a topic's description", async () => {
|
|
1317
|
+
writeLinkedComponent({
|
|
1318
|
+
usage: 'A button.',
|
|
1319
|
+
topicDescription: 'Install Acme; theme it as `astryx docs theme` says.',
|
|
1320
|
+
});
|
|
1321
|
+
|
|
1322
|
+
const {data} = await integrationDocConflicts(undefined, {cwd: tmpDir});
|
|
1323
|
+
|
|
1324
|
+
expect(data.strayPointers).toEqual([
|
|
1325
|
+
expect.objectContaining({
|
|
1326
|
+
topic: 'acme-setup',
|
|
1327
|
+
kind: 'topic',
|
|
1328
|
+
field: 'description',
|
|
1329
|
+
link: '{@link @astryxdesign/cli:generic:theme}',
|
|
1330
|
+
}),
|
|
1331
|
+
]);
|
|
1332
|
+
});
|
|
1333
|
+
});
|