@astryxdesign/cli 0.7.2 → 0.7.3-canary.02f5fe4
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 +104 -71
- package/api/build/_adapter.d.mts +58 -0
- package/api/build/_adapter.mjs +127 -3
- package/api/build/blocks/blocks.d.mts +92 -0
- package/api/build/blocks/blocks.mjs +528 -0
- package/api/build/blocks/blocks.test.mjs +238 -0
- package/api/build/blocks/blocks.weights.json +1 -0
- package/api/build/build.mjs +25 -6
- package/api/build/build.type.mjs +1 -1
- package/api/build/kit/answer.d.mts +18 -0
- package/api/build/kit/answer.mjs +195 -0
- package/api/build/kit/answer.test.mjs +108 -0
- package/api/build/kit/kind.d.mts +25 -0
- package/api/build/kit/kind.mjs +170 -0
- package/api/build/kit/kit.d.mts +2 -4
- package/api/build/kit/kit.mjs +8 -398
- package/api/build/kit/rank.d.mts +3 -32
- package/api/build/kit/rank.mjs +25 -342
- package/api/build/kit/rank.test.mjs +2 -1
- package/api/build/kit/signals.d.mts +79 -0
- package/api/build/kit/signals.mjs +295 -0
- package/api/build/kit/start.d.mts +32 -0
- package/api/build/kit/start.mjs +163 -0
- package/api/build/kit/words.d.mts +72 -0
- package/api/build/kit/words.mjs +202 -0
- package/api/build/tree.d.mts +20 -0
- package/api/build/tree.mjs +53 -0
- package/api/build/tree.test.mjs +185 -0
- package/api/build/tree.weights.json +1 -0
- package/api/component/_adapter.mjs +39 -5
- package/api/component/component-replacement.test.mjs +1 -0
- package/api/component/component.mjs +17 -3
- package/api/component/component.test.mjs +15 -0
- package/api/component/doc-links.test.mjs +2 -2
- package/api/discover/_adapter.d.mts +1 -2
- package/api/discover/_adapter.mjs +42 -17
- package/api/discover/_adapter.test.mjs +42 -0
- package/api/discover/discover.mjs +0 -1
- package/api/docs/_adapter.d.mts +2 -2
- package/api/docs/_adapter.mjs +82 -23
- package/api/docs/docs-homes.test.mjs +127 -0
- package/api/docs/docs.doc.mjs +3 -3
- package/api/docs/docs.test.mjs +35 -19
- package/api/docs/integration-tree.test.mjs +20 -20
- package/api/docs/integrationDocs.test.mjs +1 -1
- package/api/docs/namespace-section.test.mjs +6 -6
- package/api/docs/reference-blocks.test.mjs +2 -2
- package/api/doctor/_adapter.d.mts +33 -13
- package/api/doctor/_adapter.mjs +55 -25
- package/api/doctor/doctor.d.mts +23 -5
- package/api/doctor/doctor.mjs +183 -40
- package/api/doctor/doctor.test.mjs +95 -12
- package/api/hook/hook.mjs +1 -1
- package/api/init/init.doc.mjs +20 -6
- package/api/init/init.test.mjs +18 -2
- package/api/init/init.type.d.mts +3 -3
- package/api/init/init.type.mjs +3 -3
- package/api/init/remove/remove.mjs +17 -4
- package/api/init/remove/remove.test.mjs +114 -2
- package/api/init/run/run.mjs +69 -6
- package/api/integration/_adapter.d.mts +8 -0
- package/api/integration/_adapter.mjs +21 -1
- package/api/integration/authoring-checks.mjs +83 -9
- package/api/integration/authoring-checks.test.mjs +220 -0
- package/api/integration/pack-check.mjs +123 -4
- package/api/integration/pack-check.test.mjs +87 -0
- package/api/integration/validate-integration-case-disk.test.mjs +111 -0
- package/api/integration/validate-integration-fixes.test.mjs +6 -5
- package/api/integration/validate-integration-gaps.test.mjs +1975 -0
- package/api/integration/validate-integration.d.mts +15 -2
- package/api/integration/validate-integration.mjs +298 -39
- package/api/integration/validate-integration.type.d.mts +90 -1
- package/api/integration/validate-integration.type.mjs +67 -2
- package/api/integration/validateIntegration.doc.mjs +1 -1
- package/api/search/ORDERING.md +28 -0
- package/api/search/_adapter.d.mts +82 -2
- package/api/search/_adapter.mjs +157 -10
- package/api/search/_adapter.test.mjs +173 -0
- package/api/search/_candidate-score.d.mts +40 -0
- package/api/search/_candidate-score.mjs +238 -0
- package/api/search/_cards.json +6098 -0
- package/api/search/_compound.d.mts +50 -0
- package/api/search/_compound.mjs +170 -0
- package/api/search/_doc-searcher.d.mts +4 -0
- package/api/search/_doc-searcher.mjs +23 -0
- package/api/search/_doc-searcher.test.mjs +36 -0
- package/api/search/_doc-searcher.weights.json +1 -0
- package/api/search/_doc-text.d.mts +30 -0
- package/api/search/_doc-text.mjs +87 -0
- package/api/search/_entries.d.mts +63 -0
- package/api/search/_entries.mjs +232 -0
- package/api/search/_entries.test.mjs +48 -0
- package/api/search/_features.d.mts +35 -0
- package/api/search/_features.mjs +155 -0
- package/api/search/_gather-components.d.mts +26 -0
- package/api/search/_gather-components.mjs +284 -0
- package/api/search/_gather-docs.d.mts +20 -0
- package/api/search/_gather-docs.mjs +254 -0
- package/api/search/_gather-hooks.d.mts +12 -0
- package/api/search/_gather-hooks.mjs +52 -0
- package/api/search/_gather-templates.d.mts +25 -0
- package/api/search/_gather-templates.mjs +107 -0
- package/api/search/_lexical.d.mts +135 -0
- package/api/search/_lexical.mjs +351 -0
- package/api/search/_main-searcher.d.mts +19 -0
- package/api/search/_main-searcher.mjs +115 -0
- package/api/search/_main-searcher.test.mjs +106 -0
- package/api/search/_main-searcher.weights.json +1 -0
- package/api/search/_module-doc.d.mts +32 -0
- package/api/search/_module-doc.mjs +78 -0
- package/api/search/_order.d.mts +32 -0
- package/api/search/_order.mjs +162 -0
- package/api/search/_order.test.mjs +72 -0
- package/api/search/_priority.d.mts +50 -0
- package/api/search/_priority.mjs +139 -0
- package/api/search/_query-words.d.mts +25 -0
- package/api/search/_query-words.mjs +92 -0
- package/api/search/_query.d.mts +65 -0
- package/api/search/_query.mjs +176 -0
- package/api/search/_result.d.mts +97 -0
- package/api/search/_result.mjs +150 -0
- package/api/search/_score.d.mts +60 -0
- package/api/search/_score.mjs +283 -0
- package/api/search/_synonyms.d.mts +21 -0
- package/api/search/_synonyms.mjs +67 -0
- package/api/search/_template-searcher.d.mts +4 -0
- package/api/search/_template-searcher.mjs +23 -0
- package/api/search/_template-searcher.test.mjs +40 -0
- package/api/search/_template-searcher.weights.json +1 -0
- package/api/search/_titles.d.mts +33 -0
- package/api/search/_titles.mjs +65 -0
- package/api/search/_words.d.mts +64 -0
- package/api/search/_words.mjs +157 -0
- package/api/search/search.d.mts +22 -305
- package/api/search/search.doc.mjs +8 -6
- package/api/search/search.mjs +56 -2482
- package/api/search/search.test.mjs +14 -14
- package/api/search/search.type.d.mts +6 -1
- package/api/search/search.type.mjs +6 -1
- package/api/search/tree.d.mts +15 -0
- package/api/search/tree.mjs +40 -0
- package/api/search/tree.test.mjs +66 -0
- package/api/search/tree.weights.json +1 -0
- package/api/template/template.mjs +5 -1
- package/api/theme/build/build.packed-old-core.test.mjs +1 -0
- package/api/upgrade/_adapter.d.mts +57 -9
- package/api/upgrade/_adapter.mjs +198 -28
- package/api/upgrade/registry/registry.d.mts +10 -4
- package/api/upgrade/registry/registry.mjs +33 -16
- package/api/upgrade/registry/registry.test.mjs +85 -0
- package/api/upgrade/run/run.d.mts +4 -2
- package/api/upgrade/run/run.mjs +13 -2
- package/api/upgrade/run/run.test.mjs +417 -3
- package/api/upgrade/upgrade.doc.mjs +10 -5
- package/api/upgrade/upgrade.mjs +13 -2
- package/api/upgrade/upgrade.test.mjs +194 -0
- package/api/upgrade/upgrade.type.d.mts +5 -3
- package/api/upgrade/upgrade.type.mjs +8 -6
- package/assets/codemods/transforms/v0.7.0/__tests__/validate-data-token-ownership.test.mjs +11 -0
- package/assets/codemods/transforms/v0.7.0/validate-data-token-ownership.mjs +3 -3
- package/assets/docs/author-a-theme.doc.mjs +2 -2
- package/assets/docs/is-my-code-good.doc.mjs +287 -0
- package/assets/docs/tree/add-a-component.doc.mjs +1 -1
- package/assets/docs/tree/add-a-theme.doc.mjs +1 -1
- package/assets/docs/tree/add-a-topic.doc.mjs +2 -2
- package/assets/docs/tree/agent-guidance.doc.mjs +1 -1
- package/assets/docs/tree/api.doc.mjs +6 -0
- package/assets/docs/tree/{integrations.doc.mjs → authoring.doc.mjs} +15 -6
- package/assets/docs/tree/block-template.doc.mjs +1 -1
- package/assets/docs/tree/build-the-template.doc.mjs +1 -1
- package/assets/docs/tree/building-blocks.doc.mjs +2 -2
- package/assets/docs/tree/check-the-props.doc.mjs +1 -1
- package/assets/docs/tree/check-your-docs.doc.mjs +2 -2
- package/assets/docs/tree/checks.doc.mjs +3 -3
- package/assets/docs/tree/ci-and-containers.doc.mjs +13 -0
- package/assets/docs/tree/cli-command.doc.mjs +1 -1
- package/assets/docs/tree/cli.doc.mjs +1 -1
- package/assets/docs/tree/codemods.doc.mjs +1 -1
- package/assets/docs/tree/component-family.doc.mjs +1 -1
- package/assets/docs/tree/component-imports.doc.mjs +1 -1
- package/assets/docs/tree/components.doc.mjs +1 -1
- package/assets/docs/tree/configuration.doc.mjs +1 -1
- package/assets/docs/tree/debug-and-gap-reports.doc.mjs +1 -1
- package/assets/docs/tree/define-the-theme.doc.mjs +1 -1
- package/assets/docs/tree/describe-the-component.doc.mjs +1 -1
- package/assets/docs/tree/docs.doc.mjs +1 -1
- package/assets/docs/tree/document-the-template.doc.mjs +1 -1
- package/assets/docs/tree/document-the-theme.doc.mjs +1 -1
- package/assets/docs/tree/export-template-assets.doc.mjs +1 -1
- package/assets/docs/tree/extend-or-replace.doc.mjs +1 -1
- package/assets/docs/{authoring.doc.mjs → tree/field-reference.doc.mjs} +4 -3
- package/assets/docs/tree/fonts-and-assets.doc.mjs +1 -1
- package/assets/docs/tree/generate-a-palette.doc.mjs +1 -1
- package/assets/docs/tree/global-integrations.doc.mjs +2 -2
- package/assets/docs/tree/grade-template-with-agent.doc.mjs +4 -4
- package/assets/docs/tree/help.doc.mjs +2 -2
- package/assets/docs/tree/host-entry.doc.mjs +128 -0
- package/assets/docs/tree/links.doc.mjs +1 -1
- package/assets/docs/tree/package-and-test.doc.mjs +1 -1
- package/assets/docs/tree/page-template.doc.mjs +1 -1
- package/assets/docs/tree/publishing.doc.mjs +1 -1
- package/assets/docs/tree/quick-start.doc.mjs +1 -1
- package/assets/docs/tree/replace-a-core-component.doc.mjs +1 -1
- package/assets/docs/tree/replace-a-core-template.doc.mjs +1 -1
- package/assets/docs/tree/sections-and-placement.doc.mjs +1 -1
- package/assets/docs/tree/see-it-in-an-app.doc.mjs +1 -1
- package/assets/docs/tree/ship.doc.mjs +2 -2
- package/assets/docs/tree/short-and-findable.doc.mjs +1 -1
- package/assets/docs/tree/single-component.doc.mjs +1 -1
- package/assets/docs/tree/start-a-template.doc.mjs +1 -1
- package/assets/docs/tree/subcomponent.doc.mjs +1 -1
- package/assets/docs/tree/template-assets.doc.mjs +1 -1
- package/assets/docs/tree/template-doc-overview.doc.mjs +2 -2
- package/assets/docs/tree/template-fonts.doc.mjs +1 -1
- package/assets/docs/tree/template-grading-rubric.doc.mjs +17 -38
- 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 +1 -1
- package/assets/docs/tree/templates.doc.mjs +1 -1
- package/assets/docs/tree/test-in-an-app.doc.mjs +1 -1
- package/assets/docs/tree/test-template-in-app.doc.mjs +1 -1
- package/assets/docs/tree/themes.doc.mjs +1 -1
- package/assets/docs/tree/tokens-data.doc.mjs +2 -2
- package/assets/docs/tree/troubleshooting.doc.mjs +1 -1
- package/assets/docs/tree/upgrading.doc.mjs +1 -1
- package/assets/docs/tree/use-a-theme-in-an-app.doc.mjs +1 -1
- package/assets/docs/tree/verify-packed-template.doc.mjs +1 -1
- package/assets/docs/tree/versioning.doc.mjs +5 -1
- package/assets/docs/tree/write-good-templates.doc.mjs +1 -1
- package/assets/docs/tree/write-the-template-file.doc.mjs +1 -1
- package/assets/docs/use-integrations.doc.mjs +120 -0
- package/assets/docs/working-with-ai.doc.mjs +6 -2
- package/assets/templates/themes/butter/butterTheme.ts +4 -2
- package/assets/templates/themes/stone/stoneTheme.ts +4 -2
- package/assets/templates/themes/y2k/y2kTheme.ts +4 -2
- package/authoring/debug/parse.d.mts +1 -1
- package/authoring/doctypes/schema/schema.doc.mjs +1 -1
- package/authoring/integration/integration.doc.mjs +1 -1
- package/authoring/integration/type.ts +1 -1
- package/clients/cli/bin/astryx.mjs +7 -78
- package/clients/cli/command-result-coverage.test.mjs +6 -5
- package/clients/cli/commands/build-theme.color-scheme.test.mjs +1 -1
- package/clients/cli/commands/{component/index.mjs → component.mjs} +37 -37
- package/clients/cli/commands/component.test.mjs +6 -6
- package/clients/cli/commands/docs.doc.mjs +2 -2
- package/clients/cli/commands/docs.mjs +1 -1
- package/clients/cli/commands/docs.test.mjs +14 -14
- package/clients/cli/commands/doctor-integration.test.mjs +49 -1
- package/clients/cli/commands/doctor.doc.mjs +1 -1
- package/clients/cli/commands/doctor.mjs +40 -0
- package/clients/cli/commands/doctor.test.mjs +65 -0
- package/clients/cli/commands/{hook/index.mjs → hook.mjs} +22 -22
- package/clients/cli/commands/import-hint-correctness.test.mjs +1 -1
- package/clients/cli/commands/init.behavior.test.mjs +230 -1
- package/clients/cli/commands/init.doc.mjs +28 -9
- package/clients/cli/commands/init.flag-help.test.mjs +1 -0
- package/clients/cli/commands/init.mjs +2 -2
- package/clients/cli/commands/integration-check.test.mjs +48 -0
- package/clients/cli/commands/integration.doc.mjs +1 -1
- package/clients/cli/commands/manifest.doc.mjs +1 -1
- package/clients/cli/commands/manifest.mjs +67 -0
- package/clients/cli/commands/outside-project.test.mjs +101 -1
- package/clients/cli/commands/postinstall.doc.mjs +23 -0
- package/clients/cli/commands/postinstall.mjs +31 -0
- package/clients/cli/commands/setup-nudge.test.mjs +78 -0
- package/clients/cli/commands/{build-theme.mjs → theme.mjs} +1 -1
- package/clients/cli/commands/upgrade.doc.mjs +4 -2
- package/clients/cli/commands/upgrade.mjs +9 -2
- package/clients/cli/commands/upgrade.subfolder-path.test.mjs +97 -0
- package/clients/cli/index.mjs +54 -94
- package/clients/cli/lib/component-format.mjs +1 -1
- package/clients/cli/lib/define-command.mjs +12 -7
- package/clients/cli/lib/exit-codes.test.mjs +5 -0
- package/clients/cli/lib/manifest.test.mjs +12 -0
- package/clients/cli/lib/welcome-box.mjs +50 -0
- package/clients/cli/run.mjs +108 -0
- package/foundation/agent-docs/agent-doc-state.d.mts +32 -7
- package/foundation/agent-docs/agent-doc-state.mjs +58 -9
- package/foundation/agent-docs/agent-docs.d.mts +100 -12
- package/foundation/agent-docs/agent-docs.mjs +1017 -105
- package/foundation/agent-docs/agent-docs.path-safety.test.mjs +1239 -17
- package/foundation/agent-docs/agent-docs.test.mjs +683 -39
- package/foundation/agent-docs/computer-agent-text.d.mts +39 -2
- package/foundation/agent-docs/computer-agent-text.mjs +135 -8
- package/foundation/agent-docs/computer-agent-text.test.mjs +99 -2
- package/foundation/config/project.mjs +20 -22
- package/foundation/config/project.test.mjs +56 -0
- package/foundation/discovery/authoring-self-docs.d.mts +7 -0
- package/foundation/discovery/authoring-self-docs.mjs +16 -4
- package/foundation/discovery/authoring-self-docs.test.mjs +15 -3
- package/foundation/discovery/authoring-surface.d.mts +1 -1
- package/foundation/discovery/authoring-surface.mjs +2 -2
- package/foundation/discovery/authoring-surface.test.mjs +1 -1
- package/foundation/discovery/cli-self-docs.d.mts +2 -1
- package/foundation/discovery/cli-self-docs.mjs +4 -3
- package/foundation/discovery/cli-self-docs.test.mjs +2 -2
- package/foundation/discovery/component-discovery.d.mts +37 -0
- package/foundation/discovery/component-discovery.mjs +80 -5
- package/foundation/discovery/component-loader.d.mts +1 -1
- package/foundation/discovery/docs-discovery.mjs +14 -1
- package/foundation/discovery/module-exports.d.mts +41 -0
- package/foundation/discovery/module-exports.mjs +253 -0
- package/foundation/discovery/runner-allowlist.d.mts +2 -4
- package/foundation/discovery/runner-allowlist.mjs +2 -4
- package/foundation/discovery/runner-commands.d.mts +77 -0
- package/foundation/discovery/runner-commands.mjs +294 -0
- package/foundation/discovery/runner-commands.test.mjs +128 -0
- package/foundation/discovery/stray-pointers.test.mjs +1 -1
- package/foundation/discovery/template-adapter.mjs +18 -1
- package/foundation/discovery/template-needs.mjs +1 -44
- package/foundation/discovery/theme-discovery.mjs +147 -3
- package/foundation/doc-compiler/doc-loads.test.mjs +33 -9
- package/foundation/doc-compiler/read.mjs +59 -1
- package/foundation/doc-compiler/read.test.mjs +11 -2
- package/foundation/doc-compiler/split-topic-keywords.test.mjs +3 -2
- package/foundation/doc-compiler/tree.d.mts +32 -6
- package/foundation/doc-compiler/tree.mjs +77 -43
- package/foundation/doc-compiler/tree.test.mjs +7 -7
- package/foundation/fs/paths.d.mts +70 -8
- package/foundation/fs/paths.mjs +556 -38
- package/foundation/fs/paths.test.mjs +430 -54
- package/foundation/fs/read-record.d.mts +18 -0
- package/foundation/fs/read-record.mjs +131 -0
- package/foundation/fs/read-record.test.mjs +77 -0
- package/foundation/integrations/autolink.d.mts +2 -1
- package/foundation/integrations/autolink.mjs +4 -13
- package/foundation/integrations/cli-peer-version.d.mts +36 -0
- package/foundation/integrations/cli-peer-version.mjs +132 -0
- package/foundation/integrations/cli-peer-version.test.mjs +354 -0
- package/foundation/integrations/computer-integrations.d.mts +6 -3
- package/foundation/integrations/computer-integrations.mjs +38 -9
- package/foundation/integrations/contribution-inventory.d.mts +15 -20
- package/foundation/integrations/contribution-inventory.mjs +41 -5
- package/foundation/integrations/exports-subpaths.d.mts +57 -0
- package/foundation/integrations/exports-subpaths.mjs +113 -0
- package/foundation/integrations/integrations.d.mts +27 -1
- package/foundation/integrations/integrations.mjs +61 -2
- package/foundation/integrations/public-imports.d.mts +118 -0
- package/foundation/integrations/public-imports.mjs +1769 -0
- package/foundation/integrations/public-imports.test.mjs +1793 -0
- package/foundation/integrations/validate-contributions.d.mts +115 -0
- package/foundation/integrations/validate-contributions.mjs +1168 -13
- package/foundation/release/cache.test.mjs +24 -21
- package/foundation/release/global-state.d.mts +8 -4
- package/foundation/release/global-state.mjs +4 -2
- package/foundation/release/handoff.d.mts +23 -4
- package/foundation/release/handoff.mjs +175 -8
- package/foundation/release/notices.d.mts +62 -8
- package/foundation/release/notices.mjs +136 -25
- package/foundation/release/notices.test.mjs +21 -0
- package/foundation/release/run-context.mjs +14 -0
- package/foundation/response/response-types.doc.mjs +1 -1
- package/foundation/route/route.d.mts +285 -0
- package/foundation/route/route.mjs +324 -0
- package/foundation/route/route.test.mjs +153 -0
- package/foundation/runtime/hosted-runtime.test.mjs +403 -0
- package/foundation/runtime/locked-versions.d.mts +32 -0
- package/foundation/runtime/locked-versions.mjs +65 -0
- package/foundation/runtime/package-root.d.mts +91 -0
- package/foundation/runtime/package-root.mjs +174 -0
- package/foundation/runtime/package-set.d.mts +62 -0
- package/foundation/runtime/package-set.mjs +130 -0
- package/foundation/runtime/package-set.test.mjs +432 -0
- package/foundation/text/string-utils.d.mts +2 -1
- package/foundation/text/string-utils.mjs +16 -5
- package/foundation/text/suggest-order.d.mts +72 -0
- package/foundation/text/suggest-order.mjs +363 -0
- package/foundation/text/suggest-order.test.mjs +259 -0
- package/host/door-conformance.test.mjs +274 -0
- package/host/index.d.ts +116 -0
- package/host/index.mjs +295 -0
- package/host/index.test.mjs +302 -0
- package/package.json +14 -9
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// @generated by scripts/sync-api-types.mjs from the JSDoc in foundation/**/*.mjs.
|
|
2
|
+
// DO NOT EDIT — run `pnpm sync:api-types` to regenerate.
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Each runner command in a text, as written, with the bare command to write
|
|
6
|
+
* instead, in order.
|
|
7
|
+
*
|
|
8
|
+
* @param {string} text
|
|
9
|
+
* @returns {{written: string, bare: string, download: boolean}[]}
|
|
10
|
+
*/
|
|
11
|
+
export function runnerCommandsIn(text: string): {
|
|
12
|
+
written: string;
|
|
13
|
+
bare: string;
|
|
14
|
+
download: boolean;
|
|
15
|
+
}[];
|
|
16
|
+
/**
|
|
17
|
+
* An Astryx command a topic writes with a package runner in front.
|
|
18
|
+
*
|
|
19
|
+
* @typedef {object} RunnerCommand
|
|
20
|
+
* @property {string} topic
|
|
21
|
+
* @property {string | null} section the title of the section it is in
|
|
22
|
+
* @property {string} file the doc file
|
|
23
|
+
* @property {number | null} line the file line that holds it, when found
|
|
24
|
+
* @property {string} command the command as written
|
|
25
|
+
* @property {string} message the bare command to write instead
|
|
26
|
+
*/
|
|
27
|
+
/**
|
|
28
|
+
* Runner commands in a package's topics.
|
|
29
|
+
*
|
|
30
|
+
* @param {{records: {name: string, path: string}[], guides?: {name: string, ref?: {topicFile?: string}}[]}} discovered
|
|
31
|
+
* the package's docs, as discoverIntegrationDocs returns them
|
|
32
|
+
* @returns {Promise<RunnerCommand[]>}
|
|
33
|
+
*/
|
|
34
|
+
export function findRunnerCommands(discovered: {
|
|
35
|
+
records: {
|
|
36
|
+
name: string;
|
|
37
|
+
path: string;
|
|
38
|
+
}[];
|
|
39
|
+
guides?: {
|
|
40
|
+
name: string;
|
|
41
|
+
ref?: {
|
|
42
|
+
topicFile?: string;
|
|
43
|
+
};
|
|
44
|
+
}[];
|
|
45
|
+
}): Promise<RunnerCommand[]>;
|
|
46
|
+
/**
|
|
47
|
+
* The CLI's top-level commands, the words that make a project runner's
|
|
48
|
+
* `astryx <word>` a command; runner-commands.test.mjs keeps the list equal to
|
|
49
|
+
* the command docs.
|
|
50
|
+
*/
|
|
51
|
+
export const CLI_COMMANDS: readonly string[];
|
|
52
|
+
/**
|
|
53
|
+
* An Astryx command a topic writes with a package runner in front.
|
|
54
|
+
*/
|
|
55
|
+
export type RunnerCommand = {
|
|
56
|
+
topic: string;
|
|
57
|
+
/**
|
|
58
|
+
* the title of the section it is in
|
|
59
|
+
*/
|
|
60
|
+
section: string | null;
|
|
61
|
+
/**
|
|
62
|
+
* the doc file
|
|
63
|
+
*/
|
|
64
|
+
file: string;
|
|
65
|
+
/**
|
|
66
|
+
* the file line that holds it, when found
|
|
67
|
+
*/
|
|
68
|
+
line: number | null;
|
|
69
|
+
/**
|
|
70
|
+
* the command as written
|
|
71
|
+
*/
|
|
72
|
+
command: string;
|
|
73
|
+
/**
|
|
74
|
+
* the bare command to write instead
|
|
75
|
+
*/
|
|
76
|
+
message: string;
|
|
77
|
+
};
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Runner commands in docs: an Astryx command written with a package runner in
|
|
5
|
+
* front. Docs write the bare `astryx <command>` and the CLI shows it with the
|
|
6
|
+
* reader's runner, so a written runner is wrong for every reader on another
|
|
7
|
+
* package manager.
|
|
8
|
+
*
|
|
9
|
+
* - A download runner (`npx`, `pnpm dlx`, `yarn dlx`, `bunx`, `bun x`,
|
|
10
|
+
* `npm exec`) with the unscoped name `astryx`, with or without an
|
|
11
|
+
* `@<version>` after it, always counts, even with nothing after it: it
|
|
12
|
+
* fetches an unrelated package of that name.
|
|
13
|
+
* - A project runner (`pnpm exec`, `pnpm`, `yarn`) counts when one of the
|
|
14
|
+
* CLI's commands follows on the same line (`pnpm exec astryx docs`). Named
|
|
15
|
+
* alone, or before a word that is not a command (`yarn astryx runs the
|
|
16
|
+
* copy…`), it is left alone.
|
|
17
|
+
* - A runner and what follows it are read within one line; flags between a
|
|
18
|
+
* download runner and the name are read past.
|
|
19
|
+
* - The scoped package (`npx @astryxdesign/cli init`) never counts: it is the
|
|
20
|
+
* one form that runs before the CLI is installed, for install steps.
|
|
21
|
+
* - Other tools' commands (`npx prettier`) never count.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import fs from 'node:fs';
|
|
25
|
+
|
|
26
|
+
import {loadTopicModule} from './docs-discovery.mjs';
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @param {string} file
|
|
30
|
+
* @returns {Promise<any>} the doc, or null for one that does not load (the
|
|
31
|
+
* load problem is reported elsewhere)
|
|
32
|
+
*/
|
|
33
|
+
async function loadQuietly(file) {
|
|
34
|
+
try {
|
|
35
|
+
return await loadTopicModule(file);
|
|
36
|
+
} catch {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const DOWNLOAD_RUNNERS = [
|
|
42
|
+
'pnpm dlx',
|
|
43
|
+
'yarn dlx',
|
|
44
|
+
'npm exec --',
|
|
45
|
+
'npm exec',
|
|
46
|
+
'npx',
|
|
47
|
+
'bunx',
|
|
48
|
+
'bun x',
|
|
49
|
+
];
|
|
50
|
+
const PROJECT_RUNNERS = ['pnpm exec', 'pnpm', 'yarn'];
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* The CLI's top-level commands, the words that make a project runner's
|
|
54
|
+
* `astryx <word>` a command; runner-commands.test.mjs keeps the list equal to
|
|
55
|
+
* the command docs.
|
|
56
|
+
*/
|
|
57
|
+
export const CLI_COMMANDS = Object.freeze([
|
|
58
|
+
'blog',
|
|
59
|
+
'build',
|
|
60
|
+
'component',
|
|
61
|
+
'discover',
|
|
62
|
+
'docs',
|
|
63
|
+
'doctor',
|
|
64
|
+
'gap-report',
|
|
65
|
+
'help',
|
|
66
|
+
'hook',
|
|
67
|
+
'init',
|
|
68
|
+
'integration',
|
|
69
|
+
'manifest',
|
|
70
|
+
'postinstall',
|
|
71
|
+
'search',
|
|
72
|
+
'swizzle',
|
|
73
|
+
'template',
|
|
74
|
+
'theme',
|
|
75
|
+
'upgrade',
|
|
76
|
+
]);
|
|
77
|
+
|
|
78
|
+
/** Blanks within one line. */
|
|
79
|
+
const BLANK = '[ \\t]+';
|
|
80
|
+
|
|
81
|
+
/** @param {readonly string[]} words */
|
|
82
|
+
const alternatives = words =>
|
|
83
|
+
words.map(each => each.replace(/ /g, BLANK)).join('|');
|
|
84
|
+
|
|
85
|
+
/** After `astryx`: the name ends here (a sentence's period may follow). */
|
|
86
|
+
const NAME_END = '(?![\\w@/-]|\\.\\w)';
|
|
87
|
+
|
|
88
|
+
const DOWNLOAD_COMMAND = new RegExp(
|
|
89
|
+
`(?<![\\w@/.-])(?:${alternatives(DOWNLOAD_RUNNERS)})(?:${BLANK}--?[a-z][\\w-]*)*${BLANK}astryx(?:@[\\w^~-]+(?:\\.[\\w^~-]+)*)?${NAME_END}(?:${BLANK}(${alternatives(CLI_COMMANDS)})(?![\\w-]))?`,
|
|
90
|
+
'g',
|
|
91
|
+
);
|
|
92
|
+
const PROJECT_COMMAND = new RegExp(
|
|
93
|
+
`(?<![\\w@/.-])(?:${alternatives(PROJECT_RUNNERS)})${BLANK}astryx${BLANK}(${alternatives(CLI_COMMANDS)})(?![\\w-])`,
|
|
94
|
+
'g',
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Each runner command in a text, as written, with the bare command to write
|
|
99
|
+
* instead, in order.
|
|
100
|
+
*
|
|
101
|
+
* @param {string} text
|
|
102
|
+
* @returns {{written: string, bare: string, download: boolean}[]}
|
|
103
|
+
*/
|
|
104
|
+
export function runnerCommandsIn(text) {
|
|
105
|
+
const found = [
|
|
106
|
+
...[...String(text).matchAll(DOWNLOAD_COMMAND)].map(match => ({
|
|
107
|
+
match,
|
|
108
|
+
download: true,
|
|
109
|
+
})),
|
|
110
|
+
...[...String(text).matchAll(PROJECT_COMMAND)].map(match => ({
|
|
111
|
+
match,
|
|
112
|
+
download: false,
|
|
113
|
+
})),
|
|
114
|
+
];
|
|
115
|
+
/** @type {{at: number, written: string, bare: string, download: boolean}[]} */
|
|
116
|
+
const unique = [];
|
|
117
|
+
for (const {match, download} of found) {
|
|
118
|
+
if (unique.some(each => each.at === match.index)) continue;
|
|
119
|
+
unique.push({
|
|
120
|
+
at: match.index,
|
|
121
|
+
written: match[0],
|
|
122
|
+
bare: match[1] ? `astryx ${match[1]}` : 'astryx',
|
|
123
|
+
download,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
return unique
|
|
127
|
+
.sort((a, b) => a.at - b.at)
|
|
128
|
+
.map(({written, bare, download}) => ({written, bare, download}));
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Every string in a doc, with the title of the section it is in. Code counts:
|
|
133
|
+
* most commands sit in code blocks.
|
|
134
|
+
*
|
|
135
|
+
* @param {unknown} doc
|
|
136
|
+
* @returns {{text: string, section: string | null}[]}
|
|
137
|
+
*/
|
|
138
|
+
function allStrings(doc) {
|
|
139
|
+
/** @type {{text: string, section: string | null}[]} */
|
|
140
|
+
const strings = [];
|
|
141
|
+
// A doc that refers back to itself is walked once.
|
|
142
|
+
const visited = new WeakSet();
|
|
143
|
+
/**
|
|
144
|
+
* @param {unknown} value
|
|
145
|
+
* @param {string | null} section
|
|
146
|
+
*/
|
|
147
|
+
const walk = (value, section) => {
|
|
148
|
+
if (typeof value === 'string') {
|
|
149
|
+
strings.push({text: value, section});
|
|
150
|
+
} else if (value != null && typeof value === 'object') {
|
|
151
|
+
if (visited.has(value)) return;
|
|
152
|
+
visited.add(value);
|
|
153
|
+
if (Array.isArray(value)) {
|
|
154
|
+
for (const item of value) walk(item, section);
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
const record = /** @type {Record<string, unknown>} */ (value);
|
|
158
|
+
for (const [key, field] of Object.entries(record)) {
|
|
159
|
+
if (key === 'sections' && Array.isArray(field)) {
|
|
160
|
+
for (const each of field) {
|
|
161
|
+
walk(each, typeof each?.title === 'string' ? each.title : section);
|
|
162
|
+
}
|
|
163
|
+
} else {
|
|
164
|
+
walk(field, section);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
walk(doc, null);
|
|
170
|
+
return strings;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Where a file next holds the text, at or after a cursor (a 0-based line and
|
|
175
|
+
* column), else the first place it holds it, else null. A doc's strings are
|
|
176
|
+
* read in file order, so moving the cursor past each match puts a command the
|
|
177
|
+
* topic writes twice on its own line.
|
|
178
|
+
*
|
|
179
|
+
* @param {string[]} lines
|
|
180
|
+
* @param {string} written
|
|
181
|
+
* @param {{line: number, column: number}} cursor
|
|
182
|
+
* @returns {{line: number, column: number} | null}
|
|
183
|
+
*/
|
|
184
|
+
function locate(lines, written, cursor) {
|
|
185
|
+
for (let at = cursor.line; at < lines.length; at++) {
|
|
186
|
+
const column = lines[at].indexOf(
|
|
187
|
+
written,
|
|
188
|
+
at === cursor.line ? cursor.column : 0,
|
|
189
|
+
);
|
|
190
|
+
if (column >= 0) return {line: at, column};
|
|
191
|
+
}
|
|
192
|
+
const first = lines.findIndex(line => line.includes(written));
|
|
193
|
+
return first < 0
|
|
194
|
+
? null
|
|
195
|
+
: {line: first, column: lines[first].indexOf(written)};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* An Astryx command a topic writes with a package runner in front.
|
|
200
|
+
*
|
|
201
|
+
* @typedef {object} RunnerCommand
|
|
202
|
+
* @property {string} topic
|
|
203
|
+
* @property {string | null} section the title of the section it is in
|
|
204
|
+
* @property {string} file the doc file
|
|
205
|
+
* @property {number | null} line the file line that holds it, when found
|
|
206
|
+
* @property {string} command the command as written
|
|
207
|
+
* @property {string} message the bare command to write instead
|
|
208
|
+
*/
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Runner commands in a package's topics.
|
|
212
|
+
*
|
|
213
|
+
* @param {{records: {name: string, path: string}[], guides?: {name: string, ref?: {topicFile?: string}}[]}} discovered
|
|
214
|
+
* the package's docs, as discoverIntegrationDocs returns them
|
|
215
|
+
* @returns {Promise<RunnerCommand[]>}
|
|
216
|
+
*/
|
|
217
|
+
export async function findRunnerCommands(discovered) {
|
|
218
|
+
const topics = [
|
|
219
|
+
...discovered.records.map(record => ({
|
|
220
|
+
name: record.name,
|
|
221
|
+
file: record.path,
|
|
222
|
+
})),
|
|
223
|
+
...(discovered.guides ?? []).map(guide => ({
|
|
224
|
+
name: guide.name,
|
|
225
|
+
file: guide.ref?.topicFile,
|
|
226
|
+
})),
|
|
227
|
+
];
|
|
228
|
+
/** @type {RunnerCommand[]} */
|
|
229
|
+
const found = [];
|
|
230
|
+
for (const {name, file} of topics) {
|
|
231
|
+
const doc = file ? await loadQuietly(file) : null;
|
|
232
|
+
if (!doc || !file) continue;
|
|
233
|
+
/** @type {string[]} */
|
|
234
|
+
let lines = [];
|
|
235
|
+
try {
|
|
236
|
+
lines = fs.readFileSync(file, 'utf8').split('\n');
|
|
237
|
+
} catch {
|
|
238
|
+
// The doc loaded, so only the line number is lost.
|
|
239
|
+
}
|
|
240
|
+
let cursor = {line: 0, column: 0};
|
|
241
|
+
for (const {text, section} of allStrings(doc)) {
|
|
242
|
+
const commands = runnerCommandsIn(text);
|
|
243
|
+
if (commands.length === 0) continue;
|
|
244
|
+
// A one-line string's own literal places it, wherever the walk reached
|
|
245
|
+
// it (a section in a constant above the doc object, say); else the
|
|
246
|
+
// cursor search, in file order.
|
|
247
|
+
const forms = [`'${text}'`, `"${text}"`, JSON.stringify(text)];
|
|
248
|
+
const holds = (/** @type {string} */ line) =>
|
|
249
|
+
forms.some(form => line.includes(form));
|
|
250
|
+
// On the cursor's own line, only a copy at or after the cursor counts:
|
|
251
|
+
// the copy just matched still sits before it.
|
|
252
|
+
const after = text.includes('\n')
|
|
253
|
+
? -1
|
|
254
|
+
: lines.findIndex((line, at) =>
|
|
255
|
+
at > cursor.line
|
|
256
|
+
? holds(line)
|
|
257
|
+
: at === cursor.line &&
|
|
258
|
+
forms.some(form => line.indexOf(form, cursor.column) >= 0),
|
|
259
|
+
);
|
|
260
|
+
const literal =
|
|
261
|
+
after >= 0 || text.includes('\n') ? after : lines.findIndex(holds);
|
|
262
|
+
const column = after >= 0 && after === cursor.line ? cursor.column : 0;
|
|
263
|
+
let from = literal >= 0 ? {line: literal, column} : cursor;
|
|
264
|
+
for (const {written, bare, download} of commands) {
|
|
265
|
+
const place = locate(lines, written, from);
|
|
266
|
+
if (place) {
|
|
267
|
+
from = {line: place.line, column: place.column + written.length};
|
|
268
|
+
if (
|
|
269
|
+
place.line > cursor.line ||
|
|
270
|
+
(place.line === cursor.line && place.column >= cursor.column)
|
|
271
|
+
) {
|
|
272
|
+
cursor = from;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
const line = place ? place.line + 1 : null;
|
|
276
|
+
const where = section ? ` (section "${section}")` : '';
|
|
277
|
+
found.push({
|
|
278
|
+
topic: name,
|
|
279
|
+
section,
|
|
280
|
+
file,
|
|
281
|
+
line,
|
|
282
|
+
command: written,
|
|
283
|
+
message:
|
|
284
|
+
`"${name}"${where} writes \`${written}\`${line == null ? '' : ` (line ${line})`}. ` +
|
|
285
|
+
`Write \`${bare}\`; the CLI shows it with your project's runner` +
|
|
286
|
+
(download
|
|
287
|
+
? ', and a download runner with the bare name fetches an unrelated package called astryx.'
|
|
288
|
+
: '.'),
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
return found;
|
|
294
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import {fileURLToPath} from 'node:url';
|
|
7
|
+
import {describe, expect, it} from 'vitest';
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
CLI_COMMANDS,
|
|
11
|
+
findRunnerCommands,
|
|
12
|
+
runnerCommandsIn,
|
|
13
|
+
} from './runner-commands.mjs';
|
|
14
|
+
|
|
15
|
+
const COMMAND_DOCS = path.resolve(
|
|
16
|
+
path.dirname(fileURLToPath(import.meta.url)),
|
|
17
|
+
'../../clients/cli/commands',
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
describe('runnerCommandsIn', () => {
|
|
21
|
+
it('finds an Astryx command after a project runner, with the bare command', () => {
|
|
22
|
+
expect(runnerCommandsIn('Run `pnpm exec astryx docs theme`.')).toEqual([
|
|
23
|
+
{written: 'pnpm exec astryx docs', bare: 'astryx docs', download: false},
|
|
24
|
+
]);
|
|
25
|
+
expect(runnerCommandsIn('yarn astryx component Button')).toEqual([
|
|
26
|
+
{
|
|
27
|
+
written: 'yarn astryx component',
|
|
28
|
+
bare: 'astryx component',
|
|
29
|
+
download: false,
|
|
30
|
+
},
|
|
31
|
+
]);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('always flags the unscoped name after a download runner, even alone', () => {
|
|
35
|
+
expect(runnerCommandsIn('npx astryx doctor; or `bunx astryx`')).toEqual([
|
|
36
|
+
{written: 'npx astryx doctor', bare: 'astryx doctor', download: true},
|
|
37
|
+
{written: 'bunx astryx', bare: 'astryx', download: true},
|
|
38
|
+
]);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('leaves the scoped package, a project runner named alone, and other tools', () => {
|
|
42
|
+
expect(
|
|
43
|
+
runnerCommandsIn(
|
|
44
|
+
'Install with `npx @astryxdesign/cli init`. Run it through Yarn (`yarn astryx`). Format with `npx prettier --write`. `npx astryx-tools init`.',
|
|
45
|
+
),
|
|
46
|
+
).toEqual([]);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('flags `bun x`, a versioned name, and a name that ends a sentence', () => {
|
|
50
|
+
expect(
|
|
51
|
+
runnerCommandsIn(
|
|
52
|
+
'Run `bun x astryx doctor`, `npx astryx@latest init` or `npx astryx@0.7.1 docs`. Or just npx astryx.',
|
|
53
|
+
),
|
|
54
|
+
).toEqual([
|
|
55
|
+
{written: 'bun x astryx doctor', bare: 'astryx doctor', download: true},
|
|
56
|
+
{
|
|
57
|
+
written: 'npx astryx@latest init',
|
|
58
|
+
bare: 'astryx init',
|
|
59
|
+
download: true,
|
|
60
|
+
},
|
|
61
|
+
{written: 'npx astryx@0.7.1 docs', bare: 'astryx docs', download: true},
|
|
62
|
+
{written: 'npx astryx', bare: 'astryx', download: true},
|
|
63
|
+
]);
|
|
64
|
+
expect(
|
|
65
|
+
runnerCommandsIn('Edit astryx.config, not npx astryx.config.'),
|
|
66
|
+
).toEqual([]);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('reads a runner and its command within one line, and needs a CLI command after a project runner', () => {
|
|
70
|
+
expect(
|
|
71
|
+
runnerCommandsIn(
|
|
72
|
+
'Through Yarn, yarn astryx runs the copy in node_modules.\n```\npnpm astryx\nyarn astryx\n```',
|
|
73
|
+
),
|
|
74
|
+
).toEqual([]);
|
|
75
|
+
expect(runnerCommandsIn('npx astryx runs an unrelated package')).toEqual([
|
|
76
|
+
{written: 'npx astryx', bare: 'astryx', download: true},
|
|
77
|
+
]);
|
|
78
|
+
expect(runnerCommandsIn('pnpm\nastryx docs')).toEqual([]);
|
|
79
|
+
expect(
|
|
80
|
+
runnerCommandsIn('Run `npx -y astryx doctor` or `npx --yes astryx`.'),
|
|
81
|
+
).toEqual([
|
|
82
|
+
{written: 'npx -y astryx doctor', bare: 'astryx doctor', download: true},
|
|
83
|
+
{written: 'npx --yes astryx', bare: 'astryx', download: true},
|
|
84
|
+
]);
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
describe('findRunnerCommands', () => {
|
|
89
|
+
it('reads a topic that refers back to itself once, without crashing', async () => {
|
|
90
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'runner-self-'));
|
|
91
|
+
const file = path.join(dir, 'acme-setup.doc.mjs');
|
|
92
|
+
fs.writeFileSync(
|
|
93
|
+
file,
|
|
94
|
+
[
|
|
95
|
+
'const section = {',
|
|
96
|
+
" title: 'Overview',",
|
|
97
|
+
" content: [{type: 'prose', text: 'Run `yarn astryx docs` first.'}],",
|
|
98
|
+
'};',
|
|
99
|
+
"const doc = {type: 'generic', name: 'acme-setup', sections: [section]};",
|
|
100
|
+
'section.self = section;',
|
|
101
|
+
'doc.again = doc;',
|
|
102
|
+
'export default doc;',
|
|
103
|
+
'',
|
|
104
|
+
].join('\n'),
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
const found = await findRunnerCommands({
|
|
108
|
+
records: [{name: 'acme-setup', path: file}],
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
expect(
|
|
112
|
+
found.map(({command, section, line}) => ({command, section, line})),
|
|
113
|
+
).toEqual([{command: 'yarn astryx docs', section: 'Overview', line: 3}]);
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
describe('CLI_COMMANDS', () => {
|
|
118
|
+
it('names every top-level command the CLI documents, and nothing else', () => {
|
|
119
|
+
const documented = fs
|
|
120
|
+
.readdirSync(COMMAND_DOCS)
|
|
121
|
+
.filter(file => file.endsWith('.doc.mjs'))
|
|
122
|
+
.map(file => file.slice(0, -'.doc.mjs'.length))
|
|
123
|
+
.filter(name => !name.includes('-') || name === 'gap-report');
|
|
124
|
+
expect([...CLI_COMMANDS].sort()).toEqual(
|
|
125
|
+
[...new Set([...documented, 'help'])].sort(),
|
|
126
|
+
);
|
|
127
|
+
});
|
|
128
|
+
});
|
|
@@ -16,7 +16,7 @@ describe('pointersIn', () => {
|
|
|
16
16
|
it('leaves a walkthrough alone: more words, a flag, or a route', () => {
|
|
17
17
|
expect(
|
|
18
18
|
pointersIn(
|
|
19
|
-
'Run `astryx docs theme overview`, `astryx docs --json`, `astryx docs
|
|
19
|
+
'Run `astryx docs theme overview`, `astryx docs --json`, `astryx docs authoring/quick-start`, or `astryx docs`.',
|
|
20
20
|
),
|
|
21
21
|
).toEqual([]);
|
|
22
22
|
});
|
|
@@ -1192,6 +1192,23 @@ function reportSamePackageReplacements(pkg, autolinked, templates, errors) {
|
|
|
1192
1192
|
}
|
|
1193
1193
|
}
|
|
1194
1194
|
|
|
1195
|
+
/**
|
|
1196
|
+
* The fix for a template doc with no same-stem `.tsx`: rename a same-stem
|
|
1197
|
+
* source written in another extension, or add the `.tsx`.
|
|
1198
|
+
* @param {string} docPath
|
|
1199
|
+
* @param {string} sourcePath the `.tsx` it needs
|
|
1200
|
+
*/
|
|
1201
|
+
function missingTemplateSourceFix(docPath, sourcePath) {
|
|
1202
|
+
const source = path.basename(sourcePath);
|
|
1203
|
+
const stem = source.slice(0, -'.tsx'.length);
|
|
1204
|
+
const other = ['.jsx', '.js', '.ts', '.mjs']
|
|
1205
|
+
.map(extension => `${stem}${extension}`)
|
|
1206
|
+
.find(file => fs.existsSync(path.join(path.dirname(sourcePath), file)));
|
|
1207
|
+
return other
|
|
1208
|
+
? `Found ${other}, but a template source must be a .tsx file. Fix: rename ${other} to ${source}.`
|
|
1209
|
+
: `Fix: add ${source} beside ${path.basename(docPath)}, with the template as its default export.`;
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1195
1212
|
/**
|
|
1196
1213
|
* Discover the templates contributed by a SINGLE integration. Same per-template
|
|
1197
1214
|
* rules as {@link discoverIntegrationTemplates} (same-stem source required,
|
|
@@ -1225,7 +1242,7 @@ export async function discoverIntegrationTemplatesForOne(integration) {
|
|
|
1225
1242
|
errors.push({
|
|
1226
1243
|
package: pkgLabel,
|
|
1227
1244
|
template: id,
|
|
1228
|
-
message: `Template "${id}" is missing its same-stem source file ${path.basename(sourcePath)}
|
|
1245
|
+
message: `Template "${id}" is missing its same-stem source file ${path.basename(sourcePath)}. ${missingTemplateSourceFix(docPath, sourcePath)}`,
|
|
1229
1246
|
});
|
|
1230
1247
|
// A replacement this unusable template declares still counts, so its
|
|
1231
1248
|
// target fails closed instead of going to a sibling.
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
import * as fs from 'node:fs';
|
|
19
19
|
import * as path from 'node:path';
|
|
20
20
|
import module from 'node:module';
|
|
21
|
-
import {CLI_ROOT} from '../fs/paths.mjs';
|
|
21
|
+
import {CLI_ROOT, findWorkspaceRoot} from '../fs/paths.mjs';
|
|
22
22
|
import {detectStylingSystem} from '../agent-docs/agent-docs.mjs';
|
|
23
23
|
import {
|
|
24
24
|
decidePackageManagerIn,
|
|
@@ -71,49 +71,6 @@ const ADD_VERB = {
|
|
|
71
71
|
npm: 'npm install',
|
|
72
72
|
};
|
|
73
73
|
|
|
74
|
-
/**
|
|
75
|
-
* Whether a package.json declares a workspace (`workspaces` field).
|
|
76
|
-
* @param {string} dir
|
|
77
|
-
* @returns {{exists: boolean, declaresWorkspace: boolean}}
|
|
78
|
-
*/
|
|
79
|
-
function readPackageJson(dir) {
|
|
80
|
-
const pkgPath = path.join(dir, 'package.json');
|
|
81
|
-
if (!fs.existsSync(pkgPath)) return {exists: false, declaresWorkspace: false};
|
|
82
|
-
try {
|
|
83
|
-
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
84
|
-
return {exists: true, declaresWorkspace: pkg.workspaces != null};
|
|
85
|
-
} catch {
|
|
86
|
-
return {exists: true, declaresWorkspace: false};
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* The workspace root (AST-048 FR15): the nearest directory at or above the
|
|
92
|
-
* target that declares a workspace, by `pnpm-workspace.yaml` or a package.json
|
|
93
|
-
* `workspaces` field; outside a workspace, the nearest package.json's
|
|
94
|
-
* directory; with no package.json, the target itself.
|
|
95
|
-
* @param {string} target
|
|
96
|
-
* @returns {string}
|
|
97
|
-
*/
|
|
98
|
-
function findWorkspaceRoot(target) {
|
|
99
|
-
let dir = path.resolve(target);
|
|
100
|
-
/** @type {string | null} */
|
|
101
|
-
let nearestPackage = null;
|
|
102
|
-
for (;;) {
|
|
103
|
-
const pkg = readPackageJson(dir);
|
|
104
|
-
if (
|
|
105
|
-
fs.existsSync(path.join(dir, 'pnpm-workspace.yaml')) ||
|
|
106
|
-
pkg.declaresWorkspace
|
|
107
|
-
) {
|
|
108
|
-
return dir;
|
|
109
|
-
}
|
|
110
|
-
if (pkg.exists && nearestPackage == null) nearestPackage = dir;
|
|
111
|
-
const parent = path.dirname(dir);
|
|
112
|
-
if (parent === dir) return nearestPackage ?? path.resolve(target);
|
|
113
|
-
dir = parent;
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
74
|
/**
|
|
118
75
|
* The project's package manager for an install command (AST-048 FR17, FR19):
|
|
119
76
|
* the nearest directory from the target up to the workspace root that has a
|