@shopkit/cli
v0.5.0
Published
CLI tools for Shopkit storefront development
Readme
@shopkit/cli
CLI tools for Shopkit storefront development. Provides generators for themes, widgets, and templates, plus validators for structure compliance.
Installation
# In your monorepo
bun add @shopkit/cliUsage
Generate Commands (Angular-style)
# Generate a new theme
bun run g theme <name> [--lang=en]
bun run g theme dawn
bun run g t "My Store" --lang=hi
# Generate a new widget
bun run g widget <name> [--scope=common]
bun run g widget HeroBanner
bun run g w ProductCard --scope=common
# Generate a new template
bun run g template <name> [--theme=<theme>]
bun run g template cart
bun run g tp checkout --theme=dawnValidators
# Validate widget structures
bun run validate:widget
bun run validate:widget --staged-only
# Validate template structures
bun run validate:template
bun run validate:template --theme=dawnInteractive Mode
bun run cliApps Platform Commands (RAP-27)
The app subcommand group manages Ratio Apps Platform apps across all three v1 surfaces — Embed, Block, and Account-extension — and walks the full Create-phase lifecycle from scaffold to mounted iframe in the demo storefront.
# Scaffold a new app workspace package. Name must be kebab-case.
# Surface defaults to `embed` for back-compat; pick `block` or `account` for the other surfaces.
shopkit app create my-cart-widget
shopkit app create my-rating-block --surface block
shopkit app create my-profile-card --surface account
# Validate manifest.json against the @shopkit/apps-manifest Zod schema.
# Path can be a file or a directory containing manifest.json. Defaults to cwd.
shopkit app lint
shopkit app lint ratio-apps/app-embed-cart-summary/manifest.json
shopkit app lint ratio-apps/app-embed-cart-summary/
# Bundle the app's TypeScript into a single self-contained IIFE via tsup.
shopkit app build
shopkit app build ratio-apps/app-embed-cart-summary/
# Publish the built bundle to the local mock CDN + register in publish-registry.json.
# The demo storefront's /api/v1/apps/installed route merges the registry on every
# render, so a published app appears in the storefront on the next page load with
# no installed-apps.json edit + no Next rebuild.
shopkit app publish
shopkit app publish path/to/my-app/
# Help
shopkit app --helpEach command exits 0 on success, 1 on failure. app lint collects every validation error (no early-bail) so CI / IDE feedback loops show the full picture in one run.
Per-surface scaffold differences
| Surface | Manifest shape | Bundle dispatch | Default mount target |
|---|---|---|---|
| embed | surfaces[0].position: "footer", eager mount | Single mount<Name>() function | Storefront's <EmbeddedApps> slot |
| block | surfaces[0].blocks: [{ handle: <appId>, pages: ["product"] }] | Reads sdk.init.block.handle for multi-handle dispatch | <AppBlockSlot pageType="product"> |
| account | surfaces[0].slot: "account.profile" | Reads sdk.init.account.slot for multi-slot dispatch | <AppAccountSlot slot="account.profile"> |
Each scaffold's README has surface-specific notes explaining how to extend the starter to multi-handle / multi-slot.
Publish layout
shopkit app publish mirrors the production cdn.ratio.win + App Ecosystem service lifecycle locally:
- Bundle CDN — copied to
<repo>/ratio-apps/apps-platform-example/public/cdn/apps/{appId}/{version}/index.js. Per-version paths are immutable; older versions persist alongside newer ones (the registry only tracks the latest publish per appId). - SRI integrity — SHA-384 computed on the bundle bytes, formatted
sha384-<base64>. Stored alongside the registry entry. - Registry — manifest + bundleUrl + integrity + publishedAt written to
<repo>/ratio-apps/apps-platform-example/mock-data/publish-registry.json. The install API route reads this file fresh on every request and merges it into the response so the storefront picks up new apps without a rebuild. - Conflict policy — the static
installed-apps.jsonfixture wins on appId collision. Re-publishing one of the curated 6 reference apps via the CLI doesn't shadow the fixture's settings/networkOrigins. - Override flags —
--cdn-dirand--registry-filepoint publish at alternate destinations (used by the unit tests; also useful for parallel demo environments).
Theme Generator Output
Running bun run g theme <name> creates the following structure:
src/themes/{theme-id}/
├── theme.json # Theme configuration
├── templates/
│ ├── home/default.ts # Home page template
│ ├── home-b/default.ts # Home A/B variant
│ ├── products/default.ts # Products page template
│ ├── products-b/default.ts # Products A/B variant
│ ├── collection/default.ts # Collection page template
│ ├── collection-b/default.ts # Collection A/B variant
│ ├── orders/default.ts # Orders/Thank You page
│ └── variant-registry.ts # Template registry
└── locales/
├── common/{lang}.json # Common translations (logo, site_name, etc.)
└── pages/
├── home/{lang}.json
├── home-b/{lang}.json
├── products/{lang}.json
├── products-b/{lang}.json
├── collections/{lang}.json
├── collections-b/{lang}.json
└── orders/{lang}.jsonTemplate Structure
All generated templates follow this consistent pattern:
// Each template has three sections:
{
sections: [
// 1. Header Section
{
type: SECTION_TYPES.HEADER_SECTION,
widgets: [{ type: WIDGET_TYPES.Header }]
},
// 2. Main Content Section
{
type: SECTION_TYPES.CONTENT_SECTION,
widgets: [{
type: WIDGET_TYPES.PageContent,
settings: {
heading: "t:{page_name}.heading",
description: "t:{page_name}.description"
}
}]
},
// 3. Footer Section
{
type: SECTION_TYPES.FOOTER_SECTION,
widgets: [{ type: WIDGET_TYPES.Footer }]
}
]
}Translation Key Structure
Each page has a corresponding locale file with this structure:
{
"{page_name}": {
"page_title": "Page Title",
"heading": "Page Heading",
"description": "Welcome to the page."
}
}Translation keys are referenced in templates using the t: prefix:
t:home.heading- Resolves to the heading from home localet:products.description- Resolves to the description from products locale
Essential Widgets
The theme generator creates these essential widgets if they don't exist:
| Widget | Purpose |
|--------|---------|
| Header | Site header with navigation |
| Footer | Site footer with links |
| PageContent | Displays translated heading and description |
A/B Variant Templates
Templates with -b suffix (home-b, products-b, collection-b) are for A/B testing:
- Use the same translation structure as their base templates
- Allow different layouts/widgets for experimentation
- Can be served to different user segments
Widget Registry Generator Output
Running bun run generate:widgets (or shopkit generate:widgets) scans all widget namespaces and writes four files to src/widgets/.generated/:
src/widgets/.generated/
├── types.ts # WIDGET_TYPES enum + WidgetType union
├── manifest.ts # Production WIDGET_MANIFEST (SSR-safe imports)
├── manifest.editor.ts # Editor WIDGET_MANIFEST — imports through client shims
└── client-shims/
├── HeroBanner.tsx # "use client" shim per widget
└── ... # one file per discovered widgetProduction vs editor manifests:
manifest.tsuses standard imports — widgets render server-side as normalmanifest.editor.tsimports each widget through itsclient-shims/wrapper so every widget becomes a live client component inside the editor iframe, enabling the override store to re-render on edit without touching production traffic
The correct manifest is selected at runtime via isEditor in bootstrap/builder.ts.
Widget Generator Output
Running bun run g widget <name> creates:
src/widgets/{scope}/{WidgetName}/
├── index.tsx # Widget entry point with variant resolution
├── types.ts # TypeScript interfaces (Settings, Data, Props)
├── variants.ts # Variant registry
└── V1/
└── index.tsx # Default V1 variant componentTemplate Generator Output
Running bun run g template <name> creates:
src/themes/{theme}/templates/{template-name}/
└── default.ts # Template configuration
src/themes/{theme}/locales/pages/{template-name}/
└── {lang}.json # Translation file for each supported languageProgrammatic Usage
import {
generateWidget,
generateTheme,
generateTemplate,
validateWidgets,
validateTemplates,
} from "@shopkit/cli";
// Generate a widget programmatically
await generateWidget({ name: "HeroBanner", scope: "common" });
// Generate a theme
await generateTheme({ name: "dawn", lang: "en" });
// Validate widgets
const { passed, errors } = await runWidgetValidation();Options
| Option | Description | Default |
|--------|-------------|---------|
| --lang=<code> | Default language for theme | en |
| --scope=<name> | Widget scope/namespace | common |
| --theme=<name> | Theme name for template | - |
| --staged-only | Validate only staged files | false |
| --skip-generate | Skip registry generation | false |
License
MIT
