Storybook translation
Statically inspect supported React CSF and produce reviewable shadcn registry artifacts.
Storybook is the upstream source for component implementation, stories, tests, and documentation. Compify translates a supported story source bundle into a shadcn registry item; it does not replace Storybook or promise faithful capture of Storybook runtime behavior.
The CLI and @compify/storybook addon described here are present in the current
repository source candidate. The current CLI 0.2.0 and addon 0.1.0
manifests are not published releases. The repository's v0.1.0 GitHub source
tag already contains a CLI 0.2.0 manifest; npm's older
@compify/cli@0.1.0 came from different source history, so the two historical
artifacts must not be conflated. The project-operated API is a public alpha
rather than a generally available managed service or SLA.
CLI workflow
Build and link the CLI from source, then run it from the React package containing the stories:
compify storybook inspect src/components/Button.stories.tsx
compify storybook inspect src/components/Button.stories.tsx --json
compify storybook export src/components/Button.stories.tsx \
--name button --output .compify/button.registry.json
COMPIFY_API_URL=https://api.example.com compify login -t <token>
COMPIFY_API_URL=https://api.example.com compify storybook publish \
src/components/Button.stories.tsx \
--publishing-name button --visibility privateThe story-file argument is optional only when exactly one *.stories.* file can
be inferred under the working directory. When several exist, specify one. Use
--cwd <path> to choose another working directory. The CLI resolves the
component source from the default meta component import (including the common
const meta = { component: Button } satisfies Meta; export default meta form).
Use --component-entry <path> to override or supply that source when it cannot
be inferred:
compify storybook inspect src/components/Button.stories.tsx \
--component-entry src/components/Button.tsxSee the CLI reference for other options.
inspect reports all named stories discovered in the selected file by default.
Pass --story <export-name> to inspect, export, or publish one exact named story:
compify storybook inspect src/components/Button.stories.tsx --story Primary
compify storybook export src/components/Button.stories.tsx \
--story Primary --output .compify/button.registry.jsonWithout --story, every named story in the file is selected and must be portable
for export or publish. With it, portability diagnostics belonging only to other
named stories do not block the selected story. The component source bundle is
still resolved from the file's default meta component (or --component-entry).
Supported source boundary
The implementation is intentionally constrained to React CSF source files named
*.stories.tsx, *.stories.ts, *.stories.jsx, *.stories.js, *.stories.mjs,
or *.stories.cjs.
It also recognizes the narrow CSF Next factory form
const meta = preview.meta({ component }) where preview is imported, followed
by export const Name = meta.story({ ...static metadata... }). It never imports
or executes that preview. Story.extend, Story.input, Story.composed,
computed factory inputs and imported/dynamic configuration remain unsupported;
real Storybook 9/10 fixtures are still required before this is a broad claim.
It currently recognizes:
- a classic CSF default export or the narrow factory meta above;
- CSF3 named variable exports;
- CSF2 named function exports, including static
.argsand.storyNameassignments; - JSON-static story
argsand static story names, including bounded local const aliases and static object/array spreads; - static
includeStories/excludeStoriesarrays or regular expressions; - local export-list aliases such as
export { primary as Primary }; - a local component import referenced by default meta
component, including a default-meta variable alias, or an explicit--component-entry; and - static local imports/re-exports, string-literal
import()andrequire()targets, and bare dependencies reachable from that component entry; and - exact, package-local aliases from the nearest in-package
tsconfig.json(compilerOptions.baseUrland one-targetpathsentries) or the package root's exact string-valuedpackage.json#importsentries.
Alias configuration is parsed as bounded JSON/JSONC text and is never imported
or executed. Compify does not follow tsconfig extends. Wildcard keys or
targets, multiple fallback targets, conditional/array package imports, absolute
or external targets, symlink targets, and targets outside the story's package
root fail closed when relevant (an extends entry always fails closed). The
resolver only searches from an explicitly configured baseUrl; merely having a
tsconfig.json does not turn bare package imports into local paths.
Because shadcn consumers do not inherit the publisher's TypeScript or package alias configuration, every accepted JavaScript/TypeScript alias edge is rewritten to a deterministic relative specifier in the emitted files. Alias syntax in stylesheets is rejected rather than emitted broken. Ordinary relative imports are preserved unchanged.
Static literals include strings, numbers, booleans, null, literal arrays and objects, expression-free template literals, and simple unary literal values. Dynamic expressions are not evaluated. Dynamic args make that story non-portable and produce a warning; missing recognized meta or no named story exports is an error. Inspection reports non-portability; export and publish fail instead of emitting or uploading a partially portable artifact.
The exported registry item contains the component entry and its transitive text
files and runtime dependencies, plus story metadata, source provenance when
available, and a stable SHA-256 digest. Conventional entry/component, lib,
hook, and components/ui paths receive native shadcn file types so the pinned
client maps helpers to the corresponding consumer aliases instead of flattening
them into the component directory. Arbitrary target policy remains unsupported.
Review this derived output before publishing.
inspect --json includes a deterministic source-graph sidecar with normalized
file hashes, media kinds, import edges, original specifiers, resolution reasons,
and unresolved edges. --explain <path> prints the inclusion chain for one
file. This evidence is deliberately outside the registry/publish digest contract
so adding explanations cannot silently change the install artifact.
For a real cross-app proof, storybook handoff exports locally, invokes pinned
native shadcn without a shell, optionally runs an explicit consumer build, and
writes a digest-verifiable receipt. A receipt is built evidence only when that
build succeeds; no publish request is made. Snapshot traversal is bounded and
never follows symlinks. A native-command failure writes no success receipt,
removes only Compify's unchanged temporary artifact, and may leave consumer
changes for the operator to review before retrying.
Static parsing and security
Inspection parses the abstract syntax tree; it never imports or executes the story or component modules. In particular, it does not run decorators, loaders, play functions, render functions, hooks, environment reads, or build plugins.
The bundler also:
- confines the entry and local imports to the working/package roots, including resolution through symbolic links;
- rejects absolute local imports and unresolved local imports;
- includes only supported text extensions;
- refuses likely credential/key files, private-key content, high-confidence provider token formats, and binary content;
- limits a bundle to 500 files, 256 KiB per file, and 5 MiB total UTF-8 component source;
- rejects case-colliding paths; and
- normalizes line endings and sorts output for deterministic digests.
These controls reduce exposure; they do not certify source as safe or suitable for publication. Static text can still contain proprietary code, tokens in unrecognized formats, personal information, or undesired files. Inspect the file list and artifact, use least-privilege credentials, and publish privately until review is complete.
Limitations
Compify does not currently promise:
- Vue, Svelte, Angular, Web Components, or other renderer conversion;
- evaluation of variables, spreads, functions, computed keys, or imported data to recover dynamic args/names;
- semantic capture of decorators, loaders, play functions, parameters, global configuration, Storybook/bundler aliases, wildcard or inherited TypeScript aliases, stylesheet aliases, or Storybook preview runtime state;
- asset/binary bundling or arbitrary file types;
- automatic conversion of a story render function into a reusable component;
- perfect dependency discovery for runtime or dynamically loaded dependencies;
- combining named stories from different story files into one artifact; or
- automatic synchronization when upstream source changes.
An inspection with no error diagnostics means the supported static bundle can be produced. It does not mean the installed component will visually or behaviorally match every Storybook configuration. Test the resulting source in a consumer.
@compify/storybook addon
The addon is a thin manager-only panel and toolbar indicator for explicit
parameters.compify metadata. Build it from repository source:
cd packages/storybook
bun install --frozen-lockfile
bun run buildWhen the package is available in your Storybook environment, add it to
.storybook/main.ts:
export default {
addons: ["@compify/storybook"],
};Provide author-owned metadata on a story:
import type { CompifyParameters } from "@compify/storybook/parameters";
const compify = {
status: "portable",
component: "@acme/button",
registry: "https://registry.example.com/r/acme/button.json",
installCommand:
"bunx shadcn@4.16.2 add https://registry.example.com/r/acme/button.json",
previewUrl: "https://storybook.example.com/?path=/story/button--primary",
reasons: ["Reviewed static bundle"],
} satisfies CompifyParameters;
export const Primary = {
parameters: { compify },
};Supported statuses are portable, partial, not-portable, and unknown.
The addon reads only this explicit metadata. It does not inspect source, read
credentials or environment variables, make network requests, execute displayed
commands, publish files, or create a local bridge. Only HTTP(S) preview URLs are
clickable. Its status is an author assertion, not the result of CLI inspection.
Use the CLI separately for static inspection, export, and authenticated publish.
The default https://api.compify.app currently includes
POST /cli/publish-story as a project-operated public-alpha surface. Set
COMPIFY_API_URL or the global --api-url to use an operator-controlled
self-hosted API implementing the same current-source contract. Neither option
turns the unpublished CLI/addon candidates into releases; the alpha has no
managed-service SLA, while a self-hosted operator owns its service obligations.
public items are indexed, unlisted items are direct-address only, and
private items are owner-only. The addon and CLI do not currently synchronize
metadata automatically.