@wix/himalaya-cli 0.1151.0 → 0.1153.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/bookings-v2/src/extensions.d.ts +33 -2
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/wix-data/src/index.d.ts +14 -2
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-bookings.mjs +71 -5
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-data.mjs +3 -2
- package/dist/cli.mjs +141 -29
- package/dist/preview-assets.generated.bin +0 -0
- package/guides/cms-collections.md +4 -1
- package/guides/getting-started.md +3 -1
- package/guides/himi-skill.md +3 -1
- package/guides/owner-lite-site-binding.md +26 -0
- package/guides/push-cli-onboarding.md +42 -1
- package/guides/sdk-reference.json +23 -11
- package/guides/templates.md +1 -0
- package/guides/wix-api-usage.md +5 -1
- package/package.json +2 -2
- package/templates/beauty-salon/dev/index.ts +6 -3
- package/templates/community-club/dev/index.ts +6 -3
- package/templates/community-club-owner/dev/index.ts +44 -1
- package/templates/community-club-owner/tier5-src/himi-owner-lite-event/index.ts +52 -8
- package/templates/events-meetups/dev/index.ts +6 -3
- package/templates/events-meetups-owner/dev/index.ts +44 -1
- package/templates/events-meetups-owner/tier5-src/himi-owner-lite-event/index.ts +52 -8
- package/templates/gym-fitness/dev/index.ts +6 -3
- package/templates/gym-fitness-owner/dev/index.ts +44 -1
- package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-event/index.ts +52 -8
- package/templates/nonprofit-cause/dev/index.ts +6 -3
- package/templates/nonprofit-cause-owner/dev/index.ts +44 -1
- package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-event/index.ts +52 -8
- package/templates/owner-lite/dev/index.ts +44 -1
- package/templates/owner-lite/tier5-src/himi-owner-lite-event/index.ts +52 -8
- package/templates/podcast-creator/dev/index.ts +6 -3
- package/templates/podcast-creator-owner/dev/index.ts +44 -1
- package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-event/index.ts +52 -8
- package/templates/portfolio-creative/dev/index.ts +6 -3
- package/templates/restaurant/dev/index.ts +6 -3
- package/templates/restaurant-owner/dev/index.ts +44 -1
- package/templates/restaurant-owner/tier5-src/himi-owner-lite-event/index.ts +52 -8
- package/templates/retail-store/dev/index.ts +7 -3
- package/templates/yoga-studio/dev/index.ts +6 -3
- package/templates/yoga-studio-owner/dev/index.ts +44 -1
- package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-event/index.ts +52 -8
|
@@ -21,6 +21,8 @@ export interface WixServicePayment {
|
|
|
21
21
|
}
|
|
22
22
|
export interface WixService {
|
|
23
23
|
id: string;
|
|
24
|
+
/** Optimistic-concurrency token — required by every v2 write (updateService reads it if omitted). */
|
|
25
|
+
revision?: string;
|
|
24
26
|
name?: string;
|
|
25
27
|
description?: string;
|
|
26
28
|
/** Session length in minutes. */
|
|
@@ -84,13 +86,34 @@ export interface WixGetServiceResponse {
|
|
|
84
86
|
/** Fetch a single service by id — GET /bookings/v2/services/{id}. Visitor-readable
|
|
85
87
|
* (public GA route, not the owner `_api/` gateway). The detail/edit-prefill source. */
|
|
86
88
|
export declare function getService(serviceId: string): Promise<WixGetServiceResponse>;
|
|
87
|
-
/** Writable service fields for create/update
|
|
89
|
+
/** Writable service fields for create/update, in the ERGONOMIC shape a form has. The helpers
|
|
90
|
+
* below encode it onto the Bookings v2 entity — they never send these flat fields as-is,
|
|
91
|
+
* because v2 silently ignores a flat `price`/`duration` (HTTP 200, revision bumps, nothing
|
|
92
|
+
* written: #3605, verified live 2026-10-01).
|
|
93
|
+
* - `price` → `payment.fixed.price.{value,currency}` (value is a decimal STRING), merged
|
|
94
|
+
* with the service's existing `payment` so `payment.options` (mandatory: online and/or
|
|
95
|
+
* in-person) and the currency carry over.
|
|
96
|
+
* - `duration` (minutes) → `schedule.availabilityConstraints.{durations,sessionDurations}`. */
|
|
88
97
|
export interface WixServiceWrite {
|
|
89
98
|
name?: string;
|
|
90
99
|
description?: string;
|
|
91
100
|
/** Session length in minutes; required, > 0 on create. */
|
|
92
101
|
duration?: number;
|
|
102
|
+
/** Fixed price as a number (e.g. 120). Encoded onto `payment.fixed.price`. */
|
|
93
103
|
price?: number;
|
|
104
|
+
/** ISO currency for `price`. Defaults to the service's current currency on update; required on create with a price. */
|
|
105
|
+
currency?: string;
|
|
106
|
+
/** Resource ids of the staff who deliver it (`queryStaff()` → `id`). An APPOINTMENT service needs ≥1;
|
|
107
|
+
* createService defaults to the site's `defaultStaff` resource when omitted. NOT staff-member ids. */
|
|
108
|
+
staffMemberIds?: string[];
|
|
109
|
+
/** v2 service type; createService defaults to "APPOINTMENT". */
|
|
110
|
+
type?: "APPOINTMENT" | "CLASS" | "COURSE";
|
|
111
|
+
onlineBooking?: {
|
|
112
|
+
enabled?: boolean;
|
|
113
|
+
};
|
|
114
|
+
/** Raw v2 payment object — wins over `price`/`currency` when both are given. */
|
|
115
|
+
payment?: Record<string, unknown>;
|
|
116
|
+
/** @deprecated v1 only — v2 services carry `category.id`; ignored by create/update. */
|
|
94
117
|
category?: string;
|
|
95
118
|
}
|
|
96
119
|
export interface WixCreateServiceRequest {
|
|
@@ -107,7 +130,15 @@ export interface WixUpdateServiceResponse {
|
|
|
107
130
|
}
|
|
108
131
|
export interface WixDeleteServiceResponse {
|
|
109
132
|
}
|
|
110
|
-
/**
|
|
133
|
+
/** Encode an ergonomic write onto the v2 service entity. `existing` (the current service, for
|
|
134
|
+
* an update) supplies the currency and `payment.options` a price change must carry. Exported
|
|
135
|
+
* for tests; throws instead of sending a payload Bookings would accept and then ignore. */
|
|
136
|
+
export declare function encodeServiceWrite(write: WixServiceWrite, existing?: WixService): Record<string, unknown>;
|
|
137
|
+
/** Create a service — POST /bookings/v2/services (the v1 route 400s for a v2 site: #3605).
|
|
138
|
+
* `name` + `duration` are required. Defaults: type APPOINTMENT, online booking on, and the
|
|
139
|
+
* site's `defaultStaff` resource when `staffMemberIds` is omitted (an APPOINTMENT service
|
|
140
|
+
* 400s MISSING_APPOINTMENT_RESOURCES without one), and — for a `price` without `currency` —
|
|
141
|
+
* the currency the site's existing services use. */
|
|
111
142
|
export declare function createService(service: WixServiceWrite): Promise<WixCreateServiceResponse>;
|
|
112
143
|
export declare function updateService(serviceId: string, service: WixServiceWrite & {
|
|
113
144
|
revision?: string;
|
|
@@ -15,12 +15,17 @@ export interface WixDataPaging {
|
|
|
15
15
|
limit?: number;
|
|
16
16
|
offset?: number;
|
|
17
17
|
}
|
|
18
|
+
/** One sort key — `{ fieldName: "postedDate", order: "DESC" }`. Keys apply in array order. */
|
|
19
|
+
export interface WixDataSort {
|
|
20
|
+
fieldName: string;
|
|
21
|
+
order?: "ASC" | "DESC";
|
|
22
|
+
}
|
|
18
23
|
export interface WixQueryDataItemsRequest {
|
|
19
24
|
dataCollectionId: string;
|
|
20
25
|
query?: {
|
|
21
26
|
filter?: Record<string, unknown>;
|
|
22
27
|
paging?: WixDataPaging;
|
|
23
|
-
sort?:
|
|
28
|
+
sort?: WixDataSort[];
|
|
24
29
|
};
|
|
25
30
|
/** When true, `pagingMetadata.total` is filled (offset paging only). Default omitted. */
|
|
26
31
|
returnTotalCount?: boolean;
|
|
@@ -72,9 +77,16 @@ export interface WixUpdateDataItemRequest {
|
|
|
72
77
|
* const rest = await queryDataItems("Listings", 50, undefined, false, 50);
|
|
73
78
|
* ```
|
|
74
79
|
*
|
|
80
|
+
* `sort` orders server-side, so "newest first" is one call instead of fetching every row
|
|
81
|
+
* and sorting on the client (which also breaks once the collection outgrows `limit`):
|
|
82
|
+
*
|
|
83
|
+
* ```ts
|
|
84
|
+
* await queryDataItems("Jobs", 1, undefined, false, 0, [{ fieldName: "postedDate", order: "DESC" }]);
|
|
85
|
+
* ```
|
|
86
|
+
*
|
|
75
87
|
* To read ONE row, use `getDataItem` rather than a page plus a client-side
|
|
76
88
|
* `.find()` — that pattern breaks silently once the collection outgrows `limit`. */
|
|
77
|
-
export declare function queryDataItems(dataCollectionId: string, limit?: number, filter?: Record<string, unknown>, returnTotalCount?: boolean, offset?: number): Promise<WixQueryDataItemsResponse>;
|
|
89
|
+
export declare function queryDataItems(dataCollectionId: string, limit?: number, filter?: Record<string, unknown>, returnTotalCount?: boolean, offset?: number, sort?: WixDataSort[]): Promise<WixQueryDataItemsResponse>;
|
|
78
90
|
/** Read ONE item by id — GET /wix-data/v2/items/{dataItemId}?dataCollectionId=…
|
|
79
91
|
*
|
|
80
92
|
* Reach for this on a detail screen. Before it existed the only read this package
|
|
@@ -23,12 +23,77 @@ async function getService(serviceId) {
|
|
|
23
23
|
const res = await wixCall("GET", `/bookings/v2/services/${serviceId}`);
|
|
24
24
|
return { ...res, service: decodeService(res.service) };
|
|
25
25
|
}
|
|
26
|
-
function
|
|
27
|
-
|
|
26
|
+
function encodeServiceWrite(write, existing) {
|
|
27
|
+
const { price, currency, duration, payment, category: _legacyCategory, ...rest } = write;
|
|
28
|
+
const out = { ...rest };
|
|
29
|
+
if (payment) {
|
|
30
|
+
out.payment = payment;
|
|
31
|
+
} else if (price != null) {
|
|
32
|
+
if (!Number.isFinite(price) || price < 0) throw new Error(`Bookings service price must be a number >= 0 (got ${price})`);
|
|
33
|
+
const prior = existing?.payment ?? {};
|
|
34
|
+
const cur = currency ?? prior.fixed?.price?.currency ?? prior.rateLabel?.currency ?? prior.currency;
|
|
35
|
+
if (!cur) throw new Error('Bookings service price needs a currency: pass `currency` (e.g. "USD") \u2014 the service has none to inherit');
|
|
36
|
+
const options = prior.options;
|
|
37
|
+
const { price: _flat, currency: _flatCur, ...priorRest } = prior;
|
|
38
|
+
out.payment = {
|
|
39
|
+
...priorRest,
|
|
40
|
+
rateType: "FIXED",
|
|
41
|
+
fixed: { price: { value: String(price), currency: cur } },
|
|
42
|
+
// v2 400s unless online or inPerson is true; keep the service's own choice when it has one.
|
|
43
|
+
options: options && (options.online || options.inPerson) ? options : { ...options ?? {}, online: true }
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
if (duration != null) {
|
|
47
|
+
if (!Number.isFinite(duration) || duration <= 0) throw new Error(`Bookings service duration must be > 0 minutes (got ${duration})`);
|
|
48
|
+
out.schedule = { availabilityConstraints: { durations: [{ minutes: duration }], sessionDurations: [duration] } };
|
|
49
|
+
}
|
|
50
|
+
return out;
|
|
51
|
+
}
|
|
52
|
+
async function siteServiceCurrency() {
|
|
53
|
+
const PAGE = 100;
|
|
54
|
+
for (let offset = 0; offset < 500; offset += PAGE) {
|
|
55
|
+
const { services, pagingMetadata } = await queryServices({ query: { paging: { limit: PAGE, offset } } });
|
|
56
|
+
for (const x of services) {
|
|
57
|
+
const p = x.payment;
|
|
58
|
+
const cur = p?.fixed?.price?.currency ?? p?.varied?.defaultPrice?.currency ?? p?.varied?.minPrice?.currency ?? p?.varied?.maxPrice?.currency ?? p?.subscription?.amountPerBillingCycle?.currency ?? p?.subscription?.enrollmentFeeAmount?.currency ?? p?.rateLabel?.currency ?? p?.currency;
|
|
59
|
+
if (cur) return cur;
|
|
60
|
+
}
|
|
61
|
+
const meta = pagingMetadata;
|
|
62
|
+
if (services.length < PAGE || meta?.hasNext === false) return void 0;
|
|
63
|
+
}
|
|
64
|
+
return void 0;
|
|
28
65
|
}
|
|
29
|
-
function
|
|
66
|
+
async function createService(service) {
|
|
67
|
+
if (!service?.name) throw new Error("createService: name is required");
|
|
68
|
+
if (service.duration == null) throw new Error("createService: duration (minutes) is required");
|
|
69
|
+
const type = service.type ?? "APPOINTMENT";
|
|
70
|
+
let staffMemberIds = service.staffMemberIds;
|
|
71
|
+
if (type === "APPOINTMENT" && !staffMemberIds?.length) {
|
|
72
|
+
const staff = await queryStaff();
|
|
73
|
+
const pick = staff.find((r) => (r.tags ?? []).includes("defaultStaff")) ?? staff[0];
|
|
74
|
+
if (!pick?.id) throw new Error("createService: an APPOINTMENT service needs a staff member, and this site has none \u2014 add staff in Bookings, or pass staffMemberIds (resource ids from queryStaff())");
|
|
75
|
+
staffMemberIds = [pick.id];
|
|
76
|
+
}
|
|
77
|
+
let currency = service.currency;
|
|
78
|
+
if (service.price != null && !service.payment && !currency) currency = await siteServiceCurrency();
|
|
79
|
+
const body = encodeServiceWrite({ ...service, type, ...currency ? { currency } : {}, ...staffMemberIds ? { staffMemberIds } : {} });
|
|
80
|
+
if (!body.payment) body.payment = { rateType: "NO_FEE", options: { inPerson: true } };
|
|
81
|
+
const res = await wixCall("POST", "/bookings/v2/services", {
|
|
82
|
+
service: { onlineBooking: { enabled: true }, ...body }
|
|
83
|
+
});
|
|
84
|
+
return { ...res, service: decodeService(res.service) };
|
|
85
|
+
}
|
|
86
|
+
async function updateService(serviceId, service) {
|
|
30
87
|
if (!serviceId) throw new Error("updateService: serviceId is required");
|
|
31
|
-
|
|
88
|
+
const { revision, ...write } = service;
|
|
89
|
+
const needsCurrent = !revision || write.price != null && !write.payment;
|
|
90
|
+
const current = needsCurrent ? (await getService(serviceId)).service : void 0;
|
|
91
|
+
const rev = revision ?? current?.revision;
|
|
92
|
+
if (!rev) throw new Error(`updateService: could not read the current revision of service ${serviceId}`);
|
|
93
|
+
const res = await wixCall("PATCH", `/bookings/v2/services/${serviceId}`, {
|
|
94
|
+
service: { id: serviceId, revision: rev, ...encodeServiceWrite(write, current) }
|
|
95
|
+
});
|
|
96
|
+
return { ...res, service: decodeService(res.service) };
|
|
32
97
|
}
|
|
33
98
|
function confirmBooking(bookingId, revision) {
|
|
34
99
|
if (!bookingId || !revision) throw new Error("confirmBooking: bookingId + revision required");
|
|
@@ -198,7 +263,7 @@ function deleteService(serviceId, opts) {
|
|
|
198
263
|
if (!serviceId) throw new Error("deleteService: serviceId is required");
|
|
199
264
|
return wixCall(
|
|
200
265
|
"DELETE",
|
|
201
|
-
`/bookings/
|
|
266
|
+
`/bookings/v2/services/${encodeURIComponent(serviceId)}`,
|
|
202
267
|
void 0,
|
|
203
268
|
opts
|
|
204
269
|
);
|
|
@@ -213,6 +278,7 @@ export {
|
|
|
213
278
|
declineBooking,
|
|
214
279
|
decodeService,
|
|
215
280
|
deleteService,
|
|
281
|
+
encodeServiceWrite,
|
|
216
282
|
getBooking,
|
|
217
283
|
getBookingFull,
|
|
218
284
|
getService,
|
|
@@ -7,7 +7,7 @@ function wixCall(method, path, body) {
|
|
|
7
7
|
}
|
|
8
8
|
return globalThis.himalaya.wixApi.call(method, path, body);
|
|
9
9
|
}
|
|
10
|
-
function queryDataItems(dataCollectionId, limit = 50, filter, returnTotalCount = false, offset = 0) {
|
|
10
|
+
function queryDataItems(dataCollectionId, limit = 50, filter, returnTotalCount = false, offset = 0, sort) {
|
|
11
11
|
if (!dataCollectionId) throw new Error("queryDataItems: dataCollectionId is required");
|
|
12
12
|
const body = {
|
|
13
13
|
dataCollectionId,
|
|
@@ -15,7 +15,8 @@ function queryDataItems(dataCollectionId, limit = 50, filter, returnTotalCount =
|
|
|
15
15
|
// `offset` is omitted when 0 so a caller that does not page sends the byte-identical
|
|
16
16
|
// body it sent before this parameter existed.
|
|
17
17
|
paging: { limit, ...offset ? { offset } : {} },
|
|
18
|
-
...filter ? { filter } : {}
|
|
18
|
+
...filter ? { filter } : {},
|
|
19
|
+
...sort?.length ? { sort: sort.map((k) => ({ fieldName: k.fieldName, order: k.order ?? "ASC" })) } : {}
|
|
19
20
|
},
|
|
20
21
|
...returnTotalCount ? { returnTotalCount: true } : {}
|
|
21
22
|
};
|
package/dist/cli.mjs
CHANGED
|
@@ -6765,7 +6765,6 @@ function walk(node, registry, path2, out2) {
|
|
|
6765
6765
|
var SYSTEM_FONT_FAMILY, TONAL_ELEVATION_ALPHA;
|
|
6766
6766
|
var init_tokens = __esm({
|
|
6767
6767
|
"../../core/ts/src/tokens.ts"() {
|
|
6768
|
-
"use strict";
|
|
6769
6768
|
SYSTEM_FONT_FAMILY = "$system";
|
|
6770
6769
|
TONAL_ELEVATION_ALPHA = [0, 0.05, 0.08, 0.11, 0.12, 0.14];
|
|
6771
6770
|
}
|
|
@@ -6792,7 +6791,6 @@ function resolveTranslation(value, catalog, sourceCatalog) {
|
|
|
6792
6791
|
var BINDING_SIGILS, BINDING_SIGIL_RE, TRANSLATION_PREFIX, TRANSLATION_KEY_RE;
|
|
6793
6792
|
var init_bindings = __esm({
|
|
6794
6793
|
"../../core/ts/src/bindings.ts"() {
|
|
6795
|
-
"use strict";
|
|
6796
6794
|
BINDING_SIGILS = ["service", "flag", "item", "t"];
|
|
6797
6795
|
BINDING_SIGIL_RE = new RegExp(
|
|
6798
6796
|
`^\\$(?:${BINDING_SIGILS.filter((s) => s !== "t").join("|")})\\b|^\\$t:`
|
|
@@ -21566,6 +21564,7 @@ __export(template_adopt_exports, {
|
|
|
21566
21564
|
ADOPT_MATRIX_FILE: () => ADOPT_MATRIX_FILE,
|
|
21567
21565
|
JOURNEY_STUB_FILE: () => JOURNEY_STUB_FILE,
|
|
21568
21566
|
PLACEHOLDER: () => PLACEHOLDER,
|
|
21567
|
+
addScreen: () => addScreen,
|
|
21569
21568
|
classifyAction: () => classifyAction,
|
|
21570
21569
|
copyHits: () => copyHits,
|
|
21571
21570
|
droppedPrimaryTaps: () => droppedPrimaryTaps,
|
|
@@ -21832,7 +21831,7 @@ function inventorySummary(inv) {
|
|
|
21832
21831
|
function writeNoFollow(dir, rel, body) {
|
|
21833
21832
|
const p = join34(dir, rel);
|
|
21834
21833
|
const parent = dirname20(p);
|
|
21835
|
-
if (existsSync32(parent) && lstatSync8(parent).isSymbolicLink()) throw new TrimRefusal(`${dirname20(rel)} is a symlink \u2014 refusing to write
|
|
21834
|
+
if (existsSync32(parent) && lstatSync8(parent).isSymbolicLink()) throw new TrimRefusal(`${dirname20(rel)} is a symlink \u2014 refusing to write through it`);
|
|
21836
21835
|
mkdirSync17(parent, { recursive: true });
|
|
21837
21836
|
if (existsSync32(p) && lstatSync8(p).isSymbolicLink()) throw new TrimRefusal(`${rel} is a symlink \u2014 refusing to write through it`);
|
|
21838
21837
|
const tmp = `${p}.${process.pid}.${Date.now()}.tmp`;
|
|
@@ -21890,6 +21889,50 @@ function replaceScreen(dir, id, entry) {
|
|
|
21890
21889
|
writeFileSync15(devPath, next);
|
|
21891
21890
|
return { ok: true, screen: id, bytes: body.length };
|
|
21892
21891
|
}
|
|
21892
|
+
function addScreen(dir, id, entry, opts = {}) {
|
|
21893
|
+
if (!/^[a-z][a-z0-9_]*$/.test(id)) throw new TrimRefusal(`screen id "${id}" must be lowercase snake_case (e.g. himi_sellers)`);
|
|
21894
|
+
const src = readAppSources(dir);
|
|
21895
|
+
const app = parseApp(src.dev, src.config);
|
|
21896
|
+
if (findRoute(app, id)) throw new TrimRefusal(`"${id}" already exists \u2014 edit it with \`himi template screens --write ${id} --from <file>\``);
|
|
21897
|
+
const lastScreen = [...app.routes].filter((r) => r.kind === "screen").sort((a, b) => b.end - a.end)[0];
|
|
21898
|
+
if (!lastScreen) throw new TrimRefusal("dev/index.ts has no SCREENS entry to add after");
|
|
21899
|
+
const body = entry.endsWith("\n") ? entry : `${entry}
|
|
21900
|
+
`;
|
|
21901
|
+
const dev = src.dev.slice(0, lastScreen.end) + body + src.dev.slice(lastScreen.end);
|
|
21902
|
+
const nav = JSON.parse(JSON.stringify(app.nav.value));
|
|
21903
|
+
if (nav.linkableScreens === void 0) nav.linkableScreens = [];
|
|
21904
|
+
if (!Array.isArray(nav.linkableScreens)) throw new TrimRefusal("config.ts navigation.linkableScreens is not an array \u2014 fix it before adding a screen");
|
|
21905
|
+
const linkable = true;
|
|
21906
|
+
if (!nav.linkableScreens.some((l) => l.screenId === id)) {
|
|
21907
|
+
const parent = opts.parent ? nav.linkableScreens.find((l) => l.screenId === opts.parent) : void 0;
|
|
21908
|
+
if (opts.parent && !parent) throw new TrimRefusal(`--parent "${opts.parent}" is not a linkable screen in config.ts`);
|
|
21909
|
+
nav.linkableScreens.push({
|
|
21910
|
+
screenId: id,
|
|
21911
|
+
isPublic: true,
|
|
21912
|
+
paramKeys: [],
|
|
21913
|
+
parentScreenId: opts.parent ?? "",
|
|
21914
|
+
tabId: opts.tab ?? (typeof parent?.tabId === "string" ? parent.tabId : "")
|
|
21915
|
+
});
|
|
21916
|
+
}
|
|
21917
|
+
const config = linkable ? src.config.slice(0, app.nav.open) + serializeAt(nav, src.config, app.nav.open) + src.config.slice(app.nav.close) : src.config;
|
|
21918
|
+
let reparsed;
|
|
21919
|
+
try {
|
|
21920
|
+
reparsed = parseApp(dev, config);
|
|
21921
|
+
} catch (e) {
|
|
21922
|
+
throw new TrimRefusal(`the new entry does not parse, nothing written: ${e.message}`);
|
|
21923
|
+
}
|
|
21924
|
+
if (!findRoute(reparsed, id)) throw new TrimRefusal(`the entry does not declare "${id}" (its key and "screenId" must both be "${id}") \u2014 nothing written`);
|
|
21925
|
+
if (reparsed.routes.length !== app.routes.length + 1) {
|
|
21926
|
+
throw new TrimRefusal(`the entry changed the screen count by ${reparsed.routes.length - app.routes.length}, not 1; add one screen at a time \u2014 nothing written`);
|
|
21927
|
+
}
|
|
21928
|
+
for (const rel of ["dev", "dev/index.ts", ...config !== src.config ? ["config.ts"] : []]) {
|
|
21929
|
+
const p = join34(dir, rel);
|
|
21930
|
+
if (existsSync32(p) && lstatSync8(p).isSymbolicLink()) throw new TrimRefusal(`${rel} is a symlink \u2014 refusing to write through it (nothing written)`);
|
|
21931
|
+
}
|
|
21932
|
+
writeNoFollow(dir, "dev/index.ts", dev);
|
|
21933
|
+
if (config !== src.config) writeNoFollow(dir, "config.ts", config);
|
|
21934
|
+
return { ok: true, screen: id, bytes: body.length, linkable };
|
|
21935
|
+
}
|
|
21893
21936
|
function specScreens(spec) {
|
|
21894
21937
|
const body = spec.replace(/<!--[\s\S]*?(?:-->|$)/g, "");
|
|
21895
21938
|
const m = /(?:^|\n)##[ \t]+Screen catalogue\b[^\n]*([\s\S]*?)(?=\n##[ \t]|$)/i.exec(body);
|
|
@@ -21930,6 +21973,7 @@ var ADOPT_FILE, JOURNEY_STUB_FILE, ADOPT_MATRIX_FILE, WIX_MODULE, RELATIVE_IMPOR
|
|
|
21930
21973
|
var init_template_adopt = __esm({
|
|
21931
21974
|
"src/template-adopt.ts"() {
|
|
21932
21975
|
init_template_trim();
|
|
21976
|
+
init_home_descriptor();
|
|
21933
21977
|
ADOPT_FILE = ".himi/adopt.json";
|
|
21934
21978
|
JOURNEY_STUB_FILE = ".himi/himi-test.stub.json";
|
|
21935
21979
|
ADOPT_MATRIX_FILE = ".himi/adopt-matrix.md";
|
|
@@ -23592,7 +23636,6 @@ function shouldRevealError(args) {
|
|
|
23592
23636
|
var EMAIL_RE, URL_HOST_RE, INTEGER_RE, NUMBER_RE;
|
|
23593
23637
|
var init_validation = __esm({
|
|
23594
23638
|
"../../core/ts/src/validation.ts"() {
|
|
23595
|
-
"use strict";
|
|
23596
23639
|
EMAIL_RE = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9-]+(\.[A-Za-z0-9-]+)*\.[A-Za-z]{2,}$/;
|
|
23597
23640
|
URL_HOST_RE = /^[A-Za-z0-9-]+(\.[A-Za-z0-9-]+)*(:[0-9]+)?$/;
|
|
23598
23641
|
INTEGER_RE = /^[+-]?[0-9]+$/;
|
|
@@ -24252,7 +24295,6 @@ function validateWorkerSubtree(raw, slotId, policy, actionCatalog) {
|
|
|
24252
24295
|
var WORKER_SUBTREE_CEILINGS, ID_PART, ACTION_ID;
|
|
24253
24296
|
var init_worker_subtree = __esm({
|
|
24254
24297
|
"../../core/ts/src/worker-subtree.ts"() {
|
|
24255
|
-
"use strict";
|
|
24256
24298
|
init_worker_subtree_safe_types_generated();
|
|
24257
24299
|
WORKER_SUBTREE_CEILINGS = Object.freeze({
|
|
24258
24300
|
maxEncodedBytes: 65536,
|
|
@@ -38729,7 +38771,6 @@ var init_wix_cache_defaults_generated = __esm({
|
|
|
38729
38771
|
{ method: "DELETE", pathPrefix: "/blog/v3/posts", cacheable: false, ttl: 0, swr: 0, tag: "blog" },
|
|
38730
38772
|
{ method: "PATCH", pathPrefix: "/blog/v3/posts", cacheable: false, ttl: 0, swr: 0, tag: "blog" },
|
|
38731
38773
|
{ method: "POST", pathPrefix: "/blog/v3/posts", cacheable: false, ttl: 0, swr: 0, tag: "blog" },
|
|
38732
|
-
{ method: "DELETE", pathPrefix: "/bookings/v1/services", cacheable: false, ttl: 0, swr: 0, tag: "bookings/services" },
|
|
38733
38774
|
{ method: "POST", pathPrefix: "/bookings/v1/services", cacheable: false, ttl: 0, swr: 0, tag: "bookings/services" },
|
|
38734
38775
|
{ method: "PUT", pathPrefix: "/bookings/v1/services", cacheable: false, ttl: 0, swr: 0, tag: "bookings/services" },
|
|
38735
38776
|
{ method: "DELETE", pathPrefix: "/bookings/v2/services", cacheable: false, ttl: 0, swr: 0, tag: "bookings/services" },
|
|
@@ -46605,7 +46646,37 @@ function countingWixCall(base) {
|
|
|
46605
46646
|
}
|
|
46606
46647
|
async function liveRenderServices(bundleJs, opts) {
|
|
46607
46648
|
const bus = hostDispatch(opts.localDbSchema, declaredCapabilitiesOf(opts.descriptor));
|
|
46608
|
-
|
|
46649
|
+
const appear = rootOnAppear(opts.descriptor);
|
|
46650
|
+
const fired = appear ? parseWorkerAction(appear.actionId) : null;
|
|
46651
|
+
if (fired) {
|
|
46652
|
+
const w = await bootBundleWorker(bundleJs, { ...liveBootOptions(opts, bus), skipLoad: true });
|
|
46653
|
+
try {
|
|
46654
|
+
const handle = w.handles[fired.service];
|
|
46655
|
+
if (handle && handle.actionNames().includes(fired.action)) {
|
|
46656
|
+
try {
|
|
46657
|
+
await handle.dispatch(fired.action, ...workerArgs(appear));
|
|
46658
|
+
} catch (e) {
|
|
46659
|
+
w.errors.push({ service: fired.service, message: `onAppear ${fired.action}: ${e?.message ?? String(e)}` });
|
|
46660
|
+
}
|
|
46661
|
+
} else {
|
|
46662
|
+
for (const [service, h] of Object.entries(w.handles)) {
|
|
46663
|
+
if (!h.actionNames().includes("load")) continue;
|
|
46664
|
+
try {
|
|
46665
|
+
await h.dispatch("load");
|
|
46666
|
+
} catch (e) {
|
|
46667
|
+
w.errors.push({ service, message: e?.message ?? String(e) });
|
|
46668
|
+
}
|
|
46669
|
+
}
|
|
46670
|
+
}
|
|
46671
|
+
return { services: snapshot(w).state, calls: w.calls, errors: w.errors };
|
|
46672
|
+
} finally {
|
|
46673
|
+
w.dispose();
|
|
46674
|
+
}
|
|
46675
|
+
}
|
|
46676
|
+
return loadBundleServicesLive(bundleJs, liveBootOptions(opts, bus));
|
|
46677
|
+
}
|
|
46678
|
+
function liveBootOptions(opts, bus) {
|
|
46679
|
+
return {
|
|
46609
46680
|
wixCall: opts.wixCall,
|
|
46610
46681
|
input: opts.input,
|
|
46611
46682
|
budgetMs: opts.budgetMs,
|
|
@@ -46619,7 +46690,7 @@ async function liveRenderServices(bundleJs, opts) {
|
|
|
46619
46690
|
// holding this ONE verb on `null` keeps the previous behaviour while the capability bus
|
|
46620
46691
|
// joins. Caught in pre-PR review; revisit when --live can mint a member token.
|
|
46621
46692
|
dispatch: (action, params) => MEMBER_IDENTITY_ACTION.test(action) ? null : bus(action, params)
|
|
46622
|
-
}
|
|
46693
|
+
};
|
|
46623
46694
|
}
|
|
46624
46695
|
async function buildReleaseFromContent(contentDir2, dest, opts = {}) {
|
|
46625
46696
|
const { freshModule, ...rest } = opts;
|
|
@@ -59116,21 +59187,21 @@ var init_preview_assets_generated = __esm({
|
|
|
59116
59187
|
"../../packages/serve/src/preview-assets.generated.ts"() {
|
|
59117
59188
|
init_preview_assets_format();
|
|
59118
59189
|
PREVIEW_ASSET_MANIFEST = [
|
|
59119
|
-
{ key: "assets/index-
|
|
59120
|
-
{ key: "fonts/Roboto-LICENSE.txt", contentType: "application/octet-stream", offset:
|
|
59121
|
-
{ key: "fonts/Roboto-Variable.ttf", contentType: "font/ttf", offset:
|
|
59122
|
-
{ key: "fonts/WixMadeforDisplay-Bold.ttf", contentType: "font/ttf", offset:
|
|
59123
|
-
{ key: "fonts/WixMadeforDisplay-ExtraBold.ttf", contentType: "font/ttf", offset:
|
|
59124
|
-
{ key: "fonts/WixMadeforDisplay-Medium.ttf", contentType: "font/ttf", offset:
|
|
59125
|
-
{ key: "fonts/WixMadeforDisplay-Regular.ttf", contentType: "font/ttf", offset:
|
|
59126
|
-
{ key: "fonts/WixMadeforDisplay-SemiBold.ttf", contentType: "font/ttf", offset:
|
|
59127
|
-
{ key: "fonts/WixMadeforText-Bold.ttf", contentType: "font/ttf", offset:
|
|
59128
|
-
{ key: "fonts/WixMadeforText-ExtraBold.ttf", contentType: "font/ttf", offset:
|
|
59129
|
-
{ key: "fonts/WixMadeforText-Medium.ttf", contentType: "font/ttf", offset:
|
|
59130
|
-
{ key: "fonts/WixMadeforText-Regular.ttf", contentType: "font/ttf", offset:
|
|
59131
|
-
{ key: "fonts/WixMadeforText-SemiBold.ttf", contentType: "font/ttf", offset:
|
|
59132
|
-
{ key: "index.html", contentType: "text/html; charset=utf-8", offset:
|
|
59133
|
-
{ key: "t5-frame.html", contentType: "text/html; charset=utf-8", offset:
|
|
59190
|
+
{ key: "assets/index-DHO0WElX.js", contentType: "application/javascript; charset=utf-8", offset: 8, length: 1127482, sha256: "9a198f7af12c683263ba55641ec1802d4ca9addb9b8d38170e6b4f60387c61c3" },
|
|
59191
|
+
{ key: "fonts/Roboto-LICENSE.txt", contentType: "application/octet-stream", offset: 1127490, length: 4610, sha256: "994062dd7af4186a739de55dd9e151c63b6301f8e2cbafc608d35c2e87da0cd2" },
|
|
59192
|
+
{ key: "fonts/Roboto-Variable.ttf", contentType: "font/ttf", offset: 1132100, length: 488584, sha256: "d7598e12c5dbef095ff8272cfc55da0250bd07fbdecbac8a530b9b277872a134" },
|
|
59193
|
+
{ key: "fonts/WixMadeforDisplay-Bold.ttf", contentType: "font/ttf", offset: 1620684, length: 90924, sha256: "ec4c7d63772d980d6e147b2df6215fd2737e4b04cebe0cf7e11dc695fb63cdcf" },
|
|
59194
|
+
{ key: "fonts/WixMadeforDisplay-ExtraBold.ttf", contentType: "font/ttf", offset: 1711608, length: 90984, sha256: "bf4c16a2774edd38a921edf3099469866cd74c094fd596f3fa4e81f54a04dfaa" },
|
|
59195
|
+
{ key: "fonts/WixMadeforDisplay-Medium.ttf", contentType: "font/ttf", offset: 1802592, length: 90980, sha256: "368faca44fb9f7207e4af784e94750b1ee59c1098b8689e0f4a918926f244fb5" },
|
|
59196
|
+
{ key: "fonts/WixMadeforDisplay-Regular.ttf", contentType: "font/ttf", offset: 1893572, length: 90872, sha256: "97ed8f1a6772abe26e3d8af5cecfc4b02faa94497d585b0398b5053b739a70c1" },
|
|
59197
|
+
{ key: "fonts/WixMadeforDisplay-SemiBold.ttf", contentType: "font/ttf", offset: 1984444, length: 90980, sha256: "642feab8a48ddf6c7a89d7c2a7c0edfa0e6e773f671287e460e222703563191e" },
|
|
59198
|
+
{ key: "fonts/WixMadeforText-Bold.ttf", contentType: "font/ttf", offset: 2075424, length: 108636, sha256: "37c179b4fefb63ea9637322b84583a2df53aa226c150294eea1675c14b1b8e29" },
|
|
59199
|
+
{ key: "fonts/WixMadeforText-ExtraBold.ttf", contentType: "font/ttf", offset: 2184060, length: 109084, sha256: "62e2fe4718129f36ecdb0ae4de385bd6b81ecc876146447167c6c140fad699a9" },
|
|
59200
|
+
{ key: "fonts/WixMadeforText-Medium.ttf", contentType: "font/ttf", offset: 2293144, length: 109048, sha256: "4ecf270136af928612c76fe4ebade91d8156975e5ea4c0cf9225501dd1788e02" },
|
|
59201
|
+
{ key: "fonts/WixMadeforText-Regular.ttf", contentType: "font/ttf", offset: 2402192, length: 107700, sha256: "dee56f09fd3efc127c1ea95557edc74110c1460a46287da36e0af25efd341ef5" },
|
|
59202
|
+
{ key: "fonts/WixMadeforText-SemiBold.ttf", contentType: "font/ttf", offset: 2509892, length: 88108, sha256: "dced97a77624bc5f8b1ae65850360908297f4588e09feb6f22792d29dc7e765b" },
|
|
59203
|
+
{ key: "index.html", contentType: "text/html; charset=utf-8", offset: 2598e3, length: 4947, sha256: "72e531f20412d0f07433cb52fac32ff7f5be87a4ef435c2d919cd5e21ec84ad3" },
|
|
59204
|
+
{ key: "t5-frame.html", contentType: "text/html; charset=utf-8", offset: 2602947, length: 17344, sha256: "d057298d75e7373e5e2c4d11b7d1bcbea88e03af555ba63d97641c78509c58e4" }
|
|
59134
59205
|
];
|
|
59135
59206
|
}
|
|
59136
59207
|
});
|
|
@@ -75883,7 +75954,7 @@ function auditUx(content, strict = false) {
|
|
|
75883
75954
|
if (usesAppNetwork(source)) {
|
|
75884
75955
|
const mocksPath = join25(dir, "dev", "net-mocks.json");
|
|
75885
75956
|
if (!existsSync22(mocksPath)) {
|
|
75886
|
-
issue(issues, strict, "missing-app-mocks", "This package uses a Wix/network client but has no app-specific dev/net-mocks.json.
|
|
75957
|
+
issue(issues, strict, "missing-app-mocks", "This package uses a Wix/network client but has no app-specific dev/net-mocks.json. `himi test --record` writes one from the live responses it sees; then add fixtures for dynamic, entitlement, or recovery flows.", "dev/net-mocks.json");
|
|
75887
75958
|
} else {
|
|
75888
75959
|
try {
|
|
75889
75960
|
const parsed2 = JSON.parse(readFileSync22(mocksPath, "utf8"));
|
|
@@ -76117,10 +76188,11 @@ var USAGE = `himi \u2014 the Himalaya authoring CLI (remote dev loop)
|
|
|
76117
76188
|
before data consent), placeholder + sample copy, the proposed Data access rows
|
|
76118
76189
|
and typed-journey stubs. Writes .himi/adopt.json, .himi/adopt-matrix.md and
|
|
76119
76190
|
.himi/himi-test.stub.json (--dry-run writes nothing)
|
|
76120
|
-
himi template screens [--show <id>] [--write <id> --from <file>] [--content <dir>]
|
|
76191
|
+
himi template screens [--show <id>] [--write <id> --from <file>] [--add <id> --from <file> [--parent <id>] [--tab <id>]] [--content <dir>]
|
|
76121
76192
|
list dev/index.ts screens (size, workers, onAppear); --show prints ONE entry so
|
|
76122
76193
|
nobody reads the whole file; --write puts an edited entry back (re-parsed, refused
|
|
76123
76194
|
if it breaks the file or changes the screen count)
|
|
76195
|
+
--add appends a NEW screen entry and registers it in linkableScreens (--parent/--tab)
|
|
76124
76196
|
himi template sweep --set "Old=>New" [--set \u2026] | --map <file.json> [--content <dir>] [--dry-run]
|
|
76125
76197
|
replace the template's sample identity/copy inside string literals only (never
|
|
76126
76198
|
identifiers) across dev/index.ts, config.ts, tokens.json and tier5-src; re-parses
|
|
@@ -76801,7 +76873,7 @@ WHAT A PATCH DOES NOT CARRY \u2014 all reported in the output, never silent
|
|
|
76801
76873
|
var COMMANDS2 = [
|
|
76802
76874
|
{ name: "captcha", summary: "create and store native CAPTCHA keys in Serve, or inspect setup status; customer setup verifies Wix site ownership; does not enable member CAPTCHA SDKs", required: "setup|status --app <app> [--content <dir>] [--site <msid>] [--ios-bundle-id <id>] [--android-package-name <id>] [--target <t>]" },
|
|
76803
76875
|
{ name: "init", summary: "scaffold a starter content package (OTA-only; no native shells). --template <t> starts from a full reference app (branded-lite = consumer storefront over a Wix site; owner-lite = owner CRUD/management) instead of the minimal counter; --templates lists bundled ones; --remote fetches the template off the serve plane (falls back to the bundled copy offline). The new app also gets its agent files (the same set `himi agent-init` writes: AGENTS.md, CLAUDE.md, the himalaya skill, HIMALAYA.md, a SessionStart hook) so the next session started inside it picks the work up; --no-agent skips them. It also makes the build board (.himi/studio/, exactly as `himi studio --no-open` does \u2014 it never opens a browser), so every later render, test, validate, push and progress entry refreshes it; and, for a template bundled with this CLI, it makes the FIRST PICTURE itself (never for a --remote one: that is its publisher's code, and a render runs it on this machine, so next[] leaves the render to the agent after it has read the code): exactly `himi render <homeScreenId> --png --mocks --device iphone` (offline \u2014 no request; never --live; --mocks loads the app's recorded data when it has any), run as a child it kills after a time budget, writing .himi/shots/<home>.png and journaling it (by: himi) as the STARTING POINT (\"Starting point: the <template> template, before any of your changes\") so the board and the journal hold a picture when init returns. init also records the source it wrote (.himi/init-source.json: a content fingerprint, Markdown left out), and the first home PNG render after the source diverges from it is journaled once as \"First look at your app\" \u2014 reported as `firstLook: {image}` or `{skipped: <reason>}`, never a failed init; `--no-first-look` skips it (for scripts and CI that render themselves). For a bundled template (never a --remote one \u2014 the preview would build and serve its publisher's code) init also STARTS the live preview itself: `himi run --watch --content <dir> --port <p> --no-open --idle-exit 30`, detached in its own process group with its output in .himi/watch.log, never waited for (the url is chosen before it builds), recorded in .himi/watch.json, and reported as `watch: {started, pid, url, board, log}` \u2014 or `{skipped: <reason>}` for a `CI` environment, a test runner, a watch already running, or a start that failed (never a failed init). Nothing else skips it: outside CI it is started, and `watch.note` says an opt-out was ignored. The envelope LEADS with `doNow`: three lines to run now (the consent preflight, the live preview \u2014 its url and board when init started it, else `himi run --watch --content <dir> &` with the board's path \u2014 and the postcard line), and ENDS with the same lines as `doNowAgain`, so an agent that reads only the head or only the tail (`| tail`) gets them; at a terminal they are also printed as plain text on stderr, before the JSON and again after it. Its `next[]` LEADS with the steps for THIS session, in this order for every app \u2014 the offline first render and that picture's `himi progress` postcard (only when init could not make the picture), the skill's consent preflight (authorize or mock any Wix or external startup request; with none, go straight on), and only then the live preview (the running one's url, or `himi run --watch &` when init did not start it) \u2014 ahead of the next-session guidance", required: "<name> [--dir] [--template <t>] [--remote] [--no-agent] [--no-first-look] | --templates" },
|
|
76804
|
-
{ name: "template", summary: "starter-template catalog + publishing. Every template is a SHOWCASE APP \u2014 a real published release you can open running before you start from it. `list` prints available templates with metadata (title/description/tags) plus each one's start prompt \u2014 pass --remote to include the serve-plane catalog so an agent can pick one; `prompt <name>` prints just that sentence for one template (the skill URL is the public door, or the plane named by --target/--serve-url); `publish` publishes an app AS a `himi init --template` (needs a himalaya.template.json; builds a previewable release + attaches the scaffold source; --source-dir overrides which tree becomes the source, --name sets the template name; bakes a launcher icon \u2014 --icon <png>, or the committed template-icons/<name>.png \u2014 and extracts accent/palette gallery meta from the scaffold's tokens.json; --visibility unlisted keeps it out of the /v1/templates catalog \u2014 templates default to public); `trim --keep <ids>` cuts an app scaffolded from a template down to those screens (the home, and any screen the navigation pins such as a drawer body, always stays) \u2014 it deletes the other SCREENS/TEMPLATED entries whole, prunes navigation (linkableScreens, tabBar, drawer, deep links, surfaces) and drops tier5-src workers no kept screen uses, then runs validate --strict and rolls everything back if it fails (--force keeps it, --dry-run writes nothing). A kept screen that links to a removed one is refused unless --links follow (keep those too) or --links drop (remove the taps). Adopting one: `inspect` writes the adoption inventory (.himi/adopt.json: screens, workers, which screens can call Wix/network on appear \u2014 bootsOffline \u2014 placeholder and sample copy, proposed Data access rows, typed-journey stubs); `screens` lists, shows or
|
|
76876
|
+
{ name: "template", summary: "starter-template catalog + publishing. Every template is a SHOWCASE APP \u2014 a real published release you can open running before you start from it. `list` prints available templates with metadata (title/description/tags) plus each one's start prompt \u2014 pass --remote to include the serve-plane catalog so an agent can pick one; `prompt <name>` prints just that sentence for one template (the skill URL is the public door, or the plane named by --target/--serve-url); `publish` publishes an app AS a `himi init --template` (needs a himalaya.template.json; builds a previewable release + attaches the scaffold source; --source-dir overrides which tree becomes the source, --name sets the template name; bakes a launcher icon \u2014 --icon <png>, or the committed template-icons/<name>.png \u2014 and extracts accent/palette gallery meta from the scaffold's tokens.json; --visibility unlisted keeps it out of the /v1/templates catalog \u2014 templates default to public); `trim --keep <ids>` cuts an app scaffolded from a template down to those screens (the home, and any screen the navigation pins such as a drawer body, always stays) \u2014 it deletes the other SCREENS/TEMPLATED entries whole, prunes navigation (linkableScreens, tabBar, drawer, deep links, surfaces) and drops tier5-src workers no kept screen uses, then runs validate --strict and rolls everything back if it fails (--force keeps it, --dry-run writes nothing). A kept screen that links to a removed one is refused unless --links follow (keep those too) or --links drop (remove the taps). Adopting one: `inspect` writes the adoption inventory (.himi/adopt.json: screens, workers, which screens can call Wix/network on appear \u2014 bootsOffline \u2014 placeholder and sample copy, proposed Data access rows, typed-journey stubs); `screens` lists, shows, rewrites or adds ONE dev/index.ts entry; `sweep` replaces sample identity/copy inside string literals; `recommend` ranks the catalog for a business description or a site's verticals; `trim --from-spec` takes the kept screens from SPEC.md's Screen catalogue", required: "list [--remote] | prompt <name> | publish [--dir <app>] [--source-dir <dir>] [--name <n>] [--icon <png>] [--visibility public|unlisted] [--target <t>] | trim --keep <id,id,\u2026>|--from-spec [SPEC.md] [--content <dir>] [--links follow|drop] [--dry-run] [--force] | inspect [--content <dir>] [--dry-run] | screens [--show <id>] [--write <id> --from <file>] [--add <id> --from <file>] | sweep --set 'Old=>New' | --map <file> [--dry-run] | recommend [--text <t>] [--verticals a,b] [--book <dir>] [--remote]" },
|
|
76805
76877
|
{ name: "build", summary: "compile tier5-src/<name>/index.ts -> dist/bundles/<name>.bundle.js with esbuild (--binary = resolve published @himalaya/*); semantic TypeScript checking belongs to himi validate", required: "[--content <dir>] [--binary]" },
|
|
76806
76878
|
{ name: "functions", summary: "inspect/invoke server functions and operate durable jobs or schedules", required: "list|describe|openapi|invoke | jobs list|get|cancel|retry|dlq | schedule describe|invoke [--app <a>] [--environment preview|production] [--release <id>]" },
|
|
76807
76879
|
{ name: "eject", summary: "take ownership of a shared flow module (auth|checkout|screens): copies its source into tier5-src/_ejected/ and repoints this package's imports at the copy. The ladder's escape hatch \u2014 for when the factory options cannot express what you need. AFTER EJECTING, CENTRAL FIXES NO LONGER REACH THIS APP, including future corrections to the fail-closed entitlement and confirm-the-session rules. The floor is unaffected: credentials still go only through the native auth actions and money only through the cashier rail.", required: "<auth|checkout|screens> [--content <dir>]" },
|
|
@@ -77944,7 +78016,13 @@ async function adoptAtInit(dir) {
|
|
|
77944
78016
|
writeInventory2(dir, inv);
|
|
77945
78017
|
return { adopt: inventorySummary2(inv) };
|
|
77946
78018
|
} catch (e) {
|
|
77947
|
-
|
|
78019
|
+
const msg = e.message;
|
|
78020
|
+
const layout = /SCREENS = runtimeSetting/.test(msg);
|
|
78021
|
+
return {
|
|
78022
|
+
adopt: {
|
|
78023
|
+
skipped: layout ? "no adoption inventory for this template: its dev/index.ts is not in the generated SCREENS layout, so `himi template inspect | screens | trim` do not apply to it \u2014 edit its source directly" : `no adoption inventory: ${msg}`
|
|
78024
|
+
}
|
|
78025
|
+
};
|
|
77948
78026
|
}
|
|
77949
78027
|
}
|
|
77950
78028
|
function keptPlan(kept) {
|
|
@@ -79035,6 +79113,13 @@ ${doNow.map((l) => ` ${l}`).join("\n")}`);
|
|
|
79035
79113
|
const app = parseApp2(src.dev, src.config);
|
|
79036
79114
|
const show = str9(flags.show);
|
|
79037
79115
|
const target = str9(flags.write);
|
|
79116
|
+
const adding = str9(flags.add);
|
|
79117
|
+
if (adding) {
|
|
79118
|
+
const from = str9(flags.from);
|
|
79119
|
+
if (!from) throw new TrimRefusal2('--add <id> needs --from <file> holding the whole new SCREENS entry (` "<id>": { "screenId": "<id>", \u2026 },`)');
|
|
79120
|
+
print(adopt.addScreen(dir, adding, readFileSync88(from, "utf8"), { parent: str9(flags.parent), tab: str9(flags.tab) }));
|
|
79121
|
+
return 0;
|
|
79122
|
+
}
|
|
79038
79123
|
if (target) {
|
|
79039
79124
|
const from = str9(flags.from);
|
|
79040
79125
|
if (!from) throw new TrimRefusal2("--write <id> needs --from <file> holding the whole entry, as --show prints it");
|
|
@@ -83468,6 +83553,10 @@ ${label2}`);
|
|
|
83468
83553
|
const deadline = Date.now() + timeoutMs;
|
|
83469
83554
|
const pending = new Map(jobs.map((j) => [String(j.jobId), String(j.platform)]));
|
|
83470
83555
|
const done = [];
|
|
83556
|
+
const statusCmd = (id) => `himi dist status ${id}${str9(flags.target) ? ` --target ${str9(flags.target)}` : ""}`;
|
|
83557
|
+
for (const j of jobs) (flags.json === true ? io.error : io.log)(` ${j.platform} dispatched as ${j.jobId}`);
|
|
83558
|
+
const MAX_POLL_ERRORS = 3;
|
|
83559
|
+
const pollErrors = /* @__PURE__ */ new Map();
|
|
83471
83560
|
while (pending.size > 0) {
|
|
83472
83561
|
if (Date.now() >= deadline) {
|
|
83473
83562
|
await printDistQrsForJobs(done);
|
|
@@ -83483,9 +83572,32 @@ ${label2}`);
|
|
|
83483
83572
|
}
|
|
83484
83573
|
await new Promise((res) => setTimeout(res, intervalMs));
|
|
83485
83574
|
for (const jobId of [...pending.keys()]) {
|
|
83486
|
-
|
|
83575
|
+
let s;
|
|
83576
|
+
let why = "";
|
|
83577
|
+
try {
|
|
83578
|
+
s = await c.distStatus(jobId);
|
|
83579
|
+
if (!s.ok) why = `HTTP ${s.status}`;
|
|
83580
|
+
} catch (e) {
|
|
83581
|
+
why = e.message;
|
|
83582
|
+
}
|
|
83583
|
+
if (why) {
|
|
83584
|
+
const n = (pollErrors.get(jobId) ?? 0) + 1;
|
|
83585
|
+
pollErrors.set(jobId, n);
|
|
83586
|
+
if (n < MAX_POLL_ERRORS) continue;
|
|
83587
|
+
await printDistQrsForJobs(done);
|
|
83588
|
+
print({
|
|
83589
|
+
ok: false,
|
|
83590
|
+
error: "dist_poll_failed",
|
|
83591
|
+
message: `could not read the status of ${pending.get(jobId)} job ${jobId} ${MAX_POLL_ERRORS} times in a row (${why}) \u2014 the build${pending.size > 1 ? "s were" : " was"} dispatched and may still finish`,
|
|
83592
|
+
pending: [...pending].map(([id, platform2]) => ({ platform: platform2, jobId: id, check: statusCmd(id) })),
|
|
83593
|
+
finished: flags.json === true ? done : done.map(humanDistJob),
|
|
83594
|
+
...failures.length ? { failures } : {}
|
|
83595
|
+
});
|
|
83596
|
+
return 1;
|
|
83597
|
+
}
|
|
83598
|
+
pollErrors.delete(jobId);
|
|
83487
83599
|
const rec = s.json ?? {};
|
|
83488
|
-
if (!
|
|
83600
|
+
if (!TERMINAL_DIST_STATUSES.has(String(rec.status))) continue;
|
|
83489
83601
|
pending.delete(jobId);
|
|
83490
83602
|
done.push(rec);
|
|
83491
83603
|
if (!flags.json) {
|
|
Binary file
|
|
@@ -25,7 +25,10 @@ must be impossible for it to touch something it didn't create.
|
|
|
25
25
|
| **Adopted** | `wixId: "<existing id>"` | **read only** — never create, never alter schema, never seed | CMS data the site already has |
|
|
26
26
|
|
|
27
27
|
An owned id must be exactly **`himi_<appSlug>_<name>`**, where `appSlug` is the
|
|
28
|
-
content
|
|
28
|
+
content package's **`name` in `himalaya.content.json`** — the name you passed to `himi init`,
|
|
29
|
+
or the folder's name when there is no manifest; **not** the dotted `id` in `config.ts` — with
|
|
30
|
+
`-` folded to `_` and lowercased (`my-app` → `himi_my_app_`). `himi cms create --dry-run`
|
|
31
|
+
prints the resolved prefix before anything is written,
|
|
29
32
|
and **`<name>` is alphanumeric — no underscores** (`menuItems`, not `menu_items`). This is
|
|
30
33
|
checked by `himi validate` and re-checked by every mutating verb. It is deliberately **not**
|
|
31
34
|
auto-prefixed: what you declare is what lands on the site, and an unprefixed id is
|
|
@@ -77,7 +77,9 @@ worker layer — trim it or mine it (`himi docs trim-a-template`): **trim** to t
|
|
|
77
77
|
`tier5-src/_shared/` helpers and Wix worker chains and write your own screens. Do not delete
|
|
78
78
|
`tier5-src/` + `dev/` and start from blank. Never read that file whole: `himi template screens`
|
|
79
79
|
lists its screens (size, workers, `onAppear`), `--show <id>` prints one entry, and
|
|
80
|
-
`--write <id> --from <file>` puts an edited entry back (refused if it breaks the file)
|
|
80
|
+
`--write <id> --from <file>` puts an edited entry back (refused if it breaks the file), and
|
|
81
|
+
`--add <id> --from <file>` appends a NEW screen and registers it as linkable — the authoring half
|
|
82
|
+
of mining a template. Not sure
|
|
81
83
|
which template fits? `himi template recommend --text "<the business>"` (or `--book <site analyze
|
|
82
84
|
dir>` for a site's verticals) ranks the catalog with reasons. For a small **non-Wix** pattern, `himi init --template chat-bot --remote`. `himi template
|
|
83
85
|
list --remote` shows the full catalog (title/description/tags) — including templates published
|
package/guides/himi-skill.md
CHANGED
|
@@ -106,7 +106,9 @@ worker layer — trim it or mine it (`himi docs trim-a-template`): **trim** to t
|
|
|
106
106
|
`tier5-src/_shared/` helpers and Wix worker chains and write your own screens. Do not delete
|
|
107
107
|
`tier5-src/` + `dev/` and start from blank. Never read that file whole: `himi template screens`
|
|
108
108
|
lists its screens (size, workers, `onAppear`), `--show <id>` prints one entry, and
|
|
109
|
-
`--write <id> --from <file>` puts an edited entry back (refused if it breaks the file)
|
|
109
|
+
`--write <id> --from <file>` puts an edited entry back (refused if it breaks the file), and
|
|
110
|
+
`--add <id> --from <file>` appends a NEW screen and registers it as linkable — the authoring half
|
|
111
|
+
of mining a template. Not sure
|
|
110
112
|
which template fits? `himi template recommend --text "<the business>"` (or `--book <site analyze
|
|
111
113
|
dir>` for a site's verticals) ranks the catalog with reasons. For a small **non-Wix** pattern, `himi init --template chat-bot --remote`. `himi template
|
|
112
114
|
list --remote` shows the full catalog (title/description/tags) — including templates published
|
|
@@ -112,6 +112,32 @@ grep -oE 'package="[^"]*"|android:label="[^"]*"' \
|
|
|
112
112
|
himi-owner-lite-app/build/intermediates/merged_manifest/debug/*/AndroidManifest.xml
|
|
113
113
|
```
|
|
114
114
|
|
|
115
|
+
## Off-repo: an app made with `himi init --template owner-lite`
|
|
116
|
+
|
|
117
|
+
Everything above describes Owner Lite as Wix builds it, with its per-customer binary. An
|
|
118
|
+
off-repo content package made with `himi init --template owner-lite` (or one of the `*-owner`
|
|
119
|
+
templates) has no `provisioning.json` and no `prepare:customer` step. What it does have is the
|
|
120
|
+
same split between the **owner** and **visitor** scopes, and that split decides what you can see:
|
|
121
|
+
|
|
122
|
+
- **The web preview is visitor-scope.** `himi run`, `himi render --live` and a pushed draft
|
|
123
|
+
preview all run with an anonymous visitor session. A visitor can read public catalog data
|
|
124
|
+
(Bookings services, Stores products, published posts), so those screens render real data
|
|
125
|
+
there. Anything owner-scoped cannot: a business's appointments and orders, its drafts, and
|
|
126
|
+
every management write (create or edit a service, product or post) answer 401/403. The
|
|
127
|
+
owner-only screens therefore show their empty or error state on the web, which is not a bug.
|
|
128
|
+
- **Owner flows are proven on a native build.** The owner signs in through the native owner
|
|
129
|
+
login (`himi dist` → install the APK/IPA). That build is the surface for "today's
|
|
130
|
+
appointments" and for the write round trips; the web preview cannot stand in for it.
|
|
131
|
+
- **Before that build exists**, prove a write with the site owner's own token against the same
|
|
132
|
+
REST endpoint the worker calls, then re-read it independently. That proves the payload, not
|
|
133
|
+
the app's sign-in. Record each native surface you could not drive as unavailable, with the
|
|
134
|
+
reason, rather than calling it passed:
|
|
135
|
+
`himi ux record --content . --platform ios --status unavailable --reason "owner flows need a native build; not driven yet"`
|
|
136
|
+
(and the same with `--platform android`).
|
|
137
|
+
- **An app that must render something to a visitor** (a public menu next to the owner tools)
|
|
138
|
+
binds the visitor-readable surfaces with a consumer provider and keeps the owner screens
|
|
139
|
+
behind the owner gate. The owner gate fails closed in a visitor session; that is its job.
|
|
140
|
+
|
|
115
141
|
## See also
|
|
116
142
|
|
|
117
143
|
- [owner-lite-publish-binary.md](owner-lite-publish-binary.md) — pointing that binary at a serve app.
|