1. INIT scaffold local theme
2. DEV iterate with HMR
3. LINT + CHECK static validation
4. BUILD produce dist/
5. PUSH upload to your dev sandbox
6. INSTALL install your pushed theme into your test store
7. SUBMIT submit for public marketplace review
8. REVIEW admin diff + approve
9. PUBLISH bundle goes to R2; marketplace listing public
10. MERCHANT INSTALL merchant picks your theme; live at <sub>.numueg.app
You'll iterate stages 2 ā 4 many times before submitting. Stage 5 + 6 are the "test on a real backend" loop.
ā http://localhost:3001/__numu/preview renders your theme against mock data. Edits in src/ hot-reload.To preview against a real merchant's customizer state:
1.
Set the merchant's external_theme.bundle_url in the hub (or via API) to http://localhost:3001/theme.js
2.
Open the merchant's hub customizer
3.
The iframe loads your local bundle ā every theme change reflects instantly
Two terminals
Run numu-theme dev in one tab, your editor in another. The plugin auto-runs schema codegen on save ā you don't need to restart Vite.
Uploads dist/ to your developer sandbox under your marketplace account. The theme is NOT publicly listed yet ā only you can install it.Behind the scenes:
The CLI zips dist/ + the source (so the marketplace can re-build server-side later if needed)
POST to /marketplace/themes/{id}/versions with Authorization: Bearer <cli token>
The backend stores the version under marketplace_theme_versions with status=developer_uploaded
1. CLI uploads ZIP POST /marketplace/themes/{id}/submit
2. Async task: theme_build_task ā
a. unzip
b. AST scan for forbidden patterns
- raw eval()
- document.write()
- fetch() to non-allowlisted hosts
- direct DOM injection of <script>
c. sandboxed Docker re-build from source
(ignores dev's dist/ ā trust nothing)
d. compare new manifest sha256 vs prior ā integrity drift
3. Status: building ā built ā in_review
If any step fails, status ā failed with an error message visible in your numu-theme status output + the hub's My-Submissions page.
1. Hub ā POST /stores/{store_id}/themes/install { theme_id, version: "latest" }
2. Backend:
- Sets store.theme_settings.theme_id = <theme_id>
- Sets store.theme_settings.external_theme.bundle_url = <R2 url>
- Resets customizer state to theme.json presets
- Records install in audit_log
3. Hub redirects to customizer with the new theme loaded
4. Storefront's per-request fetch picks up the new theme_settings on next page load
A customer visiting https://<merchant-sub>.numueg.app/ now sees your bundle render.
Merchants on ^1.0.0 auto-update on cache expiry. Merchants pinned to 1.0.0 exactly stay on it until they explicitly upgrade.Marketplace UI shows merchants a "Update available" badge when a new version they're eligible for ships.
The worker has a 5-minute watchdog (theme-marketplace-watchdog beat task) that fails orphan builds. Wait ~5 minutes, then re-submit. If it persists, check the build logs in numu-theme status for clues.
The sandboxed re-build produced a theme.js with a different hash than your local. Almost always: env vars or NODE_ENV differences. The sandbox runs npm ci && npm run build with NODE_ENV=production. Match that locally.
AST scan caught it. Themes can't inject HTML this way ā use <RichText> from the SDK or, if you really need it, talk to the platform team for an exception.