base-themes 0.1.2 → 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/CHANGELOG.md +25 -0
- package/CODE_OF_CONDUCT.md +22 -0
- package/CONTRIBUTING.md +98 -0
- package/LICENSE +21 -0
- package/README.md +316 -3
- package/RELEASE.md +80 -0
- package/SECURITY.md +49 -0
- package/bin/base-themes.mjs +143 -0
- package/dist/base-themes.css +1 -1
- package/dist/base-themes.js +857 -302
- package/dist/llms-full.txt +288 -0
- package/dist/llms.txt +79 -0
- package/dist/types/blocks/AuthCard.d.ts +2 -0
- package/dist/types/blocks/CommandPaletteBlock.d.ts +2 -0
- package/dist/types/blocks/DashboardShell.d.ts +2 -0
- package/dist/types/blocks/DataTableBlock.d.ts +2 -0
- package/dist/types/blocks/PricingPanel.d.ts +2 -0
- package/dist/types/blocks/SettingsForm.d.ts +2 -0
- package/dist/types/blocks/TeamActivityFeed.d.ts +2 -0
- package/dist/types/blocks/ThemeShowcaseCard.d.ts +2 -0
- package/dist/types/blocks/index.d.ts +8 -0
- package/dist/types/components/ui/Input.d.ts +3 -0
- package/dist/types/components/ui/index.d.ts +1 -1
- package/dist/types/lib.d.ts +1 -0
- package/docs/adoption-dashboard.md +149 -0
- package/docs/analytics-setup.md +145 -0
- package/docs/community-gallery-proposal.md +64 -0
- package/docs/community-proof-telemetry.md +47 -0
- package/docs/contributor-issue-seeds.md +240 -0
- package/docs/registry-access-telemetry.md +87 -0
- package/docs/release-announcement-kit.md +229 -0
- package/docs/search-console-setup.md +111 -0
- package/docs/theme-token-contract.md +113 -0
- package/examples/dashboard/README.md +24 -0
- package/examples/dashboard/index.html +12 -0
- package/examples/dashboard/package-lock.json +1212 -0
- package/examples/dashboard/package.json +24 -0
- package/examples/dashboard/src/App.tsx +115 -0
- package/examples/dashboard/src/main.tsx +11 -0
- package/examples/dashboard/src/styles.css +129 -0
- package/examples/dashboard/src/vite-env.d.ts +4 -0
- package/examples/dashboard/tsconfig.app.json +23 -0
- package/examples/dashboard/tsconfig.json +7 -0
- package/examples/dashboard/tsconfig.node.json +15 -0
- package/examples/dashboard/vite.config.ts +6 -0
- package/examples/next/README.md +29 -0
- package/examples/next/app/base-themes-demo.tsx +70 -0
- package/examples/next/app/layout.tsx +16 -0
- package/examples/next/app/page.tsx +9 -0
- package/examples/next/app/styles.css +106 -0
- package/examples/next/next-env.d.ts +6 -0
- package/examples/next/next.config.ts +5 -0
- package/examples/next/package-lock.json +1199 -0
- package/examples/next/package.json +27 -0
- package/examples/next/tsconfig.json +36 -0
- package/examples/registry-copy/README.md +73 -0
- package/examples/registry-copy/plan-copy.mjs +130 -0
- package/examples/theme-customization/README.md +26 -0
- package/examples/theme-customization/index.html +12 -0
- package/examples/theme-customization/package-lock.json +1212 -0
- package/examples/theme-customization/package.json +24 -0
- package/examples/theme-customization/src/App.tsx +138 -0
- package/examples/theme-customization/src/main.tsx +11 -0
- package/examples/theme-customization/src/styles.css +138 -0
- package/examples/theme-customization/src/vite-env.d.ts +4 -0
- package/examples/theme-customization/tsconfig.app.json +23 -0
- package/examples/theme-customization/tsconfig.json +7 -0
- package/examples/theme-customization/tsconfig.node.json +15 -0
- package/examples/theme-customization/vite.config.ts +6 -0
- package/examples/vite/README.md +32 -0
- package/examples/vite/index.html +12 -0
- package/examples/vite/package-lock.json +1200 -0
- package/examples/vite/package.json +24 -0
- package/examples/vite/src/App.tsx +101 -0
- package/examples/vite/src/main.tsx +11 -0
- package/examples/vite/src/styles.css +125 -0
- package/examples/vite/src/vite-env.d.ts +4 -0
- package/examples/vite/tsconfig.app.json +23 -0
- package/examples/vite/tsconfig.json +7 -0
- package/examples/vite/tsconfig.node.json +15 -0
- package/examples/vite/vite.config.ts +6 -0
- package/llms-full.txt +288 -0
- package/llms.txt +79 -0
- package/package.json +157 -14
- package/registry/items/accordion.json +101 -0
- package/registry/items/alert-dialog.json +107 -0
- package/registry/items/autocomplete.json +106 -0
- package/registry/items/avatar.json +101 -0
- package/registry/items/block-auth-card.json +105 -0
- package/registry/items/block-command-palette.json +99 -0
- package/registry/items/block-dashboard-shell.json +101 -0
- package/registry/items/block-data-table.json +99 -0
- package/registry/items/block-pricing-panel.json +99 -0
- package/registry/items/block-settings-form.json +107 -0
- package/registry/items/block-team-activity-feed.json +99 -0
- package/registry/items/block-theme-showcase-card.json +99 -0
- package/registry/items/button.json +102 -0
- package/registry/items/checkbox-group.json +106 -0
- package/registry/items/checkbox.json +102 -0
- package/registry/items/collapsible.json +101 -0
- package/registry/items/combobox.json +101 -0
- package/registry/items/context-menu.json +106 -0
- package/registry/items/csp-provider.json +96 -0
- package/registry/items/dialog.json +102 -0
- package/registry/items/direction-provider.json +101 -0
- package/registry/items/drawer.json +101 -0
- package/registry/items/field.json +101 -0
- package/registry/items/fieldset.json +101 -0
- package/registry/items/form.json +101 -0
- package/registry/items/input.json +102 -0
- package/registry/items/menu.json +101 -0
- package/registry/items/menubar.json +106 -0
- package/registry/items/meter.json +101 -0
- package/registry/items/navigation-menu.json +101 -0
- package/registry/items/number-field.json +101 -0
- package/registry/items/otp-field.json +101 -0
- package/registry/items/popover.json +102 -0
- package/registry/items/preview-card.json +101 -0
- package/registry/items/progress.json +101 -0
- package/registry/items/radio-group.json +102 -0
- package/registry/items/radio.json +101 -0
- package/registry/items/scroll-area.json +101 -0
- package/registry/items/select.json +102 -0
- package/registry/items/separator.json +101 -0
- package/registry/items/slider.json +102 -0
- package/registry/items/switch.json +102 -0
- package/registry/items/tabs.json +101 -0
- package/registry/items/theme-bauhaus.json +107 -0
- package/registry/items/theme-bento.json +107 -0
- package/registry/items/theme-calm.json +107 -0
- package/registry/items/theme-cyberpunk.json +108 -0
- package/registry/items/theme-data-dense.json +107 -0
- package/registry/items/theme-editorial.json +107 -0
- package/registry/items/theme-enterprise.json +108 -0
- package/registry/items/theme-fluent.json +107 -0
- package/registry/items/theme-glass.json +107 -0
- package/registry/items/theme-linear.json +107 -0
- package/registry/items/theme-luxury.json +107 -0
- package/registry/items/theme-material.json +107 -0
- package/registry/items/theme-minimal.json +107 -0
- package/registry/items/theme-mono.json +107 -0
- package/registry/items/theme-neo-brutalism.json +107 -0
- package/registry/items/theme-playful.json +107 -0
- package/registry/items/theme-retro.json +107 -0
- package/registry/items/theme-shadcn.json +107 -0
- package/registry/items/theme-soft-ui.json +107 -0
- package/registry/items/theme-terminal.json +107 -0
- package/registry/items/toast.json +106 -0
- package/registry/items/toggle-group.json +101 -0
- package/registry/items/toggle.json +101 -0
- package/registry/items/toolbar.json +101 -0
- package/registry/items/tooltip.json +102 -0
- package/registry/registry.json +564 -49
- package/registry/shadcn-registry.json +415 -0
- package/research/telemetry-fixtures/analytics-events.jsonl +9 -0
- package/research/telemetry-fixtures/bundle-report.json +44 -0
- package/research/telemetry-fixtures/community-proof.csv +5 -0
- package/research/telemetry-fixtures/registry-access.jsonl +10 -0
- package/research/telemetry-fixtures/search-console-export.csv +4 -0
- package/scripts/registry-plan.mjs +434 -0
- package/scripts/render-launch-actions.mjs +405 -0
- package/scripts/render-launch-status.mjs +373 -0
- package/scripts/render-release-announcement.mjs +329 -0
- package/scripts/verify-launch-readiness.mjs +415 -0
- package/scripts/verify-telemetry-fixtures.mjs +85 -0
- package/scripts/verify-telemetry-report.mjs +89 -0
- package/skills/base-themes/SKILL.md +151 -47
- package/src/blocks/AuthCard.tsx +29 -0
- package/src/blocks/Blocks.css +182 -0
- package/src/blocks/CommandPaletteBlock.tsx +32 -0
- package/src/blocks/DashboardShell.tsx +36 -0
- package/src/blocks/DataTableBlock.tsx +44 -0
- package/src/blocks/PricingPanel.tsx +28 -0
- package/src/blocks/SettingsForm.tsx +37 -0
- package/src/blocks/TeamActivityFeed.tsx +38 -0
- package/src/blocks/ThemeShowcaseCard.tsx +32 -0
- package/src/blocks/index.ts +8 -0
- package/src/components/ui/Accordion.css +42 -0
- package/src/components/ui/Accordion.tsx +41 -0
- package/src/components/ui/AlertDialog.css +40 -0
- package/src/components/ui/AlertDialog.tsx +52 -0
- package/src/components/ui/Autocomplete.css +3 -0
- package/src/components/ui/Autocomplete.tsx +50 -0
- package/src/components/ui/Avatar.css +45 -0
- package/src/components/ui/Avatar.tsx +36 -0
- package/src/components/ui/Button.css +79 -0
- package/src/components/ui/Button.tsx +20 -0
- package/src/components/ui/Checkbox.css +37 -0
- package/src/components/ui/Checkbox.tsx +32 -0
- package/src/components/ui/CheckboxGroup.tsx +21 -0
- package/src/components/ui/Collapsible.css +34 -0
- package/src/components/ui/Collapsible.tsx +29 -0
- package/src/components/ui/Combobox.css +75 -0
- package/src/components/ui/Combobox.tsx +53 -0
- package/src/components/ui/ContextMenu.css +9 -0
- package/src/components/ui/ContextMenu.tsx +47 -0
- package/src/components/ui/CspProvider.tsx +10 -0
- package/src/components/ui/Dialog.css +41 -0
- package/src/components/ui/Dialog.tsx +45 -0
- package/src/components/ui/DirectionProvider.tsx +17 -0
- package/src/components/ui/Drawer.css +77 -0
- package/src/components/ui/Drawer.tsx +56 -0
- package/src/components/ui/Field.css +19 -0
- package/src/components/ui/Field.tsx +24 -0
- package/src/components/ui/Fieldset.css +16 -0
- package/src/components/ui/Fieldset.tsx +19 -0
- package/src/components/ui/Form.css +5 -0
- package/src/components/ui/Form.tsx +12 -0
- package/src/components/ui/Input.css +50 -0
- package/src/components/ui/Input.tsx +62 -0
- package/src/components/ui/Menu.css +59 -0
- package/src/components/ui/Menu.tsx +50 -0
- package/src/components/ui/Menubar.css +26 -0
- package/src/components/ui/Menubar.tsx +42 -0
- package/src/components/ui/Meter.css +45 -0
- package/src/components/ui/Meter.tsx +37 -0
- package/src/components/ui/NavigationMenu.css +103 -0
- package/src/components/ui/NavigationMenu.tsx +64 -0
- package/src/components/ui/NumberField.css +38 -0
- package/src/components/ui/NumberField.tsx +28 -0
- package/src/components/ui/OtpField.css +28 -0
- package/src/components/ui/OtpField.tsx +24 -0
- package/src/components/ui/Popover.css +25 -0
- package/src/components/ui/Popover.tsx +37 -0
- package/src/components/ui/PreviewCard.css +33 -0
- package/src/components/ui/PreviewCard.tsx +43 -0
- package/src/components/ui/Progress.css +33 -0
- package/src/components/ui/Progress.tsx +28 -0
- package/src/components/ui/Radio.tsx +22 -0
- package/src/components/ui/RadioGroup.css +42 -0
- package/src/components/ui/RadioGroup.tsx +29 -0
- package/src/components/ui/ScrollArea.css +42 -0
- package/src/components/ui/ScrollArea.tsx +22 -0
- package/src/components/ui/Select.css +86 -0
- package/src/components/ui/Select.tsx +39 -0
- package/src/components/ui/Separator.css +14 -0
- package/src/components/ui/Separator.tsx +12 -0
- package/src/components/ui/Slider.css +39 -0
- package/src/components/ui/Slider.tsx +21 -0
- package/src/components/ui/Switch.css +45 -0
- package/src/components/ui/Switch.tsx +29 -0
- package/src/components/ui/Tabs.css +72 -0
- package/src/components/ui/Tabs.tsx +44 -0
- package/src/components/ui/Toast.css +75 -0
- package/src/components/ui/Toast.tsx +48 -0
- package/src/components/ui/Toggle.tsx +12 -0
- package/src/components/ui/ToggleGroup.css +35 -0
- package/src/components/ui/ToggleGroup.tsx +30 -0
- package/src/components/ui/Toolbar.css +60 -0
- package/src/components/ui/Toolbar.tsx +36 -0
- package/src/components/ui/Tooltip.css +14 -0
- package/src/components/ui/Tooltip.tsx +31 -0
- package/src/components/ui/index.ts +83 -0
- package/src/components/ui/useDirection.ts +1 -0
- package/src/components/ui/useToastManager.ts +11 -0
- package/src/docs/blockMeta.json +66 -0
- package/src/docs/componentMeta.json +322 -0
- package/src/docs/staticPageMeta.json +143 -0
- package/src/docs/themeMeta.json +22 -0
- package/src/styles/tokenContract.json +61 -0
- package/workers/analytics-receiver.mjs +170 -0
- package/wrangler.analytics.jsonc +12 -0
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
import { copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, statSync } from 'node:fs'
|
|
2
|
+
import { dirname, join, resolve } from 'node:path'
|
|
3
|
+
import { fileURLToPath } from 'node:url'
|
|
4
|
+
|
|
5
|
+
export function loadRegistry(path = resolve('registry/registry.json')) {
|
|
6
|
+
const registryPath = path instanceof URL ? fileURLToPath(path) : path
|
|
7
|
+
const registry = JSON.parse(readFileSync(registryPath, 'utf8'))
|
|
8
|
+
Object.defineProperty(registry, '__baseThemesRegistryPath', {
|
|
9
|
+
enumerable: false,
|
|
10
|
+
value: registryPath,
|
|
11
|
+
})
|
|
12
|
+
return registry
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function registryItemsRoot(registry) {
|
|
16
|
+
const registryPath = registry.__baseThemesRegistryPath
|
|
17
|
+
return registryPath ? join(dirname(registryPath), 'items') : resolve('registry/items')
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function registryPackageRoot(registry) {
|
|
21
|
+
const registryPath = registry.__baseThemesRegistryPath
|
|
22
|
+
return registryPath ? dirname(dirname(registryPath)) : process.cwd()
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function itemNameForBlock(blockName) {
|
|
26
|
+
return `block-${blockName}`
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function itemNameForTheme(themeName) {
|
|
30
|
+
return `theme-${themeName}`
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function readRegistryItem(registry, name) {
|
|
34
|
+
const itemPath = join(registryItemsRoot(registry), `${name}.json`)
|
|
35
|
+
if (!existsSync(itemPath)) return undefined
|
|
36
|
+
return JSON.parse(readFileSync(itemPath, 'utf8'))
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function createPlanItem(registry, name, requestedAs) {
|
|
40
|
+
const item = readRegistryItem(registry, name)
|
|
41
|
+
return {
|
|
42
|
+
name,
|
|
43
|
+
requestedAs,
|
|
44
|
+
type: item?.type,
|
|
45
|
+
title: item?.title,
|
|
46
|
+
packageImport: `base-themes/registry/items/${name}.json`,
|
|
47
|
+
hostedUrl: `/registry/items/${name}.json`,
|
|
48
|
+
registryItems: item?.meta?.agent?.registryItems ?? [],
|
|
49
|
+
packageInstall: item?.meta?.agent?.packageInstall ?? [],
|
|
50
|
+
sourceCopy: item?.meta?.agent?.sourceCopy ?? [],
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function createRegistryPlan(registry, requested) {
|
|
55
|
+
const components = new Map(registry.components.map((component) => [component.name, component]))
|
|
56
|
+
const blocks = new Map(registry.blocks.map((block) => [block.name, block]))
|
|
57
|
+
const themeStyles = new Set(registry.style.variants)
|
|
58
|
+
const componentRequests = requested.filter((name) => !name.startsWith('block:') && !name.startsWith('theme:'))
|
|
59
|
+
const blockRequests = requested
|
|
60
|
+
.filter((name) => name.startsWith('block:'))
|
|
61
|
+
.map((name) => name.slice('block:'.length))
|
|
62
|
+
const themeRequests = requested
|
|
63
|
+
.filter((name) => name.startsWith('theme:'))
|
|
64
|
+
.map((name) => name.slice('theme:'.length))
|
|
65
|
+
const missingComponents = componentRequests.filter((name) => !components.has(name))
|
|
66
|
+
const missingBlocks = blockRequests.filter((name) => !blocks.has(name))
|
|
67
|
+
const missingThemes = themeRequests.filter((name) => !themeStyles.has(name))
|
|
68
|
+
|
|
69
|
+
if (missingComponents.length > 0 || missingBlocks.length > 0 || missingThemes.length > 0) {
|
|
70
|
+
return {
|
|
71
|
+
ok: false,
|
|
72
|
+
missingComponents,
|
|
73
|
+
missingBlocks,
|
|
74
|
+
missingThemes,
|
|
75
|
+
availableComponents: [...components.keys()],
|
|
76
|
+
availableBlocks: [...blocks.keys()],
|
|
77
|
+
availableThemes: [...themeStyles],
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const selectedComponents = new Set(componentRequests)
|
|
82
|
+
const selectedBlocks = blockRequests.map((name) => blocks.get(name))
|
|
83
|
+
const selectedThemes = [...new Set(themeRequests)]
|
|
84
|
+
const componentFiles = new Set()
|
|
85
|
+
const blockFiles = new Set()
|
|
86
|
+
|
|
87
|
+
for (const block of selectedBlocks) {
|
|
88
|
+
for (const name of block.components) {
|
|
89
|
+
selectedComponents.add(name)
|
|
90
|
+
}
|
|
91
|
+
for (const file of block.files) {
|
|
92
|
+
blockFiles.add(file)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
for (const name of selectedComponents) {
|
|
97
|
+
for (const file of components.get(name).files) {
|
|
98
|
+
componentFiles.add(file)
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const registryItems = [
|
|
103
|
+
...[...selectedComponents].map((name) => createPlanItem(registry, name, name)),
|
|
104
|
+
...selectedBlocks.map((block) => createPlanItem(registry, itemNameForBlock(block.name), `block:${block.name}`)),
|
|
105
|
+
...selectedThemes.map((name) => createPlanItem(registry, itemNameForTheme(name), `theme:${name}`)),
|
|
106
|
+
]
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
ok: true,
|
|
110
|
+
components: [...selectedComponents],
|
|
111
|
+
blocks: selectedBlocks.map((block) => block.name),
|
|
112
|
+
themes: selectedThemes,
|
|
113
|
+
registryItems,
|
|
114
|
+
dependencies: registry.dependencies,
|
|
115
|
+
defaultStyle: registry.style.default,
|
|
116
|
+
availableStyles: registry.style.variants,
|
|
117
|
+
styleFiles: registry.style.files ?? [registry.style.global, registry.style.tokens],
|
|
118
|
+
blockFiles: [...blockFiles],
|
|
119
|
+
componentFiles: [...componentFiles],
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function formatRegistryPlan(plan) {
|
|
124
|
+
if (!plan.ok) {
|
|
125
|
+
const lines = []
|
|
126
|
+
if (plan.missingComponents.length > 0) lines.push(`Unknown component${plan.missingComponents.length === 1 ? '' : 's'}: ${plan.missingComponents.join(', ')}`)
|
|
127
|
+
if (plan.missingBlocks.length > 0) lines.push(`Unknown block${plan.missingBlocks.length === 1 ? '' : 's'}: ${plan.missingBlocks.join(', ')}`)
|
|
128
|
+
if (plan.missingThemes.length > 0) lines.push(`Unknown theme${plan.missingThemes.length === 1 ? '' : 's'}: ${plan.missingThemes.join(', ')}`)
|
|
129
|
+
lines.push(`Available components: ${plan.availableComponents.join(', ')}`)
|
|
130
|
+
lines.push(`Available blocks: ${plan.availableBlocks.join(', ')}`)
|
|
131
|
+
lines.push(`Available themes: ${plan.availableThemes.join(', ')}`)
|
|
132
|
+
return lines.join('\n')
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const lines = [
|
|
136
|
+
'Base Themes registry copy plan',
|
|
137
|
+
`Components: ${plan.components.join(', ') || '(none)'}`,
|
|
138
|
+
`Blocks: ${plan.blocks.join(', ') || '(none)'}`,
|
|
139
|
+
`Themes: ${plan.themes.join(', ') || '(none)'}`,
|
|
140
|
+
`Dependencies: ${plan.dependencies.join(', ')}`,
|
|
141
|
+
`Default style: ${plan.defaultStyle}`,
|
|
142
|
+
`Available styles: ${plan.availableStyles.join(', ')}`,
|
|
143
|
+
`Style files: ${plan.styleFiles.join(', ')}`,
|
|
144
|
+
]
|
|
145
|
+
|
|
146
|
+
if (plan.blockFiles.length > 0) {
|
|
147
|
+
lines.push('Block files:')
|
|
148
|
+
for (const file of plan.blockFiles) lines.push(`- ${file}`)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (plan.registryItems.length > 0) {
|
|
152
|
+
lines.push('Registry item imports:')
|
|
153
|
+
for (const item of plan.registryItems) lines.push(`- ${item.requestedAs}: ${item.packageImport}`)
|
|
154
|
+
|
|
155
|
+
lines.push('Hosted registry item URLs:')
|
|
156
|
+
for (const item of plan.registryItems) lines.push(`- ${item.requestedAs}: ${item.hostedUrl}`)
|
|
157
|
+
|
|
158
|
+
const blockRegistryItems = plan.registryItems.filter((item) => item.registryItems.length > 0)
|
|
159
|
+
if (blockRegistryItems.length > 0) {
|
|
160
|
+
lines.push('Block registryItems metadata:')
|
|
161
|
+
for (const item of blockRegistryItems) lines.push(`- ${item.requestedAs}: ${item.registryItems.join(', ')}`)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
lines.push('Package install steps:')
|
|
165
|
+
for (const item of plan.registryItems) {
|
|
166
|
+
if (item.packageInstall.length === 0) continue
|
|
167
|
+
lines.push(`- ${item.requestedAs}: ${item.packageInstall.join(' | ')}`)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
lines.push('Source-copy steps:')
|
|
171
|
+
for (const item of plan.registryItems) {
|
|
172
|
+
if (item.sourceCopy.length === 0) continue
|
|
173
|
+
lines.push(`- ${item.requestedAs}: ${item.sourceCopy.join(' | ')}`)
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
lines.push('Component files:')
|
|
178
|
+
for (const file of plan.componentFiles) lines.push(`- ${file}`)
|
|
179
|
+
|
|
180
|
+
return lines.join('\n')
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export function createRegistryAdd(registry, requested, options = {}) {
|
|
184
|
+
const plan = createRegistryPlan(registry, requested)
|
|
185
|
+
if (!plan.ok) return { ok: false, plan }
|
|
186
|
+
|
|
187
|
+
const targetRoot = resolve(options.target ?? process.cwd())
|
|
188
|
+
const sourceRoot = registryPackageRoot(registry)
|
|
189
|
+
const dryRun = Boolean(options.dryRun)
|
|
190
|
+
const force = Boolean(options.force)
|
|
191
|
+
const files = [...new Set([...plan.styleFiles, ...plan.blockFiles, ...plan.componentFiles])]
|
|
192
|
+
const copied = []
|
|
193
|
+
const skipped = []
|
|
194
|
+
const missingSources = []
|
|
195
|
+
|
|
196
|
+
for (const file of files) {
|
|
197
|
+
const source = join(sourceRoot, file)
|
|
198
|
+
const target = join(targetRoot, file)
|
|
199
|
+
|
|
200
|
+
if (!existsSync(source)) {
|
|
201
|
+
missingSources.push({ file, source, target })
|
|
202
|
+
continue
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const targetExists = existsSync(target)
|
|
206
|
+
if (targetExists && !force) {
|
|
207
|
+
skipped.push({ file, source, target, reason: 'exists' })
|
|
208
|
+
continue
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
copied.push({ file, source, target, overwritten: targetExists })
|
|
212
|
+
if (!dryRun) {
|
|
213
|
+
mkdirSync(dirname(target), { recursive: true })
|
|
214
|
+
copyFileSync(source, target)
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return {
|
|
219
|
+
ok: missingSources.length === 0,
|
|
220
|
+
plan,
|
|
221
|
+
targetRoot,
|
|
222
|
+
sourceRoot,
|
|
223
|
+
dryRun,
|
|
224
|
+
force,
|
|
225
|
+
copied,
|
|
226
|
+
skipped,
|
|
227
|
+
missingSources,
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export function formatRegistryAdd(result) {
|
|
232
|
+
if (!result.plan.ok) return formatRegistryPlan(result.plan)
|
|
233
|
+
|
|
234
|
+
const lines = [
|
|
235
|
+
result.dryRun ? 'Base Themes add dry run' : 'Base Themes add',
|
|
236
|
+
`Target: ${result.targetRoot}`,
|
|
237
|
+
`Components: ${result.plan.components.join(', ') || '(none)'}`,
|
|
238
|
+
`Blocks: ${result.plan.blocks.join(', ') || '(none)'}`,
|
|
239
|
+
`Themes: ${result.plan.themes.join(', ') || '(none)'}`,
|
|
240
|
+
`Dependencies: ${result.plan.dependencies.join(', ')}`,
|
|
241
|
+
]
|
|
242
|
+
|
|
243
|
+
if (result.copied.length > 0) {
|
|
244
|
+
lines.push(result.dryRun ? 'Files to copy:' : 'Copied files:')
|
|
245
|
+
for (const file of result.copied) {
|
|
246
|
+
const label = file.overwritten ? 'overwrite' : 'create'
|
|
247
|
+
lines.push(`- ${label}: ${file.file}`)
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (result.skipped.length > 0) {
|
|
252
|
+
lines.push('Skipped files:')
|
|
253
|
+
for (const file of result.skipped) lines.push(`- ${file.file} (${file.reason}; use --force to overwrite)`)
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (result.missingSources.length > 0) {
|
|
257
|
+
lines.push('Missing source files:')
|
|
258
|
+
for (const file of result.missingSources) lines.push(`- ${file.file}`)
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
lines.push('Next steps:')
|
|
262
|
+
lines.push('- Install dependencies from the plan if they are not already installed.')
|
|
263
|
+
lines.push("- Import 'base-themes/styles.css' once, or wire copied registry style files in your app entry.")
|
|
264
|
+
lines.push('- Review copied imports, format the target app, then run lint, test, and build.')
|
|
265
|
+
|
|
266
|
+
if (result.skipped.length > 0 && !result.force) {
|
|
267
|
+
lines.push('Note: existing files were left untouched. Re-run with --force only after reviewing local changes.')
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return lines.join('\n')
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
export function formatRegistryList(registry) {
|
|
274
|
+
return [
|
|
275
|
+
'Base Themes registry',
|
|
276
|
+
`Components (${registry.components.length}): ${registry.components.map((component) => component.name).join(', ')}`,
|
|
277
|
+
`Blocks (${registry.blocks.length}): ${registry.blocks.map((block) => block.name).join(', ')}`,
|
|
278
|
+
`Styles (${registry.style.variants.length}): ${registry.style.variants.join(', ')}`,
|
|
279
|
+
`Pages (${registry.pages.length}): ${registry.pages.map((page) => page.route).join(', ')}`,
|
|
280
|
+
].join('\n')
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function readJsonIfExists(path) {
|
|
284
|
+
if (!existsSync(path)) return undefined
|
|
285
|
+
return JSON.parse(readFileSync(path, 'utf8'))
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function detectPackageManager(root) {
|
|
289
|
+
if (existsSync(join(root, 'pnpm-lock.yaml'))) return 'pnpm'
|
|
290
|
+
if (existsSync(join(root, 'yarn.lock'))) return 'yarn'
|
|
291
|
+
if (existsSync(join(root, 'bun.lockb')) || existsSync(join(root, 'bun.lock'))) return 'bun'
|
|
292
|
+
return 'npm'
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function installCommand(packageManager) {
|
|
296
|
+
if (packageManager === 'pnpm') return 'pnpm add base-themes @base-ui/react react react-dom'
|
|
297
|
+
if (packageManager === 'yarn') return 'yarn add base-themes @base-ui/react react react-dom'
|
|
298
|
+
if (packageManager === 'bun') return 'bun add base-themes @base-ui/react react react-dom'
|
|
299
|
+
return 'npm install base-themes @base-ui/react react react-dom'
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function walkSourceFiles(root, limit = 240) {
|
|
303
|
+
const results = []
|
|
304
|
+
const ignored = new Set(['.git', '.next', 'dist', 'node_modules', 'coverage', '.turbo'])
|
|
305
|
+
|
|
306
|
+
function visit(dir) {
|
|
307
|
+
if (results.length >= limit || !existsSync(dir)) return
|
|
308
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
309
|
+
if (results.length >= limit || ignored.has(entry.name)) continue
|
|
310
|
+
const path = join(dir, entry.name)
|
|
311
|
+
if (entry.isDirectory()) {
|
|
312
|
+
visit(path)
|
|
313
|
+
continue
|
|
314
|
+
}
|
|
315
|
+
if (/\.(tsx?|jsx?|html)$/.test(entry.name)) results.push(path)
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
for (const candidate of ['src', 'app', 'pages']) {
|
|
320
|
+
visit(join(root, candidate))
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
return results
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
export function createDoctorReport(root = process.cwd()) {
|
|
327
|
+
const packagePath = resolve(root, 'package.json')
|
|
328
|
+
const packageJson = readJsonIfExists(packagePath)
|
|
329
|
+
const packageManager = detectPackageManager(root)
|
|
330
|
+
const install = installCommand(packageManager)
|
|
331
|
+
const dependencies = {
|
|
332
|
+
...packageJson?.dependencies,
|
|
333
|
+
...packageJson?.devDependencies,
|
|
334
|
+
...packageJson?.peerDependencies,
|
|
335
|
+
}
|
|
336
|
+
const isBaseThemesPackage = packageJson?.name === 'base-themes'
|
|
337
|
+
const sourceFiles = existsSync(root) && statSync(root).isDirectory() ? walkSourceFiles(root) : []
|
|
338
|
+
const sourceText = sourceFiles.map((file) => readFileSync(file, 'utf8')).join('\n')
|
|
339
|
+
const hasStyleAttribute = /data-style=/.test(sourceText)
|
|
340
|
+
const hasThemeAttribute = /data-theme=/.test(sourceText)
|
|
341
|
+
const hasUseThemeWorkflow = /useTheme\s*\(/.test(sourceText)
|
|
342
|
+
const checks = [
|
|
343
|
+
{
|
|
344
|
+
label: 'package.json exists',
|
|
345
|
+
ok: Boolean(packageJson),
|
|
346
|
+
detail: packageJson ? packagePath : 'No package.json found in current working directory.',
|
|
347
|
+
fix: 'Run base-themes doctor from a Vite, Next.js, Remix, or React app root that contains package.json.',
|
|
348
|
+
},
|
|
349
|
+
{
|
|
350
|
+
label: 'base-themes dependency is installed or declared',
|
|
351
|
+
ok: Boolean(dependencies['base-themes'] || isBaseThemesPackage),
|
|
352
|
+
detail: dependencies['base-themes'] ? `base-themes ${dependencies['base-themes']}` : isBaseThemesPackage ? 'Running inside the base-themes package workspace.' : 'Add base-themes to dependencies.',
|
|
353
|
+
fix: install,
|
|
354
|
+
},
|
|
355
|
+
{
|
|
356
|
+
label: '@base-ui/react peer dependency is declared',
|
|
357
|
+
ok: Boolean(dependencies['@base-ui/react']),
|
|
358
|
+
detail: dependencies['@base-ui/react'] ? `@base-ui/react ${dependencies['@base-ui/react']}` : 'Install @base-ui/react with base-themes.',
|
|
359
|
+
fix: install,
|
|
360
|
+
},
|
|
361
|
+
{
|
|
362
|
+
label: 'React peer dependencies are declared',
|
|
363
|
+
ok: Boolean(dependencies.react && dependencies['react-dom']),
|
|
364
|
+
detail: dependencies.react && dependencies['react-dom'] ? `react ${dependencies.react}, react-dom ${dependencies['react-dom']}` : 'Install react and react-dom.',
|
|
365
|
+
fix: install,
|
|
366
|
+
},
|
|
367
|
+
{
|
|
368
|
+
label: 'base-themes/styles.css import found',
|
|
369
|
+
ok: sourceText.includes('base-themes/styles.css'),
|
|
370
|
+
detail: sourceText.includes('base-themes/styles.css') ? 'Found package CSS import in source files.' : 'Import base-themes/styles.css once at app startup.',
|
|
371
|
+
fix: "Add `import 'base-themes/styles.css'` in your app entry, such as src/main.tsx, app/layout.tsx, or pages/_app.tsx.",
|
|
372
|
+
},
|
|
373
|
+
{
|
|
374
|
+
label: 'data-style attribute found',
|
|
375
|
+
ok: hasStyleAttribute || hasUseThemeWorkflow,
|
|
376
|
+
detail: hasStyleAttribute ? 'Found data-style usage.' : hasUseThemeWorkflow ? 'Found useTheme workflow, which applies data-style at runtime.' : 'Set data-style on the app root or themed container.',
|
|
377
|
+
fix: 'Add `data-style="bento"` to your app root, html element, or themed container, or use the exported useTheme hook.',
|
|
378
|
+
},
|
|
379
|
+
{
|
|
380
|
+
label: 'data-theme attribute found',
|
|
381
|
+
ok: hasThemeAttribute || hasUseThemeWorkflow,
|
|
382
|
+
detail: hasThemeAttribute ? 'Found data-theme usage.' : hasUseThemeWorkflow ? 'Found useTheme workflow, which applies data-theme at runtime.' : 'Set data-theme to light, dark, or use the useTheme hook workflow.',
|
|
383
|
+
fix: 'Add `data-theme="light"` or `data-theme="dark"` next to data-style, or use the exported useTheme hook.',
|
|
384
|
+
},
|
|
385
|
+
]
|
|
386
|
+
|
|
387
|
+
return {
|
|
388
|
+
ok: checks.every((check) => check.ok),
|
|
389
|
+
root,
|
|
390
|
+
packageManager,
|
|
391
|
+
checkedFiles: sourceFiles.length,
|
|
392
|
+
links: {
|
|
393
|
+
docs: 'https://base-themes.bangwu.me/docs/installation',
|
|
394
|
+
cli: 'https://base-themes.bangwu.me/docs/cli',
|
|
395
|
+
repo: 'https://github.com/markbang/base-themes',
|
|
396
|
+
fork: 'https://github.com/markbang/base-themes/fork',
|
|
397
|
+
showAndTell: 'https://github.com/markbang/base-themes/discussions/new?category=show-and-tell',
|
|
398
|
+
featureRequest: 'https://github.com/markbang/base-themes/issues/new?template=feature_request.yml',
|
|
399
|
+
bugReport: 'https://github.com/markbang/base-themes/issues/new?template=bug_report.yml',
|
|
400
|
+
gallerySubmission: 'https://github.com/markbang/base-themes/issues/new?template=gallery_submission.yml',
|
|
401
|
+
},
|
|
402
|
+
checks,
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
export function formatDoctorReport(report) {
|
|
407
|
+
const lines = [
|
|
408
|
+
'Base Themes doctor',
|
|
409
|
+
`Project: ${report.root}`,
|
|
410
|
+
`Source files scanned: ${report.checkedFiles}`,
|
|
411
|
+
'',
|
|
412
|
+
]
|
|
413
|
+
|
|
414
|
+
for (const check of report.checks) {
|
|
415
|
+
lines.push(`${check.ok ? 'PASS' : 'WARN'} ${check.label}`)
|
|
416
|
+
lines.push(` ${check.detail}`)
|
|
417
|
+
if (!check.ok && check.fix) lines.push(` Fix: ${check.fix}`)
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
lines.push('')
|
|
421
|
+
if (report.ok) {
|
|
422
|
+
lines.push('Result: Base Themes installation looks complete.')
|
|
423
|
+
lines.push('Next: try `base-themes plan button select block:dashboard-shell theme:enterprise` or open https://base-themes.bangwu.me/docs/cli for source-copy workflows.')
|
|
424
|
+
lines.push('Public signal: if this worked in a real app, star https://github.com/markbang/base-themes, fork https://github.com/markbang/base-themes/fork, or share what worked at https://github.com/markbang/base-themes/discussions/new?category=show-and-tell.')
|
|
425
|
+
lines.push('Gallery: submit a screenshot or repo at https://github.com/markbang/base-themes/issues/new?template=gallery_submission.yml after permission to feature is clear.')
|
|
426
|
+
} else {
|
|
427
|
+
lines.push('Result: Review WARN items before shipping this integration.')
|
|
428
|
+
lines.push(`Install command: ${installCommand(report.packageManager)}`)
|
|
429
|
+
lines.push('Docs: https://base-themes.bangwu.me/docs/installation')
|
|
430
|
+
lines.push('Feedback: https://github.com/markbang/base-themes/issues/new?template=bug_report.yml')
|
|
431
|
+
lines.push('Missing component, block, or theme? Request it at https://github.com/markbang/base-themes/issues/new?template=feature_request.yml')
|
|
432
|
+
}
|
|
433
|
+
return lines.join('\n')
|
|
434
|
+
}
|