Documentation
¶
Index ¶
- Constants
- func BuildArtifactFor(ctx context.Context, projectRoot string, l LocalExt, debug bool) (string, *output.ExitError)
- func ConnectTheme(ctx context.Context, partner *client.Client, ...) *output.ExitError
- func ConnectionSucceeded(raw json.RawMessage) bool
- func EnsureAppToken(ctx context.Context, d *Dashboard, mgr *internalauth.Manager, ...) (string, error)
- func MigrateV1Extension(root, dir, id, name, typ, version string) error
- func NewOAuthHandler(cfg OAuthConfig) http.Handler
- func PollThemeVersionTask(ctx context.Context, store *client.Client, extRef, taskID string, ...) (string, *output.ExitError)
- func WriteBackExtensionVersion(root, dir, id, version string) error
- type App
- type AppConfig
- type AppsResp
- type CompleteInfo
- type Dashboard
- func (d *Dashboard) CreateApp(ctx context.Context, partnerID, appName string) (App, error)
- func (d *Dashboard) ExtensionDeploy(ctx context.Context, partnerID, clientID, storeID string, appPayload any) (DeployResp, error)
- func (d *Dashboard) ExtensionDev(ctx context.Context, partnerID, clientID, storeID string, appPayload any) (map[string]any, error)
- func (d *Dashboard) GenerateVersion(ctx context.Context, partnerID, clientID, isDev, storeID string) (GenerateVersionResp, error)
- func (d *Dashboard) GetAppConfig(ctx context.Context, partnerID, clientID string) (AppConfig, error)
- func (d *Dashboard) GetApps(ctx context.Context, partnerID string) (AppsResp, error)
- func (d *Dashboard) GetCompleteInfo(ctx context.Context, clientID string) (CompleteInfo, error)
- func (d *Dashboard) GetExtensionVersions(ctx context.Context, partnerID, clientID string, q map[string]any) (ExtensionsResp, error)
- func (d *Dashboard) GetPartners(ctx context.Context) (PartnersResp, error)
- func (d *Dashboard) GetTemplate(ctx context.Context, templateType string) (TemplateResp, error)
- func (d *Dashboard) GetVersions(ctx context.Context, partnerID, clientID string, offset, limit int) (VersionsResp, error)
- type DeployDeps
- type DeployResp
- type DeployResult
- type DeployedExt
- type DevResult
- type Extension
- type ExtensionsResp
- type GenerateVersionResp
- type LocalExt
- type OAuthConfig
- type Pair
- type Partner
- type PartnersResp
- type TemplateResp
- type UpsertResult
- type VersionItem
- type VersionsResp
Constants ¶
const ( ThemeConnectionPathApp = "/openapi/2025-06/theme-extensions/connection" ThemeConnectionPathStandalone = "/openapi/2024-07/theme-extensions/connection" )
Theme-extension connection endpoints — two flows, each matching its v1 source:
- standalone `te connect` + `te release`: 2024-07 for BOTH connection and publications (v1 lib/partner-api/axios.js pinned /openapi/2024-07). 2025-06 has no /publications at all.
- `app deploy`'s theme leg: 2025-06 connection (v1 lib/app/api/partnerOpenapi.js posts openapi/2025-06/theme-extensions/connection). Its publish is a separate STORE-side 2020-07/publications call, so it never touches the 2024-07 publications endpoint and the connect/publish version pairing is irrelevant.
NOTE: "version has no doc" from the publish endpoint is NOT a connection-version problem. It means the built version has no parsed doctree, caused by a malformed upload bundle — see ZipThemeApp (the "theme-app/" wrapper must be present) and the te templates (assets-manifest.json must sit at the theme-app root).
Variables ¶
This section is empty.
Functions ¶
func BuildArtifactFor ¶
func BuildArtifactFor(ctx context.Context, projectRoot string, l LocalExt, debug bool) (string, *output.ExitError)
BuildArtifactFor produces a deployable artifact for a single local extension, dispatching by type. It is the real BuildArtifact wired by `app deploy`:
- checkout: Vite build via buildCheckout, returns the bundle path.
- theme: zip extensions/<Dir> into app-deploy/<Name>.zip, returns its path.
- function: javy-compile extensions/<Dir>/src/index.js to app-deploy/<Name>.<hash>.wasm.
func ConnectTheme ¶
func ConnectTheme(ctx context.Context, partner *client.Client, extName, extensionID, connPath string) *output.ExitError
ConnectTheme POSTs the connection (type:"link") via the partner client at connPath. Exported so te connect reuses the SAME body + judgment.
func ConnectionSucceeded ¶
func ConnectionSucceeded(raw json.RawMessage) bool
ConnectionSucceeded reports success for either historical marker. Exported so the te package reuses the SAME judgment for the publications endpoint (its 2xx bodies carry the same {code,message} envelope).
func EnsureAppToken ¶
func EnsureAppToken(ctx context.Context, d *Dashboard, mgr *internalauth.Manager, partnerID, clientID string) (string, error)
EnsureAppToken runs the acquisition chain: Dashboard app-config (client_secret + partner_id) -> saiga ExchangeAppAT -> keychain app:<client_id>. The secret is never persisted. clientID comes from current-app (caller resolves).
func MigrateV1Extension ¶
MigrateV1Extension writes a v2 shoplazza.extension.toml (with the deployed id) for a legacy extension.config.json and marks the json deprecated (kept, not deleted). appId/partnerId are not carried over. No-op when there's no v1 json.
func NewOAuthHandler ¶
func NewOAuthHandler(cfg OAuthConfig) http.Handler
NewOAuthHandler returns an http.Handler (a *http.ServeMux) with the install, callback, and root routes mounted. Used by `app dev`'s devserver.
func PollThemeVersionTask ¶
func PollThemeVersionTask(ctx context.Context, store *client.Client, extRef, taskID string, pollInterval time.Duration, maxRetry int) (string, *output.ExitError)
PollThemeVersionTask polls GET version-tasks/{taskID} up to maxRetry times, waiting pollInterval between polls. state 1 → success (version_id); state 2 → failure (message); exhausting the retries → timeout. Polls before sleeping so an already-finished/failed task is detected on the first iteration.
extRef is a display-only reference to the extension — app deploy passes the extension NAME, te's dev push passes the extension ID (the name isn't in scope there); the message is honest for both.
A state-2 task and a poll timeout are SERVER-reported outcomes (the backend accepted the task and then failed / never finished it), not CLI bugs — both are API-class, not internal.
func WriteBackExtensionVersion ¶
WriteBackExtensionVersion updates id/version in extensions/<dir>/shoplazza.extension.toml, preserving the other keys. The toml is a CACHE; the truth source is the remote GetExtensionVersions diff (so a stale/missing id self-heals on the next deploy/dev/release). v1 parity: deploy.js:156 / dev.js:154 write the id back after every upsert — without it the next run cannot id-match and falls back to name matching or re-creating the extension.
Types ¶
type CompleteInfo ¶
type Dashboard ¶
type Dashboard struct {
// contains filtered or unexported fields
}
Dashboard is the typed client for Partner Dashboard /api/cli/v2 endpoints. Bearer = partner token. The caller supplies a client already pointed at the Dashboard host.
func (*Dashboard) ExtensionDeploy ¶
func (d *Dashboard) ExtensionDeploy(ctx context.Context, partnerID, clientID, storeID string, appPayload any) (DeployResp, error)
9
func (*Dashboard) ExtensionDev ¶
func (d *Dashboard) ExtensionDev(ctx context.Context, partnerID, clientID, storeID string, appPayload any) (map[string]any, error)
8
func (*Dashboard) GenerateVersion ¶
func (d *Dashboard) GenerateVersion(ctx context.Context, partnerID, clientID, isDev, storeID string) (GenerateVersionResp, error)
11 — needs ?store_id so the backend can resolve/create the (dev) store; without it the backend defaults to store 0 and 500s (v1 sent store_id too).
func (*Dashboard) GetAppConfig ¶
func (d *Dashboard) GetAppConfig(ctx context.Context, partnerID, clientID string) (AppConfig, error)
5 — returns the app under "app" with its secret (field "secret"); never persist the secret. partner_id isn't in the response, so carry the caller's.
func (*Dashboard) GetCompleteInfo ¶
6
func (*Dashboard) GetExtensionVersions ¶
func (d *Dashboard) GetExtensionVersions(ctx context.Context, partnerID, clientID string, q map[string]any) (ExtensionsResp, error)
10
func (*Dashboard) GetPartners ¶
func (d *Dashboard) GetPartners(ctx context.Context) (PartnersResp, error)
1
func (*Dashboard) GetTemplate ¶
4
func (*Dashboard) GetVersions ¶
func (d *Dashboard) GetVersions(ctx context.Context, partnerID, clientID string, offset, limit int) (VersionsResp, error)
12
type DeployDeps ¶
type DeployDeps struct {
Dashboard *Dashboard
Store *client.Client
Partner *client.Client // partner-openapi client (app token + app-client-id header): theme connection + function create/commit
HTTPClient *http.Client // OSS POST client (nil -> default)
PartnerID string
ClientID string
StoreID string // numeric store id → ?store_id on GenerateVersion (backend resolves the target store)
ProjectRoot string // locates a function's src/index.js (source_code)
Locals []LocalExt
BuildArtifact func(ctx context.Context, l LocalExt) (string, *output.ExitError)
IsDev bool
// ThemePollInterval/MaxRetry drive upsertTheme's task polling. Zero values
// default to v1's 1s / 10 (tests inject fast values).
ThemePollInterval time.Duration
ThemePollMaxRetry int
// Progress, when non-nil, reports the build/upload/upsert and report steps as
// live timed lines. nil disables reporting (the default for tests).
Progress *output.Progress
}
DeployDeps are the inputs to Deploy. BuildArtifact is injected so tests don't need Node/Vite; the command wires the real buildCheckout/zipExtension.
type DeployResp ¶
type DeployResult ¶
type DeployResult struct {
Version string `json:"version"`
Extensions []DeployedExt `json:"extensions"`
}
func Deploy ¶
func Deploy(ctx context.Context, deps DeployDeps) (DeployResult, *output.ExitError)
Deploy runs the chain: remote extension_versions -> diff -> per-type build -> OSS upload (checkout/theme only) -> upsert -> Dashboard deploy. checkout, theme and function legs are all implemented.
type DeployedExt ¶
type DevResult ¶
type DevResult struct {
InstallURL string `json:"install_url"`
AppURL string `json:"app_url"`
RedirectURL string `json:"redirect_url"`
Version string `json:"version"`
Extensions []DeployedExt `json:"extensions"`
}
DevResult is the printable outcome of a dev session: the install URL plus the tunnel-derived OAuth URIs (mirrors v1's "Your App/Redirect/Install URL").
func DevReport ¶
func DevReport(ctx context.Context, deps DeployDeps, publicURL, appPath, callbackPath string) (DevResult, *output.ExitError)
DevReport runs the dev half: build/upload/upsert the locals (is_dev), then report the dev session to the Dashboard /dev endpoint with the tunnel-derived URIs, and return the install URL. publicURL is the tunnel base; appPath and callbackPath are the OAuth routes (e.g. "/auth", "/auth/callback").
type Extension ¶
type Extension struct {
ExtensionID string `json:"extension_id"`
ExtensionName string `json:"extension_name"`
ExtensionType string `json:"extension_type"` // theme|checkout|function
ExtensionVersion string `json:"extension_version"`
// ExtensionVersionID is flexStr: the Dashboard's extension_versions /
// version/generate responses return it as a JSON NUMBER, while functions/
// create returns it as a string. flexStr decodes either and re-marshals as a
// quoted string for the deploy body (the shape that /deploy accepts).
ExtensionVersionID flexStr `json:"extension_version_id"`
ResourceURL string `json:"resource_url"`
// Exts is the version description sent to the version-task body as `exts`
// (v1 field name). json:"-" — the version-task/PUT bodies are hand-built maps,
// so this is a pure internal carrier and never marshals into any request body.
Exts string `json:"-"`
}
type ExtensionsResp ¶
type ExtensionsResp struct {
Extensions []Extension `json:"extensions"`
}
type GenerateVersionResp ¶
type LocalExt ¶
type LocalExt struct {
Dir string
Name string
Type string
Version string // optional; from the extension toml. Diff ignores it.
ExtensionID string // optional; set once known
AppID string // v1 extension.config.json `appId`; empty for v2. Diff ignores it.
}
LocalExt is a local extension discovered under extensions/<Dir>.
func ScanLocalExtensions ¶
ScanLocalExtensions reads <root>/extensions/*/shoplazza.extension.toml and returns one LocalExt per subdir that has an extension toml. A missing extensions/ dir is tolerated (returns nil, nil), and subdirs WITHOUT a toml are skipped (not extension dirs). A toml that exists but fails to decode is a validation error — silently skipping it would make deploy/dev quietly ignore the extension.
type OAuthConfig ¶
type OAuthConfig struct {
ClientID string
ClientSecret string
RedirectURI string
Scopes string // space- or comma-separated; urlencoded into the authorize URL
InstallPath string // e.g. "/auth"
CallbackPath string // e.g. "/auth/callback"
HTTPClient *http.Client // token exchange client (nil -> http.DefaultClient)
TokenURL func(shop string) string // nil -> https://<shop>/admin/oauth/token (injectable for tests)
NewState func() (string, error) // nil -> 16 random bytes hex (injectable for tests)
}
OAuthConfig configures the install-handshake handler used by `app dev`.
The handler completes the OAuth install handshake purely to exercise the flow during local development. The token returned by the exchange is DISCARDED — it is never written to keychain, config, or any file.
type Pair ¶
Pair maps a local extension to its remote counterpart. Remote is nil when the local extension is new (to be created).
func Diff ¶
Diff matches local extensions to remote extension_versions deterministically (no interactive disambiguation). Rules applied in order:
- exact extension_id
- exact (name, type)
- single unmatched local of a type + single unmatched remote of that type → match
- any type with unmatched local(s) AND remaining unmatched remote(s) → validation error
A local whose type has zero unmatched remotes is NEW (Remote == nil).
type Partner ¶
type Partner struct {
ID flexStr `json:"id"`
// The Dashboard returns the partner's display name as "business_name" on both
// /partners and /info (the app name uses "name", but the partner name does
// not). Use the raw field the API actually sends, in and out.
BusinessName string `json:"business_name"`
}
type PartnersResp ¶
type PartnersResp struct {
Partners []Partner `json:"partners"`
}
type TemplateResp ¶
type UpsertResult ¶
UpsertResult is the outcome of an extension-upsert leg (theme today; reused by the function leg later). Mirrors v1 upsertTheme's resolved value {extension_id, extension_version, extension_version_id}.
func RegisterThemeExtension ¶
func RegisterThemeExtension(ctx context.Context, ext Extension, store *client.Client, pollInterval time.Duration, maxRetry int) (UpsertResult, *output.ExitError)
RegisterThemeExtension performs the no-connect registration segment shared by app deploy (theme leg) and te build/serve: PUT theme-extensions (create when ext.ExtensionID empty, else update with the existing id), POST version-tasks, poll to completion. It NEVER calls the connection endpoint — connect is an explicit step (deploy's create path, or `te connect`) so te can register without binding the extension to an app.
Version: an explicit ext.ExtensionVersion always wins (update bump OR a chosen create version, e.g. te build --version on a fresh extension); falls back to "1.0.0" only when none was supplied (app deploy's create path always sets "1.0.0", so its behavior is unchanged).
func UpsertFunction ¶
func UpsertFunction(ctx context.Context, ext Extension, partner *client.Client, entryJSPath, wasmPath string) (UpsertResult, *output.ExitError)
UpsertFunction ports v1 lib/app/services/extension-upsert/upsertFunction.js.
Create path (ext.ExtensionID == "" || ext.ExtensionVersion == ""): multipart POST functions/create with name, namespace, source_code (the CONTENT of entryJSPath), and the compiled wasm (wasmPath) as the file part.
Commit path (both set): multipart POST functions/commit, additionally sending function_id + version.
The request rides the partner client's headers (app-client-id + Access-Token) via DoRaw, which applies c.Headers last. On code "SUCCESS" (case-insensitive, matching v1 intent) we map data → UpsertResult; otherwise we error naming the extension.
type VersionItem ¶
type VersionsResp ¶
type VersionsResp struct {
Versions []VersionItem `json:"versions"`
HasMore bool `json:"has_more"`
}
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package project models an app project: its root, multi-toml configs, and the project-level .shoplazza/app-state.json that records the active config.
|
Package project models an app project: its root, multi-toml configs, and the project-level .shoplazza/app-state.json that records the active config. |
|
Package scaffold rewrites an already-cloned extension template into a finished extension.
|
Package scaffold rewrites an already-cloned extension template into a finished extension. |