banhaten 0.1.1 → 0.1.3
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 +86 -309
- package/banhaten.config.example.json +1 -1
- package/docs/design-system/README.md +11 -0
- package/docs/design-system/appearance-presets.md +184 -0
- package/docs/design-system/appearances/default.md +94 -0
- package/docs/design-system/appearances/rounded.md +95 -0
- package/docs/design-system/appearances/sharp.md +95 -0
- package/docs/design-system/component-showcase-consistency-report.md +217 -0
- package/docs/design-system/component-token-consistency-audit.md +163 -0
- package/docs/design-system/components/README.md +74 -0
- package/docs/design-system/components/accordion.md +51 -0
- package/docs/design-system/components/activity-feed.md +92 -0
- package/docs/design-system/components/alert-dialog.md +70 -0
- package/docs/design-system/components/alert.md +79 -0
- package/docs/design-system/components/aspect-ratio.md +44 -0
- package/docs/design-system/components/attribute.md +87 -0
- package/docs/design-system/components/autocomplete.md +74 -0
- package/docs/design-system/components/avatar.md +52 -0
- package/docs/design-system/components/badge.md +53 -0
- package/docs/design-system/components/banner.md +85 -0
- package/docs/design-system/components/breadcrumbs.md +174 -0
- package/docs/design-system/components/button-group.md +83 -0
- package/docs/design-system/components/button.md +77 -0
- package/docs/design-system/components/card.md +78 -0
- package/docs/design-system/components/carousel.md +44 -0
- package/docs/design-system/components/catalog-components.md +45 -0
- package/docs/design-system/components/chart.md +43 -0
- package/docs/design-system/components/checkbox.md +52 -0
- package/docs/design-system/components/collapsible.md +48 -0
- package/docs/design-system/components/command-bar.md +57 -0
- package/docs/design-system/components/command.md +60 -0
- package/docs/design-system/components/context-menu.md +44 -0
- package/docs/design-system/components/date-picker.md +77 -0
- package/docs/design-system/components/divider.md +101 -0
- package/docs/design-system/components/empty-state.md +55 -0
- package/docs/design-system/components/field.md +69 -0
- package/docs/design-system/components/file-upload.md +185 -0
- package/docs/design-system/components/hover-card.md +46 -0
- package/docs/design-system/components/icons.md +48 -0
- package/docs/design-system/components/input-group.md +56 -0
- package/docs/design-system/components/input-otp.md +55 -0
- package/docs/design-system/components/input.md +48 -0
- package/docs/design-system/components/kbd.md +44 -0
- package/docs/design-system/components/label.md +48 -0
- package/docs/design-system/components/menu.md +59 -0
- package/docs/design-system/components/menubar.md +45 -0
- package/docs/design-system/components/modal.md +98 -0
- package/docs/design-system/components/native-select.md +52 -0
- package/docs/design-system/components/navigation-menu.md +48 -0
- package/docs/design-system/components/onboarding-step-list-item.md +80 -0
- package/docs/design-system/components/page-header.md +84 -0
- package/docs/design-system/components/pagination.md +49 -0
- package/docs/design-system/components/popover.md +58 -0
- package/docs/design-system/components/progress-slider.md +48 -0
- package/docs/design-system/components/progress.md +75 -0
- package/docs/design-system/components/radio-card.md +49 -0
- package/docs/design-system/components/radio-group.md +55 -0
- package/docs/design-system/components/resizable.md +42 -0
- package/docs/design-system/components/scroll-area.md +45 -0
- package/docs/design-system/components/select.md +50 -0
- package/docs/design-system/components/sheet.md +65 -0
- package/docs/design-system/components/sidebar.md +68 -0
- package/docs/design-system/components/skeleton.md +73 -0
- package/docs/design-system/components/slideout.md +63 -0
- package/docs/design-system/components/slider.md +61 -0
- package/docs/design-system/components/social-button.md +47 -0
- package/docs/design-system/components/spinner.md +61 -0
- package/docs/design-system/components/steps.md +63 -0
- package/docs/design-system/components/table.md +397 -0
- package/docs/design-system/components/tabs.md +52 -0
- package/docs/design-system/components/tag.md +78 -0
- package/docs/design-system/components/textarea.md +48 -0
- package/docs/design-system/components/timeline.md +81 -0
- package/docs/design-system/components/toast.md +56 -0
- package/docs/design-system/components/toggle.md +79 -0
- package/docs/design-system/components/toolbar.md +85 -0
- package/docs/design-system/components/tooltip.md +90 -0
- package/docs/design-system/components/typography.md +18 -0
- package/docs/design-system/design-system-test-missing-items.md +368 -0
- package/docs/design-system/icons.md +69 -0
- package/docs/design-system/registry-and-cli.md +41 -0
- package/docs/design-system/tabs.md +53 -0
- package/docs/design-system/token-governance.md +38 -0
- package/package.json +83 -59
- package/registry/components/alert-dialog.tsx +297 -0
- package/registry/components/aspect-ratio.tsx +30 -0
- package/registry/components/autocomplete.tsx +637 -0
- package/registry/components/avatar.tsx +258 -22
- package/registry/components/badge.tsx +97 -35
- package/registry/components/carousel.tsx +234 -0
- package/registry/components/chart.tsx +170 -0
- package/registry/components/collapsible.tsx +69 -0
- package/registry/components/command.tsx +174 -0
- package/registry/components/context-menu.tsx +236 -0
- package/registry/components/date-picker-state.ts +253 -0
- package/registry/components/date-picker.tsx +116 -159
- package/registry/components/expanded/EmptyState.tsx +155 -0
- package/registry/components/expanded/PageHeader.tsx +1 -1
- package/registry/components/expanded/breadcrumbs.css +139 -139
- package/registry/components/expanded/catalogComponentsShowcase.css +83 -83
- package/registry/components/expanded/emptyState.css +111 -0
- package/registry/components/expanded/slideout.css +1 -0
- package/registry/components/expanded/steps.css +274 -274
- package/registry/components/expanded/table.css +1 -0
- package/registry/components/expanded/timeline.css +264 -264
- package/registry/components/field.tsx +230 -0
- package/registry/components/hover-card.tsx +48 -0
- package/registry/components/input-group.tsx +130 -0
- package/registry/components/input-otp.tsx +574 -0
- package/registry/components/input.tsx +22 -12
- package/registry/components/kbd.tsx +44 -0
- package/registry/components/label.tsx +78 -0
- package/registry/components/menu.tsx +373 -8
- package/registry/components/menubar.tsx +226 -0
- package/registry/components/modal.tsx +109 -76
- package/registry/components/native-select.tsx +205 -0
- package/registry/components/navigation-menu.tsx +171 -0
- package/registry/components/popover.tsx +840 -0
- package/registry/components/radio-group.tsx +1 -1
- package/registry/components/resizable.tsx +74 -0
- package/registry/components/scroll-area.tsx +67 -0
- package/registry/components/select.tsx +6 -4
- package/registry/components/sheet.tsx +305 -0
- package/registry/components/sidebar.tsx +352 -0
- package/registry/components/skeleton.css +57 -0
- package/registry/components/skeleton.tsx +482 -0
- package/registry/components/social-button.tsx +74 -10
- package/registry/components/spinner.tsx +79 -11
- package/registry/components/{expanded/tabs.css → tabs.css} +127 -106
- package/registry/components/tabs.tsx +242 -0
- package/registry/components/textarea.tsx +2 -2
- package/registry/components/toast.tsx +131 -0
- package/registry/components/tooltip.tsx +4 -0
- package/registry/examples/alert-dialog-demo.tsx +42 -0
- package/registry/examples/aspect-ratio-demo.tsx +11 -0
- package/registry/examples/autocomplete-demo.tsx +109 -0
- package/registry/examples/avatar-demo.tsx +102 -47
- package/registry/examples/badge-demo.tsx +16 -0
- package/registry/examples/carousel-demo.tsx +25 -0
- package/registry/examples/chart-demo.tsx +33 -0
- package/registry/examples/collapsible-demo.tsx +16 -0
- package/registry/examples/command-demo.tsx +42 -0
- package/registry/examples/context-menu-demo.tsx +29 -0
- package/registry/examples/expanded/command-bar-demo.tsx +236 -0
- package/registry/examples/expanded/empty-state-demo.tsx +39 -0
- package/registry/examples/expanded/tabs-demo.tsx +1 -1
- package/registry/examples/field-demo.tsx +51 -0
- package/registry/examples/hover-card-demo.tsx +23 -0
- package/registry/examples/input-demo.tsx +1 -1
- package/registry/examples/input-group-demo.tsx +16 -0
- package/registry/examples/input-otp-demo.tsx +72 -0
- package/registry/examples/kbd-demo.tsx +11 -0
- package/registry/examples/label-demo.tsx +20 -0
- package/registry/examples/menu-demo.tsx +101 -88
- package/registry/examples/menubar-demo.tsx +34 -0
- package/registry/examples/native-select-demo.tsx +16 -0
- package/registry/examples/navigation-menu-demo.tsx +29 -0
- package/registry/examples/popover-demo.tsx +546 -0
- package/registry/examples/resizable-demo.tsx +22 -0
- package/registry/examples/scroll-area-demo.tsx +15 -0
- package/registry/examples/select-demo.tsx +1 -1
- package/registry/examples/sheet-demo.tsx +47 -0
- package/registry/examples/sidebar-demo.tsx +55 -0
- package/registry/examples/skeleton-demo.tsx +56 -0
- package/registry/examples/spinner-demo.tsx +23 -1
- package/registry/examples/tabs-demo.tsx +13 -0
- package/registry/examples/textarea-demo.tsx +1 -1
- package/registry/examples/toast-demo.tsx +35 -0
- package/registry/index.json +895 -19
- package/registry/styles/globals.css +4733 -4602
- package/registry.json +1612 -0
- package/schema/config.schema.json +48 -0
- package/schema/registry.schema.json +85 -0
- package/schema/tokens.schema.json +63 -0
- package/src/cli/index.js +1413 -184
- package/tokens/banhaten.tokens.json +1 -1
- package/registry/assets/avatars/avatar-02.jpg +0 -0
- package/registry/assets/avatars/avatar-03.jpg +0 -0
- package/registry/assets/avatars/avatar-04.jpg +0 -0
- package/registry/assets/avatars/avatar-05.jpg +0 -0
- package/registry/assets/avatars/avatar-06.jpg +0 -0
- package/registry/assets/avatars/avatar-07.jpg +0 -0
- package/registry/assets/avatars/avatar-08.jpg +0 -0
- package/registry/assets/avatars/avatar-09.jpg +0 -0
- package/registry/assets/avatars/avatar-10.jpg +0 -0
- package/registry/assets/avatars/avatar-11.jpg +0 -0
- package/registry/assets/avatars/avatar-12.jpg +0 -0
- package/registry/assets/avatars/avatar-13.jpg +0 -0
- package/registry/assets/avatars/avatar-14.jpg +0 -0
- package/registry/assets/avatars/avatar-15.jpg +0 -0
- package/registry/assets/avatars/avatar-16.jpg +0 -0
- package/registry/assets/avatars/avatar-17.jpg +0 -0
- package/registry/assets/avatars/avatar-18.jpg +0 -0
- package/registry/assets/avatars/avatar-19.jpg +0 -0
- package/registry/assets/avatars/avatar-20.jpg +0 -0
- package/registry/assets/avatars/avatar-21.jpg +0 -0
- package/registry/assets/avatars/avatar-22.jpg +0 -0
- package/registry/assets/avatars/avatar-23.jpg +0 -0
- package/registry/assets/avatars/avatar-24.jpg +0 -0
- package/registry/assets/avatars/avatar-25.jpg +0 -0
- package/registry/assets/avatars/avatar-26.jpg +0 -0
- package/registry/assets/avatars/avatar-27.jpg +0 -0
- package/registry/assets/avatars/avatar-28.jpg +0 -0
- package/registry/assets/avatars/avatar-29.jpg +0 -0
- package/registry/assets/avatars/avatar-30.jpg +0 -0
- package/registry/assets/avatars/avatar-31.jpg +0 -0
- package/registry/assets/avatars/avatar-32.jpg +0 -0
- package/registry/assets/avatars/avatar-33.jpg +0 -0
- package/registry/assets/avatars/avatar-34.jpg +0 -0
- package/registry/assets/avatars/avatar-35.jpg +0 -0
- package/registry/assets/image-assets.json +0 -744
- package/registry/assets/images/art-02.jpg +0 -0
- package/registry/assets/images/art-03.jpg +0 -0
- package/registry/assets/images/art-04.jpg +0 -0
- package/registry/assets/images/art-05.jpg +0 -0
- package/registry/assets/images/art-06.jpg +0 -0
- package/registry/assets/images/art-07.jpg +0 -0
- package/registry/assets/images/art-08.jpg +0 -0
- package/registry/assets/images/art-09.jpg +0 -0
- package/registry/assets/images/art-10.jpg +0 -0
- package/registry/assets/images/art-11.jpg +0 -0
- package/registry/assets/images/art-12.jpg +0 -0
- package/registry/assets/images/art-13.jpg +0 -0
- package/registry/assets/images/art-14.jpg +0 -0
- package/registry/assets/images/art-15.jpg +0 -0
- package/registry/assets/images/art-16.jpg +0 -0
- package/registry/assets/images/art-17.jpg +0 -0
- package/registry/assets/images/art-18.jpg +0 -0
- package/registry/assets/images/art-19.jpg +0 -0
- package/registry/assets/images/art-20.jpg +0 -0
- package/registry/assets/images/art-21.jpg +0 -0
- package/registry/assets/images/art-22.jpg +0 -0
- package/registry/assets/images/art-23.jpg +0 -0
- package/registry/assets/images/art-24.jpg +0 -0
- package/registry/assets/images/art-25.jpg +0 -0
- package/registry/assets/images/art-26.jpg +0 -0
- package/registry/assets/images/art-27.jpg +0 -0
- package/registry/assets/images/nature-01.jpg +0 -0
- package/registry/assets/images/nature-02.jpg +0 -0
- package/registry/assets/images/nature-03.jpg +0 -0
- package/registry/assets/images/nature-04.jpg +0 -0
- package/registry/assets/images/nature-05.jpg +0 -0
- package/registry/assets/images/nature-06.jpg +0 -0
- package/registry/assets/images/nature-07.jpg +0 -0
- package/registry/assets/images/nature-08.jpg +0 -0
- package/registry/assets/images/nature-09.jpg +0 -0
- package/registry/assets/images/nature-10.jpg +0 -0
- package/registry/assets/images/nature-11.jpg +0 -0
- package/registry/assets/images/nature-12.jpg +0 -0
- package/registry/assets/images/nature-13.jpg +0 -0
- package/registry/assets/images/nature-14.jpg +0 -0
- package/registry/assets/images/nature-15.jpg +0 -0
- package/registry/assets/images/nature-16.jpg +0 -0
- package/registry/assets/images/nature-17.jpg +0 -0
- package/registry/assets/images/nature-18.jpg +0 -0
- package/registry/assets/images/nature-19.jpg +0 -0
- package/registry/assets/images/nature-20.jpg +0 -0
- package/registry/components/expanded/Tabs.tsx +0 -86
package/src/cli/index.js
CHANGED
|
@@ -12,6 +12,8 @@ const packageRoot = path.resolve(
|
|
|
12
12
|
)
|
|
13
13
|
const registryRoot = path.join(packageRoot, "registry")
|
|
14
14
|
const registryIndexPath = path.join(registryRoot, "index.json")
|
|
15
|
+
const packageJsonPath = path.join(packageRoot, "package.json")
|
|
16
|
+
const schemaRoot = path.join(packageRoot, "schema")
|
|
15
17
|
|
|
16
18
|
const CONFIG_FILE = "banhaten.config.json"
|
|
17
19
|
const FONT_IMPORTS = [
|
|
@@ -30,9 +32,14 @@ const VITE_DEV_DEPENDENCIES = {
|
|
|
30
32
|
const CSS_START = "/* Banhaten design system tokens:"
|
|
31
33
|
const CSS_END = "/* End Banhaten design system tokens. */"
|
|
32
34
|
const syncExistsCache = new Map()
|
|
35
|
+
const VALUE_FLAGS = new Set(["cwd", "file", "format"])
|
|
36
|
+
let runtimeOptions = {
|
|
37
|
+
json: false,
|
|
38
|
+
silent: false,
|
|
39
|
+
}
|
|
33
40
|
|
|
34
41
|
const DEFAULT_CONFIG = {
|
|
35
|
-
$schema: "
|
|
42
|
+
$schema: ".banhaten/schema/config.schema.json",
|
|
36
43
|
style: "default",
|
|
37
44
|
tsx: true,
|
|
38
45
|
tailwind: {
|
|
@@ -46,6 +53,7 @@ const DEFAULT_CONFIG = {
|
|
|
46
53
|
}
|
|
47
54
|
|
|
48
55
|
const registry = await readJson(registryIndexPath)
|
|
56
|
+
const packageManifest = await readJson(packageJsonPath)
|
|
49
57
|
|
|
50
58
|
main().catch((error) => {
|
|
51
59
|
console.error(`banhaten: ${error.message}`)
|
|
@@ -54,6 +62,15 @@ main().catch((error) => {
|
|
|
54
62
|
|
|
55
63
|
async function main() {
|
|
56
64
|
const { command, positionals, flags } = parseArgs(process.argv.slice(2))
|
|
65
|
+
runtimeOptions = {
|
|
66
|
+
json: Boolean(flags.json),
|
|
67
|
+
silent: Boolean(flags.silent || flags.s),
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (command === "version" || flags.version || flags.v) {
|
|
71
|
+
console.log(packageManifest.version)
|
|
72
|
+
return
|
|
73
|
+
}
|
|
57
74
|
|
|
58
75
|
if (!command || command === "help" || flags.help || flags.h) {
|
|
59
76
|
printHelp()
|
|
@@ -61,7 +78,22 @@ async function main() {
|
|
|
61
78
|
}
|
|
62
79
|
|
|
63
80
|
if (command === "list") {
|
|
64
|
-
listComponents()
|
|
81
|
+
listComponents(flags)
|
|
82
|
+
return
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (command === "search") {
|
|
86
|
+
searchComponents(positionals, flags)
|
|
87
|
+
return
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (command === "docs") {
|
|
91
|
+
showComponentDocs(positionals, flags)
|
|
92
|
+
return
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (command === "view") {
|
|
96
|
+
await viewComponent(positionals, flags)
|
|
65
97
|
return
|
|
66
98
|
}
|
|
67
99
|
|
|
@@ -100,6 +132,21 @@ function parseArgs(argv) {
|
|
|
100
132
|
continue
|
|
101
133
|
}
|
|
102
134
|
|
|
135
|
+
if (arg === "-s") {
|
|
136
|
+
flags.s = true
|
|
137
|
+
continue
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (arg === "-v") {
|
|
141
|
+
flags.v = true
|
|
142
|
+
continue
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (arg === "-y") {
|
|
146
|
+
flags.yes = true
|
|
147
|
+
continue
|
|
148
|
+
}
|
|
149
|
+
|
|
103
150
|
if (!arg.startsWith("--")) {
|
|
104
151
|
positionals.push(arg)
|
|
105
152
|
continue
|
|
@@ -114,7 +161,7 @@ function parseArgs(argv) {
|
|
|
114
161
|
}
|
|
115
162
|
|
|
116
163
|
const next = argv[index + 1]
|
|
117
|
-
if ((key
|
|
164
|
+
if (VALUE_FLAGS.has(key) && next && !next.startsWith("-")) {
|
|
118
165
|
flags[key] = next
|
|
119
166
|
index += 1
|
|
120
167
|
continue
|
|
@@ -135,24 +182,196 @@ Usage:
|
|
|
135
182
|
banhaten add <component...> [--cwd <path>] [--force] [--dry-run]
|
|
136
183
|
banhaten diff [component...] [--cwd <path>] [--force]
|
|
137
184
|
banhaten update [component...] [--cwd <path>] [--force] [--dry-run]
|
|
138
|
-
banhaten list
|
|
185
|
+
banhaten list [--json]
|
|
186
|
+
banhaten search <query> [--json]
|
|
187
|
+
banhaten docs <component> [--json]
|
|
188
|
+
banhaten view <component> [--file <registry-source>] [--json]
|
|
189
|
+
banhaten version
|
|
139
190
|
|
|
140
191
|
Examples:
|
|
141
192
|
npx banhaten init
|
|
142
193
|
npx banhaten add button
|
|
143
194
|
npx banhaten diff
|
|
144
195
|
npx banhaten update button
|
|
196
|
+
npx banhaten search input
|
|
197
|
+
npx banhaten docs button
|
|
198
|
+
npx banhaten view button
|
|
199
|
+
|
|
200
|
+
Global flags:
|
|
201
|
+
--cwd <path> Run against a project directory.
|
|
202
|
+
--dry-run Preview writes without changing files.
|
|
203
|
+
--force Rewrite generated files even when already current.
|
|
204
|
+
--json Print machine-readable output where supported.
|
|
205
|
+
--silent, -s Suppress normal command output.
|
|
206
|
+
--yes, -y Accepted for non-interactive scripts.
|
|
207
|
+
--version, -v Print the installed CLI version.
|
|
145
208
|
`)
|
|
146
209
|
}
|
|
147
210
|
|
|
148
211
|
function listComponents() {
|
|
149
|
-
const
|
|
212
|
+
const components = Object.entries(registry.components).map(([name, component]) => ({
|
|
213
|
+
name,
|
|
214
|
+
description: component.description || "",
|
|
215
|
+
dependencies: Object.keys(component.dependencies || {}),
|
|
216
|
+
registryDependencies: component.registryDependencies || [],
|
|
217
|
+
}))
|
|
218
|
+
|
|
219
|
+
if (runtimeOptions.json) {
|
|
220
|
+
writeJson({ components })
|
|
221
|
+
return
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (runtimeOptions.silent) return
|
|
150
225
|
|
|
151
226
|
console.log("Available Banhaten design system components:")
|
|
152
|
-
for (const name of
|
|
153
|
-
|
|
154
|
-
|
|
227
|
+
for (const { name, description } of components) {
|
|
228
|
+
console.log(`- ${name}: ${description}`)
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function searchComponents(positionals) {
|
|
233
|
+
const query = positionals.join(" ").trim().toLowerCase()
|
|
234
|
+
if (!query) {
|
|
235
|
+
throw new Error("missing search query. Try `banhaten search input`.")
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const matches = Object.entries(registry.components)
|
|
239
|
+
.filter(([name, component]) => {
|
|
240
|
+
const haystack = [
|
|
241
|
+
name,
|
|
242
|
+
component.description,
|
|
243
|
+
...(component.parts || []),
|
|
244
|
+
...(component.registryDependencies || []),
|
|
245
|
+
]
|
|
246
|
+
.filter(Boolean)
|
|
247
|
+
.join(" ")
|
|
248
|
+
.toLowerCase()
|
|
249
|
+
|
|
250
|
+
return haystack.includes(query)
|
|
251
|
+
})
|
|
252
|
+
.map(([name, component]) => ({
|
|
253
|
+
name,
|
|
254
|
+
description: component.description || "",
|
|
255
|
+
parts: component.parts || [],
|
|
256
|
+
}))
|
|
257
|
+
|
|
258
|
+
if (runtimeOptions.json) {
|
|
259
|
+
writeJson({ query, matches })
|
|
260
|
+
return
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (runtimeOptions.silent) return
|
|
264
|
+
|
|
265
|
+
if (matches.length === 0) {
|
|
266
|
+
console.log(`No Banhaten components matched "${query}".`)
|
|
267
|
+
return
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
console.log(`Banhaten components matching "${query}":`)
|
|
271
|
+
for (const match of matches) {
|
|
272
|
+
console.log(`- ${match.name}: ${match.description}`)
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function showComponentDocs(positionals) {
|
|
277
|
+
const [name] = positionals
|
|
278
|
+
const component = getComponentOrThrow(name)
|
|
279
|
+
const docsPath = getComponentDocPath(name)
|
|
280
|
+
const result = {
|
|
281
|
+
name,
|
|
282
|
+
description: component.description || "",
|
|
283
|
+
install: `npx banhaten add ${name}`,
|
|
284
|
+
docsPath,
|
|
285
|
+
files: component.files || [],
|
|
286
|
+
examples: component.examples || [],
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (runtimeOptions.json) {
|
|
290
|
+
writeJson(result)
|
|
291
|
+
return
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if (runtimeOptions.silent) return
|
|
295
|
+
|
|
296
|
+
console.log(`${name}: ${result.description}`)
|
|
297
|
+
console.log(`Install: ${result.install}`)
|
|
298
|
+
console.log(`Docs: ${docsPath}`)
|
|
299
|
+
|
|
300
|
+
if (result.files.length > 0) {
|
|
301
|
+
console.log("Files:")
|
|
302
|
+
for (const file of result.files) {
|
|
303
|
+
console.log(`- ${file.source} -> ${file.target}`)
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if (result.examples.length > 0) {
|
|
308
|
+
console.log("Examples:")
|
|
309
|
+
for (const example of result.examples) {
|
|
310
|
+
console.log(`- ${example}`)
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
async function viewComponent(positionals, flags) {
|
|
316
|
+
const [name] = positionals
|
|
317
|
+
const component = getComponentOrThrow(name)
|
|
318
|
+
const files = component.files || []
|
|
319
|
+
const requestedFile = typeof flags.file === "string" ? flags.file : null
|
|
320
|
+
const file =
|
|
321
|
+
(requestedFile
|
|
322
|
+
? files.find((entry) => entry.source === requestedFile)
|
|
323
|
+
: files.find((entry) => /\.(ts|tsx|css)$/.test(entry.source)) || files[0]) ||
|
|
324
|
+
null
|
|
325
|
+
|
|
326
|
+
if (!file) {
|
|
327
|
+
throw new Error(`component "${name}" has no viewable registry files.`)
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const sourcePath = path.join(registryRoot, file.source)
|
|
331
|
+
const content = await fs.readFile(
|
|
332
|
+
sourcePath,
|
|
333
|
+
isTextRegistryFile(file.source) ? "utf8" : undefined
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
if (runtimeOptions.json) {
|
|
337
|
+
writeJson({
|
|
338
|
+
name,
|
|
339
|
+
file,
|
|
340
|
+
content: Buffer.isBuffer(content) ? content.toString("base64") : content,
|
|
341
|
+
encoding: Buffer.isBuffer(content) ? "base64" : "utf8",
|
|
342
|
+
})
|
|
343
|
+
return
|
|
155
344
|
}
|
|
345
|
+
|
|
346
|
+
if (runtimeOptions.silent) return
|
|
347
|
+
|
|
348
|
+
console.log(Buffer.isBuffer(content) ? content.toString("base64") : content)
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function getComponentOrThrow(name) {
|
|
352
|
+
if (!name) {
|
|
353
|
+
throw new Error("missing component name. Try `banhaten docs button`.")
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const component = registry.components[name]
|
|
357
|
+
if (!component) {
|
|
358
|
+
throw new Error(`unknown component "${name}". Run \`banhaten list\`.`)
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
return component
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function getComponentDocPath(name) {
|
|
365
|
+
const docAliases = {
|
|
366
|
+
"onboarding-step": "onboarding-step-list-item",
|
|
367
|
+
}
|
|
368
|
+
const docId = docAliases[name] || name
|
|
369
|
+
|
|
370
|
+
return `docs/design-system/components/${docId}.md`
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function writeJson(value) {
|
|
374
|
+
console.log(JSON.stringify(value, null, 2))
|
|
156
375
|
}
|
|
157
376
|
|
|
158
377
|
async function initProject(flags) {
|
|
@@ -165,6 +384,7 @@ async function initProject(flags) {
|
|
|
165
384
|
|
|
166
385
|
writes.push(
|
|
167
386
|
...(await writeConfig(cwd, config, { dryRun, force })),
|
|
387
|
+
...(await writeProjectSchemaFiles(cwd, { dryRun, force })),
|
|
168
388
|
...(await writeBaseFiles(cwd, config, { dryRun, force })),
|
|
169
389
|
...(await writeGlobalCss(cwd, config, { dryRun, force })),
|
|
170
390
|
...(await writeAliasConfig(cwd, config, { dryRun, force }))
|
|
@@ -178,7 +398,7 @@ async function initProject(flags) {
|
|
|
178
398
|
writes.push(...dependencyWrites)
|
|
179
399
|
|
|
180
400
|
printResult("Initialized Banhaten design system", writes, dryRun)
|
|
181
|
-
printInstallHint(writes, dryRun)
|
|
401
|
+
await printInstallHint(cwd, writes, dryRun)
|
|
182
402
|
}
|
|
183
403
|
|
|
184
404
|
async function addComponents(names, flags) {
|
|
@@ -236,7 +456,9 @@ async function writeComponents(requestedNames, flags, options) {
|
|
|
236
456
|
|
|
237
457
|
writes.push(
|
|
238
458
|
...(await writeConfig(cwd, config, { dryRun, force })),
|
|
459
|
+
...(await writeProjectSchemaFiles(cwd, { dryRun, force })),
|
|
239
460
|
...(await writeBaseFiles(cwd, config, { dryRun, force })),
|
|
461
|
+
...(await writeGlobalCss(cwd, config, { dryRun, force })),
|
|
240
462
|
...(await writeAliasConfig(cwd, config, { dryRun, force }))
|
|
241
463
|
)
|
|
242
464
|
|
|
@@ -262,7 +484,7 @@ async function writeComponents(requestedNames, flags, options) {
|
|
|
262
484
|
)))
|
|
263
485
|
|
|
264
486
|
printResult(options.title, writes, dryRun)
|
|
265
|
-
printInstallHint(writes, dryRun)
|
|
487
|
+
await printInstallHint(cwd, writes, dryRun)
|
|
266
488
|
}
|
|
267
489
|
|
|
268
490
|
async function findInstalledComponents(cwd, config) {
|
|
@@ -371,6 +593,24 @@ async function detectCssPath(cwd, fallback) {
|
|
|
371
593
|
async function writeConfig(cwd, config, options) {
|
|
372
594
|
const configPath = path.join(cwd, CONFIG_FILE)
|
|
373
595
|
if ((await exists(configPath)) && !options.force) {
|
|
596
|
+
const current = await fs.readFile(configPath, "utf8")
|
|
597
|
+
const currentConfig = JSON.parse(current)
|
|
598
|
+
|
|
599
|
+
if (currentConfig.$schema !== DEFAULT_CONFIG.$schema) {
|
|
600
|
+
const nextConfig = {
|
|
601
|
+
$schema: DEFAULT_CONFIG.$schema,
|
|
602
|
+
...currentConfig,
|
|
603
|
+
}
|
|
604
|
+
nextConfig.$schema = DEFAULT_CONFIG.$schema
|
|
605
|
+
const status = await writeFile(
|
|
606
|
+
configPath,
|
|
607
|
+
`${JSON.stringify(nextConfig, null, 2)}\n`,
|
|
608
|
+
{ dryRun: options.dryRun }
|
|
609
|
+
)
|
|
610
|
+
|
|
611
|
+
return [`${status} ${relative(cwd, configPath)}`]
|
|
612
|
+
}
|
|
613
|
+
|
|
374
614
|
return [`kept ${relative(cwd, configPath)}`]
|
|
375
615
|
}
|
|
376
616
|
|
|
@@ -382,6 +622,26 @@ async function writeConfig(cwd, config, options) {
|
|
|
382
622
|
return [`${status} ${relative(cwd, configPath)}`]
|
|
383
623
|
}
|
|
384
624
|
|
|
625
|
+
async function writeProjectSchemaFiles(cwd, options) {
|
|
626
|
+
const schemaFiles = [
|
|
627
|
+
"config.schema.json",
|
|
628
|
+
"registry.schema.json",
|
|
629
|
+
"tokens.schema.json",
|
|
630
|
+
]
|
|
631
|
+
const writes = []
|
|
632
|
+
|
|
633
|
+
for (const schemaFile of schemaFiles) {
|
|
634
|
+
const sourcePath = path.join(schemaRoot, schemaFile)
|
|
635
|
+
const targetPath = path.join(cwd, ".banhaten", "schema", schemaFile)
|
|
636
|
+
const source = await fs.readFile(sourcePath, "utf8")
|
|
637
|
+
const status = await writeFile(targetPath, source, options)
|
|
638
|
+
|
|
639
|
+
writes.push(`${status} ${relative(cwd, targetPath)}`)
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
return writes
|
|
643
|
+
}
|
|
644
|
+
|
|
385
645
|
async function writeBaseFiles(cwd, config, options) {
|
|
386
646
|
return writeRegistryFiles(cwd, config, registry.base.files || [], options)
|
|
387
647
|
}
|
|
@@ -409,17 +669,16 @@ async function writeGlobalCss(cwd, config, options) {
|
|
|
409
669
|
const sourcePath = path.join(registryRoot, "styles", "globals.css")
|
|
410
670
|
const tokenCss = await fs.readFile(sourcePath, "utf8")
|
|
411
671
|
const current = await readTextIfExists(cssPath)
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
}
|
|
672
|
+
const tokenBlock = current
|
|
673
|
+
? removeExistingTokenBlock(current, relative(cwd, cssPath))
|
|
674
|
+
: { content: "", found: false }
|
|
416
675
|
|
|
417
676
|
const nextCss = current
|
|
418
|
-
? `${
|
|
677
|
+
? `${tokenBlock.content.trimEnd()}\n\n${tokenCss}\n`
|
|
419
678
|
: `${tokenCss}\n`
|
|
420
679
|
const next = ensureGlobalCssImports(nextCss)
|
|
421
680
|
|
|
422
|
-
const status = await writeFile(cssPath, next,
|
|
681
|
+
const status = await writeFile(cssPath, next, options)
|
|
423
682
|
return [`${status} ${relative(cwd, cssPath)}`]
|
|
424
683
|
}
|
|
425
684
|
|
|
@@ -489,20 +748,17 @@ async function writeViteAliasConfig(cwd, aliasRoot, options) {
|
|
|
489
748
|
|
|
490
749
|
const current = await fs.readFile(viteConfigPath, "utf8")
|
|
491
750
|
const sourceRoot = shouldUseSrcDirectory(cwd) ? "./src" : "."
|
|
492
|
-
|
|
751
|
+
let next
|
|
493
752
|
|
|
494
|
-
|
|
495
|
-
current
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
) {
|
|
499
|
-
return [`kept ${relative(cwd, viteConfigPath)}`]
|
|
753
|
+
try {
|
|
754
|
+
next = updateViteConfigSource(current, { aliasRoot, sourceRoot })
|
|
755
|
+
} catch (error) {
|
|
756
|
+
throw new Error(`${relative(cwd, viteConfigPath)}: ${error.message}`)
|
|
500
757
|
}
|
|
501
758
|
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
next = addVitePluginCall(next, "tailwindcss()")
|
|
759
|
+
if (next === current) {
|
|
760
|
+
return [`kept ${relative(cwd, viteConfigPath)}`]
|
|
761
|
+
}
|
|
506
762
|
|
|
507
763
|
const status = await writeFile(viteConfigPath, next, {
|
|
508
764
|
dryRun: options.dryRun,
|
|
@@ -528,198 +784,957 @@ async function findViteConfigPath(cwd) {
|
|
|
528
784
|
}
|
|
529
785
|
|
|
530
786
|
function ensureNodeUrlImport(source) {
|
|
531
|
-
|
|
787
|
+
const imports = parseImportDeclarations(source)
|
|
788
|
+
const nodeUrlImports = imports.filter((entry) => entry.moduleSpecifier === "node:url")
|
|
789
|
+
const importedLocals = new Set(nodeUrlImports.flatMap((entry) => entry.namedLocals))
|
|
790
|
+
const missing = ["fileURLToPath", "URL"].filter((name) => !importedLocals.has(name))
|
|
532
791
|
|
|
533
|
-
|
|
792
|
+
if (missing.length === 0) return source
|
|
793
|
+
|
|
794
|
+
return prependImportLines(
|
|
795
|
+
source,
|
|
796
|
+
[`import { ${missing.join(", ")} } from "node:url"`]
|
|
797
|
+
)
|
|
534
798
|
}
|
|
535
799
|
|
|
536
|
-
function ensureTailwindViteImport(source) {
|
|
537
|
-
|
|
800
|
+
function ensureTailwindViteImport(source, localName) {
|
|
801
|
+
const imports = parseImportDeclarations(source)
|
|
802
|
+
const tailwindImport = imports.find(
|
|
803
|
+
(entry) => entry.moduleSpecifier === "@tailwindcss/vite"
|
|
804
|
+
)
|
|
538
805
|
|
|
539
|
-
|
|
540
|
-
|
|
806
|
+
if (!tailwindImport) {
|
|
807
|
+
return prependImportLines(
|
|
808
|
+
source,
|
|
809
|
+
[`import ${localName} from "@tailwindcss/vite"`]
|
|
810
|
+
)
|
|
811
|
+
}
|
|
541
812
|
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
const resolveBlock = [
|
|
548
|
-
" resolve: {",
|
|
549
|
-
" alias: {",
|
|
550
|
-
` "${aliasRoot}": fileURLToPath(new URL("${sourceRoot}", import.meta.url)),`,
|
|
551
|
-
" },",
|
|
552
|
-
" },",
|
|
553
|
-
].join("\n")
|
|
554
|
-
|
|
555
|
-
return source.replace("defineConfig({", `defineConfig({\n${resolveBlock}`)
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
function addVitePluginCall(source, pluginCall) {
|
|
559
|
-
if (source.includes(pluginCall)) return source
|
|
560
|
-
|
|
561
|
-
const pluginsPattern = /plugins:\s*\[([\s\S]*?)\]/
|
|
562
|
-
if (pluginsPattern.test(source)) {
|
|
563
|
-
return source.replace(pluginsPattern, (_match, plugins) => {
|
|
564
|
-
const currentPlugins = plugins.trim().replace(/,\s*$/, "")
|
|
565
|
-
const nextPlugins = currentPlugins
|
|
566
|
-
? `${currentPlugins}, ${pluginCall}`
|
|
567
|
-
: pluginCall
|
|
568
|
-
return `plugins: [${nextPlugins}]`
|
|
569
|
-
})
|
|
813
|
+
if (!tailwindImport.defaultLocal) {
|
|
814
|
+
throw new Error(
|
|
815
|
+
"unsupported Vite config: @tailwindcss/vite must be imported with a default binding."
|
|
816
|
+
)
|
|
570
817
|
}
|
|
571
818
|
|
|
572
|
-
|
|
819
|
+
return source
|
|
820
|
+
}
|
|
573
821
|
|
|
574
|
-
|
|
822
|
+
function updateViteConfigSource(source, { aliasRoot, sourceRoot }) {
|
|
823
|
+
const tailwindLocalName = getTailwindViteLocalName(source)
|
|
824
|
+
let next = addViteResolveAlias(source, aliasRoot, sourceRoot)
|
|
825
|
+
next = addVitePluginCall(next, `${tailwindLocalName}()`, tailwindLocalName)
|
|
826
|
+
next = ensureTailwindViteImport(next, tailwindLocalName)
|
|
827
|
+
next = ensureNodeUrlImport(next)
|
|
828
|
+
return next
|
|
575
829
|
}
|
|
576
830
|
|
|
577
|
-
|
|
578
|
-
const
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
}
|
|
831
|
+
function getTailwindViteLocalName(source) {
|
|
832
|
+
const tailwindImport = parseImportDeclarations(source).find(
|
|
833
|
+
(entry) => entry.moduleSpecifier === "@tailwindcss/vite"
|
|
834
|
+
)
|
|
582
835
|
|
|
583
|
-
return
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
836
|
+
if (!tailwindImport) return "tailwindcss"
|
|
837
|
+
if (tailwindImport.defaultLocal) return tailwindImport.defaultLocal
|
|
838
|
+
|
|
839
|
+
throw new Error(
|
|
840
|
+
"unsupported Vite config: @tailwindcss/vite must be imported with a default binding."
|
|
841
|
+
)
|
|
587
842
|
}
|
|
588
843
|
|
|
589
|
-
|
|
590
|
-
const
|
|
591
|
-
const
|
|
592
|
-
const
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
844
|
+
function addViteResolveAlias(source, aliasRoot, sourceRoot) {
|
|
845
|
+
const configObject = findViteConfigObject(source)
|
|
846
|
+
const resolveProperty = findObjectProperty(source, configObject, "resolve")
|
|
847
|
+
const aliasEntry = `${JSON.stringify(aliasRoot)}: fileURLToPath(new URL(${JSON.stringify(
|
|
848
|
+
sourceRoot
|
|
849
|
+
)}, import.meta.url))`
|
|
850
|
+
|
|
851
|
+
if (!resolveProperty) {
|
|
852
|
+
return insertObjectProperty(
|
|
853
|
+
source,
|
|
854
|
+
configObject,
|
|
855
|
+
[
|
|
856
|
+
"resolve: {",
|
|
857
|
+
" alias: {",
|
|
858
|
+
` ${aliasEntry},`,
|
|
859
|
+
" },",
|
|
860
|
+
"},",
|
|
861
|
+
].join("\n")
|
|
862
|
+
)
|
|
597
863
|
}
|
|
598
|
-
const added = [
|
|
599
|
-
...addPackageSection(packageJson, "dependencies", dependencies),
|
|
600
|
-
...addPackageSection(packageJson, "devDependencies", devDependencies),
|
|
601
|
-
]
|
|
602
864
|
|
|
603
|
-
|
|
604
|
-
|
|
865
|
+
const resolveObject = expectObjectLiteralValue(
|
|
866
|
+
source,
|
|
867
|
+
resolveProperty,
|
|
868
|
+
"unsupported Vite config: resolve must be an object literal."
|
|
869
|
+
)
|
|
870
|
+
const aliasProperty = findObjectProperty(source, resolveObject, "alias")
|
|
871
|
+
|
|
872
|
+
if (!aliasProperty) {
|
|
873
|
+
return insertObjectProperty(
|
|
874
|
+
source,
|
|
875
|
+
resolveObject,
|
|
876
|
+
[
|
|
877
|
+
"alias: {",
|
|
878
|
+
` ${aliasEntry},`,
|
|
879
|
+
"},",
|
|
880
|
+
].join("\n")
|
|
881
|
+
)
|
|
605
882
|
}
|
|
606
883
|
|
|
607
|
-
const
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
884
|
+
const aliasObject = expectObjectLiteralValue(
|
|
885
|
+
source,
|
|
886
|
+
aliasProperty,
|
|
887
|
+
"unsupported Vite config: resolve.alias must be an object literal."
|
|
888
|
+
)
|
|
611
889
|
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
}
|
|
890
|
+
if (findObjectProperty(source, aliasObject, aliasRoot)) {
|
|
891
|
+
return source
|
|
892
|
+
}
|
|
616
893
|
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
const existing = packageJson[section] || {}
|
|
894
|
+
return insertObjectProperty(source, aliasObject, `${aliasEntry},`)
|
|
895
|
+
}
|
|
620
896
|
|
|
621
|
-
|
|
622
|
-
|
|
897
|
+
function addVitePluginCall(source, pluginCall, pluginLocalName) {
|
|
898
|
+
const configObject = findViteConfigObject(source)
|
|
899
|
+
const pluginsProperty = findObjectProperty(source, configObject, "plugins")
|
|
623
900
|
|
|
624
|
-
|
|
625
|
-
|
|
901
|
+
if (!pluginsProperty) {
|
|
902
|
+
return insertObjectProperty(source, configObject, `plugins: [${pluginCall}],`)
|
|
626
903
|
}
|
|
627
904
|
|
|
628
|
-
|
|
629
|
-
|
|
905
|
+
const pluginsArray = expectArrayLiteralValue(
|
|
906
|
+
source,
|
|
907
|
+
pluginsProperty,
|
|
908
|
+
"unsupported Vite config: plugins must be an array literal."
|
|
909
|
+
)
|
|
910
|
+
|
|
911
|
+
if (arrayHasCallExpression(source, pluginsArray, pluginLocalName)) {
|
|
912
|
+
return source
|
|
630
913
|
}
|
|
631
914
|
|
|
632
|
-
return
|
|
915
|
+
return appendArrayElement(source, pluginsArray, pluginCall)
|
|
633
916
|
}
|
|
634
917
|
|
|
635
|
-
function
|
|
636
|
-
|
|
637
|
-
packageJson.dependencies?.[name] ||
|
|
638
|
-
packageJson.devDependencies?.[name] ||
|
|
639
|
-
packageJson.peerDependencies?.[name] ||
|
|
640
|
-
packageJson.optionalDependencies?.[name]
|
|
641
|
-
)
|
|
642
|
-
}
|
|
918
|
+
function findViteConfigObject(source) {
|
|
919
|
+
const expressionStart = findExportDefaultExpressionStart(source)
|
|
643
920
|
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
}
|
|
921
|
+
if (expressionStart === -1) {
|
|
922
|
+
throw new Error(
|
|
923
|
+
"unsupported Vite config: expected `export default defineConfig({ ... })` or `export default { ... }`."
|
|
924
|
+
)
|
|
925
|
+
}
|
|
649
926
|
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
}
|
|
927
|
+
if (source[expressionStart] === "{") {
|
|
928
|
+
return createBracketRange(source, expressionStart, "{")
|
|
929
|
+
}
|
|
653
930
|
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
931
|
+
const callee = readIdentifierAt(source, expressionStart)
|
|
932
|
+
if (callee?.name !== "defineConfig") {
|
|
933
|
+
throw new Error(
|
|
934
|
+
"unsupported Vite config: default export must be `defineConfig({ ... })` or an object literal."
|
|
935
|
+
)
|
|
659
936
|
}
|
|
660
937
|
|
|
661
|
-
let
|
|
662
|
-
|
|
663
|
-
|
|
938
|
+
let index = skipWhitespaceAndComments(source, callee.end)
|
|
939
|
+
if (source[index] !== "(") {
|
|
940
|
+
throw new Error("unsupported Vite config: malformed defineConfig call.")
|
|
664
941
|
}
|
|
665
942
|
|
|
666
|
-
|
|
943
|
+
index = skipWhitespaceAndComments(source, index + 1)
|
|
944
|
+
if (source[index] !== "{") {
|
|
945
|
+
throw new Error(
|
|
946
|
+
"unsupported Vite config: defineConfig must receive a config object literal."
|
|
947
|
+
)
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
return createBracketRange(source, index, "{")
|
|
667
951
|
}
|
|
668
952
|
|
|
669
|
-
function
|
|
670
|
-
|
|
671
|
-
if (path.isAbsolute(alias)) return alias
|
|
672
|
-
if (alias.startsWith("./")) return alias.slice(2)
|
|
673
|
-
if (alias.startsWith("../")) return alias
|
|
953
|
+
function findExportDefaultExpressionStart(source) {
|
|
954
|
+
let index = 0
|
|
674
955
|
|
|
675
|
-
|
|
676
|
-
const
|
|
677
|
-
|
|
956
|
+
while (index < source.length) {
|
|
957
|
+
const next = skipIgnoredSyntax(source, index)
|
|
958
|
+
if (next !== index) {
|
|
959
|
+
index = next
|
|
960
|
+
continue
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
if (!matchIdentifier(source, index, "export")) {
|
|
964
|
+
index += 1
|
|
965
|
+
continue
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
const defaultStart = skipWhitespaceAndComments(source, index + "export".length)
|
|
969
|
+
if (!matchIdentifier(source, defaultStart, "default")) {
|
|
970
|
+
index += "export".length
|
|
971
|
+
continue
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
return skipWhitespaceAndComments(source, defaultStart + "default".length)
|
|
678
975
|
}
|
|
679
976
|
|
|
680
|
-
return
|
|
977
|
+
return -1
|
|
681
978
|
}
|
|
682
979
|
|
|
683
|
-
function
|
|
684
|
-
|
|
980
|
+
function createBracketRange(source, start, open) {
|
|
981
|
+
const close = open === "{" ? "}" : open === "[" ? "]" : ")"
|
|
982
|
+
const end = findMatchingBracket(source, start, open, close)
|
|
983
|
+
return { start, end: end + 1 }
|
|
685
984
|
}
|
|
686
985
|
|
|
687
|
-
function
|
|
688
|
-
|
|
986
|
+
function expectObjectLiteralValue(source, property, message) {
|
|
987
|
+
const valueStart = skipWhitespaceAndComments(source, property.valueStart)
|
|
988
|
+
if (source[valueStart] !== "{") throw new Error(message)
|
|
989
|
+
return createBracketRange(source, valueStart, "{")
|
|
689
990
|
}
|
|
690
991
|
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
}
|
|
992
|
+
function expectArrayLiteralValue(source, property, message) {
|
|
993
|
+
const valueStart = skipWhitespaceAndComments(source, property.valueStart)
|
|
994
|
+
if (source[valueStart] !== "[") throw new Error(message)
|
|
995
|
+
return createBracketRange(source, valueStart, "[")
|
|
696
996
|
}
|
|
697
997
|
|
|
698
|
-
|
|
699
|
-
const
|
|
700
|
-
|
|
998
|
+
function findObjectProperty(source, objectRange, propertyName) {
|
|
999
|
+
const bodyEnd = objectRange.end - 1
|
|
1000
|
+
let index = objectRange.start + 1
|
|
701
1001
|
|
|
702
|
-
|
|
703
|
-
|
|
1002
|
+
while (index < bodyEnd) {
|
|
1003
|
+
index = skipWhitespaceCommentsAndCommas(source, index, bodyEnd)
|
|
1004
|
+
if (index >= bodyEnd) return null
|
|
704
1005
|
|
|
705
|
-
const
|
|
706
|
-
const
|
|
707
|
-
|
|
708
|
-
: current.toString("utf8") === content
|
|
709
|
-
if (isEqual) return "would keep"
|
|
1006
|
+
const propertyStart = index
|
|
1007
|
+
const name = readPropertyName(source, index)
|
|
1008
|
+
const propertyEnd = findTopLevelCommaOrEnd(source, propertyStart, bodyEnd)
|
|
710
1009
|
|
|
711
|
-
|
|
712
|
-
|
|
1010
|
+
if (!name) {
|
|
1011
|
+
index = propertyEnd + 1
|
|
1012
|
+
continue
|
|
1013
|
+
}
|
|
713
1014
|
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
1015
|
+
index = skipWhitespaceAndComments(source, name.end)
|
|
1016
|
+
if (source[index] !== ":") {
|
|
1017
|
+
if (name.name === propertyName) {
|
|
1018
|
+
throw new Error(
|
|
1019
|
+
`unsupported Vite config: ${propertyName} must be a normal object property.`
|
|
1020
|
+
)
|
|
1021
|
+
}
|
|
1022
|
+
index = propertyEnd + 1
|
|
1023
|
+
continue
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
if (name.name === propertyName) {
|
|
1027
|
+
return {
|
|
1028
|
+
name: name.name,
|
|
1029
|
+
start: propertyStart,
|
|
1030
|
+
end: propertyEnd,
|
|
1031
|
+
valueStart: skipWhitespaceAndComments(source, index + 1),
|
|
1032
|
+
valueEnd: propertyEnd,
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
index = propertyEnd + 1
|
|
720
1037
|
}
|
|
721
1038
|
|
|
722
|
-
|
|
1039
|
+
return null
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
function readPropertyName(source, index) {
|
|
1043
|
+
if (source[index] === "\"" || source[index] === "'") {
|
|
1044
|
+
const literal = readStringLiteral(source, index)
|
|
1045
|
+
return literal ? { name: literal.value, end: literal.end } : null
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
return readIdentifierAt(source, index)
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
function insertObjectProperty(source, objectRange, propertyText) {
|
|
1052
|
+
const insertAt = objectRange.start + 1
|
|
1053
|
+
const bodyEnd = objectRange.end - 1
|
|
1054
|
+
const body = source.slice(insertAt, bodyEnd)
|
|
1055
|
+
const hasContent = body.trim().length > 0
|
|
1056
|
+
const propertyIndent = detectObjectChildIndent(source, objectRange)
|
|
1057
|
+
const closeIndent = getLineIndent(source, bodyEnd)
|
|
1058
|
+
const indentedProperty = indentMultiline(propertyText, propertyIndent)
|
|
1059
|
+
|
|
1060
|
+
if (!hasContent) {
|
|
1061
|
+
return `${source.slice(0, insertAt)}\n${indentedProperty}\n${closeIndent}${source.slice(insertAt)}`
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
const needsTrailingNewline = source[insertAt] !== "\n" && source[insertAt] !== "\r"
|
|
1065
|
+
const insertion = `\n${indentedProperty}${needsTrailingNewline ? "\n" : ""}`
|
|
1066
|
+
return `${source.slice(0, insertAt)}${insertion}${source.slice(insertAt)}`
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
function detectObjectChildIndent(source, objectRange) {
|
|
1070
|
+
const bodyEnd = objectRange.end - 1
|
|
1071
|
+
let index = objectRange.start + 1
|
|
1072
|
+
|
|
1073
|
+
while (index < bodyEnd) {
|
|
1074
|
+
if (source[index] === "\n") {
|
|
1075
|
+
const next = skipHorizontalWhitespace(source, index + 1)
|
|
1076
|
+
if (next < bodyEnd && source[next] !== "\n" && source[next] !== "\r") {
|
|
1077
|
+
return source.slice(index + 1, next)
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
index += 1
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
return `${getLineIndent(source, objectRange.start)} `
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
function appendArrayElement(source, arrayRange, elementText) {
|
|
1087
|
+
const bodyStart = arrayRange.start + 1
|
|
1088
|
+
const bodyEnd = arrayRange.end - 1
|
|
1089
|
+
const body = source.slice(bodyStart, bodyEnd)
|
|
1090
|
+
|
|
1091
|
+
if (body.trim().length === 0) {
|
|
1092
|
+
return `${source.slice(0, bodyStart)}${elementText}${source.slice(bodyStart)}`
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
const contentEnd = trimEndIndex(source, bodyStart, bodyEnd)
|
|
1096
|
+
const hasNewline = bodyIncludesNewline(source, bodyStart, bodyEnd)
|
|
1097
|
+
const hasTrailingComma = previousCodeChar(source, contentEnd) === ","
|
|
1098
|
+
|
|
1099
|
+
if (!hasNewline) {
|
|
1100
|
+
const separator = hasTrailingComma ? " " : ", "
|
|
1101
|
+
return `${source.slice(0, contentEnd)}${separator}${elementText}${source.slice(contentEnd)}`
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
const closeIndent = getLineIndent(source, bodyEnd)
|
|
1105
|
+
const elementIndent = `${closeIndent} `
|
|
1106
|
+
const separator = hasTrailingComma ? "\n" : ",\n"
|
|
1107
|
+
return `${source.slice(0, contentEnd)}${separator}${elementIndent}${elementText},${source.slice(contentEnd)}`
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
function arrayHasCallExpression(source, arrayRange, localName) {
|
|
1111
|
+
const bodyEnd = arrayRange.end - 1
|
|
1112
|
+
let index = arrayRange.start + 1
|
|
1113
|
+
|
|
1114
|
+
while (index < bodyEnd) {
|
|
1115
|
+
index = skipWhitespaceCommentsAndCommas(source, index, bodyEnd)
|
|
1116
|
+
if (index >= bodyEnd) return false
|
|
1117
|
+
|
|
1118
|
+
const elementEnd = findTopLevelCommaOrEnd(source, index, bodyEnd)
|
|
1119
|
+
if (startsWithCallExpression(source, index, elementEnd, localName)) {
|
|
1120
|
+
return true
|
|
1121
|
+
}
|
|
1122
|
+
index = elementEnd + 1
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
return false
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
function startsWithCallExpression(source, start, end, localName) {
|
|
1129
|
+
const index = skipWhitespaceAndComments(source, start)
|
|
1130
|
+
if (!matchIdentifier(source, index, localName)) return false
|
|
1131
|
+
|
|
1132
|
+
const callStart = skipWhitespaceAndComments(source, index + localName.length)
|
|
1133
|
+
if (source[callStart] !== "(") return false
|
|
1134
|
+
|
|
1135
|
+
const callEnd = createBracketRange(source, callStart, "(").end
|
|
1136
|
+
const next = skipWhitespaceAndComments(source, callEnd)
|
|
1137
|
+
return next <= end
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
function parseImportDeclarations(source) {
|
|
1141
|
+
const declarations = []
|
|
1142
|
+
let index = 0
|
|
1143
|
+
|
|
1144
|
+
while (index < source.length) {
|
|
1145
|
+
const next = skipIgnoredSyntax(source, index)
|
|
1146
|
+
if (next !== index) {
|
|
1147
|
+
index = next
|
|
1148
|
+
continue
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
if (!matchIdentifier(source, index, "import")) {
|
|
1152
|
+
index += 1
|
|
1153
|
+
continue
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
const clauseStart = skipWhitespaceAndComments(source, index + "import".length)
|
|
1157
|
+
if (source[clauseStart] === ".") {
|
|
1158
|
+
index = clauseStart + 1
|
|
1159
|
+
continue
|
|
1160
|
+
}
|
|
1161
|
+
if (source[clauseStart] === "(") {
|
|
1162
|
+
index = clauseStart + 1
|
|
1163
|
+
continue
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
const end = findImportDeclarationEnd(source, index)
|
|
1167
|
+
const declaration = source.slice(index, end)
|
|
1168
|
+
const moduleSpecifier = getImportModuleSpecifier(declaration)
|
|
1169
|
+
|
|
1170
|
+
if (moduleSpecifier) {
|
|
1171
|
+
declarations.push({
|
|
1172
|
+
start: index,
|
|
1173
|
+
end,
|
|
1174
|
+
moduleSpecifier,
|
|
1175
|
+
defaultLocal: getDefaultImportLocal(declaration),
|
|
1176
|
+
namedLocals: getNamedImportLocals(declaration),
|
|
1177
|
+
})
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
index = end
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
return declarations
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
function findImportDeclarationEnd(source, start) {
|
|
1187
|
+
const stack = []
|
|
1188
|
+
let sawModuleSpecifier = false
|
|
1189
|
+
let index = start
|
|
1190
|
+
|
|
1191
|
+
while (index < source.length) {
|
|
1192
|
+
const skipped = skipComment(source, index)
|
|
1193
|
+
if (skipped !== index) {
|
|
1194
|
+
index = skipped
|
|
1195
|
+
continue
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
if (source[index] === "\"" || source[index] === "'") {
|
|
1199
|
+
const literal = readStringLiteral(source, index)
|
|
1200
|
+
if (!literal) return source.length
|
|
1201
|
+
sawModuleSpecifier = true
|
|
1202
|
+
index = literal.end
|
|
1203
|
+
continue
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
if (isOpeningBracket(source[index])) {
|
|
1207
|
+
stack.push(expectedClosingBracket(source[index]))
|
|
1208
|
+
index += 1
|
|
1209
|
+
continue
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
if (isClosingBracket(source[index])) {
|
|
1213
|
+
if (stack.at(-1) === source[index]) stack.pop()
|
|
1214
|
+
index += 1
|
|
1215
|
+
continue
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
if (stack.length === 0 && source[index] === ";") return index + 1
|
|
1219
|
+
if (
|
|
1220
|
+
stack.length === 0 &&
|
|
1221
|
+
sawModuleSpecifier &&
|
|
1222
|
+
(source[index] === "\n" || source[index] === "\r")
|
|
1223
|
+
) {
|
|
1224
|
+
return index + 1
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
index += 1
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
return source.length
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
function getImportModuleSpecifier(declaration) {
|
|
1234
|
+
let moduleSpecifier = null
|
|
1235
|
+
let index = 0
|
|
1236
|
+
|
|
1237
|
+
while (index < declaration.length) {
|
|
1238
|
+
const skipped = skipComment(declaration, index)
|
|
1239
|
+
if (skipped !== index) {
|
|
1240
|
+
index = skipped
|
|
1241
|
+
continue
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
if (declaration[index] === "\"" || declaration[index] === "'") {
|
|
1245
|
+
const literal = readStringLiteral(declaration, index)
|
|
1246
|
+
if (!literal) break
|
|
1247
|
+
moduleSpecifier = literal.value
|
|
1248
|
+
index = literal.end
|
|
1249
|
+
continue
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
index += 1
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
return moduleSpecifier
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
function getDefaultImportLocal(declaration) {
|
|
1259
|
+
let index = skipWhitespaceAndComments(declaration, "import".length)
|
|
1260
|
+
|
|
1261
|
+
if (matchIdentifier(declaration, index, "type")) {
|
|
1262
|
+
index = skipWhitespaceAndComments(declaration, index + "type".length)
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
if (declaration[index] === "\"" || declaration[index] === "{" || declaration[index] === "*") {
|
|
1266
|
+
return null
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
const local = readIdentifierAt(declaration, index)
|
|
1270
|
+
return local?.name ?? null
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
function getNamedImportLocals(declaration) {
|
|
1274
|
+
const open = declaration.indexOf("{")
|
|
1275
|
+
if (open === -1) return []
|
|
1276
|
+
|
|
1277
|
+
const close = findMatchingBracket(declaration, open, "{", "}")
|
|
1278
|
+
const namedImportBody = declaration.slice(open + 1, close)
|
|
1279
|
+
const locals = []
|
|
1280
|
+
|
|
1281
|
+
for (const specifier of splitTopLevelList(namedImportBody)) {
|
|
1282
|
+
const trimmed = specifier.trim()
|
|
1283
|
+
if (!trimmed) continue
|
|
1284
|
+
|
|
1285
|
+
const withoutType = trimmed.startsWith("type ")
|
|
1286
|
+
? trimmed.slice("type ".length).trim()
|
|
1287
|
+
: trimmed
|
|
1288
|
+
const asIndex = withoutType.toLowerCase().lastIndexOf(" as ")
|
|
1289
|
+
const localName = asIndex === -1
|
|
1290
|
+
? withoutType
|
|
1291
|
+
: withoutType.slice(asIndex + " as ".length).trim()
|
|
1292
|
+
const local = readIdentifierAt(localName, 0)
|
|
1293
|
+
|
|
1294
|
+
if (local) locals.push(local.name)
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
return locals
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
function splitTopLevelList(source) {
|
|
1301
|
+
const items = []
|
|
1302
|
+
let start = 0
|
|
1303
|
+
let index = 0
|
|
1304
|
+
|
|
1305
|
+
while (index < source.length) {
|
|
1306
|
+
const skipped = skipIgnoredSyntax(source, index)
|
|
1307
|
+
if (skipped !== index) {
|
|
1308
|
+
index = skipped
|
|
1309
|
+
continue
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
if (source[index] === ",") {
|
|
1313
|
+
items.push(source.slice(start, index))
|
|
1314
|
+
start = index + 1
|
|
1315
|
+
}
|
|
1316
|
+
index += 1
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
items.push(source.slice(start))
|
|
1320
|
+
return items
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
function prependImportLines(source, lines) {
|
|
1324
|
+
if (lines.length === 0) return source
|
|
1325
|
+
|
|
1326
|
+
let insertAt = 0
|
|
1327
|
+
if (source.startsWith("#!")) {
|
|
1328
|
+
const lineEnd = source.indexOf("\n")
|
|
1329
|
+
insertAt = lineEnd === -1 ? source.length : lineEnd + 1
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
const prefix = `${lines.join("\n")}\n`
|
|
1333
|
+
return `${source.slice(0, insertAt)}${prefix}${source.slice(insertAt)}`
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
function findTopLevelCommaOrEnd(source, start, end) {
|
|
1337
|
+
const stack = []
|
|
1338
|
+
let index = start
|
|
1339
|
+
|
|
1340
|
+
while (index < end) {
|
|
1341
|
+
const next = skipIgnoredSyntax(source, index)
|
|
1342
|
+
if (next !== index) {
|
|
1343
|
+
index = next
|
|
1344
|
+
continue
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
if (isOpeningBracket(source[index])) {
|
|
1348
|
+
stack.push(expectedClosingBracket(source[index]))
|
|
1349
|
+
index += 1
|
|
1350
|
+
continue
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
if (isClosingBracket(source[index])) {
|
|
1354
|
+
if (stack.at(-1) === source[index]) stack.pop()
|
|
1355
|
+
index += 1
|
|
1356
|
+
continue
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1359
|
+
if (stack.length === 0 && source[index] === ",") return index
|
|
1360
|
+
index += 1
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
return end
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
function findMatchingBracket(source, start, open, close) {
|
|
1367
|
+
if (source[start] !== open) {
|
|
1368
|
+
throw new Error("unsupported Vite config: malformed JavaScript object.")
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
const stack = []
|
|
1372
|
+
let index = start
|
|
1373
|
+
|
|
1374
|
+
while (index < source.length) {
|
|
1375
|
+
const next = skipIgnoredSyntax(source, index)
|
|
1376
|
+
if (next !== index) {
|
|
1377
|
+
index = next
|
|
1378
|
+
continue
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1381
|
+
if (isOpeningBracket(source[index])) {
|
|
1382
|
+
stack.push(expectedClosingBracket(source[index]))
|
|
1383
|
+
index += 1
|
|
1384
|
+
continue
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
if (isClosingBracket(source[index])) {
|
|
1388
|
+
const expected = stack.pop()
|
|
1389
|
+
if (expected !== source[index]) {
|
|
1390
|
+
throw new Error("unsupported Vite config: malformed JavaScript brackets.")
|
|
1391
|
+
}
|
|
1392
|
+
if (stack.length === 0) return index
|
|
1393
|
+
index += 1
|
|
1394
|
+
continue
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
index += 1
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1400
|
+
throw new Error(`unsupported Vite config: missing closing ${close}.`)
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
function skipIgnoredSyntax(source, index) {
|
|
1404
|
+
const commentEnd = skipComment(source, index)
|
|
1405
|
+
if (commentEnd !== index) return commentEnd
|
|
1406
|
+
|
|
1407
|
+
if (source[index] === "\"" || source[index] === "'" || source[index] === "`") {
|
|
1408
|
+
return skipQuotedText(source, index)
|
|
1409
|
+
}
|
|
1410
|
+
|
|
1411
|
+
return index
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
function skipComment(source, index) {
|
|
1415
|
+
if (source[index] !== "/") return index
|
|
1416
|
+
|
|
1417
|
+
if (source[index + 1] === "/") {
|
|
1418
|
+
const lineEnd = source.indexOf("\n", index + 2)
|
|
1419
|
+
return lineEnd === -1 ? source.length : lineEnd + 1
|
|
1420
|
+
}
|
|
1421
|
+
|
|
1422
|
+
if (source[index + 1] === "*") {
|
|
1423
|
+
const blockEnd = source.indexOf("*/", index + 2)
|
|
1424
|
+
return blockEnd === -1 ? source.length : blockEnd + 2
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1427
|
+
return index
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
function skipQuotedText(source, index) {
|
|
1431
|
+
const quote = source[index]
|
|
1432
|
+
index += 1
|
|
1433
|
+
|
|
1434
|
+
while (index < source.length) {
|
|
1435
|
+
if (source[index] === "\\") {
|
|
1436
|
+
index += 2
|
|
1437
|
+
continue
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
if (source[index] === quote) return index + 1
|
|
1441
|
+
index += 1
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1444
|
+
return source.length
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1447
|
+
function readStringLiteral(source, index) {
|
|
1448
|
+
const quote = source[index]
|
|
1449
|
+
if (quote !== "\"" && quote !== "'") return null
|
|
1450
|
+
|
|
1451
|
+
let value = ""
|
|
1452
|
+
index += 1
|
|
1453
|
+
|
|
1454
|
+
while (index < source.length) {
|
|
1455
|
+
if (source[index] === "\\") {
|
|
1456
|
+
value += source[index + 1] ?? ""
|
|
1457
|
+
index += 2
|
|
1458
|
+
continue
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1461
|
+
if (source[index] === quote) {
|
|
1462
|
+
return { value, end: index + 1 }
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1465
|
+
value += source[index]
|
|
1466
|
+
index += 1
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1469
|
+
return null
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1472
|
+
function readIdentifierAt(source, index) {
|
|
1473
|
+
if (!isIdentifierStart(source[index])) return null
|
|
1474
|
+
|
|
1475
|
+
let end = index + 1
|
|
1476
|
+
while (end < source.length && isIdentifierPart(source[end])) {
|
|
1477
|
+
end += 1
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1480
|
+
return { name: source.slice(index, end), end }
|
|
1481
|
+
}
|
|
1482
|
+
|
|
1483
|
+
function matchIdentifier(source, index, name) {
|
|
1484
|
+
if (source.slice(index, index + name.length) !== name) return false
|
|
1485
|
+
|
|
1486
|
+
const before = source[index - 1]
|
|
1487
|
+
const after = source[index + name.length]
|
|
1488
|
+
return !isIdentifierPart(before) && !isIdentifierPart(after)
|
|
1489
|
+
}
|
|
1490
|
+
|
|
1491
|
+
function isIdentifierStart(char) {
|
|
1492
|
+
return Boolean(char && /[A-Za-z_$]/.test(char))
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
function isIdentifierPart(char) {
|
|
1496
|
+
return Boolean(char && /[A-Za-z0-9_$]/.test(char))
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
function isOpeningBracket(char) {
|
|
1500
|
+
return char === "{" || char === "[" || char === "("
|
|
1501
|
+
}
|
|
1502
|
+
|
|
1503
|
+
function isClosingBracket(char) {
|
|
1504
|
+
return char === "}" || char === "]" || char === ")"
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1507
|
+
function expectedClosingBracket(char) {
|
|
1508
|
+
if (char === "{") return "}"
|
|
1509
|
+
if (char === "[") return "]"
|
|
1510
|
+
return ")"
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1513
|
+
function skipWhitespaceAndComments(source, index) {
|
|
1514
|
+
while (index < source.length) {
|
|
1515
|
+
const next = skipComment(source, index)
|
|
1516
|
+
if (next !== index) {
|
|
1517
|
+
index = next
|
|
1518
|
+
continue
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
if (!isWhitespace(source[index])) return index
|
|
1522
|
+
index += 1
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1525
|
+
return index
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1528
|
+
function skipWhitespaceCommentsAndCommas(source, index, end) {
|
|
1529
|
+
while (index < end) {
|
|
1530
|
+
const next = skipWhitespaceAndComments(source, index)
|
|
1531
|
+
if (next !== index) {
|
|
1532
|
+
index = next
|
|
1533
|
+
continue
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1536
|
+
if (source[index] !== ",") return index
|
|
1537
|
+
index += 1
|
|
1538
|
+
}
|
|
1539
|
+
|
|
1540
|
+
return index
|
|
1541
|
+
}
|
|
1542
|
+
|
|
1543
|
+
function skipHorizontalWhitespace(source, index) {
|
|
1544
|
+
while (source[index] === " " || source[index] === "\t") {
|
|
1545
|
+
index += 1
|
|
1546
|
+
}
|
|
1547
|
+
return index
|
|
1548
|
+
}
|
|
1549
|
+
|
|
1550
|
+
function isWhitespace(char) {
|
|
1551
|
+
return char === " " || char === "\t" || char === "\n" || char === "\r"
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1554
|
+
function indentMultiline(text, indent) {
|
|
1555
|
+
return text
|
|
1556
|
+
.split("\n")
|
|
1557
|
+
.map((line) => `${indent}${line}`)
|
|
1558
|
+
.join("\n")
|
|
1559
|
+
}
|
|
1560
|
+
|
|
1561
|
+
function getLineIndent(source, index) {
|
|
1562
|
+
let lineStart = index
|
|
1563
|
+
while (lineStart > 0 && source[lineStart - 1] !== "\n") {
|
|
1564
|
+
lineStart -= 1
|
|
1565
|
+
}
|
|
1566
|
+
|
|
1567
|
+
return source.slice(lineStart, skipHorizontalWhitespace(source, lineStart))
|
|
1568
|
+
}
|
|
1569
|
+
|
|
1570
|
+
function trimEndIndex(source, start, end) {
|
|
1571
|
+
while (end > start && isWhitespace(source[end - 1])) {
|
|
1572
|
+
end -= 1
|
|
1573
|
+
}
|
|
1574
|
+
return end
|
|
1575
|
+
}
|
|
1576
|
+
|
|
1577
|
+
function previousCodeChar(source, index) {
|
|
1578
|
+
index -= 1
|
|
1579
|
+
while (index >= 0 && isWhitespace(source[index])) {
|
|
1580
|
+
index -= 1
|
|
1581
|
+
}
|
|
1582
|
+
return source[index]
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1585
|
+
function bodyIncludesNewline(source, start, end) {
|
|
1586
|
+
for (let index = start; index < end; index += 1) {
|
|
1587
|
+
if (source[index] === "\n" || source[index] === "\r") return true
|
|
1588
|
+
}
|
|
1589
|
+
return false
|
|
1590
|
+
}
|
|
1591
|
+
|
|
1592
|
+
async function getPackageChanges(cwd, dependencies) {
|
|
1593
|
+
const devDependencies = { ...SETUP_DEV_DEPENDENCIES }
|
|
1594
|
+
if (await findViteConfigPath(cwd)) {
|
|
1595
|
+
Object.assign(devDependencies, VITE_DEV_DEPENDENCIES)
|
|
1596
|
+
}
|
|
1597
|
+
|
|
1598
|
+
return {
|
|
1599
|
+
dependencies,
|
|
1600
|
+
devDependencies,
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
|
|
1604
|
+
async function updatePackageJson(cwd, changes, options) {
|
|
1605
|
+
const packagePath = path.join(cwd, "package.json")
|
|
1606
|
+
const dependencies = changes.dependencies || {}
|
|
1607
|
+
const devDependencies = changes.devDependencies || {}
|
|
1608
|
+
const packageJson = (await readJsonIfExists(packagePath)) || {
|
|
1609
|
+
name: path.basename(cwd).toLowerCase(),
|
|
1610
|
+
version: "0.0.0",
|
|
1611
|
+
private: true,
|
|
1612
|
+
}
|
|
1613
|
+
const added = [
|
|
1614
|
+
...addPackageSection(packageJson, "dependencies", dependencies),
|
|
1615
|
+
...addPackageSection(packageJson, "devDependencies", devDependencies),
|
|
1616
|
+
]
|
|
1617
|
+
|
|
1618
|
+
if (added.length === 0 && (await exists(packagePath))) {
|
|
1619
|
+
return [`kept ${relative(cwd, packagePath)}`]
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1622
|
+
const status = await writeFile(packagePath, `${JSON.stringify(packageJson, null, 2)}\n`, {
|
|
1623
|
+
dryRun: options.dryRun,
|
|
1624
|
+
force: true,
|
|
1625
|
+
})
|
|
1626
|
+
|
|
1627
|
+
return [
|
|
1628
|
+
`${status} ${relative(cwd, packagePath)}`,
|
|
1629
|
+
]
|
|
1630
|
+
}
|
|
1631
|
+
|
|
1632
|
+
function addPackageSection(packageJson, section, dependencies) {
|
|
1633
|
+
const added = []
|
|
1634
|
+
const existing = packageJson[section] || {}
|
|
1635
|
+
|
|
1636
|
+
for (const [name, version] of Object.entries(dependencies)) {
|
|
1637
|
+
if (hasPackageDependency(packageJson, name)) continue
|
|
1638
|
+
|
|
1639
|
+
existing[name] = version
|
|
1640
|
+
added.push(name)
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1643
|
+
if (added.length > 0 || packageJson[section]) {
|
|
1644
|
+
packageJson[section] = sortObject(existing)
|
|
1645
|
+
}
|
|
1646
|
+
|
|
1647
|
+
return added
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
function hasPackageDependency(packageJson, name) {
|
|
1651
|
+
return Boolean(
|
|
1652
|
+
packageJson.dependencies?.[name] ||
|
|
1653
|
+
packageJson.devDependencies?.[name] ||
|
|
1654
|
+
packageJson.peerDependencies?.[name] ||
|
|
1655
|
+
packageJson.optionalDependencies?.[name]
|
|
1656
|
+
)
|
|
1657
|
+
}
|
|
1658
|
+
|
|
1659
|
+
function applyTemplate(source, config) {
|
|
1660
|
+
return source
|
|
1661
|
+
.replaceAll("@/lib/utils", config.aliases.utils)
|
|
1662
|
+
.replaceAll("@/hooks", config.aliases.hooks)
|
|
1663
|
+
}
|
|
1664
|
+
|
|
1665
|
+
function isTextRegistryFile(source) {
|
|
1666
|
+
return !/\.(avif|eot|gif|ico|jpe?g|otf|png|ttf|webp|woff2?)$/i.test(source)
|
|
1667
|
+
}
|
|
1668
|
+
|
|
1669
|
+
function resolveTarget(cwd, config, target) {
|
|
1670
|
+
const replacements = {
|
|
1671
|
+
"{{ui}}": aliasToPath(cwd, config.aliases.ui),
|
|
1672
|
+
"{{utils}}": stripExtension(aliasToPath(cwd, config.aliases.utils)),
|
|
1673
|
+
"{{hooks}}": aliasToPath(cwd, config.aliases.hooks),
|
|
1674
|
+
}
|
|
1675
|
+
|
|
1676
|
+
let resolved = target
|
|
1677
|
+
for (const [placeholder, value] of Object.entries(replacements)) {
|
|
1678
|
+
resolved = resolved.replaceAll(placeholder, value)
|
|
1679
|
+
}
|
|
1680
|
+
|
|
1681
|
+
return resolved
|
|
1682
|
+
}
|
|
1683
|
+
|
|
1684
|
+
function aliasToPath(cwd, alias) {
|
|
1685
|
+
if (!alias) return ""
|
|
1686
|
+
if (path.isAbsolute(alias)) return alias
|
|
1687
|
+
if (alias.startsWith("./")) return alias.slice(2)
|
|
1688
|
+
if (alias.startsWith("../")) return alias
|
|
1689
|
+
|
|
1690
|
+
if (alias.startsWith("@/") || alias.startsWith("~/")) {
|
|
1691
|
+
const rest = alias.slice(2)
|
|
1692
|
+
return path.join(shouldUseSrcDirectory(cwd) ? "src" : "", rest)
|
|
1693
|
+
}
|
|
1694
|
+
|
|
1695
|
+
return alias
|
|
1696
|
+
}
|
|
1697
|
+
|
|
1698
|
+
function shouldUseSrcDirectory(cwd) {
|
|
1699
|
+
return pathExistsSyncHint(cwd, "src/app") || pathExistsSyncHint(cwd, "src")
|
|
1700
|
+
}
|
|
1701
|
+
|
|
1702
|
+
function pathExistsSyncHint(cwd, segment) {
|
|
1703
|
+
return Boolean(syncExistsCache.get(path.join(cwd, segment)))
|
|
1704
|
+
}
|
|
1705
|
+
|
|
1706
|
+
async function warmPathHints(cwd) {
|
|
1707
|
+
for (const segment of ["src", "src/app"]) {
|
|
1708
|
+
const target = path.join(cwd, segment)
|
|
1709
|
+
syncExistsCache.set(target, await exists(target))
|
|
1710
|
+
}
|
|
1711
|
+
}
|
|
1712
|
+
|
|
1713
|
+
async function writeFile(targetPath, content, options) {
|
|
1714
|
+
const alreadyExists = await exists(targetPath)
|
|
1715
|
+
const isBinary = Buffer.isBuffer(content)
|
|
1716
|
+
|
|
1717
|
+
if (options.dryRun) {
|
|
1718
|
+
if (!alreadyExists) return "would write"
|
|
1719
|
+
|
|
1720
|
+
const current = await fs.readFile(targetPath)
|
|
1721
|
+
const isEqual = isBinary
|
|
1722
|
+
? current.equals(content)
|
|
1723
|
+
: current.toString("utf8") === content
|
|
1724
|
+
if (isEqual) return "would keep"
|
|
1725
|
+
|
|
1726
|
+
return "would update"
|
|
1727
|
+
}
|
|
1728
|
+
|
|
1729
|
+
if (alreadyExists && !options.force) {
|
|
1730
|
+
const current = await fs.readFile(targetPath)
|
|
1731
|
+
const isEqual = isBinary
|
|
1732
|
+
? current.equals(content)
|
|
1733
|
+
: current.toString("utf8") === content
|
|
1734
|
+
if (isEqual) return "kept"
|
|
1735
|
+
}
|
|
1736
|
+
|
|
1737
|
+
await fs.mkdir(path.dirname(targetPath), { recursive: true })
|
|
723
1738
|
await fs.writeFile(targetPath, content, isBinary ? undefined : "utf8")
|
|
724
1739
|
return alreadyExists ? "updated" : "wrote"
|
|
725
1740
|
}
|
|
@@ -734,9 +1749,76 @@ async function readJsonc(filePath) {
|
|
|
734
1749
|
}
|
|
735
1750
|
|
|
736
1751
|
function stripJsonComments(content) {
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
1752
|
+
let output = ""
|
|
1753
|
+
let inString = false
|
|
1754
|
+
let escaped = false
|
|
1755
|
+
|
|
1756
|
+
for (let index = 0; index < content.length; index += 1) {
|
|
1757
|
+
const char = content[index]
|
|
1758
|
+
const next = content[index + 1]
|
|
1759
|
+
|
|
1760
|
+
if (inString) {
|
|
1761
|
+
output += char
|
|
1762
|
+
|
|
1763
|
+
if (escaped) {
|
|
1764
|
+
escaped = false
|
|
1765
|
+
} else if (char === "\\") {
|
|
1766
|
+
escaped = true
|
|
1767
|
+
} else if (char === "\"") {
|
|
1768
|
+
inString = false
|
|
1769
|
+
}
|
|
1770
|
+
|
|
1771
|
+
continue
|
|
1772
|
+
}
|
|
1773
|
+
|
|
1774
|
+
if (char === "\"") {
|
|
1775
|
+
inString = true
|
|
1776
|
+
output += char
|
|
1777
|
+
continue
|
|
1778
|
+
}
|
|
1779
|
+
|
|
1780
|
+
if (char === "/" && next === "/") {
|
|
1781
|
+
output += " "
|
|
1782
|
+
index += 2
|
|
1783
|
+
|
|
1784
|
+
while (index < content.length && content[index] !== "\n" && content[index] !== "\r") {
|
|
1785
|
+
output += " "
|
|
1786
|
+
index += 1
|
|
1787
|
+
}
|
|
1788
|
+
|
|
1789
|
+
if (index < content.length) {
|
|
1790
|
+
output += content[index]
|
|
1791
|
+
if (content[index] === "\r" && content[index + 1] === "\n") {
|
|
1792
|
+
index += 1
|
|
1793
|
+
output += content[index]
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1796
|
+
|
|
1797
|
+
continue
|
|
1798
|
+
}
|
|
1799
|
+
|
|
1800
|
+
if (char === "/" && next === "*") {
|
|
1801
|
+
output += " "
|
|
1802
|
+
index += 2
|
|
1803
|
+
|
|
1804
|
+
while (index < content.length) {
|
|
1805
|
+
if (content[index] === "*" && content[index + 1] === "/") {
|
|
1806
|
+
output += " "
|
|
1807
|
+
index += 1
|
|
1808
|
+
break
|
|
1809
|
+
}
|
|
1810
|
+
|
|
1811
|
+
output += content[index] === "\n" || content[index] === "\r" ? content[index] : " "
|
|
1812
|
+
index += 1
|
|
1813
|
+
}
|
|
1814
|
+
|
|
1815
|
+
continue
|
|
1816
|
+
}
|
|
1817
|
+
|
|
1818
|
+
output += char
|
|
1819
|
+
}
|
|
1820
|
+
|
|
1821
|
+
return output
|
|
740
1822
|
}
|
|
741
1823
|
|
|
742
1824
|
async function readJsonIfExists(filePath) {
|
|
@@ -766,27 +1848,136 @@ async function exists(filePath) {
|
|
|
766
1848
|
}
|
|
767
1849
|
}
|
|
768
1850
|
|
|
769
|
-
function removeExistingTokenBlock(content) {
|
|
770
|
-
const
|
|
771
|
-
|
|
1851
|
+
function removeExistingTokenBlock(content, fileLabel = "CSS file") {
|
|
1852
|
+
const lines = splitLinesPreservingEndings(content)
|
|
1853
|
+
const kept = []
|
|
1854
|
+
let found = false
|
|
1855
|
+
let inTokenBlock = false
|
|
772
1856
|
|
|
773
|
-
const
|
|
774
|
-
|
|
1857
|
+
for (const line of lines) {
|
|
1858
|
+
const trimmed = line.trimStart()
|
|
775
1859
|
|
|
776
|
-
|
|
1860
|
+
if (trimmed.startsWith(CSS_START)) {
|
|
1861
|
+
if (found || inTokenBlock) {
|
|
1862
|
+
throw new Error(`${fileLabel}: multiple Banhaten token blocks found.`)
|
|
1863
|
+
}
|
|
1864
|
+
found = true
|
|
1865
|
+
inTokenBlock = true
|
|
1866
|
+
continue
|
|
1867
|
+
}
|
|
1868
|
+
|
|
1869
|
+
if (trimmed.startsWith(CSS_END)) {
|
|
1870
|
+
if (!inTokenBlock) {
|
|
1871
|
+
throw new Error(`${fileLabel}: Banhaten token end marker has no start marker.`)
|
|
1872
|
+
}
|
|
1873
|
+
inTokenBlock = false
|
|
1874
|
+
continue
|
|
1875
|
+
}
|
|
1876
|
+
|
|
1877
|
+
if (!inTokenBlock) {
|
|
1878
|
+
kept.push(line)
|
|
1879
|
+
}
|
|
1880
|
+
}
|
|
1881
|
+
|
|
1882
|
+
if (inTokenBlock) {
|
|
1883
|
+
throw new Error(`${fileLabel}: Banhaten token block is missing its end marker.`)
|
|
1884
|
+
}
|
|
1885
|
+
|
|
1886
|
+
return { content: kept.join(""), found }
|
|
777
1887
|
}
|
|
778
1888
|
|
|
779
1889
|
function ensureGlobalCssImports(content) {
|
|
780
|
-
const
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
1890
|
+
const cleaned = splitLinesPreservingEndings(content)
|
|
1891
|
+
.filter((line) => {
|
|
1892
|
+
const importSpecifier = readCssImportSpecifier(line)
|
|
1893
|
+
return (
|
|
1894
|
+
importSpecifier !== "tailwindcss" &&
|
|
1895
|
+
!isManagedInterFontImport(importSpecifier)
|
|
1896
|
+
)
|
|
1897
|
+
})
|
|
1898
|
+
.join("")
|
|
786
1899
|
.trimStart()
|
|
787
1900
|
return `${TAILWIND_IMPORT}\n${FONT_IMPORTS.join("\n")}\n${cleaned}`
|
|
788
1901
|
}
|
|
789
1902
|
|
|
1903
|
+
function splitLinesPreservingEndings(content) {
|
|
1904
|
+
const lines = []
|
|
1905
|
+
let lineStart = 0
|
|
1906
|
+
let index = 0
|
|
1907
|
+
|
|
1908
|
+
while (index < content.length) {
|
|
1909
|
+
if (content[index] === "\r" && content[index + 1] === "\n") {
|
|
1910
|
+
lines.push(content.slice(lineStart, index + 2))
|
|
1911
|
+
index += 2
|
|
1912
|
+
lineStart = index
|
|
1913
|
+
continue
|
|
1914
|
+
}
|
|
1915
|
+
|
|
1916
|
+
if (content[index] === "\n") {
|
|
1917
|
+
lines.push(content.slice(lineStart, index + 1))
|
|
1918
|
+
index += 1
|
|
1919
|
+
lineStart = index
|
|
1920
|
+
continue
|
|
1921
|
+
}
|
|
1922
|
+
|
|
1923
|
+
index += 1
|
|
1924
|
+
}
|
|
1925
|
+
|
|
1926
|
+
if (lineStart < content.length) {
|
|
1927
|
+
lines.push(content.slice(lineStart))
|
|
1928
|
+
}
|
|
1929
|
+
|
|
1930
|
+
return lines
|
|
1931
|
+
}
|
|
1932
|
+
|
|
1933
|
+
function readCssImportSpecifier(line) {
|
|
1934
|
+
const trimmed = line.trim()
|
|
1935
|
+
const importPrefix = "@import"
|
|
1936
|
+
if (!trimmed.startsWith(importPrefix)) return null
|
|
1937
|
+
|
|
1938
|
+
let index = importPrefix.length
|
|
1939
|
+
while (trimmed[index] === " " || trimmed[index] === "\t") {
|
|
1940
|
+
index += 1
|
|
1941
|
+
}
|
|
1942
|
+
|
|
1943
|
+
const quote = trimmed[index]
|
|
1944
|
+
if (quote !== "\"" && quote !== "'") return null
|
|
1945
|
+
|
|
1946
|
+
const start = index + 1
|
|
1947
|
+
index = start
|
|
1948
|
+
while (index < trimmed.length && trimmed[index] !== quote) {
|
|
1949
|
+
index += 1
|
|
1950
|
+
}
|
|
1951
|
+
|
|
1952
|
+
if (index >= trimmed.length) return null
|
|
1953
|
+
const specifier = trimmed.slice(start, index)
|
|
1954
|
+
index += 1
|
|
1955
|
+
|
|
1956
|
+
while (trimmed[index] === " " || trimmed[index] === "\t") {
|
|
1957
|
+
index += 1
|
|
1958
|
+
}
|
|
1959
|
+
|
|
1960
|
+
if (trimmed[index] === ";") index += 1
|
|
1961
|
+
|
|
1962
|
+
while (trimmed[index] === " " || trimmed[index] === "\t") {
|
|
1963
|
+
index += 1
|
|
1964
|
+
}
|
|
1965
|
+
|
|
1966
|
+
return index === trimmed.length ? specifier : null
|
|
1967
|
+
}
|
|
1968
|
+
|
|
1969
|
+
function isManagedInterFontImport(specifier) {
|
|
1970
|
+
if (!specifier) return false
|
|
1971
|
+
if (specifier === "@fontsource/inter") return true
|
|
1972
|
+
if (specifier === "@fontsource-variable/inter") return true
|
|
1973
|
+
|
|
1974
|
+
for (const weight of ["400", "500", "600", "700"]) {
|
|
1975
|
+
if (specifier === `@fontsource/inter/${weight}.css`) return true
|
|
1976
|
+
}
|
|
1977
|
+
|
|
1978
|
+
return false
|
|
1979
|
+
}
|
|
1980
|
+
|
|
790
1981
|
function appendAliasSegment(alias, segment) {
|
|
791
1982
|
if (!alias) return null
|
|
792
1983
|
return `${alias.replace(/\/$/, "")}/${segment}`
|
|
@@ -807,20 +1998,58 @@ function relative(cwd, targetPath) {
|
|
|
807
1998
|
}
|
|
808
1999
|
|
|
809
2000
|
function printResult(title, writes, dryRun) {
|
|
2001
|
+
if (runtimeOptions.json) {
|
|
2002
|
+
writeJson({ dryRun, title, writes })
|
|
2003
|
+
return
|
|
2004
|
+
}
|
|
2005
|
+
|
|
2006
|
+
if (runtimeOptions.silent) return
|
|
2007
|
+
|
|
810
2008
|
console.log(`${dryRun ? "Dry run: " : ""}${title}`)
|
|
811
2009
|
for (const write of writes) {
|
|
812
2010
|
console.log(`- ${write}`)
|
|
813
2011
|
}
|
|
814
2012
|
}
|
|
815
2013
|
|
|
816
|
-
function printInstallHint(writes, dryRun) {
|
|
817
|
-
if (dryRun) return
|
|
2014
|
+
async function printInstallHint(cwd, writes, dryRun) {
|
|
2015
|
+
if (dryRun || runtimeOptions.json || runtimeOptions.silent) return
|
|
818
2016
|
|
|
819
2017
|
const packageJsonChanged = writes.some((write) =>
|
|
820
2018
|
/^(wrote|updated) package\.json$/.test(write)
|
|
821
2019
|
)
|
|
822
2020
|
|
|
823
2021
|
if (packageJsonChanged) {
|
|
824
|
-
console.log(
|
|
2022
|
+
console.log(`\nNext: run ${await getInstallCommand(cwd)} to install the added dependencies.`)
|
|
2023
|
+
}
|
|
2024
|
+
}
|
|
2025
|
+
|
|
2026
|
+
async function getInstallCommand(cwd) {
|
|
2027
|
+
const packageJson = await readJsonIfExists(path.join(cwd, "package.json"))
|
|
2028
|
+
const declaredPackageManager = parsePackageManager(packageJson?.packageManager)
|
|
2029
|
+
|
|
2030
|
+
if (declaredPackageManager) return `${declaredPackageManager} install`
|
|
2031
|
+
|
|
2032
|
+
const lockfileManagers = [
|
|
2033
|
+
["pnpm-lock.yaml", "pnpm"],
|
|
2034
|
+
["yarn.lock", "yarn"],
|
|
2035
|
+
["bun.lockb", "bun"],
|
|
2036
|
+
["bun.lock", "bun"],
|
|
2037
|
+
["package-lock.json", "npm"],
|
|
2038
|
+
["npm-shrinkwrap.json", "npm"],
|
|
2039
|
+
]
|
|
2040
|
+
|
|
2041
|
+
for (const [lockfile, manager] of lockfileManagers) {
|
|
2042
|
+
if (await exists(path.join(cwd, lockfile))) return `${manager} install`
|
|
825
2043
|
}
|
|
2044
|
+
|
|
2045
|
+
return "npm install"
|
|
2046
|
+
}
|
|
2047
|
+
|
|
2048
|
+
function parsePackageManager(packageManager) {
|
|
2049
|
+
if (typeof packageManager !== "string") return null
|
|
2050
|
+
|
|
2051
|
+
const name = packageManager.split("@")[0]
|
|
2052
|
+
if (["npm", "pnpm", "yarn", "bun"].includes(name)) return name
|
|
2053
|
+
|
|
2054
|
+
return null
|
|
826
2055
|
}
|