@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
|
@@ -4,7 +4,7 @@ import {describe, it, expect, beforeEach, afterEach} from 'vitest';
|
|
|
4
4
|
import * as fs from 'node:fs';
|
|
5
5
|
import * as path from 'node:path';
|
|
6
6
|
import * as os from 'node:os';
|
|
7
|
-
import {findCoreDir, findProjectCoreDir, findProjectRoot, listComponents, discoverExternalPackages, existsCaseExact, findInstalledPackage} from './paths.mjs';
|
|
7
|
+
import {findAstryxProject, findCoreDir, findProjectCoreDir, findProjectRoot, listComponents, discoverExternalPackages, existsCaseExact, findInstalledPackage, findNodeModulesPackage, findPackageRoot, findProjectPackageOf, findWorkspaceRoot} from './paths.mjs';
|
|
8
8
|
import {CLI_ROOT} from './paths.mjs';
|
|
9
9
|
import {linkThisCliAsProjectCli} from '../../test-utils/release-fixtures.mjs';
|
|
10
10
|
|
|
@@ -19,13 +19,93 @@ beforeEach(() => {
|
|
|
19
19
|
});
|
|
20
20
|
|
|
21
21
|
afterEach(() => {
|
|
22
|
+
delete process.versions.pnp;
|
|
22
23
|
fs.rmSync(tmpDir, {recursive: true, force: true});
|
|
23
24
|
});
|
|
24
25
|
|
|
26
|
+
/**
|
|
27
|
+
* Write a package.json naming `name` into `dir`, creating the folder.
|
|
28
|
+
* @param {string} dir @param {string} name @param {string} [version]
|
|
29
|
+
*/
|
|
30
|
+
function writePackage(dir, name, version = '0.7.0') {
|
|
31
|
+
fs.mkdirSync(dir, {recursive: true});
|
|
32
|
+
fs.writeFileSync(path.join(dir, 'package.json'), JSON.stringify({name, version}));
|
|
33
|
+
return dir;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Write a project package.json into `dir`, creating the folder.
|
|
38
|
+
* @param {string} dir @param {Record<string, unknown>} [fields]
|
|
39
|
+
*/
|
|
40
|
+
function writeProject(dir, fields = {}) {
|
|
41
|
+
fs.mkdirSync(dir, {recursive: true});
|
|
42
|
+
fs.writeFileSync(path.join(dir, 'package.json'), JSON.stringify({name: path.basename(dir), ...fields}));
|
|
43
|
+
return dir;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Run `fn` with `home` as the home folder.
|
|
48
|
+
* @template T
|
|
49
|
+
* @param {string} home @param {() => T} fn
|
|
50
|
+
* @returns {T}
|
|
51
|
+
*/
|
|
52
|
+
function withHome(home, fn) {
|
|
53
|
+
const previous = process.env.HOME;
|
|
54
|
+
process.env.HOME = home;
|
|
55
|
+
try {
|
|
56
|
+
return fn();
|
|
57
|
+
} finally {
|
|
58
|
+
if (previous === undefined) delete process.env.HOME;
|
|
59
|
+
else process.env.HOME = previous;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Declares Core, which makes a package.json an Astryx project. */
|
|
64
|
+
const DECLARES_CORE = {dependencies: {'@astryxdesign/core': '^0.7.0'}};
|
|
65
|
+
|
|
66
|
+
/** @param {string} root @param {string} name @param {string} version */
|
|
67
|
+
function install(root, name, version) {
|
|
68
|
+
return writePackage(path.join(root, 'node_modules', ...name.split('/')), name, version);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Stand in for the runtime a Yarn Plug'n'Play process exposes:
|
|
73
|
+
* `process.versions.pnp` plus a requirable `pnpapi`. The packages themselves
|
|
74
|
+
* live outside any node_modules, the way Yarn stores them. The top-level
|
|
75
|
+
* afterEach removes `process.versions.pnp` again.
|
|
76
|
+
* @param {Record<string, string>} resolved name -> package directory
|
|
77
|
+
* @returns {string} a project folder inside the PnP runtime
|
|
78
|
+
*/
|
|
79
|
+
function withPnp(resolved) {
|
|
80
|
+
const runtime = path.join(tmpDir, 'runtime');
|
|
81
|
+
const api = path.join(runtime, 'node_modules', 'pnpapi');
|
|
82
|
+
fs.mkdirSync(api, {recursive: true});
|
|
83
|
+
fs.writeFileSync(
|
|
84
|
+
path.join(api, 'package.json'),
|
|
85
|
+
JSON.stringify({name: 'pnpapi', version: '1.0.0', main: 'index.js'}),
|
|
86
|
+
);
|
|
87
|
+
fs.writeFileSync(
|
|
88
|
+
path.join(api, 'index.js'),
|
|
89
|
+
`const resolved = ${JSON.stringify(resolved)};\n` +
|
|
90
|
+
'module.exports = {\n' +
|
|
91
|
+
' resolveToUnqualified(name) {\n' +
|
|
92
|
+
' if (!(name in resolved)) {\n' +
|
|
93
|
+
" const e = new Error(name); e.code = 'MODULE_NOT_FOUND'; throw e;\n" +
|
|
94
|
+
' }\n' +
|
|
95
|
+
' return resolved[name];\n' +
|
|
96
|
+
' },\n' +
|
|
97
|
+
'};\n',
|
|
98
|
+
);
|
|
99
|
+
Object.defineProperty(process.versions, 'pnp', {
|
|
100
|
+
value: '3',
|
|
101
|
+
configurable: true,
|
|
102
|
+
});
|
|
103
|
+
return runtime;
|
|
104
|
+
}
|
|
105
|
+
|
|
25
106
|
describe('findCoreDir', () => {
|
|
26
107
|
it('finds packages/core walking up directories', () => {
|
|
27
|
-
const coreDir = path.join(tmpDir, 'packages', 'core');
|
|
28
|
-
fs.mkdirSync(coreDir, {recursive: true});
|
|
108
|
+
const coreDir = writePackage(path.join(tmpDir, 'packages', 'core'), '@astryxdesign/core');
|
|
29
109
|
|
|
30
110
|
const nested = path.join(tmpDir, 'sub', 'deep');
|
|
31
111
|
fs.mkdirSync(nested, {recursive: true});
|
|
@@ -34,8 +114,7 @@ describe('findCoreDir', () => {
|
|
|
34
114
|
});
|
|
35
115
|
|
|
36
116
|
it('finds node_modules/@astryxdesign/core fallback', () => {
|
|
37
|
-
const nmCore =
|
|
38
|
-
fs.mkdirSync(nmCore, {recursive: true});
|
|
117
|
+
const nmCore = install(tmpDir, '@astryxdesign/core', '0.7.0');
|
|
39
118
|
|
|
40
119
|
expect(findCoreDir(tmpDir)).toBe(nmCore);
|
|
41
120
|
});
|
|
@@ -48,22 +127,111 @@ describe('findCoreDir', () => {
|
|
|
48
127
|
// This copy's carried Core is the workspace Core beside it.
|
|
49
128
|
expect(findCoreDir(tmpDir)).toBe(path.resolve(CLI_ROOT, '..', 'core'));
|
|
50
129
|
linkThisCliAsProjectCli(tmpDir);
|
|
51
|
-
|
|
130
|
+
// The project's own Core: findCoreDir falls back to the carried one.
|
|
131
|
+
expect(findProjectCoreDir(tmpDir)).toBeNull();
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// A route folder such as src/app/(shop)/orders/[id]/edit sits six folders
|
|
135
|
+
// below the project root, where Core is installed.
|
|
136
|
+
it('finds Core from any depth inside the project that installs it', () => {
|
|
137
|
+
writeProject(tmpDir, {dependencies: {'@astryxdesign/core': '^0.7.0'}});
|
|
138
|
+
const nmCore = install(tmpDir, '@astryxdesign/core', '0.7.0');
|
|
139
|
+
const route = path.join(tmpDir, 'src', 'app', '(shop)', 'orders', '[id]', 'edit');
|
|
140
|
+
fs.mkdirSync(route, {recursive: true});
|
|
141
|
+
|
|
142
|
+
expect(findCoreDir(route)).toBe(nmCore);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('searches a workspace up to its root from any depth in a member', () => {
|
|
146
|
+
writeProject(tmpDir, {private: true, workspaces: ['apps/*']});
|
|
147
|
+
const nmCore = install(tmpDir, '@astryxdesign/core', '0.7.0');
|
|
148
|
+
const member = writeProject(path.join(tmpDir, 'apps', 'web'), {dependencies: {'@astryxdesign/core': '^0.7.0'}});
|
|
149
|
+
const route = path.join(member, 'src', 'app', '(shop)', 'orders', '[id]');
|
|
150
|
+
fs.mkdirSync(route, {recursive: true});
|
|
151
|
+
|
|
152
|
+
expect(findCoreDir(route)).toBe(nmCore);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('searches only the folder and the four above it outside any Astryx project', () => {
|
|
156
|
+
install(tmpDir, '@astryxdesign/core', '0.7.0');
|
|
157
|
+
const project = writeProject(path.join(tmpDir, 'a', 'b', 'c', 'd', 'e', 'app'));
|
|
158
|
+
|
|
159
|
+
// The project's own Core: findCoreDir falls back to the carried one.
|
|
160
|
+
expect(findProjectCoreDir(project)).toBeNull();
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// A package.json that only sets the module type is not the project, so a
|
|
164
|
+
// folder deep below it still reaches the Core the project installs.
|
|
165
|
+
it('looks past a nested package.json that declares no Astryx package', () => {
|
|
166
|
+
writeProject(tmpDir, DECLARES_CORE);
|
|
167
|
+
const nmCore = install(tmpDir, '@astryxdesign/core', '0.7.0');
|
|
168
|
+
fs.mkdirSync(path.join(tmpDir, 'src'), {recursive: true});
|
|
169
|
+
fs.writeFileSync(path.join(tmpDir, 'src', 'package.json'), JSON.stringify({type: 'module'}));
|
|
170
|
+
const deep = path.join(tmpDir, 'src', 'l1', 'l2', 'l3', 'l4', 'l5', 'l6');
|
|
171
|
+
fs.mkdirSync(deep, {recursive: true});
|
|
172
|
+
|
|
173
|
+
expect(findCoreDir(deep)).toBe(nmCore);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
// Past the reach the search starts at the project root: a tool folder
|
|
177
|
+
// between them that installs its own Core but declares nothing never
|
|
178
|
+
// answers for the project, as `upgrade` targets the project's Core.
|
|
179
|
+
it('answers the project\'s Core, not a tool folder\'s, from deep inside the tool', () => {
|
|
180
|
+
const app = path.join(tmpDir, 'app');
|
|
181
|
+
writeProject(app, DECLARES_CORE);
|
|
182
|
+
const projectCore = install(app, '@astryxdesign/core', '0.7.0');
|
|
183
|
+
const tool = path.join(app, 'tools', 'codegen');
|
|
184
|
+
writePackage(tool, 'codegen');
|
|
185
|
+
install(tool, '@astryxdesign/core', '0.9.0');
|
|
186
|
+
const deep = path.join(tool, 'src', 'a', 'b', 'c', 'd', 'e');
|
|
187
|
+
fs.mkdirSync(deep, {recursive: true});
|
|
188
|
+
|
|
189
|
+
expect(findProjectCoreDir(deep)).toBe(projectCore);
|
|
190
|
+
expect(findInstalledPackage(path.join(deep, 'f'), '@astryxdesign/core')).toBe(projectCore);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
// Node loads the hoisted copy whatever the member declares: an override or
|
|
194
|
+
// resolution can force it, and a peer range can be stale. Reads answer from
|
|
195
|
+
// the copy that loads.
|
|
196
|
+
it.each([
|
|
197
|
+
['a range the copy is outside of', {devDependencies: {'@astryxdesign/core': '~0.6.8'}}],
|
|
198
|
+
['only a stale peer range', {peerDependencies: {'@astryxdesign/core': '^0.6.8'}}],
|
|
199
|
+
])('takes a workspace Core when the member declares %s', (_label, fields) => {
|
|
200
|
+
writeProject(tmpDir, {private: true, workspaces: ['apps/*'], overrides: {'@astryxdesign/core': '0.7.1'}});
|
|
201
|
+
const nmCore = install(tmpDir, '@astryxdesign/core', '0.7.1');
|
|
202
|
+
const member = writeProject(path.join(tmpDir, 'apps', 'web'), fields);
|
|
203
|
+
|
|
204
|
+
expect(findCoreDir(member)).toBe(nmCore);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it('skips a packages/core folder that holds another package', () => {
|
|
208
|
+
writePackage(path.join(tmpDir, 'packages', 'core'), '@acme/core', '1.0.0');
|
|
209
|
+
const app = path.join(tmpDir, 'apps', 'web');
|
|
210
|
+
fs.mkdirSync(app, {recursive: true});
|
|
211
|
+
|
|
212
|
+
// The project's own Core: findCoreDir falls back to the carried one.
|
|
213
|
+
expect(findProjectCoreDir(app)).toBeNull();
|
|
214
|
+
|
|
215
|
+
const nmCore = install(tmpDir, '@astryxdesign/core', '0.7.0');
|
|
216
|
+
expect(findCoreDir(app)).toBe(nmCore);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it('skips a node_modules/@astryxdesign/core folder with no package.json', () => {
|
|
220
|
+
fs.mkdirSync(path.join(tmpDir, 'node_modules', '@astryxdesign', 'core'), {recursive: true});
|
|
221
|
+
|
|
222
|
+
// The project's own Core: findCoreDir falls back to the carried one.
|
|
223
|
+
expect(findProjectCoreDir(tmpDir)).toBeNull();
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it("asks Yarn Plug'n'Play when no folder holds Core", () => {
|
|
227
|
+
const store = writePackage(path.join(tmpDir, 'store', '@astryxdesign-core'), '@astryxdesign/core');
|
|
228
|
+
const project = withPnp({'@astryxdesign/core': store});
|
|
229
|
+
|
|
230
|
+
expect(findCoreDir(project)).toBe(store);
|
|
52
231
|
});
|
|
53
232
|
});
|
|
54
233
|
|
|
55
234
|
describe('findInstalledPackage', () => {
|
|
56
|
-
/** @param {string} root @param {string} name @param {string} version */
|
|
57
|
-
function install(root, name, version) {
|
|
58
|
-
const dir = path.join(root, 'node_modules', ...name.split('/'));
|
|
59
|
-
fs.mkdirSync(dir, {recursive: true});
|
|
60
|
-
fs.writeFileSync(
|
|
61
|
-
path.join(dir, 'package.json'),
|
|
62
|
-
JSON.stringify({name, version}),
|
|
63
|
-
);
|
|
64
|
-
return dir;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
235
|
it('finds a package in the node_modules chain above startDir', () => {
|
|
68
236
|
const installed = install(tmpDir, '@astryxdesign/core', '0.6.0');
|
|
69
237
|
const nested = path.join(tmpDir, 'src', 'deep');
|
|
@@ -72,6 +240,22 @@ describe('findInstalledPackage', () => {
|
|
|
72
240
|
expect(findInstalledPackage(nested, '@astryxdesign/core')).toBe(installed);
|
|
73
241
|
});
|
|
74
242
|
|
|
243
|
+
it('finds a package from any depth inside the project', () => {
|
|
244
|
+
writeProject(tmpDir, DECLARES_CORE);
|
|
245
|
+
const installed = install(tmpDir, '@astryxdesign/core', '0.7.0');
|
|
246
|
+
const deep = path.join(tmpDir, 'apps', 'web', 'src', 'app', '(shop)', 'orders', '[id]');
|
|
247
|
+
fs.mkdirSync(deep, {recursive: true});
|
|
248
|
+
|
|
249
|
+
expect(findInstalledPackage(deep, '@astryxdesign/core')).toBe(installed);
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it('does not search past six folders outside the project', () => {
|
|
253
|
+
install(tmpDir, '@astryxdesign/core', '0.7.0');
|
|
254
|
+
const project = writeProject(path.join(tmpDir, 'a', 'b', 'c', 'd', 'e', 'f', 'app'));
|
|
255
|
+
|
|
256
|
+
expect(findInstalledPackage(project, '@astryxdesign/core')).toBeNull();
|
|
257
|
+
});
|
|
258
|
+
|
|
75
259
|
it('reports a package the project never installed as missing', () => {
|
|
76
260
|
expect(findInstalledPackage(tmpDir, '@astryxdesign/core')).toBeNull();
|
|
77
261
|
});
|
|
@@ -96,43 +280,6 @@ describe('findInstalledPackage', () => {
|
|
|
96
280
|
});
|
|
97
281
|
|
|
98
282
|
describe('under Yarn Plug\'n\'Play, which has no node_modules', () => {
|
|
99
|
-
/**
|
|
100
|
-
* Stand in for the runtime a PnP process exposes: `process.versions.pnp`
|
|
101
|
-
* plus a requirable `pnpapi`. The package itself lives outside any
|
|
102
|
-
* node_modules, the way Yarn stores it.
|
|
103
|
-
* @param {Record<string, string>} resolved name -> package directory
|
|
104
|
-
*/
|
|
105
|
-
function withPnp(resolved) {
|
|
106
|
-
const runtime = path.join(tmpDir, 'runtime');
|
|
107
|
-
const api = path.join(runtime, 'node_modules', 'pnpapi');
|
|
108
|
-
fs.mkdirSync(api, {recursive: true});
|
|
109
|
-
fs.writeFileSync(
|
|
110
|
-
path.join(api, 'package.json'),
|
|
111
|
-
JSON.stringify({name: 'pnpapi', version: '1.0.0', main: 'index.js'}),
|
|
112
|
-
);
|
|
113
|
-
fs.writeFileSync(
|
|
114
|
-
path.join(api, 'index.js'),
|
|
115
|
-
`const resolved = ${JSON.stringify(resolved)};\n` +
|
|
116
|
-
'module.exports = {\n' +
|
|
117
|
-
' resolveToUnqualified(name) {\n' +
|
|
118
|
-
' if (!(name in resolved)) {\n' +
|
|
119
|
-
" const e = new Error(name); e.code = 'MODULE_NOT_FOUND'; throw e;\n" +
|
|
120
|
-
' }\n' +
|
|
121
|
-
' return resolved[name];\n' +
|
|
122
|
-
' },\n' +
|
|
123
|
-
'};\n',
|
|
124
|
-
);
|
|
125
|
-
Object.defineProperty(process.versions, 'pnp', {
|
|
126
|
-
value: '3',
|
|
127
|
-
configurable: true,
|
|
128
|
-
});
|
|
129
|
-
return runtime;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
afterEach(() => {
|
|
133
|
-
delete process.versions.pnp;
|
|
134
|
-
});
|
|
135
|
-
|
|
136
283
|
it('falls back to the PnP runtime when there is no chain to walk', () => {
|
|
137
284
|
const store = path.join(tmpDir, 'store', '@astryxdesign-core');
|
|
138
285
|
fs.mkdirSync(store, {recursive: true});
|
|
@@ -153,6 +300,235 @@ describe('findInstalledPackage', () => {
|
|
|
153
300
|
});
|
|
154
301
|
});
|
|
155
302
|
|
|
303
|
+
describe('findProjectPackageOf', () => {
|
|
304
|
+
const NAMES = ['@astryxdesign/core', '@xds/core'];
|
|
305
|
+
|
|
306
|
+
it('takes the earlier name when one folder installs both', () => {
|
|
307
|
+
writeProject(tmpDir, DECLARES_CORE);
|
|
308
|
+
const current = install(tmpDir, '@astryxdesign/core', '0.7.0');
|
|
309
|
+
install(tmpDir, '@xds/core', '0.3.0');
|
|
310
|
+
|
|
311
|
+
expect(findProjectPackageOf(tmpDir, NAMES)).toEqual({name: '@astryxdesign/core', packageDir: current});
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
it('takes the nearest folder over an earlier name further up', () => {
|
|
315
|
+
writeProject(tmpDir, {private: true, workspaces: ['apps/*']});
|
|
316
|
+
install(tmpDir, '@astryxdesign/core', '0.7.0');
|
|
317
|
+
const member = writeProject(path.join(tmpDir, 'apps', 'legacy'), {dependencies: {'@xds/core': '0.3.0'}});
|
|
318
|
+
const legacy = install(member, '@xds/core', '0.3.0');
|
|
319
|
+
|
|
320
|
+
expect(findProjectPackageOf(member, NAMES)).toEqual({name: '@xds/core', packageDir: legacy});
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
it('returns null when none is installed', () => {
|
|
324
|
+
expect(findProjectPackageOf(tmpDir, NAMES)).toBeNull();
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
it('finds the package from a subfolder of the project', () => {
|
|
328
|
+
writeProject(tmpDir, DECLARES_CORE);
|
|
329
|
+
const current = install(tmpDir, '@astryxdesign/core', '0.7.0');
|
|
330
|
+
const deep = path.join(tmpDir, 'src', 'l1', 'l2');
|
|
331
|
+
fs.mkdirSync(deep, {recursive: true});
|
|
332
|
+
|
|
333
|
+
expect(findProjectPackageOf(deep, NAMES)).toEqual({name: '@astryxdesign/core', packageDir: current});
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
// A member uses what its workspace installs, whatever its own package.json
|
|
337
|
+
// says: an override or resolution can force the copy, and a range or peer
|
|
338
|
+
// range can be stale. Membership decides, not the declared version.
|
|
339
|
+
it.each([
|
|
340
|
+
['~0.6.8', 'dependencies'],
|
|
341
|
+
['0.7.0 - 0.8.0', 'dependencies'],
|
|
342
|
+
['>= 0.7.0', 'dependencies'],
|
|
343
|
+
['^0.6.8', 'peerDependencies'],
|
|
344
|
+
])('takes the hoisted package for a member that declares %s in %s', (range, field) => {
|
|
345
|
+
writeProject(tmpDir, {private: true, workspaces: ['apps/*']});
|
|
346
|
+
const hoisted = install(tmpDir, '@astryxdesign/core', '0.7.1');
|
|
347
|
+
const member = writeProject(path.join(tmpDir, 'apps', 'web'), {[field]: {'@astryxdesign/core': range}});
|
|
348
|
+
|
|
349
|
+
expect(findProjectPackageOf(member, NAMES)).toEqual({name: '@astryxdesign/core', packageDir: hoisted});
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
it.each([
|
|
353
|
+
['Yarn object-form workspaces', {workspaces: {packages: ['apps/*']}}, null],
|
|
354
|
+
['a ** pattern', {workspaces: ['apps/**']}, null],
|
|
355
|
+
['a brace pattern', {workspaces: ['{apps,tools}/*']}, null],
|
|
356
|
+
['a brace list of globs', {workspaces: ['{apps/*,tools/*}']}, null],
|
|
357
|
+
['a nested brace list', {workspaces: ['{apps/{web,admin},tools/*}']}, null],
|
|
358
|
+
['a pnpm-workspace.yaml block list', {}, "packages:\n - 'apps/*' # apps\n - \"tools/*\"\nonlyBuiltDependencies: []\n"],
|
|
359
|
+
['a pnpm-workspace.yaml flow list', {}, "packages: ['tools/*', apps/*]\n"],
|
|
360
|
+
])('takes the hoisted package for a member that %s lists', (_label, rootFields, yaml) => {
|
|
361
|
+
writeProject(tmpDir, {private: true, ...rootFields});
|
|
362
|
+
if (yaml) fs.writeFileSync(path.join(tmpDir, 'pnpm-workspace.yaml'), yaml);
|
|
363
|
+
const hoisted = install(tmpDir, '@astryxdesign/core', '0.7.1');
|
|
364
|
+
const member = writeProject(path.join(tmpDir, 'apps', 'web'), DECLARES_CORE);
|
|
365
|
+
|
|
366
|
+
expect(findProjectPackageOf(member, NAMES)).toEqual({name: '@astryxdesign/core', packageDir: hoisted});
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
it.each([
|
|
370
|
+
['lists other folders', {workspaces: ['packages/*']}],
|
|
371
|
+
['excludes it with a ! pattern', {workspaces: ['apps/*', '!apps/web']}],
|
|
372
|
+
['lists other folders in a brace list of globs', {workspaces: ['{packages/*,tools/*}']}],
|
|
373
|
+
])('stops at the project root when the workspace %s', (_label, rootFields) => {
|
|
374
|
+
writeProject(tmpDir, {private: true, ...rootFields});
|
|
375
|
+
install(tmpDir, '@astryxdesign/core', '0.7.1');
|
|
376
|
+
const project = writeProject(path.join(tmpDir, 'apps', 'web'), DECLARES_CORE);
|
|
377
|
+
|
|
378
|
+
expect(findProjectPackageOf(project, NAMES)).toBeNull();
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
// AST-040 FR11: a Plug'n'Play project that declares Core keeps no
|
|
382
|
+
// node_modules copy; the runtime answers for it.
|
|
383
|
+
it("asks Yarn Plug'n'Play for a project that declares Core and has no node_modules copy", () => {
|
|
384
|
+
const store = writePackage(path.join(tmpDir, 'store', '@astryxdesign-core'), '@astryxdesign/core', '0.7.1');
|
|
385
|
+
const project = withPnp({'@astryxdesign/core': store});
|
|
386
|
+
writeProject(project, DECLARES_CORE);
|
|
387
|
+
|
|
388
|
+
expect(fs.existsSync(path.join(project, 'node_modules', '@astryxdesign'))).toBe(false);
|
|
389
|
+
expect(findProjectPackageOf(project, NAMES)).toEqual({name: '@astryxdesign/core', packageDir: store});
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
// A project nested in another, outside any workspace, has its own install.
|
|
393
|
+
it('stops at the root of a nested project outside any workspace', () => {
|
|
394
|
+
writeProject(tmpDir, {dependencies: {'@astryxdesign/core': '0.8.0'}});
|
|
395
|
+
install(tmpDir, '@astryxdesign/core', '0.8.0');
|
|
396
|
+
const nested = writeProject(path.join(tmpDir, 'apps', 'web'), {dependencies: {'@astryxdesign/core': '0.9.0'}});
|
|
397
|
+
|
|
398
|
+
expect(findProjectPackageOf(nested, NAMES)).toBeNull();
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
// With no Astryx project, released versions read only this folder's own
|
|
402
|
+
// node_modules.
|
|
403
|
+
it('reads only the folder itself when no Astryx project contains it', () => {
|
|
404
|
+
writeProject(tmpDir);
|
|
405
|
+
install(tmpDir, '@astryxdesign/core', '0.7.1');
|
|
406
|
+
const plain = writeProject(path.join(tmpDir, 'plain'));
|
|
407
|
+
|
|
408
|
+
expect(findProjectPackageOf(plain, NAMES)).toBeNull();
|
|
409
|
+
const own = install(plain, '@astryxdesign/core', '0.7.1');
|
|
410
|
+
expect(findProjectPackageOf(plain, NAMES)).toEqual({name: '@astryxdesign/core', packageDir: own});
|
|
411
|
+
});
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
describe('findAstryxProject', () => {
|
|
415
|
+
it('is the nearest folder whose package.json declares an Astryx package', () => {
|
|
416
|
+
writeProject(tmpDir, DECLARES_CORE);
|
|
417
|
+
const tool = writeProject(path.join(tmpDir, 'tools', 'lint'));
|
|
418
|
+
const deep = path.join(tool, 'src');
|
|
419
|
+
fs.mkdirSync(deep, {recursive: true});
|
|
420
|
+
|
|
421
|
+
expect(findAstryxProject(deep)).toBe(tmpDir);
|
|
422
|
+
const app = writeProject(path.join(tmpDir, 'apps', 'web'), {devDependencies: {'@astryxdesign/cli': '0.7.1'}});
|
|
423
|
+
expect(findAstryxProject(app)).toBe(app);
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
it('counts a peer declaration and the legacy scope', () => {
|
|
427
|
+
const integration = writeProject(path.join(tmpDir, 'integration'), {peerDependencies: {'@astryxdesign/core': '^0.7.0'}});
|
|
428
|
+
const legacy = writeProject(path.join(tmpDir, 'legacy'), {dependencies: {'@xds/core': '0.3.0'}});
|
|
429
|
+
|
|
430
|
+
expect(findAstryxProject(integration)).toBe(integration);
|
|
431
|
+
expect(findAstryxProject(legacy)).toBe(legacy);
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
it('stops at a repository root', () => {
|
|
435
|
+
writeProject(tmpDir, DECLARES_CORE);
|
|
436
|
+
const repo = writeProject(path.join(tmpDir, 'repo'));
|
|
437
|
+
fs.mkdirSync(path.join(repo, '.git'));
|
|
438
|
+
const tools = path.join(repo, 'tools');
|
|
439
|
+
fs.mkdirSync(tools, {recursive: true});
|
|
440
|
+
|
|
441
|
+
expect(findAstryxProject(tools)).toBeNull();
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
it('never counts the home folder', () => {
|
|
445
|
+
writeProject(tmpDir, DECLARES_CORE);
|
|
446
|
+
const desktop = path.join(tmpDir, 'Desktop');
|
|
447
|
+
fs.mkdirSync(desktop, {recursive: true});
|
|
448
|
+
|
|
449
|
+
withHome(tmpDir, () => {
|
|
450
|
+
expect(findAstryxProject(desktop)).toBeNull();
|
|
451
|
+
expect(findAstryxProject(tmpDir)).toBeNull();
|
|
452
|
+
});
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
it('returns null when no folder declares Astryx', () => {
|
|
456
|
+
writeProject(tmpDir);
|
|
457
|
+
|
|
458
|
+
expect(findAstryxProject(tmpDir)).toBeNull();
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
it('counts a member its workspace lists when the workspace root declares Astryx', () => {
|
|
462
|
+
writeProject(tmpDir, {private: true, workspaces: ['apps/*'], ...DECLARES_CORE});
|
|
463
|
+
const member = writeProject(path.join(tmpDir, 'apps', 'web'));
|
|
464
|
+
const tests = writeProject(path.join(member, 'e2e'));
|
|
465
|
+
|
|
466
|
+
expect(findAstryxProject(path.join(member, 'src'))).toBe(member);
|
|
467
|
+
expect(findAstryxProject(tests)).toBe(member);
|
|
468
|
+
const other = writeProject(path.join(tmpDir, 'tools', 'lint'));
|
|
469
|
+
expect(findAstryxProject(other)).toBe(tmpDir);
|
|
470
|
+
});
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
describe('findPackageRoot', () => {
|
|
474
|
+
it('finds the nearest package.json at or above the folder', () => {
|
|
475
|
+
writeProject(tmpDir);
|
|
476
|
+
const app = writeProject(path.join(tmpDir, 'apps', 'web'));
|
|
477
|
+
const deep = path.join(app, 'src', 'app');
|
|
478
|
+
fs.mkdirSync(deep, {recursive: true});
|
|
479
|
+
|
|
480
|
+
expect(findPackageRoot(deep)).toBe(app);
|
|
481
|
+
expect(findPackageRoot(tmpDir)).toBe(tmpDir);
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
it('returns null with no package.json', () => {
|
|
485
|
+
expect(findPackageRoot(tmpDir)).toBeNull();
|
|
486
|
+
});
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
describe('findWorkspaceRoot', () => {
|
|
490
|
+
it('finds the nearest folder that declares workspaces in package.json', () => {
|
|
491
|
+
writeProject(tmpDir, {private: true, workspaces: ['apps/*']});
|
|
492
|
+
const member = writeProject(path.join(tmpDir, 'apps', 'web'));
|
|
493
|
+
|
|
494
|
+
expect(findWorkspaceRoot(path.join(member, 'src'))).toBe(tmpDir);
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
it('finds the nearest folder with pnpm-workspace.yaml', () => {
|
|
498
|
+
writeProject(tmpDir);
|
|
499
|
+
fs.writeFileSync(path.join(tmpDir, 'pnpm-workspace.yaml'), "packages:\n - 'apps/*'\n");
|
|
500
|
+
const member = writeProject(path.join(tmpDir, 'apps', 'web'));
|
|
501
|
+
|
|
502
|
+
expect(findWorkspaceRoot(member)).toBe(tmpDir);
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
it('is the nearest package.json outside a workspace, and the folder itself with none', () => {
|
|
506
|
+
const app = writeProject(path.join(tmpDir, 'app'));
|
|
507
|
+
const src = path.join(app, 'src');
|
|
508
|
+
fs.mkdirSync(src, {recursive: true});
|
|
509
|
+
|
|
510
|
+
expect(findWorkspaceRoot(src)).toBe(app);
|
|
511
|
+
expect(findWorkspaceRoot(tmpDir)).toBe(tmpDir);
|
|
512
|
+
});
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
describe('findNodeModulesPackage', () => {
|
|
516
|
+
it('finds a package hoisted to a workspace root and names the folder that holds it', () => {
|
|
517
|
+
const hoisted = install(tmpDir, '@acme/widgets', '1.0.0');
|
|
518
|
+
const member = path.join(tmpDir, 'apps', 'web');
|
|
519
|
+
fs.mkdirSync(member, {recursive: true});
|
|
520
|
+
|
|
521
|
+
expect(findNodeModulesPackage(member, '@acme/widgets')).toEqual({packageDir: hoisted, hostDir: tmpDir});
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
it('does not ask Plug\'n\'Play', () => {
|
|
525
|
+
const store = writePackage(path.join(tmpDir, 'store', 'acme-widgets'), '@acme/widgets');
|
|
526
|
+
const project = withPnp({'@acme/widgets': store});
|
|
527
|
+
|
|
528
|
+
expect(findNodeModulesPackage(project, '@acme/widgets')).toBeNull();
|
|
529
|
+
});
|
|
530
|
+
});
|
|
531
|
+
|
|
156
532
|
describe('findProjectRoot', () => {
|
|
157
533
|
it('finds root with workspaces in package.json', () => {
|
|
158
534
|
fs.writeFileSync(
|
|
@@ -0,0 +1,18 @@
|
|
|
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
|
+
* Whether every recorded path is still in the state it had when it was read.
|
|
6
|
+
* @param {Map<string, string>} recorded - From recordReads.
|
|
7
|
+
* @returns {boolean}
|
|
8
|
+
*/
|
|
9
|
+
export function unchangedSince(recorded: Map<string, string>): boolean;
|
|
10
|
+
/**
|
|
11
|
+
* Run `fn`, adding to `seen` each path it reads, with that path's state when
|
|
12
|
+
* it was first read (see the file header).
|
|
13
|
+
* @template T
|
|
14
|
+
* @param {Map<string, string>} seen
|
|
15
|
+
* @param {() => T} fn
|
|
16
|
+
* @returns {T}
|
|
17
|
+
*/
|
|
18
|
+
export function recordReads<T>(seen: Map<string, string>, fn: () => T): T;
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file Which files and folders an operation read, so a long-running caller
|
|
5
|
+
* can tell whether its inputs changed since.
|
|
6
|
+
*
|
|
7
|
+
* @input An operation run inside `recordReads(seen, fn)`.
|
|
8
|
+
* @output `seen` gets every file whose content the operation read through fs
|
|
9
|
+
* and every folder it listed or probed, each with its state when first
|
|
10
|
+
* read; `unchangedSince(seen)` says whether all are still in that state.
|
|
11
|
+
* @position Used by search's adapter to reuse a gathered catalog only while
|
|
12
|
+
* it is exact. Outside `recordReads` nothing is recorded and every fs call
|
|
13
|
+
* behaves as before.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import {AsyncLocalStorage} from 'node:async_hooks';
|
|
17
|
+
import {createRequire, syncBuiltinESMExports} from 'node:module';
|
|
18
|
+
import * as path from 'node:path';
|
|
19
|
+
import {fileURLToPath} from 'node:url';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* The paths the running operation touched: every file it read and every
|
|
23
|
+
* folder it listed or probed.
|
|
24
|
+
* @type {AsyncLocalStorage<Map<string, string>>}
|
|
25
|
+
*/
|
|
26
|
+
const reads = new AsyncLocalStorage();
|
|
27
|
+
|
|
28
|
+
/** node:fs's module object, whose functions every import of it shares. */
|
|
29
|
+
const fs = createRequire(import.meta.url)('node:fs');
|
|
30
|
+
|
|
31
|
+
/** The fs reads whose path argument is recorded. */
|
|
32
|
+
const RECORDED = [
|
|
33
|
+
'readFileSync',
|
|
34
|
+
'readdirSync',
|
|
35
|
+
'statSync',
|
|
36
|
+
'lstatSync',
|
|
37
|
+
'existsSync',
|
|
38
|
+
'realpathSync',
|
|
39
|
+
'accessSync',
|
|
40
|
+
'openSync',
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
/** The reads that see a file's content, so the file itself is checked. */
|
|
44
|
+
const CONTENT_READS = new Set(['readFileSync', 'openSync']);
|
|
45
|
+
|
|
46
|
+
let recording = false;
|
|
47
|
+
|
|
48
|
+
/** fs.statSync as loaded, before installRecorder wraps it. */
|
|
49
|
+
const statSyncRaw = fs.statSync;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Wrap fs's read calls once so that, inside `reads.run`, each path they are
|
|
53
|
+
* given is added to that run's set. Outside a run they behave exactly as
|
|
54
|
+
* before.
|
|
55
|
+
*/
|
|
56
|
+
function installRecorder() {
|
|
57
|
+
if (recording) return;
|
|
58
|
+
recording = true;
|
|
59
|
+
for (const name of RECORDED) {
|
|
60
|
+
const original = fs[name];
|
|
61
|
+
if (typeof original !== 'function') continue;
|
|
62
|
+
/**
|
|
63
|
+
* @this {unknown}
|
|
64
|
+
* @param {...any} args
|
|
65
|
+
*/
|
|
66
|
+
const wrapped = function (...args) {
|
|
67
|
+
const seen = reads.getStore();
|
|
68
|
+
const target = args[0];
|
|
69
|
+
if (seen && (typeof target === 'string' || target instanceof URL)) {
|
|
70
|
+
const file =
|
|
71
|
+
typeof target === 'string'
|
|
72
|
+
? path.resolve(target)
|
|
73
|
+
: fileURLToPath(target);
|
|
74
|
+
// A file read is checked itself; a probe or listing is checked by its
|
|
75
|
+
// folder, whose time changes when an entry is added, removed or
|
|
76
|
+
// renamed.
|
|
77
|
+
const checked =
|
|
78
|
+
CONTENT_READS.has(name) || name === 'readdirSync'
|
|
79
|
+
? file
|
|
80
|
+
: path.dirname(file);
|
|
81
|
+
// Its state is taken when it is first read, before the read, so a
|
|
82
|
+
// change made while the operation runs shows as a change afterwards.
|
|
83
|
+
if (!seen.has(checked)) seen.set(checked, stateOf(checked));
|
|
84
|
+
}
|
|
85
|
+
return original.apply(this, args);
|
|
86
|
+
};
|
|
87
|
+
Object.assign(wrapped, original);
|
|
88
|
+
fs[name] = wrapped;
|
|
89
|
+
}
|
|
90
|
+
// ESM imports of node:fs see the wrapped functions too.
|
|
91
|
+
syncBuiltinESMExports();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* One path's state: its change time and size, or that it is absent.
|
|
96
|
+
* @param {string} file
|
|
97
|
+
* @returns {string}
|
|
98
|
+
*/
|
|
99
|
+
function stateOf(file) {
|
|
100
|
+
try {
|
|
101
|
+
const stat = statSyncRaw(file);
|
|
102
|
+
return `${stat.mtimeMs}\0${stat.size}`;
|
|
103
|
+
} catch {
|
|
104
|
+
return '-';
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Whether every recorded path is still in the state it had when it was read.
|
|
110
|
+
* @param {Map<string, string>} recorded - From recordReads.
|
|
111
|
+
* @returns {boolean}
|
|
112
|
+
*/
|
|
113
|
+
export function unchangedSince(recorded) {
|
|
114
|
+
for (const [file, state] of recorded) {
|
|
115
|
+
if (stateOf(file) !== state) return false;
|
|
116
|
+
}
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Run `fn`, adding to `seen` each path it reads, with that path's state when
|
|
122
|
+
* it was first read (see the file header).
|
|
123
|
+
* @template T
|
|
124
|
+
* @param {Map<string, string>} seen
|
|
125
|
+
* @param {() => T} fn
|
|
126
|
+
* @returns {T}
|
|
127
|
+
*/
|
|
128
|
+
export function recordReads(seen, fn) {
|
|
129
|
+
installRecorder();
|
|
130
|
+
return reads.run(seen, fn);
|
|
131
|
+
}
|