@gentleduck/registry-build
v0.2.1
Published
Config-driven registry and index builder CLI.
Readme
@gentleduck/registry-build
A generic, extension-driven build system for component registries.
The runner is entirely extension-driven: users define their config with defineConfig(), register extensions (custom or built-in), and the builder runs them in order. The package provides built-in extensions for UI registries via uiRegistryPreset(), or users can create their own extensions for any domain.
Source Layout
The src folder is split by responsibility so the runtime flow is easy to trace:
src/configresolves config files through folder modules such asloader/,merge/, andresolution/, each with its ownindex.tsand local support files.src/pipelineowns the build context, cache, and folder-based phases where each larger phase keeps its*.types.tsand*.lib.tsbeside the runner.src/extensionscontains folder-based extension modules such asbanner/,colors/,component-index/,extension/, andvalidate/, with UI-specific helpers isolated undersrc/extensions/ui.src/extensions/uinow splits ownership by concern:ui.registry.types.tsfor registry items,ui.config.types.tsfor UI config contracts,ui.collection.types.tsfor collection adapters, andui.schema.tsorui.collection.tsfor behavior.src/commands/buildandsrc/adapters/component-indexfollow the same module-folder pattern, keeping command or adapter types beside their implementation files.src/config/types.tsowns the config contract,src/config/loader/loader.types.tsowns loader-specific contracts, andsrc/extensions/extension/extension.types.tsowns the extension runtime contract.src/libcontains shared filesystem, hashing, path, and transformation utilities.
Usage
Create a registry-build.config.ts beside the app or package that consumes the generated registry:
import { defineConfig, uiRegistryPreset } from '@gentleduck/registry-build'
export default defineConfig({
extensions: [...uiRegistryPreset()],
output: {
dir: '.',
},
sources: {
'registry:ui': {
path: '../../packages/registry-ui/src',
packageName: '@example/registry-ui',
referencePath: '/registry-ui/src',
},
},
registries: {
uis: [
{
name: 'button',
root_folder: 'button',
type: 'registry:ui',
},
],
},
})Every registry item type is validated as registry:${string}, and defineConfig() enforces the same namespace across source keys, package mappings, target paths, schema item types, and inline registry entries.
Run the builder from that consumer directory:
registry-build buildIn a workspace app, the local installed binary works the same way:
./node_modules/.bin/registry-build buildGeneric Collections
For non-UI consumers, start with collections plus extensions:
import { defineConfig } from '@gentleduck/registry-build'
import { archRepositoryExtension } from './arch-repository.extension'
export default defineConfig({
collections: {
packages: {
data: './data/packages.json',
metadata: {
repoOrder: ['core', 'extra'],
},
sources: {
pkgbuilds: {
glob: '**/PKGBUILD',
path: './pkgbuilds',
},
},
},
},
extensions: [
archRepositoryExtension({
collection: 'packages',
}),
],
output: {
dir: './dist',
},
})See the runnable example in examples/arch-package-index.
Incremental Builds
The builder keeps a local cache under <output.dir>/.registry-build/ by default.
Useful CLI flags:
registry-build build --changed-only
registry-build build --changed-only --changed ../../packages/registry-ui/src/button/button.tsx--changed-onlykeeps the incremental cache on and reuses unchanged outputs aggressively--changed <paths...>narrows rebuild work to entries affected by those paths
The build summary table now reports actual rewritten files per phase, so a warm no-op build should usually show Files as -.
Extensions
Attach optional behavior explicitly:
import { defineConfig, uiRegistryPreset } from '@gentleduck/registry-build'
export default defineConfig({
extensions: [
...uiRegistryPreset({
banner: { name: 'My Registry' },
colors: { /* colors/themes config */ },
componentIndex: { /* component-index config */ },
}),
],
output: {
dir: '.',
},
})Config Composition
Configs can extend one or more preset files:
import { defineConfig } from '@gentleduck/registry-build'
export default defineConfig({
extends: ['./presets/theme-preset.ts', './presets/source-preset.ts'],
output: {
dir: './apps/docs',
},
})extends is path-aware:
- source paths are resolved relative to the file that declared them
- theme/color data files are resolved relative to the preset that declared them
- sources, registries, target paths, package mappings, and theme maps are merged
For code-driven composition, import preset objects and merge them through the public API:
import { defineConfig, mergeRegistryBuildConfigs } from '@gentleduck/registry-build'
import { baseConfig } from './presets/base'
import { themeConfig } from './presets/theme'
export default defineConfig(mergeRegistryBuildConfigs(baseConfig, themeConfig, {
output: {
dir: '.',
},
}))Consumer-specific presets (e.g., monorepo source layouts, project-specific theme maps) should live beside the consumer config, not inside this package.
Output
The builder writes:
<output.dir>/
.registry-build/
__ui_registry__/index.tsx
public/r/
colors/
components/
themes/
index.json
themes.css