app

package
v0.0.0-...-1d1a5ee Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 23, 2026 License: MIT Imports: 34 Imported by: 0

Documentation

Index

Constants

View Source
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

func MigrateV1Extension(root, dir, id, name, typ, version string) error

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

func WriteBackExtensionVersion(root, dir, id, version string) error

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 App

type App struct {
	ID       flexStr  `json:"id"` // internal numeric app id (apps-list endpoint)
	ClientID string   `json:"client_id"`
	Name     string   `json:"name"`
	Scopes   []string `json:"scopes"`
}

type AppConfig

type AppConfig struct {
	ClientID     string   `json:"client_id"`
	Name         string   `json:"name"`
	Scopes       []string `json:"scopes"`
	ClientSecret string   `json:"client_secret"` // endpoint 5 only — never persisted
	PartnerID    string   `json:"partner_id"`    // endpoint 5 only
}

type AppsResp

type AppsResp struct {
	Apps  []App `json:"apps"`
	Total int   `json:"total"`
}

type CompleteInfo

type CompleteInfo struct {
	User    map[string]any `json:"user"`
	Partner Partner        `json:"partner"`
	App     App            `json:"app"`
}

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 NewDashboard

func NewDashboard(c *client.Client, partnerToken string) *Dashboard

func (*Dashboard) CreateApp

func (d *Dashboard) CreateApp(ctx context.Context, partnerID, appName string) (App, error)

3 — v1 parity: createApp sends only app_name (no app_type).

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) GetApps

func (d *Dashboard) GetApps(ctx context.Context, partnerID string) (AppsResp, error)

2

func (*Dashboard) GetCompleteInfo

func (d *Dashboard) GetCompleteInfo(ctx context.Context, clientID string) (CompleteInfo, error)

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

func (d *Dashboard) GetTemplate(ctx context.Context, templateType string) (TemplateResp, error)

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 DeployResp struct {
	Version string `json:"version"`
	Name    string `json:"name"`
}

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 DeployedExt struct {
	ExtensionID string `json:"extension_id"`
	Name        string `json:"name"`
	Type        string `json:"type"`
	Version     string `json:"version"`
	VersionID   string `json:"version_id"`
	ResourceURL string `json:"resource_url"`
}

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 GenerateVersionResp struct {
	AppVersion string      `json:"app_version"`
	Extensions []Extension `json:"extensions"`
}

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

func ScanLocalExtensions(root string) ([]LocalExt, *output.ExitError)

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

type Pair struct {
	Local  LocalExt
	Remote *Extension
}

Pair maps a local extension to its remote counterpart. Remote is nil when the local extension is new (to be created).

func Diff

func Diff(locals []LocalExt, remotes []Extension) ([]Pair, *output.ExitError)

Diff matches local extensions to remote extension_versions deterministically (no interactive disambiguation). Rules applied in order:

  1. exact extension_id
  2. exact (name, type)
  3. single unmatched local of a type + single unmatched remote of that type → match
  4. 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 TemplateResp struct {
	TemplateType string `json:"template_type"`
	HTTPS        string `json:"https"`
}

type UpsertResult

type UpsertResult struct {
	ExtensionID        string
	ExtensionVersion   string
	ExtensionVersionID string
}

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 VersionItem struct {
	ID      int    `json:"id"`
	Version string `json:"version"`
}

type VersionsResp

type VersionsResp struct {
	Versions []VersionItem `json:"versions"`
	HasMore  bool          `json:"has_more"`
}

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL