@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
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file Tests for read-record: which paths an operation read, and whether
|
|
5
|
+
* any changed since, including a change made while the operation ran.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {describe, it, expect} from 'vitest';
|
|
9
|
+
import * as fs from 'node:fs';
|
|
10
|
+
import * as os from 'node:os';
|
|
11
|
+
import * as path from 'node:path';
|
|
12
|
+
import {recordReads, unchangedSince} from './read-record.mjs';
|
|
13
|
+
|
|
14
|
+
function scratch() {
|
|
15
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'astryx-read-record-'));
|
|
16
|
+
const file = path.join(dir, 'doc.txt');
|
|
17
|
+
fs.writeFileSync(file, 'one');
|
|
18
|
+
return {dir, file};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
describe('recordReads', () => {
|
|
22
|
+
it('records a read file and a listed folder, and sees them unchanged', () => {
|
|
23
|
+
const {dir, file} = scratch();
|
|
24
|
+
/** @type {Map<string, string>} */
|
|
25
|
+
const seen = new Map();
|
|
26
|
+
recordReads(seen, () => {
|
|
27
|
+
fs.readFileSync(file, 'utf8');
|
|
28
|
+
fs.readdirSync(dir);
|
|
29
|
+
});
|
|
30
|
+
expect([...seen.keys()]).toEqual(expect.arrayContaining([file, dir]));
|
|
31
|
+
expect(unchangedSince(seen)).toBe(true);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('sees an edit made after the operation', () => {
|
|
35
|
+
const {file} = scratch();
|
|
36
|
+
/** @type {Map<string, string>} */
|
|
37
|
+
const seen = new Map();
|
|
38
|
+
recordReads(seen, () => fs.readFileSync(file, 'utf8'));
|
|
39
|
+
const later = new Date(Date.now() + 5_000);
|
|
40
|
+
fs.writeFileSync(file, 'two, longer');
|
|
41
|
+
fs.utimesSync(file, later, later);
|
|
42
|
+
expect(unchangedSince(seen)).toBe(false);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('sees an edit made while the operation ran, after the file was read', () => {
|
|
46
|
+
const {file} = scratch();
|
|
47
|
+
/** @type {Map<string, string>} */
|
|
48
|
+
const seen = new Map();
|
|
49
|
+
recordReads(seen, () => {
|
|
50
|
+
fs.readFileSync(file, 'utf8');
|
|
51
|
+
const later = new Date(Date.now() + 5_000);
|
|
52
|
+
fs.writeFileSync(file, 'edited mid-run');
|
|
53
|
+
fs.utimesSync(file, later, later);
|
|
54
|
+
});
|
|
55
|
+
expect(unchangedSince(seen)).toBe(false);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('sees a file added to a folder it probed', () => {
|
|
59
|
+
const {dir} = scratch();
|
|
60
|
+
/** @type {Map<string, string>} */
|
|
61
|
+
const seen = new Map();
|
|
62
|
+
recordReads(seen, () => fs.existsSync(path.join(dir, 'missing.txt')));
|
|
63
|
+
const later = new Date(Date.now() + 5_000);
|
|
64
|
+
fs.writeFileSync(path.join(dir, 'missing.txt'), 'now here');
|
|
65
|
+
fs.utimesSync(dir, later, later);
|
|
66
|
+
expect(unchangedSince(seen)).toBe(false);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('records nothing outside an operation', () => {
|
|
70
|
+
const {file} = scratch();
|
|
71
|
+
/** @type {Map<string, string>} */
|
|
72
|
+
const seen = new Map();
|
|
73
|
+
recordReads(seen, () => {});
|
|
74
|
+
fs.readFileSync(file, 'utf8');
|
|
75
|
+
expect(seen.size).toBe(0);
|
|
76
|
+
});
|
|
77
|
+
});
|
|
@@ -21,7 +21,8 @@ export function readDeclaredDependencies(projectDir: string): Array<{
|
|
|
21
21
|
*
|
|
22
22
|
* Walking up matters because hoisting is normal — yarn and npm workspaces lift
|
|
23
23
|
* a workspace member's dependency to the repo root, so the app's own
|
|
24
|
-
* node_modules can be empty of a package it genuinely declares.
|
|
24
|
+
* node_modules can be empty of a package it genuinely declares. Integrations
|
|
25
|
+
* named in astryx.config resolve through the same walk.
|
|
25
26
|
*
|
|
26
27
|
* Presence is decided by the package's own package.json, not the directory, so
|
|
27
28
|
* a stale empty directory is not mistaken for an install.
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
|
|
40
40
|
import * as fs from 'node:fs';
|
|
41
41
|
import * as path from 'node:path';
|
|
42
|
+
import {findNodeModulesPackage} from '../fs/paths.mjs';
|
|
42
43
|
import {findManifestPaths, loadIntegrations} from './integrations.mjs';
|
|
43
44
|
import {resolveProviders} from './provider-resolution.mjs';
|
|
44
45
|
|
|
@@ -123,7 +124,8 @@ export function readDeclaredDependencies(projectDir) {
|
|
|
123
124
|
*
|
|
124
125
|
* Walking up matters because hoisting is normal — yarn and npm workspaces lift
|
|
125
126
|
* a workspace member's dependency to the repo root, so the app's own
|
|
126
|
-
* node_modules can be empty of a package it genuinely declares.
|
|
127
|
+
* node_modules can be empty of a package it genuinely declares. Integrations
|
|
128
|
+
* named in astryx.config resolve through the same walk.
|
|
127
129
|
*
|
|
128
130
|
* Presence is decided by the package's own package.json, not the directory, so
|
|
129
131
|
* a stale empty directory is not mistaken for an install.
|
|
@@ -135,18 +137,7 @@ export function readDeclaredDependencies(projectDir) {
|
|
|
135
137
|
*/
|
|
136
138
|
export function resolveInstalledPackageDir(name, fromDir) {
|
|
137
139
|
if (!isBarePackageName(name)) return null;
|
|
138
|
-
|
|
139
|
-
let dir = path.resolve(fromDir);
|
|
140
|
-
for (let i = 0; i < 50; i++) {
|
|
141
|
-
const packageDir = path.join(dir, 'node_modules', ...segments);
|
|
142
|
-
if (fs.existsSync(path.join(packageDir, 'package.json'))) {
|
|
143
|
-
return {packageDir, hostDir: dir};
|
|
144
|
-
}
|
|
145
|
-
const parent = path.dirname(dir);
|
|
146
|
-
if (parent === dir) break;
|
|
147
|
-
dir = parent;
|
|
148
|
-
}
|
|
149
|
-
return null;
|
|
140
|
+
return findNodeModulesPackage(fromDir, name);
|
|
150
141
|
}
|
|
151
142
|
|
|
152
143
|
/**
|
|
@@ -0,0 +1,36 @@
|
|
|
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
|
+
* The running CLI version.
|
|
6
|
+
* @returns {string}
|
|
7
|
+
*/
|
|
8
|
+
export function cliVersion(): string;
|
|
9
|
+
/**
|
|
10
|
+
* Why the running CLI is outside an integration's declared
|
|
11
|
+
* `@astryxdesign/cli` peer range, or null when it is inside it (or when no
|
|
12
|
+
* peer is declared or the package.json cannot be read).
|
|
13
|
+
*
|
|
14
|
+
* A CLI below the range is told to upgrade Core and the CLI together, with
|
|
15
|
+
* the project's own install command, and then to run the upgrade codemods.
|
|
16
|
+
* A CLI above it (the range has an upper bound, such as `^0.6.0`) is
|
|
17
|
+
* told to look for a release of the package that supports it, since
|
|
18
|
+
* upgrading the CLI cannot help there; the author of the package in the
|
|
19
|
+
* current folder is told to test with this CLI and widen the range.
|
|
20
|
+
*
|
|
21
|
+
* @param {import('./integrations.mjs').LoadedIntegration} integration
|
|
22
|
+
* @param {string} [version] the running CLI version; defaults to this CLI's
|
|
23
|
+
* own package.json version
|
|
24
|
+
* @param {string} [cwd] where the install command would run; decides the
|
|
25
|
+
* package manager it names
|
|
26
|
+
* @param {{own?: boolean, from?: string}} [options] `own`: the package is
|
|
27
|
+
* the one being authored in the current folder, so above the range its
|
|
28
|
+
* author is told to test with this CLI and widen the range instead; `from`:
|
|
29
|
+
* the version the project's upgrade starts from when it is not the running
|
|
30
|
+
* CLI's, such as the Core of a project with no CLI of its own
|
|
31
|
+
* @returns {string | null}
|
|
32
|
+
*/
|
|
33
|
+
export function cliPeerVersionProblem(integration: import("./integrations.mjs").LoadedIntegration, version?: string, cwd?: string, { own, from }?: {
|
|
34
|
+
own?: boolean;
|
|
35
|
+
from?: string;
|
|
36
|
+
}): string | null;
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file Warn when the running CLI is outside an integration's declared peer
|
|
5
|
+
* range.
|
|
6
|
+
*
|
|
7
|
+
* An integration published against a newer CLI declares
|
|
8
|
+
* `"@astryxdesign/cli": ">=X.Y.Z"` in peerDependencies. When the running CLI
|
|
9
|
+
* does not satisfy that range, the integration's newer contributions may not
|
|
10
|
+
* load correctly. This module detects that mismatch and says why, naming the
|
|
11
|
+
* package, the range, the running version, and what to do.
|
|
12
|
+
*
|
|
13
|
+
* @input A loaded integration (with __packageDir) and the running CLI version.
|
|
14
|
+
* @output The message, or null when the CLI is inside the range.
|
|
15
|
+
* @position foundation/integrations — read by api/integration/validate-integration,
|
|
16
|
+
* which reports it as a `cli_peer_version` note in `doctor integration
|
|
17
|
+
* validate` and `integration check`, never a warning, so it fails nothing
|
|
18
|
+
* (architecture:cli-surface INV35).
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import * as fs from 'node:fs';
|
|
22
|
+
import * as path from 'node:path';
|
|
23
|
+
import {satisfiesRange, semverCompare} from '../env/semver.mjs';
|
|
24
|
+
import {
|
|
25
|
+
detectPackageManager,
|
|
26
|
+
getCliInvocation,
|
|
27
|
+
} from '../env/package-manager.mjs';
|
|
28
|
+
import {CLI_PACKAGE, lowestAdmitted} from './cli-requirement.mjs';
|
|
29
|
+
|
|
30
|
+
/** The command that adds a package, by package manager. */
|
|
31
|
+
const ADD_VERB = /** @type {Record<string, string>} */ ({
|
|
32
|
+
pnpm: 'pnpm add',
|
|
33
|
+
yarn: 'yarn add',
|
|
34
|
+
bun: 'bun add',
|
|
35
|
+
npm: 'npm install',
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Read the CLI's own version from its package.json.
|
|
40
|
+
* @returns {string}
|
|
41
|
+
*/
|
|
42
|
+
function readCliVersion() {
|
|
43
|
+
const cliPkg = path.resolve(import.meta.dirname, '..', '..', 'package.json');
|
|
44
|
+
return JSON.parse(fs.readFileSync(cliPkg, 'utf8')).version;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Cached CLI version, read once. @type {string | undefined} */
|
|
48
|
+
let _cliVersion;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* The running CLI version.
|
|
52
|
+
* @returns {string}
|
|
53
|
+
*/
|
|
54
|
+
export function cliVersion() {
|
|
55
|
+
if (_cliVersion == null) _cliVersion = readCliVersion();
|
|
56
|
+
return _cliVersion;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Why the running CLI is outside an integration's declared
|
|
61
|
+
* `@astryxdesign/cli` peer range, or null when it is inside it (or when no
|
|
62
|
+
* peer is declared or the package.json cannot be read).
|
|
63
|
+
*
|
|
64
|
+
* A CLI below the range is told to upgrade Core and the CLI together, with
|
|
65
|
+
* the project's own install command, and then to run the upgrade codemods.
|
|
66
|
+
* A CLI above it (the range has an upper bound, such as `^0.6.0`) is
|
|
67
|
+
* told to look for a release of the package that supports it, since
|
|
68
|
+
* upgrading the CLI cannot help there; the author of the package in the
|
|
69
|
+
* current folder is told to test with this CLI and widen the range.
|
|
70
|
+
*
|
|
71
|
+
* @param {import('./integrations.mjs').LoadedIntegration} integration
|
|
72
|
+
* @param {string} [version] the running CLI version; defaults to this CLI's
|
|
73
|
+
* own package.json version
|
|
74
|
+
* @param {string} [cwd] where the install command would run; decides the
|
|
75
|
+
* package manager it names
|
|
76
|
+
* @param {{own?: boolean, from?: string}} [options] `own`: the package is
|
|
77
|
+
* the one being authored in the current folder, so above the range its
|
|
78
|
+
* author is told to test with this CLI and widen the range instead; `from`:
|
|
79
|
+
* the version the project's upgrade starts from when it is not the running
|
|
80
|
+
* CLI's, such as the Core of a project with no CLI of its own
|
|
81
|
+
* @returns {string | null}
|
|
82
|
+
*/
|
|
83
|
+
export function cliPeerVersionProblem(
|
|
84
|
+
integration,
|
|
85
|
+
version,
|
|
86
|
+
cwd = process.cwd(),
|
|
87
|
+
{own = false, from} = {},
|
|
88
|
+
) {
|
|
89
|
+
const ver = version ?? cliVersion();
|
|
90
|
+
if (!integration.__packageDir) return null;
|
|
91
|
+
|
|
92
|
+
let pkg;
|
|
93
|
+
try {
|
|
94
|
+
pkg = JSON.parse(
|
|
95
|
+
fs.readFileSync(path.join(integration.__packageDir, 'package.json'), 'utf8'),
|
|
96
|
+
);
|
|
97
|
+
} catch {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const range = pkg?.peerDependencies?.[CLI_PACKAGE];
|
|
102
|
+
if (typeof range !== 'string') return null;
|
|
103
|
+
if (satisfiesRange(ver, range)) return null;
|
|
104
|
+
|
|
105
|
+
const name = integration.name ?? pkg.name ?? '(integration)';
|
|
106
|
+
const lowest = lowestAdmitted(range);
|
|
107
|
+
if (lowest != null && semverCompare(ver, lowest) < 0) {
|
|
108
|
+
// Core and the CLI move together, then the upgrade codemods run, as the
|
|
109
|
+
// project-upgrade notice does: moving only the CLI would leave it ahead of
|
|
110
|
+
// Core. A package's own folder holds them as dev dependencies.
|
|
111
|
+
const verb = ADD_VERB[detectPackageManager(cwd)] ?? ADD_VERB.npm;
|
|
112
|
+
const install = `${verb}${own ? ' -D' : ''} @astryxdesign/core@latest ${CLI_PACKAGE}@latest`;
|
|
113
|
+
const upgrade = `${getCliInvocation(cwd, {installed: true})} upgrade --from ${from ?? ver} --apply`;
|
|
114
|
+
return (
|
|
115
|
+
`${name} needs ${CLI_PACKAGE} ${range}, but this CLI is ${ver}, so ` +
|
|
116
|
+
`some of what it ships may not load. Upgrade Core and the CLI ` +
|
|
117
|
+
`together: \`${install}\`, then \`${upgrade}\`.`
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
if (own) {
|
|
121
|
+
return (
|
|
122
|
+
`${name} declares ${CLI_PACKAGE} ${range}, but this CLI is ${ver}, ` +
|
|
123
|
+
`outside that range. Test the package with this CLI, and if it works, ` +
|
|
124
|
+
`widen the ${CLI_PACKAGE} range in its peerDependencies.`
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
return (
|
|
128
|
+
`${name} supports ${CLI_PACKAGE} ${range}, but this CLI is ${ver}, ` +
|
|
129
|
+
`outside that range, so some of what it ships may not work as its ` +
|
|
130
|
+
`author tested. Look for a release of ${name} that supports ${ver}.`
|
|
131
|
+
);
|
|
132
|
+
}
|
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file Tests for the CLI peer version warning.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {describe, it, expect, beforeAll, afterAll} from 'vitest';
|
|
8
|
+
import * as fs from 'node:fs';
|
|
9
|
+
import * as os from 'node:os';
|
|
10
|
+
import * as path from 'node:path';
|
|
11
|
+
import {cliPeerVersionProblem, cliVersion} from './cli-peer-version.mjs';
|
|
12
|
+
import {runCli} from '../../test-utils/run-cli.mjs';
|
|
13
|
+
|
|
14
|
+
/** @type {string} */
|
|
15
|
+
let tmpDir;
|
|
16
|
+
|
|
17
|
+
beforeAll(() => {
|
|
18
|
+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'astryx-cli-peer-'));
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
afterAll(() => {
|
|
22
|
+
fs.rmSync(tmpDir, {recursive: true, force: true});
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @param {string} name
|
|
27
|
+
* @param {Record<string, unknown>} pkgJson
|
|
28
|
+
*/
|
|
29
|
+
function makeIntegration(name, pkgJson) {
|
|
30
|
+
const dir = path.join(tmpDir, name.replace(/[/@]/g, '_'));
|
|
31
|
+
fs.mkdirSync(dir, {recursive: true});
|
|
32
|
+
fs.writeFileSync(path.join(dir, 'package.json'), JSON.stringify(pkgJson));
|
|
33
|
+
return {name, version: String(pkgJson.version ?? '1.0.0'), __packageDir: dir};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
describe('cliPeerVersionProblem', () => {
|
|
37
|
+
it('returns null when no @astryxdesign/cli peer is declared', () => {
|
|
38
|
+
const integration = makeIntegration('@test/no-peer', {
|
|
39
|
+
name: '@test/no-peer',
|
|
40
|
+
version: '1.0.0',
|
|
41
|
+
});
|
|
42
|
+
expect(cliPeerVersionProblem(integration, '0.6.5', tmpDir)).toBeNull();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('returns null when the CLI satisfies the peer range', () => {
|
|
46
|
+
const integration = makeIntegration('@test/satisfied', {
|
|
47
|
+
name: '@test/satisfied',
|
|
48
|
+
version: '1.0.0',
|
|
49
|
+
peerDependencies: {'@astryxdesign/cli': '>=0.6.0'},
|
|
50
|
+
});
|
|
51
|
+
expect(cliPeerVersionProblem(integration, '0.6.5', tmpDir)).toBeNull();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('warns when the CLI is below the peer range', () => {
|
|
55
|
+
const integration = makeIntegration('@test/needs-newer', {
|
|
56
|
+
name: '@test/needs-newer',
|
|
57
|
+
version: '2.0.0',
|
|
58
|
+
peerDependencies: {'@astryxdesign/cli': '>=0.8.0'},
|
|
59
|
+
});
|
|
60
|
+
const project = path.join(tmpDir, 'npm-project');
|
|
61
|
+
fs.mkdirSync(project, {recursive: true});
|
|
62
|
+
fs.writeFileSync(path.join(project, 'package-lock.json'), '{}\n');
|
|
63
|
+
expect(cliPeerVersionProblem(integration, '0.6.5', project)).toBe(
|
|
64
|
+
'@test/needs-newer needs @astryxdesign/cli >=0.8.0, but this CLI is 0.6.5, so some of what it ships may not load. Upgrade Core and the CLI together: `npm install @astryxdesign/core@latest @astryxdesign/cli@latest`, then `npx astryx upgrade --from 0.6.5 --apply`.',
|
|
65
|
+
);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("names the project's own package manager in the upgrade command", () => {
|
|
69
|
+
const project = path.join(tmpDir, 'pnpm-project');
|
|
70
|
+
fs.mkdirSync(project, {recursive: true});
|
|
71
|
+
fs.writeFileSync(path.join(project, 'pnpm-lock.yaml'), "lockfileVersion: '9.0'\n");
|
|
72
|
+
const integration = makeIntegration('@test/pnpm', {
|
|
73
|
+
name: '@test/pnpm',
|
|
74
|
+
version: '1.0.0',
|
|
75
|
+
peerDependencies: {'@astryxdesign/cli': '>=0.8.0'},
|
|
76
|
+
});
|
|
77
|
+
expect(cliPeerVersionProblem(integration, '0.6.5', project)).toContain(
|
|
78
|
+
'`pnpm add @astryxdesign/core@latest @astryxdesign/cli@latest`, then `pnpm exec astryx upgrade --from 0.6.5 --apply`',
|
|
79
|
+
);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('starts the upgrade from the version it is given, such as a Core-only project\'s Core', () => {
|
|
83
|
+
const project = path.join(tmpDir, 'from-npm-project');
|
|
84
|
+
fs.mkdirSync(project, {recursive: true});
|
|
85
|
+
fs.writeFileSync(path.join(project, 'package-lock.json'), '{}\n');
|
|
86
|
+
const integration = makeIntegration('@test/from', {
|
|
87
|
+
name: '@test/from',
|
|
88
|
+
version: '1.0.0',
|
|
89
|
+
peerDependencies: {'@astryxdesign/cli': '>=0.8.0'},
|
|
90
|
+
});
|
|
91
|
+
expect(
|
|
92
|
+
cliPeerVersionProblem(integration, '0.7.3', project, {from: '0.6.0'}),
|
|
93
|
+
).toContain('upgrade --from 0.6.0 --apply');
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('installs them as dev dependencies in the package being authored', () => {
|
|
97
|
+
const project = path.join(tmpDir, 'own-npm-project');
|
|
98
|
+
fs.mkdirSync(project, {recursive: true});
|
|
99
|
+
fs.writeFileSync(path.join(project, 'package-lock.json'), '{}\n');
|
|
100
|
+
const integration = makeIntegration('@test/own-below', {
|
|
101
|
+
name: '@test/own-below',
|
|
102
|
+
version: '1.0.0',
|
|
103
|
+
peerDependencies: {'@astryxdesign/cli': '>=0.8.0'},
|
|
104
|
+
});
|
|
105
|
+
expect(
|
|
106
|
+
cliPeerVersionProblem(integration, '0.6.5', project, {own: true}),
|
|
107
|
+
).toContain(
|
|
108
|
+
'`npm install -D @astryxdesign/core@latest @astryxdesign/cli@latest`, then `npx astryx upgrade --from 0.6.5 --apply`',
|
|
109
|
+
);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('does not ask for an upgrade when the CLI is newer than the range', () => {
|
|
113
|
+
const integration = makeIntegration('@test/older', {
|
|
114
|
+
name: '@test/older',
|
|
115
|
+
version: '1.0.0',
|
|
116
|
+
peerDependencies: {'@astryxdesign/cli': '^0.6.0'},
|
|
117
|
+
});
|
|
118
|
+
expect(cliPeerVersionProblem(integration, '0.7.3', tmpDir)).toBe(
|
|
119
|
+
'@test/older supports @astryxdesign/cli ^0.6.0, but this CLI is 0.7.3, outside that range, so some of what it ships may not work as its author tested. Look for a release of @test/older that supports 0.7.3.',
|
|
120
|
+
);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('handles a caret range that excludes the running CLI', () => {
|
|
124
|
+
const integration = makeIntegration('@test/caret', {
|
|
125
|
+
name: '@test/caret',
|
|
126
|
+
version: '1.0.0',
|
|
127
|
+
peerDependencies: {'@astryxdesign/cli': '^1.0.0'},
|
|
128
|
+
});
|
|
129
|
+
expect(cliPeerVersionProblem(integration, '0.6.5', tmpDir)).toContain(
|
|
130
|
+
'^1.0.0',
|
|
131
|
+
);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('returns null for a caret range the CLI satisfies', () => {
|
|
135
|
+
const integration = makeIntegration('@test/caret-ok', {
|
|
136
|
+
name: '@test/caret-ok',
|
|
137
|
+
version: '1.0.0',
|
|
138
|
+
peerDependencies: {'@astryxdesign/cli': '^0.6.0'},
|
|
139
|
+
});
|
|
140
|
+
expect(cliPeerVersionProblem(integration, '0.6.5', tmpDir)).toBeNull();
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('tells the author to test and widen the range when the package is their own', () => {
|
|
144
|
+
const integration = makeIntegration('@test/own', {
|
|
145
|
+
name: '@test/own',
|
|
146
|
+
version: '1.0.0',
|
|
147
|
+
peerDependencies: {'@astryxdesign/cli': '^0.6.0'},
|
|
148
|
+
});
|
|
149
|
+
expect(cliPeerVersionProblem(integration, '0.7.3', tmpDir, {own: true})).toBe(
|
|
150
|
+
'@test/own declares @astryxdesign/cli ^0.6.0, but this CLI is 0.7.3, outside that range. Test the package with this CLI, and if it works, widen the @astryxdesign/cli range in its peerDependencies.',
|
|
151
|
+
);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('warns for an optional peer the CLI does not satisfy', () => {
|
|
155
|
+
// Optional peers are still peers: the integration needs a newer CLI to
|
|
156
|
+
// work fully. The warning is about the CLI version, not about whether
|
|
157
|
+
// the peer is required for install.
|
|
158
|
+
const integration = makeIntegration('@test/optional', {
|
|
159
|
+
name: '@test/optional',
|
|
160
|
+
version: '1.0.0',
|
|
161
|
+
peerDependencies: {'@astryxdesign/cli': '>=0.9.0'},
|
|
162
|
+
peerDependenciesMeta: {'@astryxdesign/cli': {optional: true}},
|
|
163
|
+
});
|
|
164
|
+
expect(cliPeerVersionProblem(integration, '0.6.5', tmpDir)).not.toBeNull();
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('handles a prerelease CLI version gracefully', () => {
|
|
168
|
+
const integration = makeIntegration('@test/prerelease', {
|
|
169
|
+
name: '@test/prerelease',
|
|
170
|
+
version: '1.0.0',
|
|
171
|
+
peerDependencies: {'@astryxdesign/cli': '>=0.7.0'},
|
|
172
|
+
});
|
|
173
|
+
// A canary like 0.7.0-canary.abc123 should satisfy >=0.7.0
|
|
174
|
+
expect(cliPeerVersionProblem(integration, '0.7.0-canary.abc123', tmpDir)).toBeNull();
|
|
175
|
+
// A canary below the floor should warn
|
|
176
|
+
expect(
|
|
177
|
+
cliPeerVersionProblem(integration, '0.6.5-canary.xyz', tmpDir),
|
|
178
|
+
).not.toBeNull();
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it('returns null when __packageDir is missing', () => {
|
|
182
|
+
const integration = {name: '@test/no-dir', version: '1.0.0'};
|
|
183
|
+
expect(cliPeerVersionProblem(integration, '0.6.5', tmpDir)).toBeNull();
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('returns null when package.json cannot be read', () => {
|
|
187
|
+
const integration = {
|
|
188
|
+
name: '@test/bad-dir',
|
|
189
|
+
version: '1.0.0',
|
|
190
|
+
__packageDir: path.join(tmpDir, 'nonexistent'),
|
|
191
|
+
};
|
|
192
|
+
expect(cliPeerVersionProblem(integration, '0.6.5', tmpDir)).toBeNull();
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
describe('cliVersion', () => {
|
|
197
|
+
it('returns a valid semver string', () => {
|
|
198
|
+
const ver = cliVersion();
|
|
199
|
+
expect(ver).toMatch(/^\d+\.\d+\.\d+/);
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
describe('as a note that fails nothing', () => {
|
|
204
|
+
/**
|
|
205
|
+
* An integration package at `dir` whose CLI peer range is `range`.
|
|
206
|
+
* @param {string} dir
|
|
207
|
+
* @param {string} range
|
|
208
|
+
*/
|
|
209
|
+
function writeIntegration(dir, range) {
|
|
210
|
+
fs.mkdirSync(dir, {recursive: true});
|
|
211
|
+
fs.writeFileSync(
|
|
212
|
+
path.join(dir, 'package.json'),
|
|
213
|
+
JSON.stringify({
|
|
214
|
+
name: '@acme/widgets',
|
|
215
|
+
version: '1.0.0',
|
|
216
|
+
peerDependencies: {'@astryxdesign/cli': range},
|
|
217
|
+
}),
|
|
218
|
+
);
|
|
219
|
+
fs.writeFileSync(path.join(dir, 'astryx.integration.mjs'), 'export default {};\n');
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* A project with a lockfile that installs @acme/widgets.
|
|
224
|
+
* @param {string} range
|
|
225
|
+
*/
|
|
226
|
+
function project(range) {
|
|
227
|
+
const root = fs.mkdtempSync(path.join(tmpDir, 'project-'));
|
|
228
|
+
fs.writeFileSync(
|
|
229
|
+
path.join(root, 'package.json'),
|
|
230
|
+
JSON.stringify({
|
|
231
|
+
name: 'app',
|
|
232
|
+
version: '1.0.0',
|
|
233
|
+
dependencies: {'@acme/widgets': '^1.0.0'},
|
|
234
|
+
}),
|
|
235
|
+
);
|
|
236
|
+
fs.writeFileSync(path.join(root, 'package-lock.json'), '{}\n');
|
|
237
|
+
writeIntegration(path.join(root, 'node_modules', '@acme', 'widgets'), range);
|
|
238
|
+
return root;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/** @param {string} [dev] */
|
|
242
|
+
const below = (dev = '') =>
|
|
243
|
+
`@acme/widgets needs @astryxdesign/cli >=99.0.0, but this CLI is ${cliVersion()}, so some of what it ships may not load. Upgrade Core and the CLI together: \`npm install${dev} @astryxdesign/core@latest @astryxdesign/cli@latest\`, then \`npx astryx upgrade --from ${cliVersion()} --apply\`.`;
|
|
244
|
+
|
|
245
|
+
it('doctor integration validate lists it in data.notes, never in data.issues, and exits 0', async () => {
|
|
246
|
+
const cwd = project('>=99.0.0');
|
|
247
|
+
const {status, stdout} = await runCli(
|
|
248
|
+
['doctor', 'integration', 'validate', '@acme/widgets', '--json'],
|
|
249
|
+
{cwd},
|
|
250
|
+
);
|
|
251
|
+
const {data} = JSON.parse(stdout);
|
|
252
|
+
expect(status).toBe(0);
|
|
253
|
+
expect(data.notes).toContainEqual({code: 'cli_peer_version', message: below()});
|
|
254
|
+
expect(data.issues.map((/** @type {{code: string}} */ i) => i.code)).not.toContain(
|
|
255
|
+
'cli_peer_version',
|
|
256
|
+
);
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it('integration check --strict carries the note and still exits 0', async () => {
|
|
260
|
+
const pkg = fs.mkdtempSync(path.join(tmpDir, 'package-'));
|
|
261
|
+
writeIntegration(pkg, '>=99.0.0');
|
|
262
|
+
fs.writeFileSync(path.join(pkg, 'package-lock.json'), '{}\n');
|
|
263
|
+
const {status, stdout} = await runCli(
|
|
264
|
+
['--json', 'integration', 'check', '--strict'],
|
|
265
|
+
{cwd: pkg},
|
|
266
|
+
);
|
|
267
|
+
const envelope = JSON.parse(stdout);
|
|
268
|
+
const validate = envelope.data.checks[0].response.data;
|
|
269
|
+
expect(validate.notes).toContainEqual({
|
|
270
|
+
code: 'cli_peer_version',
|
|
271
|
+
message: below(' -D'),
|
|
272
|
+
});
|
|
273
|
+
expect(validate.issues.map((/** @type {{code: string}} */ i) => i.code)).not.toContain(
|
|
274
|
+
'cli_peer_version',
|
|
275
|
+
);
|
|
276
|
+
expect(envelope.data.failed).toBe(false);
|
|
277
|
+
expect(status).toBe(0);
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
it('starts the upgrade from the Core of a project with no CLI of its own', async () => {
|
|
281
|
+
const cwd = project('>=99.0.0');
|
|
282
|
+
const pkgJson = path.join(cwd, 'package.json');
|
|
283
|
+
const pkg = JSON.parse(fs.readFileSync(pkgJson, 'utf8'));
|
|
284
|
+
pkg.dependencies['@astryxdesign/core'] = '0.6.0';
|
|
285
|
+
fs.writeFileSync(pkgJson, JSON.stringify(pkg));
|
|
286
|
+
const core = path.join(cwd, 'node_modules', '@astryxdesign', 'core');
|
|
287
|
+
fs.mkdirSync(core, {recursive: true});
|
|
288
|
+
fs.writeFileSync(
|
|
289
|
+
path.join(core, 'package.json'),
|
|
290
|
+
JSON.stringify({name: '@astryxdesign/core', version: '0.6.0'}),
|
|
291
|
+
);
|
|
292
|
+
const {status, stdout} = await runCli(
|
|
293
|
+
['doctor', 'integration', 'validate', '@acme/widgets', '--json'],
|
|
294
|
+
{cwd},
|
|
295
|
+
);
|
|
296
|
+
expect(status).toBe(0);
|
|
297
|
+
const note = JSON.parse(stdout).data.notes.find(
|
|
298
|
+
(/** @type {{code: string}} */ n) => n.code === 'cli_peer_version',
|
|
299
|
+
);
|
|
300
|
+
expect(note.message).toContain('upgrade --from 0.6.0 --apply');
|
|
301
|
+
expect(note.message).not.toContain(`--from ${cliVersion()}`);
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
it('asks for a release of an installed package that supports a CLI above its range', async () => {
|
|
305
|
+
const cwd = project('^0.6.0');
|
|
306
|
+
const {stdout} = await runCli(
|
|
307
|
+
['doctor', 'integration', 'validate', '@acme/widgets', '--json'],
|
|
308
|
+
{cwd},
|
|
309
|
+
);
|
|
310
|
+
expect(JSON.parse(stdout).data.notes).toContainEqual({
|
|
311
|
+
code: 'cli_peer_version',
|
|
312
|
+
message: `@acme/widgets supports @astryxdesign/cli ^0.6.0, but this CLI is ${cliVersion()}, outside that range, so some of what it ships may not work as its author tested. Look for a release of @acme/widgets that supports ${cliVersion()}.`,
|
|
313
|
+
});
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
it('tells the author of the package in the current folder to test and widen a range below this CLI', async () => {
|
|
317
|
+
const pkg = fs.mkdtempSync(path.join(tmpDir, 'package-'));
|
|
318
|
+
writeIntegration(pkg, '^0.6.0');
|
|
319
|
+
const {status, stdout} = await runCli(
|
|
320
|
+
['--json', 'integration', 'check', '--strict'],
|
|
321
|
+
{cwd: pkg},
|
|
322
|
+
);
|
|
323
|
+
const envelope = JSON.parse(stdout);
|
|
324
|
+
expect(envelope.data.checks[0].response.data.notes).toContainEqual({
|
|
325
|
+
code: 'cli_peer_version',
|
|
326
|
+
message: `@acme/widgets declares @astryxdesign/cli ^0.6.0, but this CLI is ${cliVersion()}, outside that range. Test the package with this CLI, and if it works, widen the @astryxdesign/cli range in its peerDependencies.`,
|
|
327
|
+
});
|
|
328
|
+
expect(envelope.data.failed).toBe(false);
|
|
329
|
+
expect(status).toBe(0);
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
it('leaves integration verify as it was: no warning, no issue', async () => {
|
|
333
|
+
const pkg = fs.mkdtempSync(path.join(tmpDir, 'package-'));
|
|
334
|
+
writeIntegration(pkg, '>=99.0.0');
|
|
335
|
+
const {stdout} = await runCli(['--json', 'integration', 'verify'], {
|
|
336
|
+
cwd: pkg,
|
|
337
|
+
});
|
|
338
|
+
expect(stdout).not.toContain('cli_peer_version');
|
|
339
|
+
const text = await runCli(['integration', 'verify'], {cwd: pkg});
|
|
340
|
+
expect(text.stdout + text.stderr).not.toContain('needs @astryxdesign/cli');
|
|
341
|
+
}, 120_000);
|
|
342
|
+
|
|
343
|
+
it('says nothing when the CLI is in the range', async () => {
|
|
344
|
+
const cwd = project(`>=${cliVersion()}`);
|
|
345
|
+
const {stdout} = await runCli(
|
|
346
|
+
['doctor', 'integration', 'validate', '@acme/widgets', '--json'],
|
|
347
|
+
{cwd},
|
|
348
|
+
);
|
|
349
|
+
const {data} = JSON.parse(stdout);
|
|
350
|
+
expect(
|
|
351
|
+
(data.notes ?? []).map((/** @type {{code: string}} */ n) => n.code),
|
|
352
|
+
).not.toContain('cli_peer_version');
|
|
353
|
+
});
|
|
354
|
+
});
|
|
@@ -34,15 +34,18 @@ export function computerInstallRoots(cliRoot: string, options?: {
|
|
|
34
34
|
export function peerProblem(pkg: Record<string, any>, release: string): string | null;
|
|
35
35
|
/**
|
|
36
36
|
* The computer-level integrations installed beside the CLI at `cliRoot`, in
|
|
37
|
-
* name order (declaration order when the root declares them)
|
|
37
|
+
* name order (declaration order when the root declares them), then each
|
|
38
|
+
* integration a host approves (`approved`, architecture:cli-surface INV30),
|
|
39
|
+
* resolved by name from the package set that holds that CLI alone. Reads
|
|
38
40
|
* package.json files only.
|
|
39
|
-
* @param {{cliRoot: string, release: string, cacheDir?: string}} options
|
|
41
|
+
* @param {{cliRoot: string, release: string, cacheDir?: string, approved?: readonly string[]}} options
|
|
40
42
|
* @returns {ComputerIntegrationCandidate[]}
|
|
41
43
|
*/
|
|
42
|
-
export function scanComputerIntegrations({ cliRoot, release, cacheDir }: {
|
|
44
|
+
export function scanComputerIntegrations({ cliRoot, release, cacheDir, approved, }: {
|
|
43
45
|
cliRoot: string;
|
|
44
46
|
release: string;
|
|
45
47
|
cacheDir?: string;
|
|
48
|
+
approved?: readonly string[];
|
|
46
49
|
}): ComputerIntegrationCandidate[];
|
|
47
50
|
/**
|
|
48
51
|
* Load the compatible computer-level integrations as provider candidates,
|