banhaten 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +177 -139
- package/CONTRIBUTING.md +46 -46
- package/README.md +192 -192
- package/SECURITY.md +15 -15
- package/banhaten.config.example.json +11 -11
- package/docs/design-spacing.md +13 -13
- package/docs/design-system/README.md +22 -22
- package/docs/design-system/adoption-quality-remediation.md +87 -87
- package/docs/design-system/agent-usage.md +13 -13
- package/docs/design-system/appearance-presets.md +189 -189
- package/docs/design-system/appearances/default.md +94 -94
- package/docs/design-system/appearances/rounded.md +95 -95
- package/docs/design-system/appearances/sharp.md +95 -95
- package/docs/design-system/component-docs-pattern.md +81 -81
- package/docs/design-system/component-implementation-ownership-audit.md +245 -245
- package/docs/design-system/component-quality-audit-2026-08-20.md +225 -225
- package/docs/design-system/component-showcase-consistency-report.md +219 -219
- package/docs/design-system/component-token-consistency-audit.md +173 -173
- package/docs/design-system/components/README.md +91 -90
- package/docs/design-system/components/accordion.md +52 -52
- package/docs/design-system/components/activity-feed.md +93 -93
- package/docs/design-system/components/activity-heatmap.md +56 -56
- package/docs/design-system/components/alert-dialog.md +71 -71
- package/docs/design-system/components/alert.md +103 -97
- package/docs/design-system/components/app-header.md +97 -0
- package/docs/design-system/components/aspect-ratio.md +45 -45
- package/docs/design-system/components/attribute.md +101 -89
- package/docs/design-system/components/autocomplete.md +75 -75
- package/docs/design-system/components/avatar.md +53 -53
- package/docs/design-system/components/badge.md +55 -55
- package/docs/design-system/components/banner.md +86 -86
- package/docs/design-system/components/breadcrumbs.md +181 -177
- package/docs/design-system/components/button-group.md +97 -97
- package/docs/design-system/components/button.md +80 -80
- package/docs/design-system/components/card.md +102 -88
- package/docs/design-system/components/carousel.md +46 -46
- package/docs/design-system/components/catalog-components.md +46 -46
- package/docs/design-system/components/checkbox.md +53 -53
- package/docs/design-system/components/collapsible.md +63 -63
- package/docs/design-system/components/command.md +61 -61
- package/docs/design-system/components/context-menu.md +45 -45
- package/docs/design-system/components/date-picker.md +80 -80
- package/docs/design-system/components/divider.md +102 -102
- package/docs/design-system/components/empty-state.md +56 -56
- package/docs/design-system/components/field.md +70 -70
- package/docs/design-system/components/file-upload.md +186 -186
- package/docs/design-system/components/hover-card.md +47 -47
- package/docs/design-system/components/icons.md +48 -48
- package/docs/design-system/components/input-group.md +57 -57
- package/docs/design-system/components/input-otp.md +56 -56
- package/docs/design-system/components/input.md +111 -109
- package/docs/design-system/components/item.md +58 -58
- package/docs/design-system/components/kbd.md +49 -49
- package/docs/design-system/components/label.md +49 -49
- package/docs/design-system/components/menu.md +94 -73
- package/docs/design-system/components/menubar.md +46 -46
- package/docs/design-system/components/modal.md +104 -104
- package/docs/design-system/components/native-select.md +53 -53
- package/docs/design-system/components/navigation-menu.md +49 -49
- package/docs/design-system/components/onboarding-step-list-item.md +81 -81
- package/docs/design-system/components/page-header.md +127 -93
- package/docs/design-system/components/pagination.md +56 -56
- package/docs/design-system/components/popover.md +59 -59
- package/docs/design-system/components/progress-slider.md +49 -49
- package/docs/design-system/components/progress.md +76 -76
- package/docs/design-system/components/radio-card.md +50 -50
- package/docs/design-system/components/radio-group.md +56 -56
- package/docs/design-system/components/resizable.md +43 -43
- package/docs/design-system/components/scroll-area.md +50 -50
- package/docs/design-system/components/segmented-control.md +54 -54
- package/docs/design-system/components/select.md +68 -64
- package/docs/design-system/components/sheet.md +66 -66
- package/docs/design-system/components/sidebar.md +167 -111
- package/docs/design-system/components/skeleton.md +74 -74
- package/docs/design-system/components/slideout.md +64 -64
- package/docs/design-system/components/slider.md +62 -62
- package/docs/design-system/components/social-button.md +49 -49
- package/docs/design-system/components/spinner.md +62 -62
- package/docs/design-system/components/steps.md +65 -65
- package/docs/design-system/components/table.md +499 -422
- package/docs/design-system/components/tabs.md +74 -59
- package/docs/design-system/components/tag.md +79 -79
- package/docs/design-system/components/textarea.md +49 -49
- package/docs/design-system/components/timeline.md +82 -82
- package/docs/design-system/components/toast.md +57 -57
- package/docs/design-system/components/toggle.md +80 -80
- package/docs/design-system/components/toolbar.md +89 -89
- package/docs/design-system/components/tooltip.md +92 -92
- package/docs/design-system/components/typography.md +18 -18
- package/docs/design-system/density-and-elevation.md +65 -65
- package/docs/design-system/design-system-test-missing-items.md +368 -368
- package/docs/design-system/direction-and-localization.md +23 -23
- package/docs/design-system/form-recipes.md +73 -73
- package/docs/design-system/gap-promotion-policy.md +21 -21
- package/docs/design-system/icons.md +74 -74
- package/docs/design-system/installation.md +70 -70
- package/docs/design-system/launch-comparison-shadcn.md +98 -98
- package/docs/design-system/registry-and-cli.md +64 -64
- package/docs/design-system/release-policy.md +30 -30
- package/docs/design-system/roadmap.md +14 -14
- package/docs/design-system/support-matrix.md +23 -23
- package/docs/design-system/tabs.md +54 -54
- package/docs/design-system/template-gap-audit.md +32 -32
- package/docs/design-system/templates.md +59 -59
- package/docs/design-system/token-governance.md +72 -72
- package/package.json +98 -98
- package/registry/component-contract.json +40 -40
- package/registry/components/accordion.tsx +155 -155
- package/registry/components/alert-dialog.tsx +297 -297
- package/registry/components/alert.tsx +309 -293
- package/registry/components/app-header.tsx +151 -0
- package/registry/components/aspect-ratio.tsx +30 -30
- package/registry/components/attribute.tsx +489 -454
- package/registry/components/autocomplete.tsx +598 -598
- package/registry/components/avatar.tsx +385 -385
- package/registry/components/badge.tsx +630 -629
- package/registry/components/button-group.tsx +565 -565
- package/registry/components/button.tsx +128 -127
- package/registry/components/card.tsx +651 -638
- package/registry/components/carousel.tsx +296 -296
- package/registry/components/chart.tsx +203 -203
- package/registry/components/checkbox.tsx +247 -247
- package/registry/components/collapsible.tsx +109 -109
- package/registry/components/command.tsx +208 -208
- package/registry/components/context-menu.tsx +236 -236
- package/registry/components/date-picker.tsx +1289 -1289
- package/registry/components/expanded/ActivityFeed.tsx +227 -227
- package/registry/components/expanded/ActivityHeatmap.tsx +64 -64
- package/registry/components/expanded/Banner.tsx +179 -179
- package/registry/components/expanded/Breadcrumbs.tsx +135 -131
- package/registry/components/expanded/CatalogComponentsShowcase.tsx +205 -205
- package/registry/components/expanded/CatalogDivider.tsx +49 -49
- package/registry/components/expanded/EmptyState.tsx +155 -155
- package/registry/components/expanded/FileUpload.tsx +543 -543
- package/registry/components/expanded/OnboardingStepListItem.tsx +60 -60
- package/registry/components/expanded/PageHeader.tsx +209 -179
- package/registry/components/expanded/Slideout.tsx +108 -108
- package/registry/components/expanded/Steps.tsx +402 -402
- package/registry/components/expanded/Table.tsx +1109 -1092
- package/registry/components/expanded/Timeline.tsx +203 -203
- package/registry/components/expanded/activityFeed.css +248 -248
- package/registry/components/expanded/activityHeatmap.css +66 -66
- package/registry/components/expanded/banner.css +260 -260
- package/registry/components/expanded/breadcrumbs.css +157 -140
- package/registry/components/expanded/catalogComponentsShowcase.css +87 -87
- package/registry/components/expanded/divider.css +75 -75
- package/registry/components/expanded/emptyState.css +111 -111
- package/registry/components/expanded/fileUpload.css +457 -457
- package/registry/components/expanded/onboardingStepListItem.css +135 -135
- package/registry/components/expanded/pageHeader.css +284 -248
- package/registry/components/expanded/slideout.css +276 -276
- package/registry/components/expanded/steps.css +356 -356
- package/registry/components/expanded/table.css +655 -606
- package/registry/components/expanded/timeline.css +286 -286
- package/registry/components/field.tsx +283 -283
- package/registry/components/hover-card.tsx +48 -48
- package/registry/components/input-content.ts +107 -107
- package/registry/components/input-group.tsx +130 -130
- package/registry/components/input-otp.tsx +584 -584
- package/registry/components/input.tsx +992 -990
- package/registry/components/item.tsx +189 -189
- package/registry/components/kbd.tsx +246 -246
- package/registry/components/label.tsx +78 -78
- package/registry/components/menu.tsx +1091 -978
- package/registry/components/menubar.tsx +226 -226
- package/registry/components/modal.tsx +872 -872
- package/registry/components/native-select.tsx +205 -205
- package/registry/components/navigation-menu.tsx +171 -171
- package/registry/components/pagination.css +36 -36
- package/registry/components/pagination.tsx +560 -560
- package/registry/components/popover.tsx +338 -338
- package/registry/components/progress-slider.tsx +216 -216
- package/registry/components/radio-card.tsx +676 -676
- package/registry/components/radio-group.tsx +570 -570
- package/registry/components/resizable.tsx +108 -108
- package/registry/components/scroll-area.tsx +91 -91
- package/registry/components/segmented-control.tsx +87 -87
- package/registry/components/select-content.tsx +218 -218
- package/registry/components/select.tsx +937 -937
- package/registry/components/sheet.tsx +305 -305
- package/registry/components/sidebar.tsx +1535 -1433
- package/registry/components/skeleton.css +54 -54
- package/registry/components/skeleton.tsx +482 -482
- package/registry/components/slider.tsx +380 -380
- package/registry/components/social-button.tsx +387 -387
- package/registry/components/spinner.tsx +78 -78
- package/registry/components/table-elements.tsx +287 -128
- package/registry/components/tabs.css +246 -222
- package/registry/components/tabs.tsx +245 -241
- package/registry/components/tag.tsx +423 -423
- package/registry/components/textarea.tsx +610 -610
- package/registry/components/toast.tsx +136 -136
- package/registry/components/toggle.tsx +256 -256
- package/registry/components/toolbar.tsx +539 -539
- package/registry/components/tooltip.tsx +433 -433
- package/registry/examples/alert-demo.tsx +34 -34
- package/registry/examples/alert-dialog-demo.tsx +42 -42
- package/registry/examples/app-header-demo.tsx +40 -0
- package/registry/examples/aspect-ratio-demo.tsx +11 -11
- package/registry/examples/attribute-demo.tsx +65 -65
- package/registry/examples/autocomplete-demo.tsx +109 -109
- package/registry/examples/avatar-demo.tsx +132 -132
- package/registry/examples/badge-demo.tsx +69 -69
- package/registry/examples/button-demo.tsx +86 -86
- package/registry/examples/button-group-demo.tsx +63 -63
- package/registry/examples/card-demo.tsx +86 -86
- package/registry/examples/carousel-demo.tsx +43 -43
- package/registry/examples/chart-demo.tsx +18 -18
- package/registry/examples/checkbox-demo.tsx +62 -62
- package/registry/examples/collapsible-demo.tsx +16 -16
- package/registry/examples/collection-filter-bar-demo.tsx +135 -135
- package/registry/examples/command-demo.tsx +42 -42
- package/registry/examples/command-palette-demo.tsx +235 -235
- package/registry/examples/context-menu-demo.tsx +29 -29
- package/registry/examples/control-density-contract-demo.tsx +65 -65
- package/registry/examples/date-picker-demo.tsx +127 -127
- package/registry/examples/divider-demo.tsx +17 -17
- package/registry/examples/expanded/activity-heatmap-demo.tsx +29 -29
- package/registry/examples/expanded/banner-demo.tsx +48 -48
- package/registry/examples/expanded/breadcrumbs-demo.tsx +59 -59
- package/registry/examples/expanded/empty-state-demo.tsx +39 -39
- package/registry/examples/expanded/file-upload-demo.tsx +60 -60
- package/registry/examples/expanded/onboarding-step-demo.tsx +11 -11
- package/registry/examples/expanded/slideout-demo.tsx +15 -15
- package/registry/examples/expanded/steps-demo.tsx +29 -29
- package/registry/examples/expanded/tabs-demo.tsx +13 -13
- package/registry/examples/expanded/timeline-demo.tsx +18 -18
- package/registry/examples/field-demo.tsx +51 -51
- package/registry/examples/hover-card-demo.tsx +23 -23
- package/registry/examples/input-actions-demo.tsx +36 -36
- package/registry/examples/input-demo.tsx +87 -87
- package/registry/examples/input-group-demo.tsx +16 -16
- package/registry/examples/input-otp-demo.tsx +72 -72
- package/registry/examples/item-demo.tsx +46 -46
- package/registry/examples/kbd-demo.tsx +12 -12
- package/registry/examples/label-demo.tsx +20 -20
- package/registry/examples/menu-demo.tsx +122 -122
- package/registry/examples/menubar-demo.tsx +34 -34
- package/registry/examples/modal-demo.tsx +74 -74
- package/registry/examples/native-select-demo.tsx +16 -16
- package/registry/examples/navigation-menu-demo.tsx +29 -29
- package/registry/examples/pagination-demo.tsx +20 -20
- package/registry/examples/popover-demo.tsx +547 -547
- package/registry/examples/progress-demo.tsx +37 -37
- package/registry/examples/progress-slider-demo.tsx +29 -29
- package/registry/examples/radio-card-demo.tsx +51 -51
- package/registry/examples/radio-group-demo.tsx +62 -62
- package/registry/examples/resizable-demo.tsx +22 -22
- package/registry/examples/results-toolbar-demo.tsx +44 -44
- package/registry/examples/scroll-area-demo.tsx +15 -15
- package/registry/examples/segmented-control-demo.tsx +39 -39
- package/registry/examples/select-demo.tsx +87 -87
- package/registry/examples/sheet-demo.tsx +47 -47
- package/registry/examples/sidebar-demo.tsx +101 -101
- package/registry/examples/slider-demo.tsx +31 -31
- package/registry/examples/social-button-demo.tsx +61 -61
- package/registry/examples/spinner-demo.tsx +59 -59
- package/registry/examples/tabs-demo.tsx +13 -13
- package/registry/examples/tag-demo.tsx +29 -29
- package/registry/examples/textarea-demo.tsx +79 -79
- package/registry/examples/toast-demo.tsx +35 -35
- package/registry/examples/toggle-demo.tsx +59 -59
- package/registry/examples/tooltip-demo.tsx +115 -115
- package/registry/guidance/USAGE.md +27 -27
- package/registry/hooks/use-direction.ts +207 -207
- package/registry/index.json +387 -17
- package/registry/styles/globals.css +46 -0
- package/registry.json +24 -2
- package/schema/config.schema.json +51 -51
- package/schema/registry.schema.json +194 -194
- package/schema/tokens.schema.json +167 -167
- package/src/cli/command-line.js +194 -194
- package/src/cli/config-editors.js +9 -9
- package/src/cli/doctor.js +399 -399
- package/src/cli/file-transaction.js +302 -302
- package/src/cli/global-css.js +153 -153
- package/src/cli/index.js +1002 -1002
- package/src/cli/install-plan.js +592 -592
- package/src/cli/install-receipt.js +171 -171
- package/src/cli/jsonc.js +203 -203
- package/src/cli/package-install-state.js +254 -254
- package/src/cli/package-manager.js +238 -238
- package/src/cli/package-plan.js +185 -185
- package/src/cli/package-version.js +97 -97
- package/src/cli/registry-query.js +207 -207
- package/src/cli/reporting.js +55 -55
- package/src/cli/schema-validation.js +19 -19
- package/src/cli/source-scanner.js +686 -686
- package/src/cli/vite-config.js +478 -478
- package/tokens/banhaten.tokens.json +184 -0
|
@@ -1,254 +1,254 @@
|
|
|
1
|
-
import fs from "node:fs/promises"
|
|
2
|
-
import path from "node:path"
|
|
3
|
-
|
|
4
|
-
import { inspectPackageManagerContext } from "./package-manager.js"
|
|
5
|
-
import {
|
|
6
|
-
dependencyVersionSatisfies,
|
|
7
|
-
findPackageDependencies,
|
|
8
|
-
} from "./package-version.js"
|
|
9
|
-
|
|
10
|
-
const DEPENDENCY_SECTIONS = [
|
|
11
|
-
"dependencies",
|
|
12
|
-
"devDependencies",
|
|
13
|
-
"peerDependencies",
|
|
14
|
-
"optionalDependencies",
|
|
15
|
-
]
|
|
16
|
-
|
|
17
|
-
export async function inspectPackageInstallState({
|
|
18
|
-
cwd,
|
|
19
|
-
packageJson,
|
|
20
|
-
requirements,
|
|
21
|
-
}) {
|
|
22
|
-
const managerState = await inspectPackageManagerContext(cwd)
|
|
23
|
-
const { context } = managerState
|
|
24
|
-
const installedPackages = new Map()
|
|
25
|
-
|
|
26
|
-
for (const requirement of requirements) {
|
|
27
|
-
const installed = await findInstalledPackage(
|
|
28
|
-
cwd,
|
|
29
|
-
context.root,
|
|
30
|
-
requirement.name
|
|
31
|
-
)
|
|
32
|
-
if (
|
|
33
|
-
!installed ||
|
|
34
|
-
!dependencyVersionSatisfies(installed.version, requirement.range)
|
|
35
|
-
) {
|
|
36
|
-
return { evidence: [], required: true }
|
|
37
|
-
}
|
|
38
|
-
installedPackages.set(requirement.name, installed)
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
if (!context.lockfilePath) {
|
|
42
|
-
return {
|
|
43
|
-
evidence: createCleanEvidence(managerState, installedPackages),
|
|
44
|
-
required: false,
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
if (context.manager !== "npm") {
|
|
49
|
-
return { evidence: [], required: true }
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const lockEvidence = await readJsonEvidence(context.lockfilePath)
|
|
53
|
-
if (!lockEvidence) return { evidence: [], required: true }
|
|
54
|
-
|
|
55
|
-
const lockState = inspectNpmLock({
|
|
56
|
-
cwd,
|
|
57
|
-
lock: lockEvidence.value,
|
|
58
|
-
lockRoot: context.root,
|
|
59
|
-
packageJson,
|
|
60
|
-
requirements,
|
|
61
|
-
})
|
|
62
|
-
if (!lockState.compatible) return { evidence: [], required: true }
|
|
63
|
-
|
|
64
|
-
for (const [name, installed] of installedPackages) {
|
|
65
|
-
if (installed.version !== lockState.lockedVersions.get(name)) {
|
|
66
|
-
return { evidence: [], required: true }
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
return {
|
|
71
|
-
evidence: createCleanEvidence(
|
|
72
|
-
managerState,
|
|
73
|
-
installedPackages,
|
|
74
|
-
[lockEvidence]
|
|
75
|
-
),
|
|
76
|
-
required: false,
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function createCleanEvidence(
|
|
81
|
-
managerState,
|
|
82
|
-
installedPackages,
|
|
83
|
-
extra = []
|
|
84
|
-
) {
|
|
85
|
-
return [
|
|
86
|
-
...managerState.evidence,
|
|
87
|
-
...extra,
|
|
88
|
-
...[...installedPackages.values()].map(toEvidence),
|
|
89
|
-
]
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
export function inspectNpmLock({
|
|
93
|
-
cwd,
|
|
94
|
-
lock,
|
|
95
|
-
lockRoot,
|
|
96
|
-
packageJson,
|
|
97
|
-
requirements,
|
|
98
|
-
}) {
|
|
99
|
-
if (
|
|
100
|
-
!lock ||
|
|
101
|
-
typeof lock !== "object" ||
|
|
102
|
-
Array.isArray(lock) ||
|
|
103
|
-
![2, 3].includes(lock.lockfileVersion) ||
|
|
104
|
-
!lock.packages ||
|
|
105
|
-
typeof lock.packages !== "object" ||
|
|
106
|
-
Array.isArray(lock.packages)
|
|
107
|
-
) {
|
|
108
|
-
return { compatible: false, lockedVersions: new Map() }
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
const importerKey = path.relative(lockRoot, cwd).replaceAll("\\", "/")
|
|
112
|
-
const importer = lock.packages[importerKey]
|
|
113
|
-
if (!importer || typeof importer !== "object" || Array.isArray(importer)) {
|
|
114
|
-
return { compatible: false, lockedVersions: new Map() }
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
for (const section of DEPENDENCY_SECTIONS) {
|
|
118
|
-
if (!dependencySectionMatches(packageJson[section], importer[section])) {
|
|
119
|
-
return { compatible: false, lockedVersions: new Map() }
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
const lockedVersions = new Map()
|
|
124
|
-
for (const requirement of requirements) {
|
|
125
|
-
const lockedVersion = findModernLockedVersion(
|
|
126
|
-
lock.packages,
|
|
127
|
-
importerKey,
|
|
128
|
-
requirement.name
|
|
129
|
-
)
|
|
130
|
-
const declarations = findPackageDependencies(packageJson, requirement.name)
|
|
131
|
-
if (
|
|
132
|
-
!dependencyVersionSatisfies(lockedVersion, requirement.range) ||
|
|
133
|
-
declarations.length === 0 ||
|
|
134
|
-
declarations.some(
|
|
135
|
-
({ version }) => !dependencyVersionSatisfies(lockedVersion, version)
|
|
136
|
-
)
|
|
137
|
-
) {
|
|
138
|
-
return { compatible: false, lockedVersions: new Map() }
|
|
139
|
-
}
|
|
140
|
-
lockedVersions.set(requirement.name, lockedVersion)
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
return { compatible: true, lockedVersions }
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
function dependencySectionMatches(manifestSection, lockSection) {
|
|
147
|
-
const manifest = normalizeDependencySection(manifestSection)
|
|
148
|
-
const locked = normalizeDependencySection(lockSection)
|
|
149
|
-
if (!manifest || !locked) return false
|
|
150
|
-
const manifestEntries = Object.entries(manifest)
|
|
151
|
-
const lockedEntries = Object.entries(locked)
|
|
152
|
-
|
|
153
|
-
return manifestEntries.length === lockedEntries.length &&
|
|
154
|
-
manifestEntries.every(([name, range]) => locked[name] === range)
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
function normalizeDependencySection(value) {
|
|
158
|
-
if (value === undefined) return {}
|
|
159
|
-
return value && typeof value === "object" && !Array.isArray(value)
|
|
160
|
-
? value
|
|
161
|
-
: null
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
function findModernLockedVersion(packages, importerKey, name) {
|
|
165
|
-
let directory = importerKey
|
|
166
|
-
|
|
167
|
-
while (true) {
|
|
168
|
-
const prefix = directory ? `${directory}/` : ""
|
|
169
|
-
const entryKey = `${prefix}node_modules/${name}`
|
|
170
|
-
if (Object.hasOwn(packages, entryKey)) {
|
|
171
|
-
const entry = packages[entryKey]
|
|
172
|
-
return entry && typeof entry.version === "string" && !entry.link
|
|
173
|
-
? entry.version
|
|
174
|
-
: null
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
if (!directory) return null
|
|
178
|
-
const parent = path.posix.dirname(directory)
|
|
179
|
-
directory = parent === "." ? "" : parent
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
async function findInstalledPackage(cwd, projectRoot, name) {
|
|
184
|
-
for (const nodeModulesPath of nodeModulesSearchPaths(cwd, projectRoot)) {
|
|
185
|
-
const manifestPath = path.join(
|
|
186
|
-
nodeModulesPath,
|
|
187
|
-
...name.split("/"),
|
|
188
|
-
"package.json"
|
|
189
|
-
)
|
|
190
|
-
|
|
191
|
-
try {
|
|
192
|
-
const source = await fs.readFile(manifestPath)
|
|
193
|
-
const manifest = JSON.parse(source.toString("utf8"))
|
|
194
|
-
if (manifest.name === name && typeof manifest.version === "string") {
|
|
195
|
-
return { path: manifestPath, source, version: manifest.version }
|
|
196
|
-
}
|
|
197
|
-
return null
|
|
198
|
-
} catch (error) {
|
|
199
|
-
if (error.code === "ENOENT") continue
|
|
200
|
-
if (error instanceof SyntaxError) return null
|
|
201
|
-
throw error
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
return null
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
function nodeModulesSearchPaths(cwd, projectRoot) {
|
|
209
|
-
const paths = []
|
|
210
|
-
const boundary = path.resolve(projectRoot)
|
|
211
|
-
let directory = path.resolve(cwd)
|
|
212
|
-
|
|
213
|
-
while (true) {
|
|
214
|
-
paths.push(path.join(directory, "node_modules"))
|
|
215
|
-
if (directory === boundary) return paths
|
|
216
|
-
|
|
217
|
-
const parent = path.dirname(directory)
|
|
218
|
-
if (parent === directory || !isWithin(parent, boundary)) return paths
|
|
219
|
-
directory = parent
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
function isWithin(directory, boundary) {
|
|
224
|
-
const relative = path.relative(boundary, directory)
|
|
225
|
-
return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative))
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
async function readJsonEvidence(filePath) {
|
|
229
|
-
const evidence = await readFileEvidence(filePath)
|
|
230
|
-
if (!evidence) return null
|
|
231
|
-
|
|
232
|
-
try {
|
|
233
|
-
return {
|
|
234
|
-
...evidence,
|
|
235
|
-
value: JSON.parse(evidence.source.toString("utf8")),
|
|
236
|
-
}
|
|
237
|
-
} catch (error) {
|
|
238
|
-
if (error instanceof SyntaxError) return null
|
|
239
|
-
throw error
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
async function readFileEvidence(filePath) {
|
|
244
|
-
try {
|
|
245
|
-
return { path: filePath, source: await fs.readFile(filePath) }
|
|
246
|
-
} catch (error) {
|
|
247
|
-
if (error.code === "ENOENT") return null
|
|
248
|
-
throw error
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
function toEvidence({ path: evidencePath, source }) {
|
|
253
|
-
return { path: evidencePath, source }
|
|
254
|
-
}
|
|
1
|
+
import fs from "node:fs/promises"
|
|
2
|
+
import path from "node:path"
|
|
3
|
+
|
|
4
|
+
import { inspectPackageManagerContext } from "./package-manager.js"
|
|
5
|
+
import {
|
|
6
|
+
dependencyVersionSatisfies,
|
|
7
|
+
findPackageDependencies,
|
|
8
|
+
} from "./package-version.js"
|
|
9
|
+
|
|
10
|
+
const DEPENDENCY_SECTIONS = [
|
|
11
|
+
"dependencies",
|
|
12
|
+
"devDependencies",
|
|
13
|
+
"peerDependencies",
|
|
14
|
+
"optionalDependencies",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
export async function inspectPackageInstallState({
|
|
18
|
+
cwd,
|
|
19
|
+
packageJson,
|
|
20
|
+
requirements,
|
|
21
|
+
}) {
|
|
22
|
+
const managerState = await inspectPackageManagerContext(cwd)
|
|
23
|
+
const { context } = managerState
|
|
24
|
+
const installedPackages = new Map()
|
|
25
|
+
|
|
26
|
+
for (const requirement of requirements) {
|
|
27
|
+
const installed = await findInstalledPackage(
|
|
28
|
+
cwd,
|
|
29
|
+
context.root,
|
|
30
|
+
requirement.name
|
|
31
|
+
)
|
|
32
|
+
if (
|
|
33
|
+
!installed ||
|
|
34
|
+
!dependencyVersionSatisfies(installed.version, requirement.range)
|
|
35
|
+
) {
|
|
36
|
+
return { evidence: [], required: true }
|
|
37
|
+
}
|
|
38
|
+
installedPackages.set(requirement.name, installed)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (!context.lockfilePath) {
|
|
42
|
+
return {
|
|
43
|
+
evidence: createCleanEvidence(managerState, installedPackages),
|
|
44
|
+
required: false,
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (context.manager !== "npm") {
|
|
49
|
+
return { evidence: [], required: true }
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const lockEvidence = await readJsonEvidence(context.lockfilePath)
|
|
53
|
+
if (!lockEvidence) return { evidence: [], required: true }
|
|
54
|
+
|
|
55
|
+
const lockState = inspectNpmLock({
|
|
56
|
+
cwd,
|
|
57
|
+
lock: lockEvidence.value,
|
|
58
|
+
lockRoot: context.root,
|
|
59
|
+
packageJson,
|
|
60
|
+
requirements,
|
|
61
|
+
})
|
|
62
|
+
if (!lockState.compatible) return { evidence: [], required: true }
|
|
63
|
+
|
|
64
|
+
for (const [name, installed] of installedPackages) {
|
|
65
|
+
if (installed.version !== lockState.lockedVersions.get(name)) {
|
|
66
|
+
return { evidence: [], required: true }
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
evidence: createCleanEvidence(
|
|
72
|
+
managerState,
|
|
73
|
+
installedPackages,
|
|
74
|
+
[lockEvidence]
|
|
75
|
+
),
|
|
76
|
+
required: false,
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function createCleanEvidence(
|
|
81
|
+
managerState,
|
|
82
|
+
installedPackages,
|
|
83
|
+
extra = []
|
|
84
|
+
) {
|
|
85
|
+
return [
|
|
86
|
+
...managerState.evidence,
|
|
87
|
+
...extra,
|
|
88
|
+
...[...installedPackages.values()].map(toEvidence),
|
|
89
|
+
]
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function inspectNpmLock({
|
|
93
|
+
cwd,
|
|
94
|
+
lock,
|
|
95
|
+
lockRoot,
|
|
96
|
+
packageJson,
|
|
97
|
+
requirements,
|
|
98
|
+
}) {
|
|
99
|
+
if (
|
|
100
|
+
!lock ||
|
|
101
|
+
typeof lock !== "object" ||
|
|
102
|
+
Array.isArray(lock) ||
|
|
103
|
+
![2, 3].includes(lock.lockfileVersion) ||
|
|
104
|
+
!lock.packages ||
|
|
105
|
+
typeof lock.packages !== "object" ||
|
|
106
|
+
Array.isArray(lock.packages)
|
|
107
|
+
) {
|
|
108
|
+
return { compatible: false, lockedVersions: new Map() }
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const importerKey = path.relative(lockRoot, cwd).replaceAll("\\", "/")
|
|
112
|
+
const importer = lock.packages[importerKey]
|
|
113
|
+
if (!importer || typeof importer !== "object" || Array.isArray(importer)) {
|
|
114
|
+
return { compatible: false, lockedVersions: new Map() }
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
for (const section of DEPENDENCY_SECTIONS) {
|
|
118
|
+
if (!dependencySectionMatches(packageJson[section], importer[section])) {
|
|
119
|
+
return { compatible: false, lockedVersions: new Map() }
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const lockedVersions = new Map()
|
|
124
|
+
for (const requirement of requirements) {
|
|
125
|
+
const lockedVersion = findModernLockedVersion(
|
|
126
|
+
lock.packages,
|
|
127
|
+
importerKey,
|
|
128
|
+
requirement.name
|
|
129
|
+
)
|
|
130
|
+
const declarations = findPackageDependencies(packageJson, requirement.name)
|
|
131
|
+
if (
|
|
132
|
+
!dependencyVersionSatisfies(lockedVersion, requirement.range) ||
|
|
133
|
+
declarations.length === 0 ||
|
|
134
|
+
declarations.some(
|
|
135
|
+
({ version }) => !dependencyVersionSatisfies(lockedVersion, version)
|
|
136
|
+
)
|
|
137
|
+
) {
|
|
138
|
+
return { compatible: false, lockedVersions: new Map() }
|
|
139
|
+
}
|
|
140
|
+
lockedVersions.set(requirement.name, lockedVersion)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return { compatible: true, lockedVersions }
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function dependencySectionMatches(manifestSection, lockSection) {
|
|
147
|
+
const manifest = normalizeDependencySection(manifestSection)
|
|
148
|
+
const locked = normalizeDependencySection(lockSection)
|
|
149
|
+
if (!manifest || !locked) return false
|
|
150
|
+
const manifestEntries = Object.entries(manifest)
|
|
151
|
+
const lockedEntries = Object.entries(locked)
|
|
152
|
+
|
|
153
|
+
return manifestEntries.length === lockedEntries.length &&
|
|
154
|
+
manifestEntries.every(([name, range]) => locked[name] === range)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function normalizeDependencySection(value) {
|
|
158
|
+
if (value === undefined) return {}
|
|
159
|
+
return value && typeof value === "object" && !Array.isArray(value)
|
|
160
|
+
? value
|
|
161
|
+
: null
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function findModernLockedVersion(packages, importerKey, name) {
|
|
165
|
+
let directory = importerKey
|
|
166
|
+
|
|
167
|
+
while (true) {
|
|
168
|
+
const prefix = directory ? `${directory}/` : ""
|
|
169
|
+
const entryKey = `${prefix}node_modules/${name}`
|
|
170
|
+
if (Object.hasOwn(packages, entryKey)) {
|
|
171
|
+
const entry = packages[entryKey]
|
|
172
|
+
return entry && typeof entry.version === "string" && !entry.link
|
|
173
|
+
? entry.version
|
|
174
|
+
: null
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (!directory) return null
|
|
178
|
+
const parent = path.posix.dirname(directory)
|
|
179
|
+
directory = parent === "." ? "" : parent
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async function findInstalledPackage(cwd, projectRoot, name) {
|
|
184
|
+
for (const nodeModulesPath of nodeModulesSearchPaths(cwd, projectRoot)) {
|
|
185
|
+
const manifestPath = path.join(
|
|
186
|
+
nodeModulesPath,
|
|
187
|
+
...name.split("/"),
|
|
188
|
+
"package.json"
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
try {
|
|
192
|
+
const source = await fs.readFile(manifestPath)
|
|
193
|
+
const manifest = JSON.parse(source.toString("utf8"))
|
|
194
|
+
if (manifest.name === name && typeof manifest.version === "string") {
|
|
195
|
+
return { path: manifestPath, source, version: manifest.version }
|
|
196
|
+
}
|
|
197
|
+
return null
|
|
198
|
+
} catch (error) {
|
|
199
|
+
if (error.code === "ENOENT") continue
|
|
200
|
+
if (error instanceof SyntaxError) return null
|
|
201
|
+
throw error
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return null
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function nodeModulesSearchPaths(cwd, projectRoot) {
|
|
209
|
+
const paths = []
|
|
210
|
+
const boundary = path.resolve(projectRoot)
|
|
211
|
+
let directory = path.resolve(cwd)
|
|
212
|
+
|
|
213
|
+
while (true) {
|
|
214
|
+
paths.push(path.join(directory, "node_modules"))
|
|
215
|
+
if (directory === boundary) return paths
|
|
216
|
+
|
|
217
|
+
const parent = path.dirname(directory)
|
|
218
|
+
if (parent === directory || !isWithin(parent, boundary)) return paths
|
|
219
|
+
directory = parent
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function isWithin(directory, boundary) {
|
|
224
|
+
const relative = path.relative(boundary, directory)
|
|
225
|
+
return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative))
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
async function readJsonEvidence(filePath) {
|
|
229
|
+
const evidence = await readFileEvidence(filePath)
|
|
230
|
+
if (!evidence) return null
|
|
231
|
+
|
|
232
|
+
try {
|
|
233
|
+
return {
|
|
234
|
+
...evidence,
|
|
235
|
+
value: JSON.parse(evidence.source.toString("utf8")),
|
|
236
|
+
}
|
|
237
|
+
} catch (error) {
|
|
238
|
+
if (error instanceof SyntaxError) return null
|
|
239
|
+
throw error
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
async function readFileEvidence(filePath) {
|
|
244
|
+
try {
|
|
245
|
+
return { path: filePath, source: await fs.readFile(filePath) }
|
|
246
|
+
} catch (error) {
|
|
247
|
+
if (error.code === "ENOENT") return null
|
|
248
|
+
throw error
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function toEvidence({ path: evidencePath, source }) {
|
|
253
|
+
return { path: evidencePath, source }
|
|
254
|
+
}
|