@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
|
@@ -51,6 +51,8 @@ import {
|
|
|
51
51
|
validateInstalledIntegration,
|
|
52
52
|
validateLocalIntegration,
|
|
53
53
|
} from './validate-integration.mjs';
|
|
54
|
+
import {rootProblem} from '../../foundation/integrations/validate-contributions.mjs';
|
|
55
|
+
import {normalizeProviderId} from '../../foundation/identity/provider-identity.mjs';
|
|
54
56
|
|
|
55
57
|
/** @param {string} value */
|
|
56
58
|
export function shellArg(value) {
|
|
@@ -58,15 +60,61 @@ export function shellArg(value) {
|
|
|
58
60
|
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
59
61
|
}
|
|
60
62
|
|
|
63
|
+
/**
|
|
64
|
+
* The provider ID a package without a name is checked under: a valid bare
|
|
65
|
+
* package name no published package can hold (npm names are lowercase).
|
|
66
|
+
*/
|
|
67
|
+
const UNNAMED_PROVIDER = 'astryx-unnamed-local-package';
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* A message as the package sees it: the stand-in provider ID gone, its docs
|
|
71
|
+
* named by their path in the package.
|
|
72
|
+
* @param {string} message
|
|
73
|
+
*/
|
|
74
|
+
function unnamedShown(message) {
|
|
75
|
+
return message
|
|
76
|
+
.split(`${UNNAMED_PROVIDER}/`)
|
|
77
|
+
.join('')
|
|
78
|
+
.split(UNNAMED_PROVIDER)
|
|
79
|
+
.join('this package');
|
|
80
|
+
}
|
|
81
|
+
|
|
61
82
|
/**
|
|
62
83
|
* Resolve one local or installed integration for an authoring check.
|
|
63
84
|
* @param {string | undefined} pkg
|
|
64
85
|
* @param {string} cwd
|
|
65
86
|
*/
|
|
66
87
|
async function resolveIntegration(pkg, cwd) {
|
|
67
|
-
|
|
68
|
-
? validateInstalledIntegration(pkg, cwd)
|
|
69
|
-
: validateLocalIntegration(cwd);
|
|
88
|
+
const resolved = pkg
|
|
89
|
+
? await validateInstalledIntegration(pkg, cwd)
|
|
90
|
+
: await validateLocalIntegration(cwd);
|
|
91
|
+
const integration = resolved.found ? resolved.integration : undefined;
|
|
92
|
+
if (!integration) return resolved;
|
|
93
|
+
// A root that is not a readable folder is reported in the issues; the
|
|
94
|
+
// checks read nothing from it rather than fail on it.
|
|
95
|
+
/** @type {typeof integration & {__unnamed?: boolean}} */
|
|
96
|
+
const usable = {...integration};
|
|
97
|
+
// The provider ID readers key the package on: the manifest's providerId,
|
|
98
|
+
// else its name. A package with neither (validate warned about it) is
|
|
99
|
+
// still checked; the docs check names its docs under a stand-in ID.
|
|
100
|
+
try {
|
|
101
|
+
usable.providerId = normalizeProviderId(
|
|
102
|
+
integration.__declaredProviderId ?? integration.name,
|
|
103
|
+
);
|
|
104
|
+
} catch {
|
|
105
|
+
usable.__unnamed = true;
|
|
106
|
+
}
|
|
107
|
+
for (const kind of /** @type {const} */ ([
|
|
108
|
+
'components',
|
|
109
|
+
'templates',
|
|
110
|
+
'codemods',
|
|
111
|
+
'docs',
|
|
112
|
+
'themes',
|
|
113
|
+
])) {
|
|
114
|
+
const root = usable[kind];
|
|
115
|
+
if (root != null && rootProblem(root)) usable[kind] = undefined;
|
|
116
|
+
}
|
|
117
|
+
return {...resolved, integration: usable};
|
|
70
118
|
}
|
|
71
119
|
|
|
72
120
|
/**
|
|
@@ -387,8 +435,20 @@ export async function integrationDocConflicts(pkg, options = {}) {
|
|
|
387
435
|
};
|
|
388
436
|
}
|
|
389
437
|
|
|
438
|
+
// The docs tree names a package's docs by its provider ID, which a
|
|
439
|
+
// package.json without a name does not give: its docs are checked under a
|
|
440
|
+
// stand-in ID, which no message names.
|
|
441
|
+
const unnamed = /** @type {{__unnamed?: boolean}} */ (resolved.integration)
|
|
442
|
+
.__unnamed;
|
|
443
|
+
const docsIntegration = unnamed
|
|
444
|
+
? {
|
|
445
|
+
...resolved.integration,
|
|
446
|
+
name: UNNAMED_PROVIDER,
|
|
447
|
+
providerId: UNNAMED_PROVIDER,
|
|
448
|
+
}
|
|
449
|
+
: resolved.integration;
|
|
390
450
|
const [discovered, builtinTopics] = await Promise.all([
|
|
391
|
-
discoverIntegrationDocs(
|
|
451
|
+
discoverIntegrationDocs(docsIntegration),
|
|
392
452
|
Promise.resolve(discoverBuiltinTopics()),
|
|
393
453
|
]);
|
|
394
454
|
const {records, errors} = discovered;
|
|
@@ -458,27 +518,40 @@ export async function integrationDocConflicts(pkg, options = {}) {
|
|
|
458
518
|
// its docs (spec:AST-046, spec:AST-047).
|
|
459
519
|
if (errors.length === 0) {
|
|
460
520
|
for (const {severity, message} of await packageDocsProblems(
|
|
461
|
-
/** @type {{name: string}} */ (
|
|
521
|
+
/** @type {{name: string}} */ (docsIntegration),
|
|
462
522
|
discovered,
|
|
463
523
|
)) {
|
|
464
|
-
issues.push({
|
|
524
|
+
issues.push({
|
|
525
|
+
code: 'invalid_doc_graph',
|
|
526
|
+
severity,
|
|
527
|
+
message: unnamedShown(message),
|
|
528
|
+
});
|
|
465
529
|
}
|
|
466
530
|
|
|
467
531
|
// A reference block includes content rather than linking to it, so one
|
|
468
532
|
// that cannot include what it names loses that content for every reader:
|
|
469
533
|
// an error, where a link that names no doc still prints as written.
|
|
470
534
|
for (const message of await packageReferenceProblems(
|
|
471
|
-
/** @type {{name: string}} */ (
|
|
535
|
+
/** @type {{name: string}} */ (docsIntegration),
|
|
472
536
|
discovered,
|
|
473
537
|
)) {
|
|
474
|
-
issues.push({
|
|
538
|
+
issues.push({
|
|
539
|
+
code: 'invalid_doc_reference',
|
|
540
|
+
severity: 'error',
|
|
541
|
+
message: unnamedShown(message),
|
|
542
|
+
});
|
|
475
543
|
}
|
|
476
544
|
|
|
477
545
|
// One fact has one owner: a passage two topics repeat goes stale in one
|
|
478
546
|
// of them. Each is listed and is a warning (architecture:cli-surface
|
|
479
547
|
// DEC-8), so `integration check --strict` fails until it is fixed;
|
|
480
548
|
// `doctor integration docs` keeps its exit code, as it fails on errors.
|
|
481
|
-
copiedPassages.push(
|
|
549
|
+
copiedPassages.push(
|
|
550
|
+
...(await integrationCopiedPassages(discovered)).map(passage => ({
|
|
551
|
+
...passage,
|
|
552
|
+
message: unnamedShown(passage.message),
|
|
553
|
+
})),
|
|
554
|
+
);
|
|
482
555
|
for (const {message} of copiedPassages) {
|
|
483
556
|
issues.push({code: 'copied_passage', severity: 'warning', message});
|
|
484
557
|
}
|
|
@@ -488,6 +561,7 @@ export async function integrationDocConflicts(pkg, options = {}) {
|
|
|
488
561
|
...(await integrationFluff(discovered)).map(each => ({
|
|
489
562
|
...each,
|
|
490
563
|
file: fromPackage(each.file),
|
|
564
|
+
message: unnamedShown(each.message),
|
|
491
565
|
})),
|
|
492
566
|
);
|
|
493
567
|
for (const {message} of fluff) {
|
|
@@ -14,6 +14,7 @@ vi.mock('../../foundation/discovery/template-conflict-release.mjs', () => ({
|
|
|
14
14
|
}));
|
|
15
15
|
import * as fs from 'node:fs';
|
|
16
16
|
import * as path from 'node:path';
|
|
17
|
+
import {validateIntegration} from './validate-integration.mjs';
|
|
17
18
|
import {discoverCoreTemplates} from '../../foundation/discovery/template-adapter.mjs';
|
|
18
19
|
import {discoverOwnedComponents} from '../../foundation/discovery/component-discovery.mjs';
|
|
19
20
|
import {discoverBuiltinTopics} from '../../foundation/discovery/docs-discovery.mjs';
|
|
@@ -1331,3 +1332,222 @@ describe('links in component docs and topic descriptions', () => {
|
|
|
1331
1332
|
]);
|
|
1332
1333
|
});
|
|
1333
1334
|
});
|
|
1335
|
+
|
|
1336
|
+
describe('validateIntegration runner-command notes', () => {
|
|
1337
|
+
/** The runner-command notes, read back into their parts. */
|
|
1338
|
+
async function runnerNotes() {
|
|
1339
|
+
const {data} = await validateIntegration(undefined, {cwd: tmpDir});
|
|
1340
|
+
const notes = (data.notes ?? [])
|
|
1341
|
+
.filter((/** @type {any} */ note) => note.code === 'runner_command')
|
|
1342
|
+
.map((/** @type {any} */ note) => {
|
|
1343
|
+
const m =
|
|
1344
|
+
/^(\S+): "([^"]+)"(?: \(section "([^"]+)"\))? writes `([^`]+)`(?: \(line (\d+)\))?\./.exec(
|
|
1345
|
+
note.message,
|
|
1346
|
+
);
|
|
1347
|
+
return {
|
|
1348
|
+
file: m?.[1],
|
|
1349
|
+
topic: m?.[2],
|
|
1350
|
+
section: m?.[3] ?? null,
|
|
1351
|
+
command: m?.[4],
|
|
1352
|
+
line: m?.[5] ? Number(m[5]) : null,
|
|
1353
|
+
message: note.message,
|
|
1354
|
+
};
|
|
1355
|
+
});
|
|
1356
|
+
return {data, notes};
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1359
|
+
function writeTopic(/** @type {string} */ text) {
|
|
1360
|
+
fs.writeFileSync(
|
|
1361
|
+
path.join(tmpDir, 'package.json'),
|
|
1362
|
+
JSON.stringify({name: '@acme/widgets', version: '1.2.3'}),
|
|
1363
|
+
);
|
|
1364
|
+
fs.writeFileSync(
|
|
1365
|
+
path.join(tmpDir, 'astryx.integration.mjs'),
|
|
1366
|
+
`export default {docs: './docs'};\n`,
|
|
1367
|
+
);
|
|
1368
|
+
fs.mkdirSync(path.join(tmpDir, 'docs'), {recursive: true});
|
|
1369
|
+
const doc = {
|
|
1370
|
+
type: 'generic',
|
|
1371
|
+
name: 'acme-setup',
|
|
1372
|
+
title: 'Fixture',
|
|
1373
|
+
description: 'Fixture topic.',
|
|
1374
|
+
sections: [
|
|
1375
|
+
{
|
|
1376
|
+
title: 'Overview',
|
|
1377
|
+
content: [
|
|
1378
|
+
{type: 'prose', text},
|
|
1379
|
+
{type: 'code', lang: 'bash', code: 'npx @astryxdesign/cli init'},
|
|
1380
|
+
],
|
|
1381
|
+
},
|
|
1382
|
+
],
|
|
1383
|
+
};
|
|
1384
|
+
fs.writeFileSync(
|
|
1385
|
+
path.join(tmpDir, 'docs', 'acme-setup.doc.mjs'),
|
|
1386
|
+
`export default ${JSON.stringify(doc)};\n`,
|
|
1387
|
+
);
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
it('lists a runner command with its line and fix, and raises no issue', async () => {
|
|
1391
|
+
writeTopic('Check it with `pnpm exec astryx doctor` first.');
|
|
1392
|
+
|
|
1393
|
+
const {data, notes} = await runnerNotes();
|
|
1394
|
+
const [hit, ...rest] = notes;
|
|
1395
|
+
|
|
1396
|
+
expect(rest).toEqual([]);
|
|
1397
|
+
expect(hit).toMatchObject({
|
|
1398
|
+
topic: 'acme-setup',
|
|
1399
|
+
section: 'Overview',
|
|
1400
|
+
file: 'docs/acme-setup.doc.mjs',
|
|
1401
|
+
line: 1,
|
|
1402
|
+
command: 'pnpm exec astryx doctor',
|
|
1403
|
+
});
|
|
1404
|
+
expect(hit.message).toContain('Write `astryx doctor`');
|
|
1405
|
+
// A note only: no issue, so no exit changes on it.
|
|
1406
|
+
expect(
|
|
1407
|
+
data.issues.map((/** @type {any} */ issue) => issue.code),
|
|
1408
|
+
).not.toContain('runner_command');
|
|
1409
|
+
});
|
|
1410
|
+
|
|
1411
|
+
it('puts each copy of a repeated command on its own line', async () => {
|
|
1412
|
+
fs.writeFileSync(
|
|
1413
|
+
path.join(tmpDir, 'package.json'),
|
|
1414
|
+
JSON.stringify({name: '@acme/widgets', version: '1.2.3'}),
|
|
1415
|
+
);
|
|
1416
|
+
fs.writeFileSync(
|
|
1417
|
+
path.join(tmpDir, 'astryx.integration.mjs'),
|
|
1418
|
+
`export default {docs: './docs'};\n`,
|
|
1419
|
+
);
|
|
1420
|
+
fs.mkdirSync(path.join(tmpDir, 'docs'), {recursive: true});
|
|
1421
|
+
const doc = {
|
|
1422
|
+
type: 'generic',
|
|
1423
|
+
name: 'acme-setup',
|
|
1424
|
+
title: 'Fixture',
|
|
1425
|
+
description: 'Fixture topic.',
|
|
1426
|
+
sections: [
|
|
1427
|
+
{
|
|
1428
|
+
title: 'First',
|
|
1429
|
+
content: [{type: 'prose', text: 'Run `npx astryx doctor`.'}],
|
|
1430
|
+
},
|
|
1431
|
+
{
|
|
1432
|
+
title: 'Second',
|
|
1433
|
+
content: [{type: 'prose', text: 'Run `npx astryx doctor` again.'}],
|
|
1434
|
+
},
|
|
1435
|
+
{
|
|
1436
|
+
title: 'Third',
|
|
1437
|
+
content: [{type: 'prose', text: 'Or plain `npx astryx`.'}],
|
|
1438
|
+
},
|
|
1439
|
+
],
|
|
1440
|
+
};
|
|
1441
|
+
fs.writeFileSync(
|
|
1442
|
+
path.join(tmpDir, 'docs', 'acme-setup.doc.mjs'),
|
|
1443
|
+
`export default ${JSON.stringify(doc, null, 2)};\n`,
|
|
1444
|
+
);
|
|
1445
|
+
const lines = fs
|
|
1446
|
+
.readFileSync(path.join(tmpDir, 'docs', 'acme-setup.doc.mjs'), 'utf8')
|
|
1447
|
+
.split('\n');
|
|
1448
|
+
const lineHolding = (/** @type {string} */ text) =>
|
|
1449
|
+
lines.findIndex(line => line.includes(text)) + 1;
|
|
1450
|
+
|
|
1451
|
+
const {notes} = await runnerNotes();
|
|
1452
|
+
|
|
1453
|
+
expect(
|
|
1454
|
+
notes.map((/** @type {any} */ each) => [
|
|
1455
|
+
each.section,
|
|
1456
|
+
each.line,
|
|
1457
|
+
each.command,
|
|
1458
|
+
]),
|
|
1459
|
+
).toEqual([
|
|
1460
|
+
['First', lineHolding('Run `npx astryx doctor`.'), 'npx astryx doctor'],
|
|
1461
|
+
['Second', lineHolding('again.'), 'npx astryx doctor'],
|
|
1462
|
+
['Third', lineHolding('Or plain'), 'npx astryx'],
|
|
1463
|
+
]);
|
|
1464
|
+
});
|
|
1465
|
+
|
|
1466
|
+
it('puts each copy of an identical one-line string on its own line', async () => {
|
|
1467
|
+
fs.writeFileSync(
|
|
1468
|
+
path.join(tmpDir, 'package.json'),
|
|
1469
|
+
JSON.stringify({name: '@acme/widgets', version: '1.2.3'}),
|
|
1470
|
+
);
|
|
1471
|
+
fs.writeFileSync(
|
|
1472
|
+
path.join(tmpDir, 'astryx.integration.mjs'),
|
|
1473
|
+
`export default {docs: './docs'};\n`,
|
|
1474
|
+
);
|
|
1475
|
+
fs.mkdirSync(path.join(tmpDir, 'docs'), {recursive: true});
|
|
1476
|
+
const block = {type: 'code', lang: 'bash', code: 'npx astryx doctor'};
|
|
1477
|
+
const doc = {
|
|
1478
|
+
type: 'generic',
|
|
1479
|
+
name: 'acme-setup',
|
|
1480
|
+
title: 'Fixture',
|
|
1481
|
+
description: 'Fixture topic.',
|
|
1482
|
+
sections: [
|
|
1483
|
+
{title: 'First', content: [block]},
|
|
1484
|
+
{title: 'Second', content: [block]},
|
|
1485
|
+
],
|
|
1486
|
+
};
|
|
1487
|
+
fs.writeFileSync(
|
|
1488
|
+
path.join(tmpDir, 'docs', 'acme-setup.doc.mjs'),
|
|
1489
|
+
`export default ${JSON.stringify(doc, null, 2)};\n`,
|
|
1490
|
+
);
|
|
1491
|
+
const lines = fs
|
|
1492
|
+
.readFileSync(path.join(tmpDir, 'docs', 'acme-setup.doc.mjs'), 'utf8')
|
|
1493
|
+
.split('\n');
|
|
1494
|
+
const copies = lines.flatMap((line, at) =>
|
|
1495
|
+
line.includes('"npx astryx doctor"') ? [at + 1] : [],
|
|
1496
|
+
);
|
|
1497
|
+
|
|
1498
|
+
const {notes} = await runnerNotes();
|
|
1499
|
+
|
|
1500
|
+
expect(copies).toHaveLength(2);
|
|
1501
|
+
expect(
|
|
1502
|
+
notes.map((/** @type {any} */ each) => [each.section, each.line]),
|
|
1503
|
+
).toEqual([
|
|
1504
|
+
['First', copies[0]],
|
|
1505
|
+
['Second', copies[1]],
|
|
1506
|
+
]);
|
|
1507
|
+
});
|
|
1508
|
+
|
|
1509
|
+
it("places a section's command on its own line when the section sits in a constant above the doc", async () => {
|
|
1510
|
+
fs.writeFileSync(
|
|
1511
|
+
path.join(tmpDir, 'package.json'),
|
|
1512
|
+
JSON.stringify({name: '@acme/widgets', version: '1.2.3'}),
|
|
1513
|
+
);
|
|
1514
|
+
fs.writeFileSync(
|
|
1515
|
+
path.join(tmpDir, 'astryx.integration.mjs'),
|
|
1516
|
+
`export default {docs: './docs'};\n`,
|
|
1517
|
+
);
|
|
1518
|
+
fs.mkdirSync(path.join(tmpDir, 'docs'), {recursive: true});
|
|
1519
|
+
fs.writeFileSync(
|
|
1520
|
+
path.join(tmpDir, 'docs', 'acme-setup.doc.mjs'),
|
|
1521
|
+
[
|
|
1522
|
+
"const install = {title: 'Install', content: [{type: 'prose', text: 'Run `npx astryx doctor`.'}]};",
|
|
1523
|
+
'',
|
|
1524
|
+
'export default {',
|
|
1525
|
+
" type: 'generic',",
|
|
1526
|
+
" name: 'acme-setup',",
|
|
1527
|
+
" title: 'Fixture',",
|
|
1528
|
+
" description: 'Install first: `npx astryx doctor`.',",
|
|
1529
|
+
' sections: [install],',
|
|
1530
|
+
'};',
|
|
1531
|
+
'',
|
|
1532
|
+
].join('\n'),
|
|
1533
|
+
);
|
|
1534
|
+
|
|
1535
|
+
const {notes} = await runnerNotes();
|
|
1536
|
+
|
|
1537
|
+
expect(
|
|
1538
|
+
notes.map((/** @type {any} */ each) => [each.section, each.line]),
|
|
1539
|
+
).toEqual(
|
|
1540
|
+
expect.arrayContaining([
|
|
1541
|
+
[null, 7],
|
|
1542
|
+
['Install', 1],
|
|
1543
|
+
]),
|
|
1544
|
+
);
|
|
1545
|
+
});
|
|
1546
|
+
|
|
1547
|
+
it('lists nothing for bare commands and the scoped install step', async () => {
|
|
1548
|
+
writeTopic('Check it with `astryx doctor` first.');
|
|
1549
|
+
|
|
1550
|
+
const {notes} = await runnerNotes();
|
|
1551
|
+
expect((await runnerNotes()).notes).toEqual([]);
|
|
1552
|
+
});
|
|
1553
|
+
});
|
|
@@ -26,6 +26,11 @@ import {fileURLToPath} from 'node:url';
|
|
|
26
26
|
import {spawnSync} from 'node:child_process';
|
|
27
27
|
import {assertWithin} from '../../foundation/fs/path-safety.mjs';
|
|
28
28
|
import {resolvePackageDir} from '../../foundation/integrations/integrations.mjs';
|
|
29
|
+
import {
|
|
30
|
+
literalThemeName,
|
|
31
|
+
rootProblem,
|
|
32
|
+
symlinkedFolder,
|
|
33
|
+
} from '../../foundation/integrations/validate-contributions.mjs';
|
|
29
34
|
import {
|
|
30
35
|
componentReplacesCliProblem,
|
|
31
36
|
docsTreeCliProblem,
|
|
@@ -953,6 +958,10 @@ async function validatePackedThemeExports(
|
|
|
953
958
|
);
|
|
954
959
|
}
|
|
955
960
|
|
|
961
|
+
// The name the source gives defineTheme, when the file says it: the
|
|
962
|
+
// build names its files after it.
|
|
963
|
+
const builtName = literalThemeName(source, localTheme.exportName);
|
|
964
|
+
|
|
956
965
|
const declaredOutputs = declaredThemeBuildOutputs(
|
|
957
966
|
localIntegration,
|
|
958
967
|
localTheme,
|
|
@@ -1097,7 +1106,15 @@ async function validatePackedThemeExports(
|
|
|
1097
1106
|
? `Theme "${localTheme.slug}" module is not the module \`${formatCliCommand(`theme build ${sourceRelative}`, localIntegration.__packageDir)}\` writes and does not only wrap it: ${localWrapper.reason}. A module that wraps the built module may only import the theme's own stylesheet and font stylesheet for their side effects and re-export from the built module either everything or only its theme, as ${localTheme.exportName}.`
|
|
1098
1107
|
: localWrapper && !localWrapper.ok
|
|
1099
1108
|
? `Theme "${localTheme.slug}" module re-exports ${localWrapper.reason}, which does not match ${sourceRelative}. Run \`${formatCliCommand(`theme build ${sourceRelative}`, localIntegration.__packageDir)}\` before packing.`
|
|
1100
|
-
:
|
|
1109
|
+
: (renamedOutputMessage(
|
|
1110
|
+
localTheme,
|
|
1111
|
+
output.kind,
|
|
1112
|
+
output.local,
|
|
1113
|
+
source,
|
|
1114
|
+
builtName,
|
|
1115
|
+
localIntegration.__packageDir,
|
|
1116
|
+
) ??
|
|
1117
|
+
`Theme "${localTheme.slug}" ${output.kind} does not match ${sourceRelative}. Run \`${formatCliCommand(`theme build ${sourceRelative}`, localIntegration.__packageDir)}\` before packing.`),
|
|
1101
1118
|
),
|
|
1102
1119
|
);
|
|
1103
1120
|
}
|
|
@@ -1265,7 +1282,17 @@ export async function integrationPackCheck(options = {}) {
|
|
|
1265
1282
|
// ── Phase 1: structure validation ──
|
|
1266
1283
|
const localResult = await validateLocalIntegration(cwd);
|
|
1267
1284
|
/** @type {Issue[]} */
|
|
1268
|
-
const issues = [
|
|
1285
|
+
const issues = [
|
|
1286
|
+
...localResult.issues,
|
|
1287
|
+
// Validate lists these layout findings without counting them. A
|
|
1288
|
+
// package.json npm cannot pack is what fails the pack here, as it did
|
|
1289
|
+
// before they were named; the rest stay warnings, which fail nothing.
|
|
1290
|
+
...(localResult.found ? (localResult.listed ?? []) : []).map(issue =>
|
|
1291
|
+
issue.code === 'invalid_package_json'
|
|
1292
|
+
? {...issue, severity: /** @type {const} */ ('error')}
|
|
1293
|
+
: issue,
|
|
1294
|
+
),
|
|
1295
|
+
];
|
|
1269
1296
|
|
|
1270
1297
|
if (!localResult.found || !localResult.integration) {
|
|
1271
1298
|
return emptyReceipt(pkgName, [
|
|
@@ -1314,6 +1341,8 @@ export async function integrationPackCheck(options = {}) {
|
|
|
1314
1341
|
);
|
|
1315
1342
|
}
|
|
1316
1343
|
for (const root of fileInv.roots) {
|
|
1344
|
+
// A root that is not a readable folder has its own issue.
|
|
1345
|
+
if (rootProblem(/** @type {string} */ (loaded[root.kind]))) continue;
|
|
1317
1346
|
if (root.files.length === 0) {
|
|
1318
1347
|
issues.push(
|
|
1319
1348
|
error(
|
|
@@ -1403,6 +1432,9 @@ export async function integrationPackCheck(options = {}) {
|
|
|
1403
1432
|
);
|
|
1404
1433
|
/** @type {string|null} */
|
|
1405
1434
|
let scratchBase = null;
|
|
1435
|
+
/** @type {string|undefined} the packed copy of the package */
|
|
1436
|
+
let extractDir;
|
|
1437
|
+
const issuesBeforePack = issues.length;
|
|
1406
1438
|
|
|
1407
1439
|
try {
|
|
1408
1440
|
// npm pack
|
|
@@ -1451,11 +1483,16 @@ export async function integrationPackCheck(options = {}) {
|
|
|
1451
1483
|
for (const root of fileInv.roots) {
|
|
1452
1484
|
const missing = root.files.filter(f => !packResult.packedPaths.has(f));
|
|
1453
1485
|
if (missing.length === root.files.length && root.files.length > 0) {
|
|
1486
|
+
const absolute = /** @type {string} */ (loaded[root.kind]);
|
|
1487
|
+
const link = symlinkedFolder(absolute, packageDir);
|
|
1454
1488
|
issues.push(
|
|
1455
1489
|
error(
|
|
1456
1490
|
'root_not_packed',
|
|
1457
1491
|
`Declared ${root.kind} root "${root.path}" has 0 of ${root.files.length} expected ` +
|
|
1458
|
-
`files in the pack list.
|
|
1492
|
+
`files in the pack list. ` +
|
|
1493
|
+
(link
|
|
1494
|
+
? `It is reached through the symlink "${path.relative(packageDir, link.path).split(path.sep).join('/')}", and npm pack does not follow a symlinked folder. Fix: replace the link with the folder itself, or point "${root.kind}" at the folder it links to.`
|
|
1495
|
+
: `Add "${root.path.replace(/^\.\//, '')}" to "files" in package.json.`),
|
|
1459
1496
|
),
|
|
1460
1497
|
);
|
|
1461
1498
|
} else {
|
|
@@ -1494,7 +1531,6 @@ export async function integrationPackCheck(options = {}) {
|
|
|
1494
1531
|
}
|
|
1495
1532
|
|
|
1496
1533
|
// ── Phase 3: extract + identity comparison ──
|
|
1497
|
-
let extractDir;
|
|
1498
1534
|
try {
|
|
1499
1535
|
extractDir = resolvePackageDir(pkgName, scratchBase);
|
|
1500
1536
|
} catch (err) {
|
|
@@ -1616,11 +1652,94 @@ export async function integrationPackCheck(options = {}) {
|
|
|
1616
1652
|
issues,
|
|
1617
1653
|
);
|
|
1618
1654
|
} finally {
|
|
1655
|
+
// The packed copy lives in a scratch folder that is gone once this
|
|
1656
|
+
// returns: name its files the way the package does.
|
|
1657
|
+
if (extractDir && scratchBase) {
|
|
1658
|
+
packageRelative(issues.slice(issuesBeforePack), extractDir, scratchBase);
|
|
1659
|
+
}
|
|
1619
1660
|
removeTreeBestEffort(tgzTmpDir, 'temporary tarball directory', issues);
|
|
1620
1661
|
removeTreeBestEffort(scratchBase, 'scratch consumer', issues);
|
|
1621
1662
|
}
|
|
1622
1663
|
}
|
|
1623
1664
|
|
|
1665
|
+
/**
|
|
1666
|
+
* When a theme's source names it something other than the file the package
|
|
1667
|
+
* exports for it, a rebuild writes new files under that name and leaves the
|
|
1668
|
+
* exported one stale forever: say so, with both ways out. Null otherwise.
|
|
1669
|
+
* @param {{slug: string}} theme
|
|
1670
|
+
* @param {string} kind 'module' or 'stylesheet'
|
|
1671
|
+
* @param {string} exported the file the package exports for it
|
|
1672
|
+
* @param {string} source the theme's source
|
|
1673
|
+
* @param {string | null} builtName the literal defineTheme name, if any
|
|
1674
|
+
* @param {string} packageDir
|
|
1675
|
+
* @returns {string | null}
|
|
1676
|
+
*/
|
|
1677
|
+
function renamedOutputMessage(
|
|
1678
|
+
theme,
|
|
1679
|
+
kind,
|
|
1680
|
+
exported,
|
|
1681
|
+
source,
|
|
1682
|
+
builtName,
|
|
1683
|
+
packageDir,
|
|
1684
|
+
) {
|
|
1685
|
+
if (builtName == null || builtName === theme.slug) return null;
|
|
1686
|
+
const extension = kind === 'module' ? '.js' : '.css';
|
|
1687
|
+
if (path.basename(exported) === `${builtName}${extension}`) return null;
|
|
1688
|
+
const rel = (/** @type {string} */ file) =>
|
|
1689
|
+
path.relative(packageDir, file).split(path.sep).join('/');
|
|
1690
|
+
const built = rel(
|
|
1691
|
+
path.join(path.dirname(source), `${builtName}${extension}`),
|
|
1692
|
+
);
|
|
1693
|
+
return `Theme "${theme.slug}" ${kind} is ${rel(exported)}, but ${path.basename(source)} calls defineTheme with name: '${builtName}', so \`${formatCliCommand(`theme build ${rel(source)}`, packageDir)}\` writes ${built} instead and rebuilding never updates ${path.basename(exported)}. Fix: point the package export that serves it at ${built} after building, or set name: '${theme.slug}' in ${path.basename(source)} and rebuild.`;
|
|
1694
|
+
}
|
|
1695
|
+
|
|
1696
|
+
/**
|
|
1697
|
+
* Rewrite the scratch copy's absolute paths in each issue's message: a file
|
|
1698
|
+
* of the packed package as its path in the package, and any other path in the
|
|
1699
|
+
* scratch consumer (such as the module that resolved an import) as that
|
|
1700
|
+
* consumer. Both folders are gone once verify returns.
|
|
1701
|
+
* @param {Issue[]} issues
|
|
1702
|
+
* @param {string} packedDir the packed package inside the scratch consumer
|
|
1703
|
+
* @param {string} consumerDir the scratch consumer
|
|
1704
|
+
*/
|
|
1705
|
+
function packageRelative(issues, packedDir, consumerDir) {
|
|
1706
|
+
/** @param {string} dir */
|
|
1707
|
+
const spellings = dir => {
|
|
1708
|
+
const out = [dir];
|
|
1709
|
+
try {
|
|
1710
|
+
const real = fs.realpathSync(dir);
|
|
1711
|
+
if (real !== dir) out.push(real);
|
|
1712
|
+
} catch {
|
|
1713
|
+
// Already removed: the plain path is all there is.
|
|
1714
|
+
}
|
|
1715
|
+
return out;
|
|
1716
|
+
};
|
|
1717
|
+
/** @param {string} text */
|
|
1718
|
+
const escape = text => text.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&');
|
|
1719
|
+
const packed = spellings(packedDir);
|
|
1720
|
+
const consumer = spellings(consumerDir);
|
|
1721
|
+
for (const issue of issues) {
|
|
1722
|
+
if (typeof issue.message !== 'string') continue;
|
|
1723
|
+
let message = issue.message;
|
|
1724
|
+
for (const prefix of packed) {
|
|
1725
|
+
message = message
|
|
1726
|
+
.split(`${prefix}${path.sep}`)
|
|
1727
|
+
.join('')
|
|
1728
|
+
.replace(
|
|
1729
|
+
new RegExp(`${escape(prefix)}(?![\\w-]|\\.\\w)`, 'gu'),
|
|
1730
|
+
'the package root',
|
|
1731
|
+
);
|
|
1732
|
+
}
|
|
1733
|
+
for (const prefix of consumer) {
|
|
1734
|
+
message = message.replace(
|
|
1735
|
+
new RegExp(`${escape(prefix)}[^\\s"'\`]*`, 'gu'),
|
|
1736
|
+
'a consumer project',
|
|
1737
|
+
);
|
|
1738
|
+
}
|
|
1739
|
+
issue.message = message;
|
|
1740
|
+
}
|
|
1741
|
+
}
|
|
1742
|
+
|
|
1624
1743
|
/**
|
|
1625
1744
|
* Assemble the receipt.
|
|
1626
1745
|
* @param {string} name
|
|
@@ -580,6 +580,24 @@ describe('a theme module that loads its own styles', () => {
|
|
|
580
580
|
expect(cli.status, cli.stderr).toBe(0);
|
|
581
581
|
});
|
|
582
582
|
|
|
583
|
+
it('says a rebuild cannot fix exports that name the theme after its folder once its source renames it', async () => {
|
|
584
|
+
writePackage({
|
|
585
|
+
files: ['astryx.integration.mjs', 'themes'],
|
|
586
|
+
themeExports: true,
|
|
587
|
+
});
|
|
588
|
+
fs.writeFileSync(
|
|
589
|
+
path.join(tmpDir, 'themes', 'ocean', 'oceanTheme.ts'),
|
|
590
|
+
"import {defineTheme} from '@astryxdesign/core/theme';\n\nexport const oceanTheme = defineTheme({name: 'sea'});\n",
|
|
591
|
+
);
|
|
592
|
+
|
|
593
|
+
const result = await integrationPackCheck({cwd: tmpDir});
|
|
594
|
+
|
|
595
|
+
expect(result.data.issues.map(issue => issue.message)).toEqual([
|
|
596
|
+
`Theme "ocean" module is themes/ocean/ocean.js, but oceanTheme.ts calls defineTheme with name: 'sea', so \`${formatCliCommand('theme build themes/ocean/oceanTheme.ts', tmpDir)}\` writes themes/ocean/sea.js instead and rebuilding never updates ocean.js. Fix: point the package export that serves it at themes/ocean/sea.js after building, or set name: 'ocean' in oceanTheme.ts and rebuild.`,
|
|
597
|
+
`Theme "ocean" stylesheet is themes/ocean/ocean.css, but oceanTheme.ts calls defineTheme with name: 'sea', so \`${formatCliCommand('theme build themes/ocean/oceanTheme.ts', tmpDir)}\` writes themes/ocean/sea.css instead and rebuilding never updates ocean.css. Fix: point the package export that serves it at themes/ocean/sea.css after building, or set name: 'ocean' in oceanTheme.ts and rebuild.`,
|
|
598
|
+
]);
|
|
599
|
+
});
|
|
600
|
+
|
|
583
601
|
it('compares a partial export set with the module named after the theme', async () => {
|
|
584
602
|
writeRenamedOcean();
|
|
585
603
|
setThemeExports({'./themes/ocean': './themes/ocean/deep-sea.js'});
|
|
@@ -1919,6 +1937,75 @@ describe('integrationPackCheck', () => {
|
|
|
1919
1937
|
expect(sourceWarnings[0].message).toContain('stray.tsx');
|
|
1920
1938
|
});
|
|
1921
1939
|
|
|
1940
|
+
it('says npm pack skips a symlinked root instead of asking for a "files" entry', async () => {
|
|
1941
|
+
writePackage({
|
|
1942
|
+
manifest:
|
|
1943
|
+
"export default {themes: './themes', components: './components'};\n",
|
|
1944
|
+
});
|
|
1945
|
+
const real = path.join(tmpDir, 'real-components');
|
|
1946
|
+
fs.mkdirSync(real, {recursive: true});
|
|
1947
|
+
fs.writeFileSync(
|
|
1948
|
+
path.join(real, 'Card.doc.mjs'),
|
|
1949
|
+
"export default {type: 'component', name: 'Card', displayName: 'Card', usage: {description: 'Card.'}, props: []};\n",
|
|
1950
|
+
);
|
|
1951
|
+
fs.writeFileSync(
|
|
1952
|
+
path.join(real, 'Card.tsx'),
|
|
1953
|
+
'export function Card() { return null; }\n',
|
|
1954
|
+
);
|
|
1955
|
+
fs.symlinkSync('real-components', path.join(tmpDir, 'components'));
|
|
1956
|
+
|
|
1957
|
+
const result = await integrationPackCheck({cwd: tmpDir});
|
|
1958
|
+
const codes = result.data.issues.map(issue => issue.code);
|
|
1959
|
+
expect(codes).toContain('symlinked_root');
|
|
1960
|
+
const notPacked = result.data.issues.find(
|
|
1961
|
+
issue => issue.code === 'root_not_packed',
|
|
1962
|
+
);
|
|
1963
|
+
expect(notPacked?.message).toBe(
|
|
1964
|
+
'Declared components root "components" has 0 of 2 expected files in the pack list. It is reached through the symlink "components", and npm pack does not follow a symlinked folder. Fix: replace the link with the folder itself, or point "components" at the folder it links to.',
|
|
1965
|
+
);
|
|
1966
|
+
});
|
|
1967
|
+
|
|
1968
|
+
it('names packed files by their path in the package, never the scratch copy', async () => {
|
|
1969
|
+
writePackage({
|
|
1970
|
+
manifest:
|
|
1971
|
+
"export default {themes: './themes', docs: './docs/a.doc.mjs'};\n",
|
|
1972
|
+
});
|
|
1973
|
+
fs.mkdirSync(path.join(tmpDir, 'docs'), {recursive: true});
|
|
1974
|
+
fs.writeFileSync(path.join(tmpDir, 'docs', 'a.doc.mjs'), '');
|
|
1975
|
+
|
|
1976
|
+
const result = await integrationPackCheck({cwd: tmpDir});
|
|
1977
|
+
const messages = result.data.issues.map(issue => issue.message);
|
|
1978
|
+
expect(messages.join('\n')).not.toContain(
|
|
1979
|
+
path.join(tmpDir, '.astryx-pack-check-'),
|
|
1980
|
+
);
|
|
1981
|
+
expect(messages).toContain(
|
|
1982
|
+
'Packed: Declared docs root is a file, not a folder: docs/a.doc.mjs. Fix: point "docs" in the manifest at the folder that holds them.',
|
|
1983
|
+
);
|
|
1984
|
+
// The root's own issue covers it: no empty-root error, no raw errno.
|
|
1985
|
+
const codes = result.data.issues.map(issue => issue.code);
|
|
1986
|
+
expect(codes).not.toContain('empty_contribution_root');
|
|
1987
|
+
expect(codes).not.toContain('local_discovery_failed');
|
|
1988
|
+
expect(codes).not.toContain('packed_discovery_failed');
|
|
1989
|
+
});
|
|
1990
|
+
|
|
1991
|
+
it('fails on a package.json npm cannot pack, which validate only warns about', async () => {
|
|
1992
|
+
writePackage({files: ['astryx.integration.mjs', 'themes']});
|
|
1993
|
+
const packageFile = path.join(tmpDir, 'package.json');
|
|
1994
|
+
const pkg = JSON.parse(fs.readFileSync(packageFile, 'utf-8'));
|
|
1995
|
+
delete pkg.name;
|
|
1996
|
+
fs.writeFileSync(packageFile, `${JSON.stringify(pkg, null, 2)}\n`);
|
|
1997
|
+
|
|
1998
|
+
const result = await integrationPackCheck({cwd: tmpDir});
|
|
1999
|
+
|
|
2000
|
+
expect(result.data.packable).toBe(false);
|
|
2001
|
+
expect(
|
|
2002
|
+
result.data.issues.map(issue => [issue.code, issue.severity]),
|
|
2003
|
+
).toEqual([
|
|
2004
|
+
['invalid_package_json', 'error'],
|
|
2005
|
+
['pack_failed', 'error'],
|
|
2006
|
+
]);
|
|
2007
|
+
});
|
|
2008
|
+
|
|
1922
2009
|
it('cleans up its scratch consumer when npm pack fails', async () => {
|
|
1923
2010
|
writePackage({
|
|
1924
2011
|
files: ['astryx.integration.mjs', 'themes'],
|