@wix/himalaya-cli 0.916.0 → 0.918.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/ecom/src/extensions.d.ts +14 -4
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/members-feed-viewer/src/index.d.ts +3 -2
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-ecom.mjs +12 -2
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-members-feed-viewer.mjs +37 -6
- package/dist/cli.mjs +17 -4
- package/guides/releasing-with-himi-serve.md +33 -0
- package/guides/sdk-reference.json +9 -3
- package/package.json +2 -2
- package/templates/branded-lite/tier5-src/himi-branded-lite-forms/index.ts +32 -11
|
@@ -106,6 +106,39 @@ template with no current release, or a release with no bundles, is **refused** r
|
|
|
106
106
|
reported as a successful revocation — a kill-switch that silently switched nothing off is the
|
|
107
107
|
one failure mode worth being loud about.
|
|
108
108
|
|
|
109
|
+
### Attributing already-published apps (operator only)
|
|
110
|
+
|
|
111
|
+
Publishing a release and owning the slug are two different writes, and they have not always
|
|
112
|
+
coincided. A self-serve `himi push` claims the slug for you today, but an app published before
|
|
113
|
+
auto-claiming existed — or published by an operator or CI credential, whose scope already covers
|
|
114
|
+
the slug so it claims nothing on anyone's behalf — has a live release and **no owner**. That owner cannot
|
|
115
|
+
fix it themselves either: `claim` refuses a slug that is already published. `apps backfill` is
|
|
116
|
+
the operator door that binds those slugs to the account that should have them:
|
|
117
|
+
|
|
118
|
+
```
|
|
119
|
+
himi-serve apps backfill --subject <accountGuid> --target bo --control-token "$TOK"
|
|
120
|
+
himi-serve apps backfill --subject <accountGuid> --slugs todo,notes --apply --target bo --control-token "$TOK"
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
**It rehearses unless you pass `--apply`, and there is no undo.** The ownership store has no
|
|
124
|
+
delete and no transfer, and `claim` never reassigns — so a slug bound to the wrong subject stays
|
|
125
|
+
bound, and its real owner gets `slug_taken` from then on. The safe outcome is therefore the one
|
|
126
|
+
you get by forgetting a flag: without `--apply` the route reports exactly what it *would* claim
|
|
127
|
+
and writes nothing. Omitting `--slugs` means every app the release store lists (bounded
|
|
128
|
+
server-side); an empty or valueless `--slugs` is refused rather than read as "everything", and so
|
|
129
|
+
is any argument the command does not recognise — a stray word or a mistyped flag is usually a
|
|
130
|
+
`--slugs` that went missing, and missing is the spelling that means every app.
|
|
131
|
+
|
|
132
|
+
It bypasses exactly one refusal — `slug_published` — and keeps every other: reserved names
|
|
133
|
+
(`wix-*`, `himi-template-*`, the platform names) stay unclaimable, an app someone else already
|
|
134
|
+
owns is reported `slug_taken` rather than moved, and a slug nothing has published is reported
|
|
135
|
+
`not_published` rather than reserved. The backfilled claims do not spend the owner's own
|
|
136
|
+
self-serve quota.
|
|
137
|
+
|
|
138
|
+
The route (`POST /v1/admin/registry/backfill`) is gated on **scope, not role**: a self-serve or
|
|
139
|
+
narrower-than-`*` credential is refused `operator_only_backfill`, which means no cookie caller
|
|
140
|
+
can reach it. That is the difference between a backfill and a land-grab.
|
|
141
|
+
|
|
109
142
|
### Registry visibility (public | unlisted)
|
|
110
143
|
|
|
111
144
|
Whether `GET /v1/apps` (and so the **Himi Player picker**) lists the app is a per-app,
|
|
@@ -12881,7 +12881,7 @@
|
|
|
12881
12881
|
{
|
|
12882
12882
|
"name": "isEmptyCurrentCartError",
|
|
12883
12883
|
"kind": "function",
|
|
12884
|
-
"jsdoc": "/** True when a thrown bridge/HTTP error means \"no current cart yet\" — Wix's\n * empty-cart signal. `GET /ecom/v1/carts/current`
|
|
12884
|
+
"jsdoc": "/** True when a thrown bridge/HTTP error means \"no current cart yet\" — Wix's\n * empty-cart signal. `GET /ecom/v1/carts/current` answers 404\n * `OWNED_CART_NOT_FOUND` until the visitor/member adds an item (a current cart\n * is created on first add-to-cart), and Wix's own description calls that the\n * expected pre-first-add state.\n *\n * DELIBERATELY NARROW — it keys on the typed application error code, never on the\n * 404 status and no longer on the human message \"Cart not found\". A bare\n * 404 with no code, a 403, a 500, a transport failure and every other cart error\n * code stay real failures and still throw. */",
|
|
12885
12885
|
"decl": "export function isEmptyCurrentCartError(error: unknown): boolean;"
|
|
12886
12886
|
},
|
|
12887
12887
|
{
|
|
@@ -12893,7 +12893,7 @@
|
|
|
12893
12893
|
{
|
|
12894
12894
|
"name": "getCurrentCart",
|
|
12895
12895
|
"kind": "function",
|
|
12896
|
-
"jsdoc": "/** Read the current cart — GET /ecom/v1/carts/current. The API wraps the cart in\n * `{ cart: {...} }`; this unwraps it so callers get the cart (line items +\n * subtotal) directly. Verified live. An `OWNED_CART_NOT_FOUND` 404 is the empty\n * cart (never added an item) —
|
|
12896
|
+
"jsdoc": "/** Read the current cart — GET /ecom/v1/carts/current. The API wraps the cart in\n * `{ cart: {...} }`; this unwraps it so callers get the cart (line items +\n * subtotal) directly. Verified live. An `OWNED_CART_NOT_FOUND` 404 is the empty\n * cart (never added an item) — resolved as `{ lineItems: [] }`, not thrown, so a\n * signed-out visitor's first mount is a successful read of an empty cart.\n * `{ lineItems: [] }` is the shape every caller already handles: the badge readers\n * sum `(cart.lineItems ?? [])` and the cart screen additionally takes\n * `cart.subtotal?.formattedAmount ?? \"\"` and `(await getCurrentCart()).id || \"\"` —\n * all three degrade to empty/0 on an absent field, so no caller special-cases it. */",
|
|
12897
12897
|
"decl": "export async function getCurrentCart(): Promise<WixCartDetail>;",
|
|
12898
12898
|
"http": "GET /ecom/v1/carts/current"
|
|
12899
12899
|
},
|
|
@@ -18923,10 +18923,16 @@
|
|
|
18923
18923
|
"jsdoc": "",
|
|
18924
18924
|
"decl": "export interface MfvNotification {\n id?: string;\n type?: string;\n title?: string;\n message?: string;\n action?: string;\n sentDate?: string;\n /** UNSEEN | SEEN_BUT_UNREAD | READ | … */\n status?: string;\n deeplink?: MfvLink;\n icon?: MfvIcon;\n memberId?: string;\n}"
|
|
18925
18925
|
},
|
|
18926
|
+
{
|
|
18927
|
+
"name": "__resetMfvRouteForTests",
|
|
18928
|
+
"kind": "function",
|
|
18929
|
+
"jsdoc": "/** Reset the learned route. Tests only — there is no runtime caller. */",
|
|
18930
|
+
"decl": "export function __resetMfvRouteForTests(): void;"
|
|
18931
|
+
},
|
|
18926
18932
|
{
|
|
18927
18933
|
"name": "queryNotifications",
|
|
18928
18934
|
"kind": "function",
|
|
18929
|
-
"jsdoc": "/** List the signed-in member's notifications, newest first. MOBILE channel
|
|
18935
|
+
"jsdoc": "/** List the signed-in member's notifications, newest first. MOBILE channel. */",
|
|
18930
18936
|
"decl": "export async function queryNotifications(limit = 25): Promise<MfvNotification[]>;"
|
|
18931
18937
|
},
|
|
18932
18938
|
{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wix/himalaya-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.918.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "himi — the Himalaya authoring CLI. Build a Tier-5 content package on your own machine, push it to the production serve as a draft, preview it on web + Himi Player, and read worker logs back — the remote dev loop. Wraps @himalaya/serve-cli for transport.",
|
|
6
6
|
"keywords": [
|
|
@@ -64,5 +64,5 @@
|
|
|
64
64
|
"artifactId": "himalaya-cli"
|
|
65
65
|
}
|
|
66
66
|
},
|
|
67
|
-
"falconPackageHash": "
|
|
67
|
+
"falconPackageHash": "c1b47ea3d5bf730113aa0b5871851e82b129947c5c28bdd9911344a1"
|
|
68
68
|
}
|
|
@@ -1,21 +1,29 @@
|
|
|
1
1
|
// himi-branded-lite-forms — branded-lite "Forms" worker with a SAMPLE-DATA fallback
|
|
2
2
|
// (same pattern as the other branded-lite verticals). Reads the site's real Wix Forms
|
|
3
|
-
// (queryForms
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
3
|
+
// (queryForms, which REQUIRES a namespace filter — a bare query is answered
|
|
4
|
+
// `400 MISSING_NAMESPACE_FILTER` for every site, #2769); when the site has no Forms
|
|
5
|
+
// app (403/404/428) or has no forms, it substitutes representative SAMPLE forms and
|
|
6
|
+
// flags `isMock`, so the screen shows a clearly-labelled "Sample content" banner.
|
|
7
|
+
// A query that FAILS for any other reason is reported as a failure — sample content
|
|
8
|
+
// there would state "this site has none yet", which the app never established.
|
|
9
|
+
// Real forms always win the moment any exist.
|
|
7
10
|
import { defineService } from "../_shared/collection-service.js";
|
|
11
|
+
import { copyParam } from "../_shared/himi-copy.js";
|
|
8
12
|
import { queryForms, type WixForm } from "@himalaya/wix-forms";
|
|
9
13
|
|
|
10
14
|
interface Row { id: string; title: string; subtitle: string }
|
|
11
15
|
interface S {
|
|
12
|
-
status: "loading" | "ready";
|
|
16
|
+
status: "loading" | "ready" | "error";
|
|
13
17
|
forms: Row[]; emptyText: string;
|
|
14
18
|
isMock: boolean; notMock: boolean; mockBanner: string;
|
|
15
19
|
}
|
|
16
20
|
|
|
17
21
|
const STATUS_LABEL: Record<string, string> = { PUBLISHED: "Published", DRAFT: "Draft" };
|
|
18
22
|
|
|
23
|
+
/** The Wix Forms namespace the Forms app owns. Booking/event registration forms live
|
|
24
|
+
* in their own namespaces and are not this screen's subject. */
|
|
25
|
+
const FORMS_NAMESPACE = "wix.form_app.form";
|
|
26
|
+
|
|
19
27
|
function norm(f: WixForm): Row {
|
|
20
28
|
const title = (f.name || "Form").trim() || "Form";
|
|
21
29
|
const fieldCount = Array.isArray(f.formFields) ? f.formFields.length : 0;
|
|
@@ -24,15 +32,25 @@ function norm(f: WixForm): Row {
|
|
|
24
32
|
return { id: f.id || "form", title, subtitle: parts.join(" · ") };
|
|
25
33
|
}
|
|
26
34
|
|
|
27
|
-
|
|
35
|
+
/** Two banners, because the sample rows answer two different questions and only one of
|
|
36
|
+
* them is a statement about the customer's site.
|
|
37
|
+
*
|
|
38
|
+
* There used to be one banner, "this site has no forms yet", shown whenever the rows were
|
|
39
|
+
* samples — including when the query had FAILED. A site WITH forms got told it had none.
|
|
40
|
+
* The sample fallback itself is deliberate and covered by the renderer's offline test,
|
|
41
|
+
* which keeps these verticals showing labelled sample content rather than a blank screen.
|
|
42
|
+
* So what was wrong was never the samples — it was claiming to know something the app had
|
|
43
|
+
* not established. */
|
|
44
|
+
const MOCK_BANNER_EMPTY = "Sample content — this site has no forms yet.";
|
|
45
|
+
const MOCK_BANNER_UNAVAILABLE = "Sample content — this site's forms couldn't be loaded.";
|
|
28
46
|
const MOCK: Row[] = [
|
|
29
47
|
{ id: "sample-contact", title: "Contact Us", subtitle: "4 fields · Published" },
|
|
30
48
|
{ id: "sample-subscribe", title: "Newsletter Signup", subtitle: "2 fields · Published" },
|
|
31
49
|
{ id: "sample-feedback", title: "Feedback", subtitle: "5 fields · Published" },
|
|
32
50
|
];
|
|
33
51
|
|
|
34
|
-
function mockState(s: S): S {
|
|
35
|
-
return { ...s, status: "ready", forms: MOCK.map((r) => ({ ...r })), emptyText: "", isMock: true, notMock: false, mockBanner:
|
|
52
|
+
function mockState(s: S, banner: string): S {
|
|
53
|
+
return { ...s, status: "ready", forms: MOCK.map((r) => ({ ...r })), emptyText: "", isMock: true, notMock: false, mockBanner: banner };
|
|
36
54
|
}
|
|
37
55
|
|
|
38
56
|
const svc = defineService<S, { load: (s: S) => Promise<S> }>("himiBrandedForms", {
|
|
@@ -40,12 +58,15 @@ const svc = defineService<S, { load: (s: S) => Promise<S> }>("himiBrandedForms",
|
|
|
40
58
|
actions: {
|
|
41
59
|
load: async (s) => {
|
|
42
60
|
try {
|
|
43
|
-
const r = await queryForms({ paging: { limit: 50 } });
|
|
61
|
+
const r = await queryForms({ filter: { namespace: FORMS_NAMESPACE }, paging: { limit: 50 } });
|
|
44
62
|
const forms = (r.forms ?? []).map(norm);
|
|
45
63
|
if (forms.length) return { ...s, status: "ready", forms, emptyText: "", isMock: false, notMock: true, mockBanner: "" };
|
|
46
|
-
return mockState(s);
|
|
64
|
+
return mockState(s, copyParam("forms.emptyBanner", MOCK_BANNER_EMPTY));
|
|
47
65
|
} catch {
|
|
48
|
-
|
|
66
|
+
// The samples still render — a blank screen helps nobody and this vertical
|
|
67
|
+
// deliberately ships a fallback — but the banner no longer asserts the site is
|
|
68
|
+
// empty, because a failed query established no such thing.
|
|
69
|
+
return mockState(s, copyParam("forms.unavailableBanner", MOCK_BANNER_UNAVAILABLE));
|
|
49
70
|
}
|
|
50
71
|
},
|
|
51
72
|
},
|