base-themes 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/CHANGELOG.md +25 -0
- package/CODE_OF_CONDUCT.md +22 -0
- package/CONTRIBUTING.md +98 -0
- package/LICENSE +21 -0
- package/README.md +324 -5
- 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/public/previews/base-themes-bauhaus.png +0 -0
- package/public/previews/base-themes-bento.png +0 -0
- package/public/previews/base-themes-calm.png +0 -0
- package/public/previews/base-themes-cyberpunk.png +0 -0
- package/public/previews/base-themes-data-dense.png +0 -0
- package/public/previews/base-themes-editorial.png +0 -0
- package/public/previews/base-themes-enterprise.png +0 -0
- package/public/previews/base-themes-fluent.png +0 -0
- package/public/previews/base-themes-glass.png +0 -0
- package/public/previews/base-themes-linear.png +0 -0
- package/public/previews/base-themes-luxury.png +0 -0
- package/public/previews/base-themes-material.png +0 -0
- package/public/previews/base-themes-minimal.png +0 -0
- package/public/previews/base-themes-mono.png +0 -0
- package/public/previews/base-themes-neo-brutalism.png +0 -0
- package/public/previews/base-themes-playful.png +0 -0
- package/public/previews/base-themes-retro.png +0 -0
- package/public/previews/base-themes-shadcn.png +0 -0
- package/public/previews/base-themes-soft-ui.png +0 -0
- package/public/previews/base-themes-terminal.png +0 -0
- 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 +156 -43
- 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,373 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process'
|
|
2
|
+
import { readdirSync, readFileSync } from 'node:fs'
|
|
3
|
+
import { basename, resolve } from 'node:path'
|
|
4
|
+
|
|
5
|
+
function fail(message, details = []) {
|
|
6
|
+
console.error(`Launch status unavailable: ${message}`)
|
|
7
|
+
for (const detail of details) console.error(`- ${detail}`)
|
|
8
|
+
process.exit(1)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function telemetryDate(filePath) {
|
|
12
|
+
return basename(filePath).match(/^telemetry-(\d{4}-\d{2}-\d{2})\.(json|md)$/)?.[1]
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function telemetrySources() {
|
|
16
|
+
const byDate = new Map()
|
|
17
|
+
|
|
18
|
+
for (const file of readdirSync(resolve('research')).filter((name) => /^telemetry-\d{4}-\d{2}-\d{2}\.(json|md)$/.test(name)).sort()) {
|
|
19
|
+
const filePath = resolve('research', file)
|
|
20
|
+
const date = telemetryDate(filePath)
|
|
21
|
+
const current = byDate.get(date)
|
|
22
|
+
if (!current || file.endsWith('.json')) byDate.set(date, filePath)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return [...byDate.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([, filePath]) => filePath)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function latestTelemetryPath() {
|
|
29
|
+
const files = telemetrySources()
|
|
30
|
+
|
|
31
|
+
return files.length ? files.at(-1) : undefined
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function previousTelemetryPath(currentSource, currentGeneratedAt) {
|
|
35
|
+
const files = telemetrySources()
|
|
36
|
+
if (!currentSource) return undefined
|
|
37
|
+
|
|
38
|
+
if (currentSource === 'live collect-telemetry') {
|
|
39
|
+
const currentDate = currentGeneratedAt ?? new Date().toISOString().slice(0, 10)
|
|
40
|
+
return files.filter((filePath) => telemetryDate(filePath) < currentDate).at(-1)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const currentIndex = files.indexOf(resolve(currentSource))
|
|
44
|
+
if (currentIndex <= 0) return undefined
|
|
45
|
+
return files[currentIndex - 1]
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function loadTelemetry(filePath) {
|
|
49
|
+
if (filePath.endsWith('.md')) return loadMarkdownTelemetry(filePath)
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
return JSON.parse(readFileSync(filePath, 'utf8'))
|
|
53
|
+
} catch (error) {
|
|
54
|
+
fail('latest telemetry JSON could not be parsed', [`${filePath}: ${error.message}`])
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function numericSignalValue(value) {
|
|
59
|
+
const match = String(value).match(/\d+/)
|
|
60
|
+
return match ? Number(match[0]) : value
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function signalFromMarkdownRow(label, current, status) {
|
|
64
|
+
const normalized = label.toLowerCase()
|
|
65
|
+
const passed = /^pass$/i.test(status.trim())
|
|
66
|
+
|
|
67
|
+
if (normalized.startsWith('npm weekly downloads')) {
|
|
68
|
+
return { id: 'npm-weekly-downloads', label: 'npm weekly downloads >= 100', threshold: '>= 100', current: numericSignalValue(current), passed }
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (normalized.startsWith('github stars')) {
|
|
72
|
+
return { id: 'github-stars', label: 'GitHub stars >= 10', threshold: '>= 10', current: numericSignalValue(current), passed }
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (normalized.startsWith('external human issue or pr')) {
|
|
76
|
+
return { id: 'external-human-issue-or-pr', label: 'External human issue or PR present', threshold: '> 0 external non-maintainer, non-bot issue or PR', current: numericSignalValue(current), passed }
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (normalized.startsWith('at least one fork')) {
|
|
80
|
+
return { id: 'forks', label: 'At least one fork', threshold: '> 0', current: numericSignalValue(current), passed }
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return undefined
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function loadMarkdownTelemetry(filePath) {
|
|
87
|
+
const markdown = readFileSync(filePath, 'utf8')
|
|
88
|
+
const adoptionSection = markdown.match(/## Adoption Status\n\n([\s\S]*?)(?=\n## )/)?.[1] ?? ''
|
|
89
|
+
const signals = adoptionSection
|
|
90
|
+
.split('\n')
|
|
91
|
+
.map((line) => line.match(/^\|\s*(.*?)\s*\|\s*(.*?)\s*\|\s*(.*?)\s*\|$/))
|
|
92
|
+
.filter(Boolean)
|
|
93
|
+
.map((match) => signalFromMarkdownRow(match[1], match[2], match[3]))
|
|
94
|
+
.filter(Boolean)
|
|
95
|
+
|
|
96
|
+
if (signals.length === 0) {
|
|
97
|
+
fail('historical telemetry Markdown could not be parsed', [filePath])
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const passedSignals = signals.filter((signal) => signal.passed).length
|
|
101
|
+
const signalCount = signals.length
|
|
102
|
+
const completionThreshold = 3
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
generatedAt: telemetryDate(filePath),
|
|
106
|
+
repo: 'markbang/base-themes',
|
|
107
|
+
packageName: 'base-themes',
|
|
108
|
+
adoption: {
|
|
109
|
+
completionThreshold,
|
|
110
|
+
passedSignals,
|
|
111
|
+
signalCount,
|
|
112
|
+
score: `${passedSignals}/${signalCount}`,
|
|
113
|
+
externallyValidated: passedSignals >= completionThreshold,
|
|
114
|
+
signals,
|
|
115
|
+
},
|
|
116
|
+
errors: [],
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function loadDashboardTelemetry(filePath) {
|
|
121
|
+
const markdown = readFileSync(filePath, 'utf8')
|
|
122
|
+
const signals = markdown
|
|
123
|
+
.split('\n')
|
|
124
|
+
.map((line) => line.match(/^\|\s*(.*?)\s*\|\s*(.*?)\s*\|\s*(.*?)\s*\|$/))
|
|
125
|
+
.filter(Boolean)
|
|
126
|
+
.map((match) => signalFromMarkdownRow(match[1], match[2], match[3]))
|
|
127
|
+
.filter(Boolean)
|
|
128
|
+
|
|
129
|
+
if (signals.length === 0) {
|
|
130
|
+
fail('adoption dashboard telemetry table could not be parsed', [filePath])
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const passedSignals = signals.filter((signal) => signal.passed).length
|
|
134
|
+
const signalCount = signals.length
|
|
135
|
+
const completionThreshold = 3
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
generatedAt: 'dashboard-baseline',
|
|
139
|
+
repo: 'markbang/base-themes',
|
|
140
|
+
packageName: 'base-themes',
|
|
141
|
+
adoption: {
|
|
142
|
+
completionThreshold,
|
|
143
|
+
passedSignals,
|
|
144
|
+
signalCount,
|
|
145
|
+
score: `${passedSignals}/${signalCount}`,
|
|
146
|
+
externallyValidated: passedSignals >= completionThreshold,
|
|
147
|
+
signals,
|
|
148
|
+
},
|
|
149
|
+
searchConsole: undefined,
|
|
150
|
+
analytics: undefined,
|
|
151
|
+
registryAccess: undefined,
|
|
152
|
+
communityProof: undefined,
|
|
153
|
+
bundleReport: undefined,
|
|
154
|
+
errors: [],
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function loadLiveTelemetry() {
|
|
159
|
+
try {
|
|
160
|
+
return JSON.parse(execFileSync('node', ['scripts/collect-telemetry.mjs', '--json', '--no-write'], {
|
|
161
|
+
encoding: 'utf8',
|
|
162
|
+
maxBuffer: 20 * 1024 * 1024,
|
|
163
|
+
}))
|
|
164
|
+
} catch (error) {
|
|
165
|
+
fail('live telemetry could not be collected', [error.message])
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function parseFencedValue(section, label) {
|
|
170
|
+
const fence = '```'
|
|
171
|
+
const match = section.match(new RegExp(`${label}:\\s*\\n\\n${fence}(?:md|text)?\\n([\\s\\S]*?)\\n${fence}`))
|
|
172
|
+
return match?.[1]?.trim()
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function recommendedGoodFirstIssues() {
|
|
176
|
+
const repoUrl = 'https://github.com/markbang/base-themes'
|
|
177
|
+
const source = readFileSync('docs/contributor-issue-seeds.md', 'utf8')
|
|
178
|
+
return source.split(/^## Seed \d+: /m).slice(1).map((section) => {
|
|
179
|
+
const title = parseFencedValue(section, 'Title')
|
|
180
|
+
const labels = parseFencedValue(section, 'Labels')
|
|
181
|
+
?.split(',')
|
|
182
|
+
.map((label) => label.trim())
|
|
183
|
+
.filter(Boolean)
|
|
184
|
+
const body = parseFencedValue(section, 'Body')
|
|
185
|
+
|
|
186
|
+
if (!title || !labels?.includes('type: good first issue') || !body) return undefined
|
|
187
|
+
|
|
188
|
+
const search = new URLSearchParams()
|
|
189
|
+
search.set('title', title)
|
|
190
|
+
search.set('labels', labels.join(','))
|
|
191
|
+
search.set('body', body)
|
|
192
|
+
|
|
193
|
+
return {
|
|
194
|
+
title,
|
|
195
|
+
url: `${repoUrl}/issues/new?${search.toString()}`,
|
|
196
|
+
}
|
|
197
|
+
}).filter(Boolean).slice(0, 2)
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function missingSignals(payload) {
|
|
201
|
+
return (payload.adoption?.signals ?? []).filter((signal) => !signal.passed)
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function nextAction(signal) {
|
|
205
|
+
if (signal.id === 'github-stars') {
|
|
206
|
+
return 'Share the release through at least three external channels and ask for one repo star from users who try it.'
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (signal.id === 'external-human-issue-or-pr') {
|
|
210
|
+
return 'Publish prepared good-first issues and route users to feature, bug, Show and tell, or gallery links after first use.'
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (signal.id === 'forks') {
|
|
214
|
+
return 'Ask users who want to adapt a theme, block, or docs example to fork the repo, run the Fork-to-first-change workflow, and share the changed example or block.'
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (signal.id === 'npm-weekly-downloads') {
|
|
218
|
+
return 'Improve install snippets, README positioning, npm metadata, examples, and release-channel traffic.'
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return 'Re-run telemetry after launch activity and compare the adoption slope.'
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function buildSignalTrends(currentTelemetry, previousTelemetry) {
|
|
225
|
+
if (!previousTelemetry) return []
|
|
226
|
+
|
|
227
|
+
const previousSignalsById = new Map((previousTelemetry.adoption?.signals ?? []).map((signal) => [signal.id, signal]))
|
|
228
|
+
return (currentTelemetry.adoption?.signals ?? []).map((signal) => {
|
|
229
|
+
const previous = previousSignalsById.get(signal.id)
|
|
230
|
+
const currentValue = typeof signal.current === 'number' ? signal.current : undefined
|
|
231
|
+
const previousValue = typeof previous?.current === 'number' ? previous.current : undefined
|
|
232
|
+
const delta = currentValue !== undefined && previousValue !== undefined ? currentValue - previousValue : undefined
|
|
233
|
+
|
|
234
|
+
return {
|
|
235
|
+
id: signal.id,
|
|
236
|
+
label: signal.label,
|
|
237
|
+
current: signal.current,
|
|
238
|
+
previous: previous?.current ?? null,
|
|
239
|
+
delta: delta ?? null,
|
|
240
|
+
changed: delta !== undefined ? delta !== 0 : signal.current !== previous?.current,
|
|
241
|
+
passed: signal.passed,
|
|
242
|
+
previouslyPassed: previous?.passed ?? null,
|
|
243
|
+
}
|
|
244
|
+
})
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function buildPayload(telemetry, source, previousTelemetry, previousSource) {
|
|
248
|
+
const adoption = telemetry.adoption ?? {}
|
|
249
|
+
const failedSignals = missingSignals(telemetry)
|
|
250
|
+
const goodFirstIssues = recommendedGoodFirstIssues()
|
|
251
|
+
const telemetryErrors = Array.isArray(telemetry.errors) ? telemetry.errors : []
|
|
252
|
+
|
|
253
|
+
return {
|
|
254
|
+
generatedAt: new Date().toISOString(),
|
|
255
|
+
telemetryReport: basename(source),
|
|
256
|
+
telemetryGeneratedAt: telemetry.generatedAt,
|
|
257
|
+
repo: telemetry.repo,
|
|
258
|
+
packageName: telemetry.packageName,
|
|
259
|
+
score: adoption.score,
|
|
260
|
+
passedSignals: adoption.passedSignals,
|
|
261
|
+
signalCount: adoption.signalCount,
|
|
262
|
+
completionThreshold: adoption.completionThreshold,
|
|
263
|
+
externallyValidated: Boolean(adoption.externallyValidated),
|
|
264
|
+
publicTelemetryComplete: telemetryErrors.length === 0,
|
|
265
|
+
telemetryErrors,
|
|
266
|
+
publicSignals: adoption.signals ?? [],
|
|
267
|
+
previousTelemetryReport: previousSource ? basename(previousSource) : null,
|
|
268
|
+
signalTrends: buildSignalTrends(telemetry, previousTelemetry),
|
|
269
|
+
missingSignals: failedSignals.map((signal) => {
|
|
270
|
+
const missingSignal = {
|
|
271
|
+
id: signal.id,
|
|
272
|
+
label: signal.label,
|
|
273
|
+
current: signal.current,
|
|
274
|
+
threshold: signal.threshold,
|
|
275
|
+
nextAction: nextAction(signal),
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (signal.id === 'external-human-issue-or-pr') {
|
|
279
|
+
missingSignal.recommendedGoodFirstIssues = goodFirstIssues
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return missingSignal
|
|
283
|
+
}),
|
|
284
|
+
supportingEvidence: {
|
|
285
|
+
githubTraffic: Boolean(telemetry.github?.traffic && !telemetry.github.traffic.error),
|
|
286
|
+
searchConsole: Boolean(telemetry.searchConsole),
|
|
287
|
+
websiteAnalytics: Boolean(telemetry.analytics),
|
|
288
|
+
registryAccess: Boolean(telemetry.registryAccess),
|
|
289
|
+
communityProof: Boolean(telemetry.communityProof),
|
|
290
|
+
},
|
|
291
|
+
conclusion: telemetryErrors.length
|
|
292
|
+
? `Telemetry incomplete: ${telemetryErrors.length} collection error${telemetryErrors.length === 1 ? '' : 's'} occurred; do not treat this pass as complete adoption evidence.`
|
|
293
|
+
: adoption.externallyValidated
|
|
294
|
+
? 'Externally validated: at least three public adoption signals pass.'
|
|
295
|
+
: `Not externally validated: ${adoption.score ?? 'n/a'} public signals pass, below the ${adoption.completionThreshold ?? 3}/${adoption.signalCount ?? 4} gate.`,
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function renderText(status) {
|
|
300
|
+
const signalRows = status.publicSignals.map((signal) => {
|
|
301
|
+
const state = signal.passed ? 'Pass' : 'Not yet'
|
|
302
|
+
return `- ${signal.label}: ${signal.current ?? 'n/a'} (${state}; threshold ${signal.threshold})`
|
|
303
|
+
})
|
|
304
|
+
|
|
305
|
+
const missingRows = status.missingSignals.length
|
|
306
|
+
? status.missingSignals.map((signal) => {
|
|
307
|
+
const recommended = signal.recommendedGoodFirstIssues?.length
|
|
308
|
+
? ` Recommended good-first issues: ${signal.recommendedGoodFirstIssues.map((issue) => issue.title).join('; ')}.`
|
|
309
|
+
: ''
|
|
310
|
+
return `- ${signal.label}: ${signal.nextAction}${recommended}`
|
|
311
|
+
})
|
|
312
|
+
: ['- No missing public signals. Keep measuring slope after promotion.']
|
|
313
|
+
|
|
314
|
+
const evidenceRows = Object.entries(status.supportingEvidence).map(([key, available]) => `- ${key}: ${available ? 'available' : 'missing'}`)
|
|
315
|
+
const trendRows = status.signalTrends.length
|
|
316
|
+
? status.signalTrends.map((signal) => {
|
|
317
|
+
const delta = typeof signal.delta === 'number' ? `, delta ${signal.delta >= 0 ? '+' : ''}${signal.delta}` : ''
|
|
318
|
+
return `- ${signal.label}: ${signal.current ?? 'n/a'} (previous ${signal.previous ?? 'n/a'}${delta})`
|
|
319
|
+
})
|
|
320
|
+
: ['- No previous telemetry report is available for slope comparison.']
|
|
321
|
+
const errorRows = status.telemetryErrors.length
|
|
322
|
+
? status.telemetryErrors.map((error) => `- ${error}`)
|
|
323
|
+
: ['- None']
|
|
324
|
+
|
|
325
|
+
return `# Launch Status
|
|
326
|
+
|
|
327
|
+
Telemetry report: ${status.telemetryReport}
|
|
328
|
+
Public adoption score: ${status.score ?? 'n/a'}
|
|
329
|
+
Completion gate: ${status.completionThreshold}/${status.signalCount} public signals
|
|
330
|
+
Externally validated: ${status.externallyValidated ? 'yes' : 'no'}
|
|
331
|
+
|
|
332
|
+
## Public Signals
|
|
333
|
+
|
|
334
|
+
${signalRows.join('\n')}
|
|
335
|
+
|
|
336
|
+
## Public Signal Trend
|
|
337
|
+
|
|
338
|
+
Previous telemetry report: ${status.previousTelemetryReport ?? 'none'}
|
|
339
|
+
|
|
340
|
+
${trendRows.join('\n')}
|
|
341
|
+
|
|
342
|
+
## Missing-Signal Actions
|
|
343
|
+
|
|
344
|
+
${missingRows.join('\n')}
|
|
345
|
+
|
|
346
|
+
## Supporting Evidence
|
|
347
|
+
|
|
348
|
+
${evidenceRows.join('\n')}
|
|
349
|
+
|
|
350
|
+
## Telemetry Errors
|
|
351
|
+
|
|
352
|
+
${errorRows.join('\n')}
|
|
353
|
+
|
|
354
|
+
## Conclusion
|
|
355
|
+
|
|
356
|
+
${status.conclusion}
|
|
357
|
+
`
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const live = process.argv.includes('--live')
|
|
361
|
+
const savedTelemetryPath = latestTelemetryPath()
|
|
362
|
+
const source = live ? 'live collect-telemetry' : savedTelemetryPath ?? 'docs/adoption-dashboard.md'
|
|
363
|
+
|
|
364
|
+
const telemetry = live ? loadLiveTelemetry() : savedTelemetryPath ? loadTelemetry(source) : loadDashboardTelemetry(source)
|
|
365
|
+
const previousSource = previousTelemetryPath(source, telemetry.generatedAt)
|
|
366
|
+
const previousTelemetry = previousSource ? loadTelemetry(previousSource) : undefined
|
|
367
|
+
const status = buildPayload(telemetry, source, previousTelemetry, previousSource)
|
|
368
|
+
|
|
369
|
+
if (process.argv.includes('--json')) {
|
|
370
|
+
console.log(JSON.stringify(status, null, 2))
|
|
371
|
+
} else {
|
|
372
|
+
console.log(renderText(status))
|
|
373
|
+
}
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs'
|
|
2
|
+
import packageJson from '../package.json' with { type: 'json' }
|
|
3
|
+
import registry from '../registry/registry.json' with { type: 'json' }
|
|
4
|
+
|
|
5
|
+
const repoUrl = 'https://github.com/markbang/base-themes'
|
|
6
|
+
const docsUrl = 'https://base-themes.bangwu.me'
|
|
7
|
+
const registryUrl = `${docsUrl}/registry/registry.json`
|
|
8
|
+
const cliUrl = `${docsUrl}/docs/cli`
|
|
9
|
+
const blocksUrl = `${docsUrl}/blocks`
|
|
10
|
+
const llmsUrl = `${docsUrl}/llms.txt`
|
|
11
|
+
const llmsFullUrl = `${docsUrl}/llms-full.txt`
|
|
12
|
+
const forkUrl = `${repoUrl}/fork`
|
|
13
|
+
const showAndTellUrl = `${repoUrl}/discussions/new?category=show-and-tell`
|
|
14
|
+
const featureRequestUrl = `${repoUrl}/issues/new?template=feature_request.yml`
|
|
15
|
+
const gallerySubmissionUrl = `${repoUrl}/issues/new?template=gallery_submission.yml`
|
|
16
|
+
const goodFirstIssuesUrl = `${repoUrl}/issues?q=is%3Aissue+state%3Aopen+label%3A%22type%3A+good+first+issue%22`
|
|
17
|
+
const launchCampaign = `base-themes-${packageJson.version.replaceAll('.', '-')}`
|
|
18
|
+
const contributorIssueSeedPath = 'docs/contributor-issue-seeds.md'
|
|
19
|
+
|
|
20
|
+
function campaignUrl(path, source, medium, content) {
|
|
21
|
+
const url = new URL(path, docsUrl)
|
|
22
|
+
url.searchParams.set('utm_campaign', launchCampaign)
|
|
23
|
+
url.searchParams.set('utm_source', source)
|
|
24
|
+
url.searchParams.set('utm_medium', medium)
|
|
25
|
+
url.searchParams.set('utm_content', content)
|
|
26
|
+
return url.toString()
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const stats = {
|
|
30
|
+
version: packageJson.version,
|
|
31
|
+
components: registry.components.length,
|
|
32
|
+
blocks: registry.blocks.length,
|
|
33
|
+
pages: registry.pages.length,
|
|
34
|
+
styles: registry.style.variants.length,
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const channelLinks = {
|
|
38
|
+
githubRelease: campaignUrl('/docs/installation', 'github-release', 'release-notes', 'install-docs'),
|
|
39
|
+
social: campaignUrl('/blocks/dashboard-shell', 'x-bluesky', 'social', 'dashboard-shell'),
|
|
40
|
+
forum: campaignUrl('/docs/base-ui-vs-shadcn', 'hn-reddit', 'community', 'base-ui-vs-shadcn'),
|
|
41
|
+
directory: campaignUrl('/docs/cli', 'devtool-directory', 'directory', 'cli-doctor'),
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const commands = [
|
|
45
|
+
'npm install base-themes @base-ui/react react react-dom',
|
|
46
|
+
'npx base-themes list',
|
|
47
|
+
'npx base-themes list --json',
|
|
48
|
+
'npx base-themes plan button select block:dashboard-shell theme:enterprise',
|
|
49
|
+
'npx base-themes plan button select block:dashboard-shell theme:enterprise --json',
|
|
50
|
+
'npx base-themes add button select block:dashboard-shell theme:enterprise --target . --dry-run',
|
|
51
|
+
'npx base-themes add button select block:dashboard-shell theme:enterprise --target . --dry-run --json',
|
|
52
|
+
'npx base-themes doctor .',
|
|
53
|
+
'npx base-themes doctor . --json',
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
function parseFencedValue(section, label) {
|
|
57
|
+
const fence = '```'
|
|
58
|
+
const match = section.match(new RegExp(`${label}:\\s*\\n\\n${fence}(?:md|text)?\\n([\\s\\S]*?)\\n${fence}`))
|
|
59
|
+
return match?.[1]?.trim()
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function parseContributorIssueSeeds(source) {
|
|
63
|
+
return source.split(/^## Seed \d+: /m).slice(1).map((section, index) => {
|
|
64
|
+
const title = parseFencedValue(section, 'Title')
|
|
65
|
+
const labels = parseFencedValue(section, 'Labels')
|
|
66
|
+
?.split(',')
|
|
67
|
+
.map((label) => label.trim())
|
|
68
|
+
.filter(Boolean)
|
|
69
|
+
const body = parseFencedValue(section, 'Body')
|
|
70
|
+
|
|
71
|
+
if (!title || !labels?.length || !body) return undefined
|
|
72
|
+
|
|
73
|
+
const search = new URLSearchParams()
|
|
74
|
+
search.set('title', title)
|
|
75
|
+
search.set('labels', labels.join(','))
|
|
76
|
+
search.set('body', body)
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
number: index + 1,
|
|
80
|
+
title,
|
|
81
|
+
labels,
|
|
82
|
+
body,
|
|
83
|
+
url: `${repoUrl}/issues/new?${search.toString()}`,
|
|
84
|
+
}
|
|
85
|
+
}).filter(Boolean)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const contributorIssueSeeds = parseContributorIssueSeeds(readFileSync(contributorIssueSeedPath, 'utf8'))
|
|
89
|
+
const recommendedGoodFirstIssues = contributorIssueSeeds
|
|
90
|
+
.filter((issue) => issue.labels.includes('type: good first issue'))
|
|
91
|
+
.slice(0, 2)
|
|
92
|
+
|
|
93
|
+
function renderRecommendedGoodFirstIssues() {
|
|
94
|
+
return recommendedGoodFirstIssues.map((issue) => `- ${issue.title}: ${issue.url}`).join('\n')
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const channelChecklist = [
|
|
98
|
+
{
|
|
99
|
+
channel: 'GitHub Release',
|
|
100
|
+
action: 'Publish the release draft, then link the prepared good-first issues from recommendedIssueUrls in the release body or first comment.',
|
|
101
|
+
measure: 'Track stars, forks, release views, linked issue comments, and external human issues or PRs at T+1 day and T+7 days.',
|
|
102
|
+
primaryLink: channelLinks.githubRelease,
|
|
103
|
+
recommendedIssueUrls: recommendedGoodFirstIssues.map((issue) => issue.url),
|
|
104
|
+
shareAssetIds: ['dashboard-shell-block', 'enterprise-theme-preview'],
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
channel: 'X / Bluesky',
|
|
108
|
+
action: 'Share one block screenshot plus one command, and ask for a star, fork, or Show and tell reply.',
|
|
109
|
+
measure: 'Track GitHub stars, forks, docs route views, GitHub outbound clicks, and Show and tell Discussions after the post.',
|
|
110
|
+
primaryLink: channelLinks.social,
|
|
111
|
+
shareAssetIds: ['dashboard-shell-block', 'enterprise-theme-preview'],
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
channel: 'Hacker News / Reddit',
|
|
115
|
+
action: 'Ask for feedback on the Base UI-first + registry + agent workflow instead of asking for generic promotion.',
|
|
116
|
+
measure: 'Track referral traffic, external issues, missing-component requests, registry route requests, and npm weekly download slope.',
|
|
117
|
+
primaryLink: channelLinks.forum,
|
|
118
|
+
shareAssetIds: ['base-ui-vs-shadcn'],
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
channel: 'Product / devtool directories',
|
|
122
|
+
action: 'Submit the directory copy with docs, CLI, registry, and block URLs, then ask users to try the doctor command.',
|
|
123
|
+
measure: 'Track docs visits, install command copies, doctor/CLI docs visits, registry requests, and npm download slope.',
|
|
124
|
+
primaryLink: channelLinks.directory,
|
|
125
|
+
shareAssetIds: ['cli-doctor-workflow', 'dashboard-shell-block'],
|
|
126
|
+
},
|
|
127
|
+
]
|
|
128
|
+
|
|
129
|
+
const shareAssets = [
|
|
130
|
+
{
|
|
131
|
+
id: 'dashboard-shell-block',
|
|
132
|
+
type: 'block-route',
|
|
133
|
+
title: 'Dashboard Shell product block',
|
|
134
|
+
url: channelLinks.social,
|
|
135
|
+
imageUrl: `${docsUrl}/previews/base-themes-enterprise.png`,
|
|
136
|
+
use: 'Best first social post asset: product-screen route plus one doctor command.',
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
id: 'enterprise-theme-preview',
|
|
140
|
+
type: 'theme-preview',
|
|
141
|
+
title: 'Enterprise theme preview',
|
|
142
|
+
url: campaignUrl('/themes/enterprise', 'x-bluesky', 'social', 'enterprise-theme'),
|
|
143
|
+
imageUrl: `${docsUrl}/previews/base-themes-enterprise.png`,
|
|
144
|
+
use: 'Use for operational UI audiences comparing design-system styles.',
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
id: 'base-ui-vs-shadcn',
|
|
148
|
+
type: 'docs-route',
|
|
149
|
+
title: 'Base UI vs shadcn comparison',
|
|
150
|
+
url: channelLinks.forum,
|
|
151
|
+
imageUrl: `${docsUrl}/previews/base-themes-bento.png`,
|
|
152
|
+
use: 'Use for forum posts where the discussion is about primitives, ownership, theming, and registry workflow.',
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
id: 'cli-doctor-workflow',
|
|
156
|
+
type: 'docs-route',
|
|
157
|
+
title: 'CLI doctor workflow',
|
|
158
|
+
url: channelLinks.directory,
|
|
159
|
+
imageUrl: `${docsUrl}/previews/base-themes-data-dense.png`,
|
|
160
|
+
use: 'Use for devtool directories and package-first adoption pitches.',
|
|
161
|
+
},
|
|
162
|
+
]
|
|
163
|
+
|
|
164
|
+
function renderGitHubRelease() {
|
|
165
|
+
return `Base Themes ${stats.version} ships as a Base UI-first React theme component system with:
|
|
166
|
+
|
|
167
|
+
- ${stats.components} typed React components built on \`@base-ui/react\`
|
|
168
|
+
- ${stats.styles} CSS-token visual styles with light and dark modes
|
|
169
|
+
- ${stats.blocks} source-copyable product blocks
|
|
170
|
+
- ${stats.pages} docs, SEO, block, theme, registry, and agent routes
|
|
171
|
+
- shadcn-style registry metadata for components, blocks, pages, and themes
|
|
172
|
+
- CLI helpers: \`list\`, \`plan\`, \`add\`, and \`doctor\`, all with JSON-friendly workflows
|
|
173
|
+
- Vite, dashboard, theme-customization, Next.js, and registry-copy examples
|
|
174
|
+
- axe, package smoke, SEO, registry, community, bundle, and theme preview checks
|
|
175
|
+
|
|
176
|
+
Try it:
|
|
177
|
+
|
|
178
|
+
\`\`\`bash
|
|
179
|
+
npm install base-themes @base-ui/react react react-dom
|
|
180
|
+
npx base-themes doctor .
|
|
181
|
+
\`\`\`
|
|
182
|
+
|
|
183
|
+
Useful links:
|
|
184
|
+
|
|
185
|
+
- Install guide: ${channelLinks.githubRelease}
|
|
186
|
+
- Docs: ${docsUrl}
|
|
187
|
+
- Registry: ${registryUrl}
|
|
188
|
+
- Agent discovery: ${llmsUrl}
|
|
189
|
+
- Full agent context: ${llmsFullUrl}
|
|
190
|
+
- Blocks: ${blocksUrl}
|
|
191
|
+
- CLI: ${cliUrl}
|
|
192
|
+
|
|
193
|
+
Fork-to-first-change:
|
|
194
|
+
|
|
195
|
+
1. Fork the repo: ${forkUrl}
|
|
196
|
+
2. Run \`npm run example:theme-customization:build\` or \`npm run example:registry-copy -- plan button select block:dashboard-shell theme:enterprise --json\`.
|
|
197
|
+
3. Share the changed theme, block, or integration result in a Show and tell Discussion or focused issue.
|
|
198
|
+
|
|
199
|
+
Good-first issues to publish with this release:
|
|
200
|
+
|
|
201
|
+
${renderRecommendedGoodFirstIssues()}
|
|
202
|
+
|
|
203
|
+
If you try it and the Base UI-first direction is useful, please star the repo. If you want to adapt it, fork the repo, open an issue for missing components or rough edges, start a Show and tell Discussion, or submit a real usage example through the community gallery issue template.`
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function renderSocial() {
|
|
207
|
+
return `I shipped a stronger Base Themes release:
|
|
208
|
+
|
|
209
|
+
- Base UI-first React components
|
|
210
|
+
- ${stats.styles} CSS-token visual styles
|
|
211
|
+
- ${stats.blocks} product blocks
|
|
212
|
+
- shadcn-style registry metadata
|
|
213
|
+
- CLI: list, plan, add, doctor, and JSON output for agents
|
|
214
|
+
- React 18 + 19 support
|
|
215
|
+
|
|
216
|
+
Try:
|
|
217
|
+
npx base-themes doctor .
|
|
218
|
+
|
|
219
|
+
Dashboard block: ${channelLinks.social}
|
|
220
|
+
Repo: ${repoUrl}
|
|
221
|
+
|
|
222
|
+
If the Base UI-first direction is useful after trying it, please star the repo so I can measure whether to keep investing in the OSS kit.`
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function renderForum() {
|
|
226
|
+
return `Show HN: Base Themes - accessible Base UI React components with ${stats.styles} themes
|
|
227
|
+
|
|
228
|
+
Base Themes is an open-source React component system built on @base-ui/react. It provides typed wrappers, CSS-variable themes, ${stats.styles} curated visual styles, source-copyable registry metadata, product blocks, and CLI helpers for checking integration.
|
|
229
|
+
|
|
230
|
+
The project is package-first rather than template-first: install from npm, import one CSS file, set data-style/data-theme, and use the components or blocks. It also ships registry metadata for tools that prefer source-copy workflows.
|
|
231
|
+
|
|
232
|
+
The part I am most interested in getting feedback on: does the Base UI-first + registry + agent-friendly workflow solve a real gap for teams that like shadcn-style ownership but want a packaged multi-theme system?
|
|
233
|
+
|
|
234
|
+
Repo: ${repoUrl}
|
|
235
|
+
Docs: ${channelLinks.forum}
|
|
236
|
+
|
|
237
|
+
If you try it and the direction seems useful, please star the repo; if not, open the smallest issue that would make it usable in a real app.`
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function renderDirectory() {
|
|
241
|
+
return `Base Themes is a React component system built on Base UI. It ships accessible typed components, ${stats.styles} CSS-token visual styles, source-copyable registry metadata, product blocks, and CLI checks for integrating the package into Vite or Next.js apps.
|
|
242
|
+
|
|
243
|
+
CLI and doctor workflow: ${channelLinks.directory}
|
|
244
|
+
Star the repo after trying it if the Base UI-first package + registry workflow is useful: ${repoUrl}`
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const payload = {
|
|
248
|
+
stats,
|
|
249
|
+
attribution: {
|
|
250
|
+
campaign: launchCampaign,
|
|
251
|
+
parameters: ['utm_campaign', 'utm_source', 'utm_medium', 'utm_content'],
|
|
252
|
+
},
|
|
253
|
+
commands,
|
|
254
|
+
links: {
|
|
255
|
+
repo: repoUrl,
|
|
256
|
+
docs: docsUrl,
|
|
257
|
+
registry: registryUrl,
|
|
258
|
+
cli: cliUrl,
|
|
259
|
+
blocks: blocksUrl,
|
|
260
|
+
llms: llmsUrl,
|
|
261
|
+
llmsFull: llmsFullUrl,
|
|
262
|
+
fork: forkUrl,
|
|
263
|
+
showAndTell: showAndTellUrl,
|
|
264
|
+
featureRequest: featureRequestUrl,
|
|
265
|
+
gallerySubmission: gallerySubmissionUrl,
|
|
266
|
+
goodFirstIssues: goodFirstIssuesUrl,
|
|
267
|
+
},
|
|
268
|
+
githubRelease: renderGitHubRelease(),
|
|
269
|
+
social: renderSocial(),
|
|
270
|
+
forum: renderForum(),
|
|
271
|
+
directory: renderDirectory(),
|
|
272
|
+
callsToAction: [
|
|
273
|
+
`Star the repo if the Base UI-first direction is useful: ${repoUrl}`,
|
|
274
|
+
`Fork the repo and run the Fork-to-first-change workflow with npm run example:theme-customization:build or npm run example:registry-copy -- plan button select block:dashboard-shell theme:enterprise --json: ${forkUrl}`,
|
|
275
|
+
'Run npx base-themes add button select --target . --dry-run or npx base-themes doctor . in a real app and report rough edges.',
|
|
276
|
+
`Open a Show and tell Discussion with what worked, what was missing, and which data-style you used: ${showAndTellUrl}`,
|
|
277
|
+
`Open a feature request for the component, block, or theme that would make Base Themes usable in a real project: ${featureRequestUrl}`,
|
|
278
|
+
`Comment on a published good-first issue before opening a PR: ${goodFirstIssuesUrl}`,
|
|
279
|
+
`Submit a screenshot or repo through the community gallery template after using Base Themes: ${gallerySubmissionUrl}`,
|
|
280
|
+
],
|
|
281
|
+
recommendedGoodFirstIssues,
|
|
282
|
+
shareAssets,
|
|
283
|
+
channelChecklist,
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if (process.argv.includes('--json')) {
|
|
287
|
+
console.log(JSON.stringify(payload, null, 2))
|
|
288
|
+
} else {
|
|
289
|
+
console.log(`# Base Themes ${stats.version} Announcement Pack
|
|
290
|
+
|
|
291
|
+
## GitHub Release Draft
|
|
292
|
+
|
|
293
|
+
${payload.githubRelease}
|
|
294
|
+
|
|
295
|
+
## X / Bluesky
|
|
296
|
+
|
|
297
|
+
${payload.social}
|
|
298
|
+
|
|
299
|
+
## Hacker News / Reddit
|
|
300
|
+
|
|
301
|
+
${payload.forum}
|
|
302
|
+
|
|
303
|
+
## Product Hunt / Directory Submission
|
|
304
|
+
|
|
305
|
+
${payload.directory}
|
|
306
|
+
|
|
307
|
+
## Commands
|
|
308
|
+
|
|
309
|
+
\`\`\`bash
|
|
310
|
+
${payload.commands.join('\n')}
|
|
311
|
+
\`\`\`
|
|
312
|
+
|
|
313
|
+
## Calls To Action
|
|
314
|
+
|
|
315
|
+
${payload.callsToAction.map((call) => `- ${call}`).join('\n')}
|
|
316
|
+
|
|
317
|
+
## Recommended Good-First Issues
|
|
318
|
+
|
|
319
|
+
${payload.recommendedGoodFirstIssues.map((issue) => `- ${issue.title}: ${issue.url}`).join('\n')}
|
|
320
|
+
|
|
321
|
+
## Share Assets
|
|
322
|
+
|
|
323
|
+
${payload.shareAssets.map((asset) => `- ${asset.title}: ${asset.url} Image: ${asset.imageUrl} Use: ${asset.use}`).join('\n')}
|
|
324
|
+
|
|
325
|
+
## Channel Tracking Checklist
|
|
326
|
+
|
|
327
|
+
${payload.channelChecklist.map((item) => `- ${item.channel}: ${item.action} Assets: ${item.shareAssetIds.join(', ')} Measure: ${item.measure} Link: ${item.primaryLink}`).join('\n')}
|
|
328
|
+
`)
|
|
329
|
+
}
|