banhaten 0.4.0 → 0.5.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 +139 -100
- package/CONTRIBUTING.md +46 -44
- package/README.md +192 -190
- package/SECURITY.md +15 -15
- package/banhaten.config.example.json +11 -11
- package/docs/design-spacing.md +13 -0
- package/docs/design-system/README.md +22 -20
- 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 +90 -76
- package/docs/design-system/components/accordion.md +53 -53
- package/docs/design-system/components/activity-feed.md +94 -94
- package/docs/design-system/components/activity-heatmap.md +57 -57
- package/docs/design-system/components/agent-input.md +85 -0
- package/docs/design-system/components/alert-dialog.md +72 -72
- package/docs/design-system/components/alert.md +97 -97
- package/docs/design-system/components/approval-card.md +134 -0
- package/docs/design-system/components/aspect-ratio.md +45 -45
- package/docs/design-system/components/attribute.md +88 -88
- package/docs/design-system/components/autocomplete.md +75 -75
- package/docs/design-system/components/avatar.md +54 -54
- package/docs/design-system/components/badge.md +54 -54
- package/docs/design-system/components/banner.md +87 -87
- package/docs/design-system/components/breadcrumbs.md +176 -175
- package/docs/design-system/components/button-group.md +98 -98
- package/docs/design-system/components/button.md +83 -80
- package/docs/design-system/components/card.md +88 -88
- package/docs/design-system/components/carousel.md +46 -46
- package/docs/design-system/components/catalog-components.md +47 -47
- package/docs/design-system/components/chart.md +95 -24
- package/docs/design-system/components/checkbox.md +54 -54
- package/docs/design-system/components/collapsible.md +67 -53
- package/docs/design-system/components/command.md +62 -62
- package/docs/design-system/components/context-menu.md +46 -46
- package/docs/design-system/components/date-picker.md +81 -81
- 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 +187 -187
- package/docs/design-system/components/hover-card.md +48 -48
- package/docs/design-system/components/icons.md +48 -48
- package/docs/design-system/components/inline-citations.md +93 -0
- package/docs/design-system/components/input-group.md +58 -58
- package/docs/design-system/components/input-otp.md +56 -56
- package/docs/design-system/components/input.md +114 -60
- package/docs/design-system/components/item.md +59 -59
- package/docs/design-system/components/kbd.md +51 -49
- package/docs/design-system/components/label.md +49 -49
- package/docs/design-system/components/menu.md +71 -61
- package/docs/design-system/components/menubar.md +47 -47
- package/docs/design-system/components/modal.md +105 -105
- package/docs/design-system/components/native-select.md +54 -54
- package/docs/design-system/components/navigation-menu.md +50 -50
- package/docs/design-system/components/onboarding-step-list-item.md +82 -82
- package/docs/design-system/components/page-header.md +93 -86
- package/docs/design-system/components/pagination.md +57 -57
- package/docs/design-system/components/popover.md +60 -60
- package/docs/design-system/components/progress-slider.md +50 -50
- package/docs/design-system/components/progress.md +76 -76
- package/docs/design-system/components/radio-card.md +51 -51
- package/docs/design-system/components/radio-group.md +57 -57
- package/docs/design-system/components/resizable.md +44 -44
- package/docs/design-system/components/rolling-count.md +61 -0
- package/docs/design-system/components/scroll-area.md +51 -51
- package/docs/design-system/components/segmented-control.md +54 -54
- package/docs/design-system/components/select.md +68 -62
- package/docs/design-system/components/sheet.md +67 -67
- package/docs/design-system/components/shimmer-text.md +67 -0
- package/docs/design-system/components/sidebar.md +110 -99
- package/docs/design-system/components/skeleton.md +75 -75
- package/docs/design-system/components/slideout.md +65 -65
- package/docs/design-system/components/slider.md +63 -63
- package/docs/design-system/components/social-button.md +50 -50
- package/docs/design-system/components/spinner.md +62 -62
- package/docs/design-system/components/steps.md +65 -65
- package/docs/design-system/components/streaming-text.md +82 -0
- package/docs/design-system/components/table.md +419 -414
- package/docs/design-system/components/tabs.md +59 -55
- package/docs/design-system/components/tag.md +80 -80
- package/docs/design-system/components/task-list.md +95 -0
- package/docs/design-system/components/textarea.md +50 -50
- package/docs/design-system/components/thinking-reasoning.md +72 -0
- package/docs/design-system/components/thinking-state.md +67 -0
- package/docs/design-system/components/timeline.md +83 -83
- package/docs/design-system/components/toast.md +58 -58
- package/docs/design-system/components/toggle.md +81 -81
- package/docs/design-system/components/toolbar.md +92 -87
- package/docs/design-system/components/tooltip.md +94 -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 -68
- 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 -53
- package/docs/design-system/template-gap-audit.md +32 -0
- package/docs/design-system/templates.md +59 -0
- package/docs/design-system/token-governance.md +72 -59
- package/package.json +98 -98
- package/registry/component-contract.json +40 -40
- package/registry/components/accordion.tsx +155 -155
- package/registry/components/agent-input-parts.tsx +552 -0
- package/registry/components/agent-input-state.ts +156 -0
- package/registry/components/agent-input.css +307 -0
- package/registry/components/agent-input.tsx +382 -0
- package/registry/components/alert-dialog.tsx +297 -297
- package/registry/components/alert.tsx +293 -293
- package/registry/components/approval-card.css +409 -0
- package/registry/components/approval-card.tsx +401 -0
- package/registry/components/approval-frame.tsx +108 -0
- package/registry/components/approval-questions.tsx +559 -0
- package/registry/components/aspect-ratio.tsx +30 -30
- package/registry/components/attribute.tsx +454 -454
- package/registry/components/autocomplete.tsx +598 -651
- package/registry/components/avatar.tsx +385 -385
- package/registry/components/badge.tsx +629 -629
- package/registry/components/button-group.tsx +565 -565
- package/registry/components/button.tsx +127 -123
- package/registry/components/card.tsx +638 -638
- package/registry/components/carousel.tsx +296 -296
- package/registry/components/chart-block-data.ts +177 -0
- package/registry/components/chart-block.css +171 -0
- package/registry/components/chart-block.tsx +260 -0
- package/registry/components/chart-interactions.ts +123 -0
- package/registry/components/chart-options.cartesian.ts +162 -0
- package/registry/components/chart-options.paint.ts +99 -0
- package/registry/components/chart-options.parts.ts +72 -0
- package/registry/components/chart-options.radial.ts +109 -0
- package/registry/components/chart-options.ts +44 -0
- package/registry/components/chart-options.types.ts +67 -0
- package/registry/components/chart.tsx +203 -203
- package/registry/components/checkbox.tsx +247 -247
- package/registry/components/collapsible.tsx +109 -69
- package/registry/components/command.tsx +208 -208
- package/registry/components/context-menu.tsx +236 -236
- package/registry/components/data-chart.tsx +40 -0
- package/registry/components/date-picker.tsx +1289 -1289
- package/registry/components/echart-theme.ts +246 -0
- package/registry/components/echart.css +49 -0
- package/registry/components/echart.tsx +178 -0
- 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 +131 -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 +179 -166
- package/registry/components/expanded/Slideout.tsx +108 -108
- package/registry/components/expanded/Steps.tsx +402 -402
- package/registry/components/expanded/Table.tsx +1092 -1071
- 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 +140 -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 +248 -290
- package/registry/components/expanded/slideout.css +276 -276
- package/registry/components/expanded/steps.css +356 -356
- package/registry/components/expanded/table.css +606 -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/inline-citations.css +126 -0
- package/registry/components/inline-citations.tsx +292 -0
- 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 +990 -979
- package/registry/components/item.tsx +189 -189
- package/registry/components/kbd.tsx +246 -245
- package/registry/components/label.tsx +78 -78
- package/registry/components/menu.tsx +978 -986
- 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/rolling-count.css +37 -0
- package/registry/components/rolling-count.tsx +110 -0
- 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 -932
- package/registry/components/sheet.tsx +305 -305
- package/registry/components/shimmer-text.css +69 -0
- package/registry/components/shimmer-text.tsx +43 -0
- package/registry/components/sidebar.tsx +1433 -1230
- 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/streaming-text.css +45 -0
- package/registry/components/streaming-text.tsx +165 -0
- package/registry/components/table-elements.tsx +128 -128
- package/registry/components/tabs.css +222 -218
- package/registry/components/tabs.tsx +241 -241
- package/registry/components/tag.tsx +423 -423
- package/registry/components/task-list.css +328 -0
- package/registry/components/task-list.tsx +429 -0
- package/registry/components/textarea.tsx +610 -610
- package/registry/components/thinking-reasoning.css +178 -0
- package/registry/components/thinking-reasoning.tsx +253 -0
- package/registry/components/thinking-state.css +8 -0
- package/registry/components/thinking-state.tsx +46 -0
- 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 -431
- package/registry/examples/agent-input-demo.tsx +90 -0
- package/registry/examples/alert-demo.tsx +34 -34
- package/registry/examples/alert-dialog-demo.tsx +42 -42
- package/registry/examples/approval-card-demo.tsx +83 -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 -83
- 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-block-demo.tsx +5 -0
- package/registry/examples/chart-demo.tsx +18 -33
- 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 -61
- 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/inline-citations-demo.tsx +31 -0
- package/registry/examples/input-actions-demo.tsx +36 -0
- 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 -546
- 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/rolling-count-demo.tsx +39 -0
- 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/shimmer-text-demo.tsx +20 -0
- 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/streaming-text-demo.tsx +28 -0
- package/registry/examples/tabs-demo.tsx +13 -13
- package/registry/examples/tag-demo.tsx +29 -29
- package/registry/examples/task-list-demo.tsx +74 -0
- package/registry/examples/textarea-demo.tsx +79 -79
- package/registry/examples/thinking-reasoning-demo.tsx +74 -0
- package/registry/examples/thinking-state-demo.tsx +13 -0
- 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/guidance/charts.LICENSE.txt +24 -0
- package/registry/hooks/use-controllable-state.ts +33 -0
- package/registry/hooks/use-direction.ts +207 -207
- package/registry/hooks/use-listbox-navigation.ts +73 -0
- package/registry/hooks/use-motion-end.ts +55 -0
- package/registry/hooks/use-reduced-motion.ts +28 -0
- package/registry/index.json +1403 -155
- package/registry/styles/globals.css +946 -1088
- package/registry.json +361 -8
- package/schema/config.schema.json +51 -51
- package/schema/registry.schema.json +194 -190
- package/schema/tokens.schema.json +167 -163
- 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 -295
- package/src/cli/global-css.js +153 -153
- package/src/cli/index.js +1002 -1009
- 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 +306 -192
|
@@ -1,238 +1,238 @@
|
|
|
1
|
-
import fs from "node:fs/promises"
|
|
2
|
-
import path from "node:path"
|
|
3
|
-
import { minimatch } from "minimatch"
|
|
4
|
-
import { parse as parseYaml } from "yaml"
|
|
5
|
-
|
|
6
|
-
const LOCKFILES = [
|
|
7
|
-
{ manager: "pnpm", name: "pnpm-lock.yaml" },
|
|
8
|
-
{ manager: "yarn", name: "yarn.lock" },
|
|
9
|
-
{ manager: "bun", name: "bun.lockb" },
|
|
10
|
-
{ manager: "bun", name: "bun.lock" },
|
|
11
|
-
{ manager: "npm", name: "npm-shrinkwrap.json" },
|
|
12
|
-
{ manager: "npm", name: "package-lock.json" },
|
|
13
|
-
]
|
|
14
|
-
|
|
15
|
-
export function packageManagerStatePaths(root) {
|
|
16
|
-
return [
|
|
17
|
-
path.join(root, "package.json"),
|
|
18
|
-
path.join(root, "pnpm-workspace.yaml"),
|
|
19
|
-
...LOCKFILES.map(({ name }) => path.join(root, name)),
|
|
20
|
-
]
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export async function inspectPackageManagerContext(cwd) {
|
|
24
|
-
const context = await resolvePackageManagerContext(cwd)
|
|
25
|
-
const paths = new Set(
|
|
26
|
-
context.evidenceRoots.flatMap((root) => packageManagerStatePaths(root))
|
|
27
|
-
)
|
|
28
|
-
const evidence = await Promise.all(
|
|
29
|
-
[...paths].map(async (filePath) => {
|
|
30
|
-
try {
|
|
31
|
-
return { path: filePath, source: await fs.readFile(filePath) }
|
|
32
|
-
} catch (error) {
|
|
33
|
-
if (error.code === "ENOENT") return { path: filePath, source: null }
|
|
34
|
-
throw error
|
|
35
|
-
}
|
|
36
|
-
})
|
|
37
|
-
)
|
|
38
|
-
const confirmedContext = await resolvePackageManagerContext(cwd)
|
|
39
|
-
if (!samePackageManagerContext(context, confirmedContext)) {
|
|
40
|
-
throw new Error("package-manager context changed while preparing the install plan")
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
return { context, evidence }
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export async function resolvePackageManagerContext(cwd) {
|
|
47
|
-
const projectRoot = path.resolve(cwd)
|
|
48
|
-
const localContext = await inspectPackageManagerRoot(projectRoot)
|
|
49
|
-
const workspace = await findWorkspaceRoot(projectRoot)
|
|
50
|
-
if (workspace.root) {
|
|
51
|
-
const workspaceContext = await inspectPackageManagerRoot(workspace.root)
|
|
52
|
-
if (workspaceContext) {
|
|
53
|
-
if (
|
|
54
|
-
localContext &&
|
|
55
|
-
localContext.manager !== workspaceContext.manager
|
|
56
|
-
) {
|
|
57
|
-
throw new Error(
|
|
58
|
-
`${projectRoot} declares packageManager "${localContext.manager}", ` +
|
|
59
|
-
`but its workspace root uses "${workspaceContext.manager}".`
|
|
60
|
-
)
|
|
61
|
-
}
|
|
62
|
-
return withEvidenceRoots(workspaceContext, workspace.evidenceRoots)
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
if (localContext) {
|
|
67
|
-
return withEvidenceRoots(localContext, workspace.evidenceRoots)
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
return {
|
|
71
|
-
evidenceRoots: workspace.evidenceRoots,
|
|
72
|
-
lockfilePath: null,
|
|
73
|
-
manager: "npm",
|
|
74
|
-
root: projectRoot,
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
async function inspectPackageManagerRoot(root) {
|
|
79
|
-
const packageJson = await readJsonIfValid(path.join(root, "package.json"))
|
|
80
|
-
const declaredManager = parsePackageManager(packageJson?.packageManager)
|
|
81
|
-
|
|
82
|
-
if (declaredManager) {
|
|
83
|
-
const lockfile = await findManagerLockfile(root, declaredManager)
|
|
84
|
-
return {
|
|
85
|
-
lockfilePath: lockfile ? path.join(root, lockfile.name) : null,
|
|
86
|
-
manager: declaredManager,
|
|
87
|
-
root,
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
const lockfiles = []
|
|
92
|
-
for (const lockfile of LOCKFILES) {
|
|
93
|
-
if (await exists(path.join(root, lockfile.name))) lockfiles.push(lockfile)
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const managers = new Set(lockfiles.map(({ manager }) => manager))
|
|
97
|
-
if (managers.size > 1) {
|
|
98
|
-
throw new Error(
|
|
99
|
-
`${root} contains lockfiles for multiple package managers. ` +
|
|
100
|
-
"Remove stale lockfiles or declare packageManager in package.json."
|
|
101
|
-
)
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
if (lockfiles.length > 0) {
|
|
105
|
-
return {
|
|
106
|
-
lockfilePath: path.join(root, lockfiles[0].name),
|
|
107
|
-
manager: lockfiles[0].manager,
|
|
108
|
-
root,
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
return null
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
async function findWorkspaceRoot(projectRoot) {
|
|
116
|
-
let directory = projectRoot
|
|
117
|
-
const evidenceRoots = []
|
|
118
|
-
|
|
119
|
-
while (true) {
|
|
120
|
-
evidenceRoots.push(directory)
|
|
121
|
-
if (directory !== projectRoot) {
|
|
122
|
-
if (await isWorkspaceRoot(directory, projectRoot)) {
|
|
123
|
-
return { evidenceRoots, root: directory }
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
if (await exists(path.join(directory, ".git"))) {
|
|
128
|
-
return { evidenceRoots, root: null }
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
const parent = path.dirname(directory)
|
|
132
|
-
if (parent === directory) return { evidenceRoots, root: null }
|
|
133
|
-
directory = parent
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
function withEvidenceRoots(context, evidenceRoots) {
|
|
138
|
-
return { ...context, evidenceRoots }
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
function samePackageManagerContext(left, right) {
|
|
142
|
-
return left.manager === right.manager &&
|
|
143
|
-
left.root === right.root &&
|
|
144
|
-
left.lockfilePath === right.lockfilePath &&
|
|
145
|
-
left.evidenceRoots.join("\0") === right.evidenceRoots.join("\0")
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
async function isWorkspaceRoot(candidate, projectRoot) {
|
|
149
|
-
const relativeProject = path.relative(candidate, projectRoot).replaceAll("\\", "/")
|
|
150
|
-
const pnpmPatterns = await readPnpmWorkspacePatterns(
|
|
151
|
-
path.join(candidate, "pnpm-workspace.yaml")
|
|
152
|
-
)
|
|
153
|
-
if (pnpmPatterns && workspacePatternsMatch(pnpmPatterns, relativeProject)) {
|
|
154
|
-
return true
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
const packageJson = await readJsonIfValid(path.join(candidate, "package.json"))
|
|
158
|
-
const patterns = Array.isArray(packageJson?.workspaces)
|
|
159
|
-
? packageJson.workspaces
|
|
160
|
-
: packageJson?.workspaces?.packages
|
|
161
|
-
|
|
162
|
-
if (!Array.isArray(patterns)) return false
|
|
163
|
-
return workspacePatternsMatch(patterns, relativeProject)
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
function workspacePatternsMatch(patterns, value) {
|
|
167
|
-
const validPatterns = patterns.filter(
|
|
168
|
-
(pattern) => typeof pattern === "string" && pattern.length <= 512
|
|
169
|
-
)
|
|
170
|
-
const included = validPatterns
|
|
171
|
-
.filter((pattern) => !pattern.startsWith("!"))
|
|
172
|
-
.some((pattern) => workspacePatternMatches(pattern, value))
|
|
173
|
-
const excluded = validPatterns
|
|
174
|
-
.filter((pattern) => pattern.startsWith("!"))
|
|
175
|
-
.some((pattern) => workspacePatternMatches(pattern.slice(1), value))
|
|
176
|
-
|
|
177
|
-
return included && !excluded
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
function workspacePatternMatches(pattern, value) {
|
|
181
|
-
try {
|
|
182
|
-
return minimatch(value, pattern.replace(/^\.\//, "").replace(/\/$/, ""), {
|
|
183
|
-
dot: true,
|
|
184
|
-
nocomment: true,
|
|
185
|
-
nonegate: true,
|
|
186
|
-
})
|
|
187
|
-
} catch {
|
|
188
|
-
return false
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
async function readPnpmWorkspacePatterns(filePath) {
|
|
193
|
-
try {
|
|
194
|
-
const workspace = parseYaml(await fs.readFile(filePath, "utf8"))
|
|
195
|
-
return Array.isArray(workspace?.packages) ? workspace.packages : null
|
|
196
|
-
} catch (error) {
|
|
197
|
-
if (error.code === "ENOENT" || error.name === "YAMLParseError") return null
|
|
198
|
-
throw error
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
async function findManagerLockfile(root, manager) {
|
|
203
|
-
for (const lockfile of LOCKFILES) {
|
|
204
|
-
if (
|
|
205
|
-
lockfile.manager === manager &&
|
|
206
|
-
await exists(path.join(root, lockfile.name))
|
|
207
|
-
) {
|
|
208
|
-
return lockfile
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
return null
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
async function readJsonIfValid(filePath) {
|
|
216
|
-
try {
|
|
217
|
-
return JSON.parse(await fs.readFile(filePath, "utf8"))
|
|
218
|
-
} catch (error) {
|
|
219
|
-
if (error.code === "ENOENT" || error instanceof SyntaxError) return null
|
|
220
|
-
throw error
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
async function exists(filePath) {
|
|
225
|
-
try {
|
|
226
|
-
await fs.access(filePath)
|
|
227
|
-
return true
|
|
228
|
-
} catch {
|
|
229
|
-
return false
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
function parsePackageManager(packageManager) {
|
|
234
|
-
if (typeof packageManager !== "string") return null
|
|
235
|
-
|
|
236
|
-
const name = packageManager.split("@")[0]
|
|
237
|
-
return ["npm", "pnpm", "yarn", "bun"].includes(name) ? name : null
|
|
238
|
-
}
|
|
1
|
+
import fs from "node:fs/promises"
|
|
2
|
+
import path from "node:path"
|
|
3
|
+
import { minimatch } from "minimatch"
|
|
4
|
+
import { parse as parseYaml } from "yaml"
|
|
5
|
+
|
|
6
|
+
const LOCKFILES = [
|
|
7
|
+
{ manager: "pnpm", name: "pnpm-lock.yaml" },
|
|
8
|
+
{ manager: "yarn", name: "yarn.lock" },
|
|
9
|
+
{ manager: "bun", name: "bun.lockb" },
|
|
10
|
+
{ manager: "bun", name: "bun.lock" },
|
|
11
|
+
{ manager: "npm", name: "npm-shrinkwrap.json" },
|
|
12
|
+
{ manager: "npm", name: "package-lock.json" },
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
export function packageManagerStatePaths(root) {
|
|
16
|
+
return [
|
|
17
|
+
path.join(root, "package.json"),
|
|
18
|
+
path.join(root, "pnpm-workspace.yaml"),
|
|
19
|
+
...LOCKFILES.map(({ name }) => path.join(root, name)),
|
|
20
|
+
]
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export async function inspectPackageManagerContext(cwd) {
|
|
24
|
+
const context = await resolvePackageManagerContext(cwd)
|
|
25
|
+
const paths = new Set(
|
|
26
|
+
context.evidenceRoots.flatMap((root) => packageManagerStatePaths(root))
|
|
27
|
+
)
|
|
28
|
+
const evidence = await Promise.all(
|
|
29
|
+
[...paths].map(async (filePath) => {
|
|
30
|
+
try {
|
|
31
|
+
return { path: filePath, source: await fs.readFile(filePath) }
|
|
32
|
+
} catch (error) {
|
|
33
|
+
if (error.code === "ENOENT") return { path: filePath, source: null }
|
|
34
|
+
throw error
|
|
35
|
+
}
|
|
36
|
+
})
|
|
37
|
+
)
|
|
38
|
+
const confirmedContext = await resolvePackageManagerContext(cwd)
|
|
39
|
+
if (!samePackageManagerContext(context, confirmedContext)) {
|
|
40
|
+
throw new Error("package-manager context changed while preparing the install plan")
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return { context, evidence }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export async function resolvePackageManagerContext(cwd) {
|
|
47
|
+
const projectRoot = path.resolve(cwd)
|
|
48
|
+
const localContext = await inspectPackageManagerRoot(projectRoot)
|
|
49
|
+
const workspace = await findWorkspaceRoot(projectRoot)
|
|
50
|
+
if (workspace.root) {
|
|
51
|
+
const workspaceContext = await inspectPackageManagerRoot(workspace.root)
|
|
52
|
+
if (workspaceContext) {
|
|
53
|
+
if (
|
|
54
|
+
localContext &&
|
|
55
|
+
localContext.manager !== workspaceContext.manager
|
|
56
|
+
) {
|
|
57
|
+
throw new Error(
|
|
58
|
+
`${projectRoot} declares packageManager "${localContext.manager}", ` +
|
|
59
|
+
`but its workspace root uses "${workspaceContext.manager}".`
|
|
60
|
+
)
|
|
61
|
+
}
|
|
62
|
+
return withEvidenceRoots(workspaceContext, workspace.evidenceRoots)
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (localContext) {
|
|
67
|
+
return withEvidenceRoots(localContext, workspace.evidenceRoots)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
evidenceRoots: workspace.evidenceRoots,
|
|
72
|
+
lockfilePath: null,
|
|
73
|
+
manager: "npm",
|
|
74
|
+
root: projectRoot,
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async function inspectPackageManagerRoot(root) {
|
|
79
|
+
const packageJson = await readJsonIfValid(path.join(root, "package.json"))
|
|
80
|
+
const declaredManager = parsePackageManager(packageJson?.packageManager)
|
|
81
|
+
|
|
82
|
+
if (declaredManager) {
|
|
83
|
+
const lockfile = await findManagerLockfile(root, declaredManager)
|
|
84
|
+
return {
|
|
85
|
+
lockfilePath: lockfile ? path.join(root, lockfile.name) : null,
|
|
86
|
+
manager: declaredManager,
|
|
87
|
+
root,
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const lockfiles = []
|
|
92
|
+
for (const lockfile of LOCKFILES) {
|
|
93
|
+
if (await exists(path.join(root, lockfile.name))) lockfiles.push(lockfile)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const managers = new Set(lockfiles.map(({ manager }) => manager))
|
|
97
|
+
if (managers.size > 1) {
|
|
98
|
+
throw new Error(
|
|
99
|
+
`${root} contains lockfiles for multiple package managers. ` +
|
|
100
|
+
"Remove stale lockfiles or declare packageManager in package.json."
|
|
101
|
+
)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (lockfiles.length > 0) {
|
|
105
|
+
return {
|
|
106
|
+
lockfilePath: path.join(root, lockfiles[0].name),
|
|
107
|
+
manager: lockfiles[0].manager,
|
|
108
|
+
root,
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return null
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async function findWorkspaceRoot(projectRoot) {
|
|
116
|
+
let directory = projectRoot
|
|
117
|
+
const evidenceRoots = []
|
|
118
|
+
|
|
119
|
+
while (true) {
|
|
120
|
+
evidenceRoots.push(directory)
|
|
121
|
+
if (directory !== projectRoot) {
|
|
122
|
+
if (await isWorkspaceRoot(directory, projectRoot)) {
|
|
123
|
+
return { evidenceRoots, root: directory }
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (await exists(path.join(directory, ".git"))) {
|
|
128
|
+
return { evidenceRoots, root: null }
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const parent = path.dirname(directory)
|
|
132
|
+
if (parent === directory) return { evidenceRoots, root: null }
|
|
133
|
+
directory = parent
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function withEvidenceRoots(context, evidenceRoots) {
|
|
138
|
+
return { ...context, evidenceRoots }
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function samePackageManagerContext(left, right) {
|
|
142
|
+
return left.manager === right.manager &&
|
|
143
|
+
left.root === right.root &&
|
|
144
|
+
left.lockfilePath === right.lockfilePath &&
|
|
145
|
+
left.evidenceRoots.join("\0") === right.evidenceRoots.join("\0")
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async function isWorkspaceRoot(candidate, projectRoot) {
|
|
149
|
+
const relativeProject = path.relative(candidate, projectRoot).replaceAll("\\", "/")
|
|
150
|
+
const pnpmPatterns = await readPnpmWorkspacePatterns(
|
|
151
|
+
path.join(candidate, "pnpm-workspace.yaml")
|
|
152
|
+
)
|
|
153
|
+
if (pnpmPatterns && workspacePatternsMatch(pnpmPatterns, relativeProject)) {
|
|
154
|
+
return true
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const packageJson = await readJsonIfValid(path.join(candidate, "package.json"))
|
|
158
|
+
const patterns = Array.isArray(packageJson?.workspaces)
|
|
159
|
+
? packageJson.workspaces
|
|
160
|
+
: packageJson?.workspaces?.packages
|
|
161
|
+
|
|
162
|
+
if (!Array.isArray(patterns)) return false
|
|
163
|
+
return workspacePatternsMatch(patterns, relativeProject)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function workspacePatternsMatch(patterns, value) {
|
|
167
|
+
const validPatterns = patterns.filter(
|
|
168
|
+
(pattern) => typeof pattern === "string" && pattern.length <= 512
|
|
169
|
+
)
|
|
170
|
+
const included = validPatterns
|
|
171
|
+
.filter((pattern) => !pattern.startsWith("!"))
|
|
172
|
+
.some((pattern) => workspacePatternMatches(pattern, value))
|
|
173
|
+
const excluded = validPatterns
|
|
174
|
+
.filter((pattern) => pattern.startsWith("!"))
|
|
175
|
+
.some((pattern) => workspacePatternMatches(pattern.slice(1), value))
|
|
176
|
+
|
|
177
|
+
return included && !excluded
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function workspacePatternMatches(pattern, value) {
|
|
181
|
+
try {
|
|
182
|
+
return minimatch(value, pattern.replace(/^\.\//, "").replace(/\/$/, ""), {
|
|
183
|
+
dot: true,
|
|
184
|
+
nocomment: true,
|
|
185
|
+
nonegate: true,
|
|
186
|
+
})
|
|
187
|
+
} catch {
|
|
188
|
+
return false
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
async function readPnpmWorkspacePatterns(filePath) {
|
|
193
|
+
try {
|
|
194
|
+
const workspace = parseYaml(await fs.readFile(filePath, "utf8"))
|
|
195
|
+
return Array.isArray(workspace?.packages) ? workspace.packages : null
|
|
196
|
+
} catch (error) {
|
|
197
|
+
if (error.code === "ENOENT" || error.name === "YAMLParseError") return null
|
|
198
|
+
throw error
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
async function findManagerLockfile(root, manager) {
|
|
203
|
+
for (const lockfile of LOCKFILES) {
|
|
204
|
+
if (
|
|
205
|
+
lockfile.manager === manager &&
|
|
206
|
+
await exists(path.join(root, lockfile.name))
|
|
207
|
+
) {
|
|
208
|
+
return lockfile
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return null
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
async function readJsonIfValid(filePath) {
|
|
216
|
+
try {
|
|
217
|
+
return JSON.parse(await fs.readFile(filePath, "utf8"))
|
|
218
|
+
} catch (error) {
|
|
219
|
+
if (error.code === "ENOENT" || error instanceof SyntaxError) return null
|
|
220
|
+
throw error
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
async function exists(filePath) {
|
|
225
|
+
try {
|
|
226
|
+
await fs.access(filePath)
|
|
227
|
+
return true
|
|
228
|
+
} catch {
|
|
229
|
+
return false
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function parsePackageManager(packageManager) {
|
|
234
|
+
if (typeof packageManager !== "string") return null
|
|
235
|
+
|
|
236
|
+
const name = packageManager.split("@")[0]
|
|
237
|
+
return ["npm", "pnpm", "yarn", "bun"].includes(name) ? name : null
|
|
238
|
+
}
|