@wix/himalaya-cli 0.1329.0 → 0.1331.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/cli.mjs +149 -12
- package/guides/wix-business-context.md +19 -4
- package/guides/wix-site-http-functions.md +4 -1
- package/package.json +2 -2
package/dist/cli.mjs
CHANGED
|
@@ -7591,6 +7591,7 @@ function resolveTranslation(value, catalog3, sourceCatalog) {
|
|
|
7591
7591
|
var BINDING_SIGILS, BINDING_SIGIL_RE, TRANSLATION_PREFIX, TRANSLATION_KEY_RE;
|
|
7592
7592
|
var init_bindings = __esm({
|
|
7593
7593
|
"../../core/ts/src/bindings.ts"() {
|
|
7594
|
+
"use strict";
|
|
7594
7595
|
BINDING_SIGILS = ["service", "flag", "item", "t"];
|
|
7595
7596
|
BINDING_SIGIL_RE = new RegExp(
|
|
7596
7597
|
`^\\$(?:${BINDING_SIGILS.filter((s2) => s2 !== "t").join("|")})\\b|^\\$t:`
|
|
@@ -16629,6 +16630,7 @@ function resolveIdiom(tokens2, ctx = {}) {
|
|
|
16629
16630
|
var IDIOM_CONTEXTS, UNSAFE_KEYS;
|
|
16630
16631
|
var init_idiom = __esm({
|
|
16631
16632
|
"../../core/ts/src/idiom.ts"() {
|
|
16633
|
+
"use strict";
|
|
16632
16634
|
IDIOM_CONTEXTS = {
|
|
16633
16635
|
ios: { native: ["ios"] },
|
|
16634
16636
|
android: { native: ["material"] },
|
|
@@ -162049,6 +162051,26 @@ var init_base44_auth = __esm({
|
|
|
162049
162051
|
});
|
|
162050
162052
|
|
|
162051
162053
|
// src/site-analyze/net-guard.ts
|
|
162054
|
+
var net_guard_exports = {};
|
|
162055
|
+
__export(net_guard_exports, {
|
|
162056
|
+
ASSET_HOP: () => ASSET_HOP,
|
|
162057
|
+
REDIRECT_STATUS: () => REDIRECT_STATUS,
|
|
162058
|
+
SITE_HOP: () => SITE_HOP,
|
|
162059
|
+
assertPublicSiteUrl: () => assertPublicSiteUrl,
|
|
162060
|
+
copyIncomingHeaders: () => copyIncomingHeaders,
|
|
162061
|
+
decodedBody: () => decodedBody,
|
|
162062
|
+
discardBody: () => discardBody,
|
|
162063
|
+
followPinned: () => followPinned,
|
|
162064
|
+
guardRedirects: () => guardRedirects,
|
|
162065
|
+
guardedSiteFetch: () => guardedSiteFetch,
|
|
162066
|
+
isBlockedAddress: () => isBlockedAddress,
|
|
162067
|
+
isFetchableFontUrl: () => isFetchableFontUrl,
|
|
162068
|
+
makePinnedFetch: () => makePinnedFetch,
|
|
162069
|
+
pinAddress: () => pinAddress,
|
|
162070
|
+
pinnedFetch: () => pinnedFetch2,
|
|
162071
|
+
realPin: () => realPin,
|
|
162072
|
+
siteUrlRefusal: () => siteUrlRefusal
|
|
162073
|
+
});
|
|
162052
162074
|
import { lookup as dnsLookup } from "node:dns";
|
|
162053
162075
|
import { request as httpsRequest2 } from "node:https";
|
|
162054
162076
|
import { Readable as Readable2 } from "node:stream";
|
|
@@ -162316,6 +162338,34 @@ function makePinnedFetch(allow, label2, pin = realPin, perform = requestOnce) {
|
|
|
162316
162338
|
transport.perform = perform;
|
|
162317
162339
|
return transport;
|
|
162318
162340
|
}
|
|
162341
|
+
function guardRedirects(fetchImpl, rule = SITE_HOP) {
|
|
162342
|
+
return async (input, init) => {
|
|
162343
|
+
if (typeof input !== "string" && !(input instanceof URL)) {
|
|
162344
|
+
throw new TypeError("guardRedirects takes a url \u2014 a Request carries a redirect mode of its own");
|
|
162345
|
+
}
|
|
162346
|
+
if (init?.body != null) {
|
|
162347
|
+
throw new TypeError("guardRedirects is GET-only \u2014 a body cannot be replayed across a redirect");
|
|
162348
|
+
}
|
|
162349
|
+
const method = (init?.method ?? "GET").toUpperCase();
|
|
162350
|
+
if (method !== "GET" && method !== "HEAD") {
|
|
162351
|
+
throw new TypeError(`guardRedirects is GET-only, refusing ${method}`);
|
|
162352
|
+
}
|
|
162353
|
+
let url = new URL(input instanceof URL ? input.href : input);
|
|
162354
|
+
const mode = init?.redirect ?? "follow";
|
|
162355
|
+
for (let hop = 0; ; hop += 1) {
|
|
162356
|
+
const refusal = rule(url);
|
|
162357
|
+
if (refusal) throw new Error(refusal);
|
|
162358
|
+
const res = await fetchImpl(url.href, { ...init, redirect: "manual" });
|
|
162359
|
+
const location = res.headers.get("location");
|
|
162360
|
+
if (!REDIRECT_STATUS.has(res.status) || !location) return res;
|
|
162361
|
+
if (mode === "manual") return res;
|
|
162362
|
+
await discardBody(res);
|
|
162363
|
+
if (mode === "error") throw new Error(`redirect refused by redirect:"error" at ${url.href}`);
|
|
162364
|
+
if (hop >= MAX_REDIRECTS) throw new Error(`too many redirects from ${url.href}`);
|
|
162365
|
+
url = new URL(location, url);
|
|
162366
|
+
}
|
|
162367
|
+
};
|
|
162368
|
+
}
|
|
162319
162369
|
var PUBLIC_SITE_RULE, DECODERS, MAX_REDIRECTS, REDIRECT_STATUS, ASSET_HOP, SITE_HOP, pinnedFetch2, guardedSiteFetch;
|
|
162320
162370
|
var init_net_guard = __esm({
|
|
162321
162371
|
"src/site-analyze/net-guard.ts"() {
|
|
@@ -162509,6 +162559,19 @@ function textFromHtml(fragment) {
|
|
|
162509
162559
|
function unescapeJsonString(s2) {
|
|
162510
162560
|
return s2.replace(/\\"/g, '"').replace(/\\n/g, " ").replace(/\\\\/g, "\\");
|
|
162511
162561
|
}
|
|
162562
|
+
function veloAppIdsFromHtml(html) {
|
|
162563
|
+
const out2 = [];
|
|
162564
|
+
const take = (re2) => {
|
|
162565
|
+
re2.lastIndex = 0;
|
|
162566
|
+
let m2;
|
|
162567
|
+
while (out2.length < 4 && (m2 = re2.exec(html))) {
|
|
162568
|
+
const id = m2[1];
|
|
162569
|
+
if (id && !out2.includes(id)) out2.push(id);
|
|
162570
|
+
}
|
|
162571
|
+
};
|
|
162572
|
+
for (const re2 of [CODE_APP_ID_RE, OPEN_CODE_APP_ID_RE, GRID_APP_ID_ALL_RE]) take(re2);
|
|
162573
|
+
return out2;
|
|
162574
|
+
}
|
|
162512
162575
|
function parsePublishedHtml(html) {
|
|
162513
162576
|
const pages2 = [];
|
|
162514
162577
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -162540,6 +162603,9 @@ function parsePublishedHtml(html) {
|
|
|
162540
162603
|
const og = html.match(OG_IMAGE_RE)?.[1];
|
|
162541
162604
|
const logoCandidates = og && /^https?:/i.test(og) ? [og] : [];
|
|
162542
162605
|
const gridAppId = html.match(GRID_APP_ID_RE)?.[1];
|
|
162606
|
+
const veloAppIds = veloAppIdsFromHtml(html);
|
|
162607
|
+
CODE_APP_ID_RE.lastIndex = 0;
|
|
162608
|
+
const codeAppId = CODE_APP_ID_RE.exec(html)?.[1];
|
|
162543
162609
|
const mainPageId = html.match(MAIN_PAGE_RE)?.[1];
|
|
162544
162610
|
const htmlLang = html.match(HTML_LANG_RE)?.[1]?.trim();
|
|
162545
162611
|
const meta = {};
|
|
@@ -162559,6 +162625,8 @@ function parsePublishedHtml(html) {
|
|
|
162559
162625
|
const headings = dedupe2(rawHeadings, 20);
|
|
162560
162626
|
return {
|
|
162561
162627
|
...gridAppId ? { gridAppId } : {},
|
|
162628
|
+
...codeAppId ? { codeAppId } : {},
|
|
162629
|
+
veloAppIds,
|
|
162562
162630
|
...mainPageId ? { mainPageId } : {},
|
|
162563
162631
|
pages: pages2,
|
|
162564
162632
|
appDefIds,
|
|
@@ -162627,11 +162695,35 @@ function rankPage(p2, mainPageId) {
|
|
|
162627
162695
|
if (/\b(shop|store|about|contact|blog|plan|pricing|faq|menu|event|gallery|library|bookings?)\b/.test(hay)) return 1;
|
|
162628
162696
|
return 2;
|
|
162629
162697
|
}
|
|
162630
|
-
async function readPublishedHtml(fetchImpl, siteUrl) {
|
|
162698
|
+
async function readPublishedHtml(fetchImpl, siteUrl, opts) {
|
|
162631
162699
|
try {
|
|
162632
|
-
const res = await fetchImpl(siteUrl, { signal: AbortSignal.timeout(12e3) });
|
|
162700
|
+
const res = await fetchImpl(siteUrl, { signal: opts?.signal ?? AbortSignal.timeout(12e3) });
|
|
162633
162701
|
if (!res.ok) return parsePublishedHtml("");
|
|
162634
|
-
|
|
162702
|
+
if (!opts || !res.body) return parsePublishedHtml(await res.text());
|
|
162703
|
+
const maxBytes = opts.maxBytes ?? 8e6;
|
|
162704
|
+
const reader = res.body.getReader();
|
|
162705
|
+
const decoder = new TextDecoder();
|
|
162706
|
+
let html = "";
|
|
162707
|
+
let overlap = "";
|
|
162708
|
+
let bytes2 = 0;
|
|
162709
|
+
try {
|
|
162710
|
+
for (; ; ) {
|
|
162711
|
+
const chunk = await reader.read();
|
|
162712
|
+
if (chunk.done) break;
|
|
162713
|
+
bytes2 += chunk.value.byteLength;
|
|
162714
|
+
const text9 = decoder.decode(chunk.value, { stream: true });
|
|
162715
|
+
html += text9;
|
|
162716
|
+
if (CODE_APP_ID_DONE_RE.test(overlap + text9) || bytes2 >= maxBytes) {
|
|
162717
|
+
await reader.cancel().catch(() => {
|
|
162718
|
+
});
|
|
162719
|
+
break;
|
|
162720
|
+
}
|
|
162721
|
+
overlap = (overlap + text9).slice(-80);
|
|
162722
|
+
}
|
|
162723
|
+
} catch (e2) {
|
|
162724
|
+
if (!opts.signal?.aborted) throw e2;
|
|
162725
|
+
}
|
|
162726
|
+
html += decoder.decode();
|
|
162635
162727
|
return parsePublishedHtml(html);
|
|
162636
162728
|
} catch {
|
|
162637
162729
|
return parsePublishedHtml("");
|
|
@@ -162643,12 +162735,15 @@ function appsFromDefIds(ids) {
|
|
|
162643
162735
|
return name ? { id, name } : { id };
|
|
162644
162736
|
});
|
|
162645
162737
|
}
|
|
162646
|
-
var GRID_APP_ID_RE, MAIN_PAGE_RE, APP_DEF_RE, PAGE_ENTRY_RE, OG_IMAGE_RE, ICON_RE, HTML_LANG_RE, META_DESC_RE, OG_TITLE_RE, OG_DESC_RE, HEADING_RE, SKIP_SLUG, POPUP, WELL_KNOWN_WIX_APPS, ENTITIES2;
|
|
162738
|
+
var GRID_APP_ID_RE, CODE_APP_ID_RE, OPEN_CODE_APP_ID_RE, GRID_APP_ID_ALL_RE, MAIN_PAGE_RE, APP_DEF_RE, PAGE_ENTRY_RE, OG_IMAGE_RE, ICON_RE, HTML_LANG_RE, META_DESC_RE, OG_TITLE_RE, OG_DESC_RE, HEADING_RE, SKIP_SLUG, POPUP, WELL_KNOWN_WIX_APPS, ENTITIES2, CODE_APP_ID_DONE_RE;
|
|
162647
162739
|
var init_published_html = __esm({
|
|
162648
162740
|
"src/site-analyze/published-html.ts"() {
|
|
162649
162741
|
init_rest();
|
|
162650
162742
|
init_copy_clean();
|
|
162651
162743
|
GRID_APP_ID_RE = /"gridAppId"\s*:\s*"([0-9a-fA-F-]{36})"/;
|
|
162744
|
+
CODE_APP_ID_RE = /"codeAppId"\s*:\s*"([\w-]{1,64})"/g;
|
|
162745
|
+
OPEN_CODE_APP_ID_RE = /"openCodeAppId"\s*:\s*"([\w-]{1,64})"/g;
|
|
162746
|
+
GRID_APP_ID_ALL_RE = /"gridAppId"\s*:\s*"([0-9a-fA-F-]{36})"/g;
|
|
162652
162747
|
MAIN_PAGE_RE = /"mainPageId"\s*:\s*"([A-Za-z0-9_-]{3,20})"/;
|
|
162653
162748
|
APP_DEF_RE = /"app(?:DefinitionId|DefId)"\s*:\s*"([0-9a-fA-F-]{36})"/gi;
|
|
162654
162749
|
PAGE_ENTRY_RE = /"pageId"\s*:\s*"([A-Za-z0-9_-]{3,20})"[^{}]*?"title"\s*:\s*"((?:\\.|[^"\\])*)"[^{}]*?"pageUriSEO"\s*:\s*"((?:\\.|[^"\\])*)"/g;
|
|
@@ -162683,6 +162778,7 @@ var init_published_html = __esm({
|
|
|
162683
162778
|
"'": "'",
|
|
162684
162779
|
" ": " "
|
|
162685
162780
|
};
|
|
162781
|
+
CODE_APP_ID_DONE_RE = /"codeAppId"\s*:\s*"[\w-]{1,64}"/;
|
|
162686
162782
|
}
|
|
162687
162783
|
});
|
|
162688
162784
|
|
|
@@ -163851,13 +163947,13 @@ async function listFilesForApp(call, gridAppId, redact) {
|
|
|
163851
163947
|
}
|
|
163852
163948
|
async function readOwnerVelo(call, opts) {
|
|
163853
163949
|
if (!call) return { tried: false, files: [] };
|
|
163854
|
-
const files = [];
|
|
163855
163950
|
const ids = [];
|
|
163856
163951
|
let git2 = false;
|
|
163857
163952
|
const remember = (id) => {
|
|
163858
163953
|
if (id && !ids.includes(id)) ids.push(id);
|
|
163859
163954
|
};
|
|
163860
163955
|
remember(opts?.gridAppId);
|
|
163956
|
+
for (const id of opts?.gridAppIds ?? []) remember(id);
|
|
163861
163957
|
for (const probe of OWNER_VELO_PROBES) {
|
|
163862
163958
|
assertSafeVeloPath(probe.path, probe.method);
|
|
163863
163959
|
const { data } = await probeCall(call, probe.method, probe.path);
|
|
@@ -163865,16 +163961,27 @@ async function readOwnerVelo(call, opts) {
|
|
|
163865
163961
|
for (const id of gridAppIdsFromPayload(data)) remember(id);
|
|
163866
163962
|
if (probe.path.includes("git-repo") && (data.repo || data.repository || data.gitRepo || data.url)) git2 = true;
|
|
163867
163963
|
}
|
|
163964
|
+
let fallback;
|
|
163965
|
+
const supplied = new Set(opts?.gridAppIds ?? []);
|
|
163868
163966
|
for (const id of ids) {
|
|
163869
|
-
|
|
163967
|
+
let listed;
|
|
163968
|
+
try {
|
|
163969
|
+
listed = await listFilesForApp(call, id, opts?.redact);
|
|
163970
|
+
} catch (e2) {
|
|
163971
|
+
if (!supplied.has(id)) throw e2;
|
|
163972
|
+
continue;
|
|
163973
|
+
}
|
|
163974
|
+
const files = [];
|
|
163870
163975
|
for (const f2 of listed) {
|
|
163871
163976
|
if (!files.some((x2) => x2.path === f2.path)) files.push(f2);
|
|
163872
163977
|
}
|
|
163873
|
-
if (files.length)
|
|
163978
|
+
if (!files.length) continue;
|
|
163979
|
+
if (!opts?.accept || opts.accept(files)) {
|
|
163874
163980
|
return { tried: true, files, gridAppId: id, git: git2 };
|
|
163875
163981
|
}
|
|
163982
|
+
fallback ??= { tried: true, files, gridAppId: id, git: git2 };
|
|
163876
163983
|
}
|
|
163877
|
-
return { tried: true, files, gridAppId: ids[0], git: git2 };
|
|
163984
|
+
return fallback ?? { tried: true, files: [], gridAppId: ids[0], git: git2 };
|
|
163878
163985
|
}
|
|
163879
163986
|
function buildVeloChapter(input) {
|
|
163880
163987
|
const hint = input.look.veloHint;
|
|
@@ -172627,7 +172734,8 @@ function contextFetch(fetchImpl, signal) {
|
|
|
172627
172734
|
};
|
|
172628
172735
|
}
|
|
172629
172736
|
async function collectSiteContext(opts) {
|
|
172630
|
-
const
|
|
172737
|
+
const deadline = AbortSignal.timeout(opts.timeoutMs ?? 15e3);
|
|
172738
|
+
const fetcher = contextFetch(opts.fetchImpl ?? fetch, deadline);
|
|
172631
172739
|
const owner = opts.ownerToken ? makeTransport(fetcher, { token: opts.ownerToken, msid: opts.siteId }) : void 0;
|
|
172632
172740
|
let visitorToken;
|
|
172633
172741
|
let visitorAuth = { status: "skipped" };
|
|
@@ -172693,7 +172801,23 @@ async function collectSiteContext(opts) {
|
|
|
172693
172801
|
}
|
|
172694
172802
|
};
|
|
172695
172803
|
try {
|
|
172696
|
-
|
|
172804
|
+
let htmlIds = [];
|
|
172805
|
+
if (opts.siteUrl) {
|
|
172806
|
+
const published = await readPublishedHtml(
|
|
172807
|
+
opts.siteFetch ?? guardedSiteFetch,
|
|
172808
|
+
opts.siteUrl,
|
|
172809
|
+
{ signal: AbortSignal.any([deadline, AbortSignal.timeout(6e3)]) }
|
|
172810
|
+
);
|
|
172811
|
+
htmlIds = published.veloAppIds;
|
|
172812
|
+
}
|
|
172813
|
+
const velo2 = await readOwnerVelo(observed, {
|
|
172814
|
+
gridAppIds: htmlIds,
|
|
172815
|
+
redact: (source) => redactVeloSource(source, [opts.ownerToken ?? "", visitorToken ?? ""]),
|
|
172816
|
+
// An id whose listing has no backend source is not the code app this mode is after.
|
|
172817
|
+
// Only under --url: without it the probe behavior — first non-empty listing wins —
|
|
172818
|
+
// is part of the unchanged output contract.
|
|
172819
|
+
...opts.siteUrl ? { accept: (listed) => listed.some((f2) => backendSourcePath(f2.path) !== void 0) } : {}
|
|
172820
|
+
});
|
|
172697
172821
|
const files = velo2.files.flatMap((f2) => {
|
|
172698
172822
|
const normalized = backendSourcePath(f2.path);
|
|
172699
172823
|
return normalized ? [{ ...f2, path: normalized }] : [];
|
|
@@ -172703,8 +172827,10 @@ async function collectSiteContext(opts) {
|
|
|
172703
172827
|
opts.request ?? "",
|
|
172704
172828
|
[opts.ownerToken ?? "", visitorToken ?? ""]
|
|
172705
172829
|
);
|
|
172830
|
+
const appIdSource = opts.siteUrl && files.length && velo2.gridAppId ? htmlIds.includes(velo2.gridAppId) ? "published-html" : "probe" : void 0;
|
|
172706
172831
|
return {
|
|
172707
172832
|
...files.length ? { status: "ok" } : readFailure ?? { status: "unavailable" },
|
|
172833
|
+
...appIdSource ? { appIdSource } : {},
|
|
172708
172834
|
files: files.map((f2) => ({
|
|
172709
172835
|
path: f2.path,
|
|
172710
172836
|
kind: f2.kind,
|
|
@@ -172751,6 +172877,8 @@ var init_context = __esm({
|
|
|
172751
172877
|
init_visitor_auth();
|
|
172752
172878
|
init_velo();
|
|
172753
172879
|
init_velo_source();
|
|
172880
|
+
init_net_guard();
|
|
172881
|
+
init_published_html();
|
|
172754
172882
|
responseKeys = {
|
|
172755
172883
|
store: "products",
|
|
172756
172884
|
bookings: "services",
|
|
@@ -175598,7 +175726,7 @@ var USAGE = `himi \u2014 the Himalaya authoring CLI (remote dev loop)
|
|
|
175598
175726
|
also requires a MOBILE applicationType, because ensure
|
|
175599
175727
|
refuses a WEB client \u2014 so a WEB one carrying your
|
|
175600
175728
|
redirect looks right and is not a candidate.
|
|
175601
|
-
himi site analyze --read-only --site <msid> [--client-id <id>] [--json]
|
|
175729
|
+
himi site analyze --read-only --site <msid> [--client-id <id>] [--url <https://\u2026>] [--json]
|
|
175602
175730
|
himi site analyze --url <https://\u2026> [--client-id <id>] [--site <msid>] [--out <dir>] [--apply]
|
|
175603
175731
|
[--shell ${SHELL_ENUM}]
|
|
175604
175732
|
collect a Wix business book (APIs + Playwright look) and
|
|
@@ -175772,7 +175900,7 @@ var COMMANDS2 = [
|
|
|
175772
175900
|
{ name: "logout", summary: "remove the stored control token for a target", required: "[--target <t>]" },
|
|
175773
175901
|
{ name: "notify", summary: "Hosted --all-members requires --yes for a live send; --dry-run checks ownership/readiness and counts approved recipients without sending. `setup` optionally configures Branded/Ping from local customer key files; `status --refresh` reads live backend readiness. Setup may be deferred while building. These use serve-control and require owner plus Wix account authentication. `send`, `tokens`, `doctor`: send a REAL push notification to registered devices (RFC 0036 WS5) \u2014 APNs .p8 over HTTP/2 for iOS, FCM v1 for Android. `send` targets one device (--token / --device <registry id>) or fans out to the whole app (--all), reporting a per-device result; a token the provider says is uninstalled (APNs 410 Unregistered / FCM UNREGISTERED) is auto-disabled, while a merely wrong one (BadDeviceToken \u2014 usually a sandbox/production mismatch) is recorded and left enabled. `tokens` lists the registry. `doctor` reports which credentials resolve and which sender each platform picks. Talks to the admin-server via --admin-url/HIMALAYA_ADMIN_URL, authenticated with --admin-session/HIMALAYA_ADMIN_SESSION. `send --provider ping` is the hosted alternative: the serve plane sends to one site member (--member-email) or all approved members (--all-members) through Ping as Himalaya, with owner login instead of provider keys, and reports acceptance, never delivery. NOT `himi push`, which publishes a release.", required: "setup|status [--content <dir>] [--target <t>] [--refresh] [--yes] [--no-push-setup] | send --provider ping [--content <dir>] [--app <a>] [--site <msid>] (--member-email <email> | --all-members (--dry-run | --yes)) --title <s> [--body <s>] --link <url> [--request-id <uuid>] [--dry-run] [--json] | send|tokens|doctor [--app <a>] [--token <t> | --device <id> | --all] [--title <s>] [--body <s>] [--link <url>] [--platform ios|android] [--badge <n>] [--channel <id>] [--silent] [--dry-run] [--json]" },
|
|
175774
175902
|
{ name: "signing", summary: "READ-ONLY App Store Connect inventory for customer BYO TestFlight readiness. `inspect` lists whether the bundle id, distribution certificate, IOS_APP_STORE profile, and ASC app record exist for --bundle-id / --team-id using a local ASC .p8 (flags or ASC_KEY_* env). Never uploads or mutates Apple resources. Optional --cert-file/--profile-file classify on-disk material.", required: "inspect --bundle-id <id> --team-id <id> --asc-key-file <path> --asc-key-id <id> --asc-issuer-id <id> [--cert-file <p12> --cert-password <pw> --profile-file <mobileprovision>] [--json]" },
|
|
175775
|
-
{ name: "site", summary: "PROVISION a Wix project, then analyze or extend it. `create` is the one door that makes a site instead of adopting one: POST /funnel/projects/v1/create (a WIX site, a HEADLESS backend, a BRANDED_APP, or a VIBE site), and for headless it also mints the OAuth app whose id IS the `WIX_CLIENT_ID` \u2014 with --redirect-uri already registered, because an unregistered redirect makes native signInDirect fail at redirect-session. `--verticals` REQUIRES `--template` and reports only what Wix confirmed: measured, installing onto a project created from the BLANK template returns 200 while the vertical's API answers 428 forever, because there is no site for it to attach to (the install asks for CREATE_SITE). A real template ships its verticals installed and populated, and installing further ones onto it then works. It MUTATES your Wix account and needs --yes; without it you get the payload and nothing is sent. `list` is the read-only site inventory (with each site's premium flag, which is what explains an installed-but-428 vertical). Auth is the account-scoped `wix token` (no --site), falling back to WIX_API_KEY + WIX_ACCOUNT_ID. `client` is the same OAuth step for a site that ALREADY exists \u2014 the one himi adopts rather than makes \u2014 so a clientId no longer means a manual trip to the dashboard: `client ensure` reconciles rather than mints, because a site can already carry a client with NO registered redirect and shipping that one fails signInDirect at runtime; it reuses a client that carries --redirect-uri, PATCHes one that matches --name but lacks it (unioning, so another app's redirect survives), and creates only when there is nothing to reuse (--new forces a fresh one). --name IS the idempotency key. `client` auth is SITE-scoped (`wix token --site <msid>`) because minting is owner-of-that-site, and --yes gates every mutation: a site that already has a usable client resolves WITHOUT it, but --write-provisioning needs --yes even then because it rewrites a manifest on disk (the dry run's planned.provisioning names the file). That query endpoint IGNORES paging (measured: query.paging, cursorPaging and a top-level paging all return the full set at offset 0), so when pagingMetadata reports more clients than came back, `ensure` REFUSES EVERY outcome \u2014 create, patch AND reuse. Reuse is refused too because pickClient breaks ties on the OLDEST createdDate, so an omitted older same-named MOBILE client with the same redirect would win a later complete read and the resolved clientId would MOVE between runs \u2014 and it gets written into provisioning.json; convergence is what makes a stray duplicate untidy rather than harmful. --name cannot narrow the query (no filter is sent; the name is applied to the rows after they arrive), so the recovery is the dashboard; --new skips the query entirely. A `patched` result is verified against what Wix STORED \u2014 the redirect list is read off the PATCH response, and re-read via GET when the response omits it, because falling back to the list himi just sent would make the guard pass vacuously on exactly the silent no-op it exists to catch. `client list` is the read-only inventory of a site's clients: act on its `shippable` column rather than `hasRedirect`, because shippable ALSO requires a MOBILE applicationType and `ensure` refuses a WEB client \u2014 a WEB one carrying your redirect looks right and is not a candidate \u2014 and it sets truncated:true rather than presenting a partial inventory as the whole one. `analyze --read-only` returns API-only generation context as JSON (identity, access probes, CMS permissions and selected redacted Velo source); it needs no
|
|
175903
|
+
{ name: "site", summary: "PROVISION a Wix project, then analyze or extend it. `create` is the one door that makes a site instead of adopting one: POST /funnel/projects/v1/create (a WIX site, a HEADLESS backend, a BRANDED_APP, or a VIBE site), and for headless it also mints the OAuth app whose id IS the `WIX_CLIENT_ID` \u2014 with --redirect-uri already registered, because an unregistered redirect makes native signInDirect fail at redirect-session. `--verticals` REQUIRES `--template` and reports only what Wix confirmed: measured, installing onto a project created from the BLANK template returns 200 while the vertical's API answers 428 forever, because there is no site for it to attach to (the install asks for CREATE_SITE). A real template ships its verticals installed and populated, and installing further ones onto it then works. It MUTATES your Wix account and needs --yes; without it you get the payload and nothing is sent. `list` is the read-only site inventory (with each site's premium flag, which is what explains an installed-but-428 vertical). Auth is the account-scoped `wix token` (no --site), falling back to WIX_API_KEY + WIX_ACCOUNT_ID. `client` is the same OAuth step for a site that ALREADY exists \u2014 the one himi adopts rather than makes \u2014 so a clientId no longer means a manual trip to the dashboard: `client ensure` reconciles rather than mints, because a site can already carry a client with NO registered redirect and shipping that one fails signInDirect at runtime; it reuses a client that carries --redirect-uri, PATCHes one that matches --name but lacks it (unioning, so another app's redirect survives), and creates only when there is nothing to reuse (--new forces a fresh one). --name IS the idempotency key. `client` auth is SITE-scoped (`wix token --site <msid>`) because minting is owner-of-that-site, and --yes gates every mutation: a site that already has a usable client resolves WITHOUT it, but --write-provisioning needs --yes even then because it rewrites a manifest on disk (the dry run's planned.provisioning names the file). That query endpoint IGNORES paging (measured: query.paging, cursorPaging and a top-level paging all return the full set at offset 0), so when pagingMetadata reports more clients than came back, `ensure` REFUSES EVERY outcome \u2014 create, patch AND reuse. Reuse is refused too because pickClient breaks ties on the OLDEST createdDate, so an omitted older same-named MOBILE client with the same redirect would win a later complete read and the resolved clientId would MOVE between runs \u2014 and it gets written into provisioning.json; convergence is what makes a stray duplicate untidy rather than harmful. --name cannot narrow the query (no filter is sent; the name is applied to the rows after they arrive), so the recovery is the dashboard; --new skips the query entirely. A `patched` result is verified against what Wix STORED \u2014 the redirect list is read off the PATCH response, and re-read via GET when the response omits it, because falling back to the list himi just sent would make the guard pass vacuously on exactly the silent no-op it exists to catch. `client list` is the read-only inventory of a site's clients: act on its `shippable` column rather than `hasRedirect`, because shippable ALSO requires a MOBILE applicationType and `ensure` refuses a WEB client \u2014 a WEB one carrying your redirect looks right and is not a candidate \u2014 and it sets truncated:true rather than presenting a partial inventory as the whole one. `analyze --read-only` returns API-only generation context as JSON (identity, access probes, CMS permissions and selected redacted Velo source); it needs no browser and never uploads or applies anything. A URL is optional there: `--url` lets it read the published page's Velo code-app ids (credential-free, pinned transport) so the backend source is found on sites where the owner code-app probe 404s. `analyze` reads a site into a business-book folder (REST + Playwright, GET-only); `--url` alone is the visitor-only pass over the published pages, and `--client-id` adds the vertical probes and the app binding `--apply` requires; `site function` list/get/put/publish/invokes that site's Velo HTTP functions and is not `himi functions` (serve-plane jobs). `--apply` bakes into a brandable shell (a site shell needs no `--client-id`: it is visitor-only and binds no client); `--shell` takes the same names `himi dist --shell` does and defaults to branded-lite, and a shell that ships no prepare:customer is refused by name rather than half-built. `--shell brandy` (the site shell, RFC 0062) also derives the site's page map from its published pages and header menu \u2014 Home first, up to four tabs for the pages a visitor returns to, the rest in the drawer \u2014 into the overlay's `site` block, so it can be edited before `--apply`; only a site shell ever receives that block. It never overwrites any shell's committed apps/<shell>/provisioning.json.", required: "create <name> [--type wix|headless|branded-app|vibe] [--template <guid>] [--verticals stores,bookings,\u2026] [--folder <guid>] [--redirect-uri <uri>] [--no-oauth-app] [--write-provisioning [path]] [--site-url <url>] [--json] --yes | list | client ensure|list --site <msid> [--name <name>] [--redirect-uri <uri>] [--new] [--write-provisioning [path]] [--site-url <url>] [--json] --yes | analyze --read-only [--site <msid>] [--client-id <id>] [--url <https>] [--json] | analyze --url <https> [--client-id <id>] [--site <msid>] [--out <dir>] [--redirect-uri <uri>] [--apply] [--shell " + SHELL_ENUM + "] | function list|get|put|publish|invoke [--site <msid>] [--url <https>] [--method GET|POST|PUT|DELETE] [--source <file>] [--body <json>] [--publish] [--invoke]" },
|
|
175776
175904
|
{ name: "base44", summary: "read a BASE44 app into an app book (book.json + BOOK.md) for the agent building a Himi app from it \u2014 the Base44 counterpart of `site analyze`, READ-ONLY. `analyze --url <published app>` is the VISITOR pass: the app id, name, favicon and colours from the published SPA, its router paths, and screenshots + look tokens from the Playwright crawl. `analyze --app-id <id>` adds the OWNER pass and needs a session: the project source under <out>/source/ (the read half of `base44 eject` \u2014 GET api/apps/<id>/eject \u2014 through the same deny-list and secret scan `himi push` uses, widened by the book's token shapes; `eject` itself is never run because it CREATES a copy of the app), every entity's JSON schema, required fields and RLS rules with up to --sample redacted rows (default 5, 0 = off), backend functions, agents, connectors (names and scopes only, never tokens), secret NAMES (never values), auth visibility + providers, and the route \u2192 component \u2192 entities map from the source router. Every chapter it could not read is listed in `gaps` rather than failing the run. `login` runs Base44's device sign-in and stores the session in ~/.himi/base44/session.json (never the repo; --from-base44-cli imports `npx base44 login`'s session instead); BASE44_ACCESS_TOKEN in the environment wins, for CI. `whoami` prints who is signed in (never the token); `logout` removes the stored session. `auth-check --url <tenant> --ios-app-id <PREFIX.bundle.id>` and/or `--android-package <package> --android-sha256 <fingerprint>` checks public native-login associations and prints missing entries; it never reads an owner token. --json prints the book itself. himi docs base44-app-context", required: "analyze (--url <https://\u2026> | --app-id <id>) [--out .himi/base44-book] [--sample <n>] [--json] | login [--from-base44-cli] | whoami | logout | auth-check --url <https://tenant> [--ios-app-id <PREFIX.bundle.id>] [--android-package <package> --android-sha256 <fingerprint>]" },
|
|
175777
175905
|
{ name: "doctor", summary: 'off-VPN preflight in one shot: probe the PUBLIC data plane (www.wixapis.com \u2014 reachable off-VPN), the INTERNAL serve/control plane for the selected target (VPN-gated), and report auth state (dpx session on disk + stored control token). --dpx additionally runs a live, EADDRINUSE-safe `dpx whoami`. Machine-readable with --json (an unreachable internal plane carries a stable code:"needs_vpn" so an agent branches on the code, not the prose); human-readable otherwise. Never loads the authoring runtime \u2014 works off-repo.', required: "[--target <t>] [--dpx] [--json] [--data-url <url>] [--timeout <ms>]" },
|
|
175778
175906
|
{ name: "upgrade", summary: "install the newest published CLI (`npm i -g @wix/himalaya-cli@latest`) and report what moved. This is the explicit door: himi also updates itself when the running build is more than a week old \u2014 unattended it just does it and re-runs your command, at a terminal it asks first \u2014 and `HIMI_NO_UPDATE_CHECK=1` turns that off, leaving this command as the way to catch up. Reports `unchanged` rather than claiming success when npm exits 0 without moving the version, which is what a registry mirror serving its own pinned copy looks like.", required: "[--json]" },
|
|
@@ -183706,6 +183834,14 @@ Code: ${userCode}`);
|
|
|
183706
183834
|
io2.error(JSON.stringify({ error: "--read-only returns API context only; it cannot be combined with --apply, --out, --shell, or --redirect-uri" }));
|
|
183707
183835
|
return 2;
|
|
183708
183836
|
}
|
|
183837
|
+
if (siteUrl) {
|
|
183838
|
+
const { siteUrlRefusal: siteUrlRefusal2 } = await Promise.resolve().then(() => (init_net_guard(), net_guard_exports));
|
|
183839
|
+
const refusal = siteUrlRefusal2(siteUrl);
|
|
183840
|
+
if (refusal) {
|
|
183841
|
+
io2.error(JSON.stringify({ ok: false, code: "site_url_refused", error: refusal }));
|
|
183842
|
+
return 2;
|
|
183843
|
+
}
|
|
183844
|
+
}
|
|
183709
183845
|
const siteId = str9(flags.site) ?? process.env.WIX_SITE_ID;
|
|
183710
183846
|
if (!clientId && !siteId && !process.env.WIX_TEST_TOKEN?.trim()) {
|
|
183711
183847
|
io2.error(JSON.stringify({ error: "--read-only needs --site, --client-id, or a site-scoped WIX_TEST_TOKEN" }));
|
|
@@ -183724,6 +183860,7 @@ Code: ${userCode}`);
|
|
|
183724
183860
|
siteId,
|
|
183725
183861
|
clientId,
|
|
183726
183862
|
ownerToken,
|
|
183863
|
+
siteUrl,
|
|
183727
183864
|
request: process.env.HIMI_ANALYZE_REQUEST
|
|
183728
183865
|
});
|
|
183729
183866
|
print({ ok: true, context });
|
|
@@ -198,7 +198,7 @@ If nothing survives, the `cms` vertical is reported **not installed**, so the ap
|
|
|
198
198
|
|
|
199
199
|
Page roles come from each page URL (the Wix site slug is stripped), not the site-wide nav — a header link like “Book Online” must not relabel Home. `restaurants` only selects `menuFirst` when bookings-with-upcoming and a real catalog are absent, so a studio café does not beat the rest of the site.
|
|
200
200
|
|
|
201
|
-
The **Velo chapter** (`book.velo`) is build-time source, not a runtime dump. With owner auth it reads `GET https://editor.wix.com/_api/wix-code-ide-server-ng/v1/apps/{gridAppId}/files` (IdeService `ListFiles`, permission `VELO.IDE_FILES_READ`) and never `UpdateFiles` or `/velo/v1/http/invoke`. That route is **not** on `www.wixapis.com`. `gridAppId` comes from the published viewer model when crawled, or `openCodeAppId` when unpublished. Visitor-only runs still record that Velo is on (crawl hints) but leave `files` empty. Excerpts in JSON are capped and secret-looking literals redacted; `secrets-backend` is dropped. The CLI folder also writes the same redacted sources under `velo/` (layout preserved: `backend/…`, `public/pages/…`); each `book.velo.files[]` entry gets `localPath` (e.g. `velo/backend/events.js`). Page modules (`public/pages/<hash>.js`) bind to titles/URLs via Thunderbolt’s published `router.pagesMap` — the same map fills `look.sitePages` and `taxonomy.pageRoles` even on the plane (no browser, no editor ListPages). Editor-gated page lists stay out of reach (RFC 0048).
|
|
201
|
+
The **Velo chapter** (`book.velo`) is build-time source, not a runtime dump. With owner auth it reads `GET https://editor.wix.com/_api/wix-code-ide-server-ng/v1/apps/{gridAppId}/files` (IdeService `ListFiles`, permission `VELO.IDE_FILES_READ`) and never `UpdateFiles` or `/velo/v1/http/invoke`. That route is **not** on `www.wixapis.com`. `gridAppId` comes from the published viewer model when crawled, or `openCodeAppId` when unpublished; in read-only mode with `--url`, the published page's ids (`codeAppId` first, then `openCodeAppId`, then UUID `gridAppId`s) are tried before the owner probes, which 404 on some sites that do have code. Visitor-only runs still record that Velo is on (crawl hints) but leave `files` empty. Excerpts in JSON are capped and secret-looking literals redacted; `secrets-backend` is dropped. The CLI folder also writes the same redacted sources under `velo/` (layout preserved: `backend/…`, `public/pages/…`); each `book.velo.files[]` entry gets `localPath` (e.g. `velo/backend/events.js`). Page modules (`public/pages/<hash>.js`) bind to titles/URLs via Thunderbolt’s published `router.pagesMap` — the same map fills `look.sitePages` and `taxonomy.pageRoles` even on the plane (no browser, no editor ListPages). Editor-gated page lists stay out of reach (RFC 0048).
|
|
202
202
|
|
|
203
203
|
The **Permissions chapter** (`book.permissions`) is the same dump, folded for agents: CMS `readRole`/`writeRole` with login-required flags, parsed `authorization-config.json` (which `.jsw` anonymous cannot invoke), `loggedIn`/`currentMember`/`hasRole` call sites, Members Area / Custom Login page ids, and custom member roles when `/members/v1/roles` returns any. `pageVisibility` is explicitly `{ available: false, reason: "editor-gated" }`. Empty `memberRoles` does not mean “no members.” Workers must not call `.jsw` / `/_functions/` — `SITE_MEMBER` 403 is a login empty, not an outage.
|
|
204
204
|
|
|
@@ -226,7 +226,7 @@ Agent procedure: run the door, read the folder, judge, hand off. Do not re-fetch
|
|
|
226
226
|
|
|
227
227
|
### API-only context for generation
|
|
228
228
|
|
|
229
|
-
`himi site analyze --read-only --site <msid> [--client-id <id>] --json`
|
|
229
|
+
`himi site analyze --read-only --site <msid> [--client-id <id>] [--url <https://…>] --json`
|
|
230
230
|
returns `{ ok: true, context: { schemaVersion: 1, mode: "read-only", ... } }`.
|
|
231
231
|
It reuses the business-book readers for identity, sampled owner/visitor Stores,
|
|
232
232
|
Bookings, Events and Blog access, CMS permissions (no item samples), and Velo backend
|
|
@@ -234,12 +234,27 @@ source. A client id is optional; without it visitor reads are `skipped`. Owner a
|
|
|
234
234
|
uses `WIX_API_KEY` (headless), a site-scoped `WIX_TEST_TOKEN` (also without `--site`), or `wix token --site` as usual. Missing auth and failed reads
|
|
235
235
|
remain explicit and must not be interpreted as an empty site.
|
|
236
236
|
|
|
237
|
-
|
|
237
|
+
`--url` is optional and names the published site. When set, the Velo reader fetches
|
|
238
|
+
that page's HTML **credential-free** on the pinned public-site transport and tries
|
|
239
|
+
the code-app ids it carries (`codeAppId` → `openCodeAppId` → UUID `gridAppId`s, at
|
|
240
|
+
most four) before the owner probes — the probe endpoint 404s on some sites whose
|
|
241
|
+
code app only the published page names, and this is what finds their backend source.
|
|
242
|
+
An id whose file listing has no backend source is passed over for the next one.
|
|
243
|
+
`context.velo.appIdSource` reports whether the winning id came from `"probe"` or
|
|
244
|
+
`"published-html"`. A non-public URL (not https, non-default port, loopback/private
|
|
245
|
+
host) is refused before authentication with exit 2 and `code: "site_url_refused"`.
|
|
246
|
+
Output for callers that do not pass `--url` is unchanged (`schemaVersion` stays 1).
|
|
247
|
+
|
|
248
|
+
This mode needs no browser and never crawls pages, downloads media, uploads
|
|
238
249
|
screenshots, applies overlays, invokes HTTP functions, or writes local artifacts.
|
|
239
250
|
`--apply`, `--out`, `--shell` and `--redirect-uri` are rejected before authentication.
|
|
240
251
|
An explicit transport allowlist permits only the discovery reads and anonymous OAuth
|
|
241
252
|
mint; redirects are refused, responses capped at 2 MB, and API work shares a 15-second
|
|
242
|
-
deadline. The
|
|
253
|
+
deadline. The optional `--url` published-HTML read is the one exemption, and it runs on
|
|
254
|
+
its own transport, never the credentialed one: a GET to the public site, pinned to
|
|
255
|
+
globally-reachable addresses with **every redirect hop re-judged** against the
|
|
256
|
+
public-site rule, streamed with an 8 MB cap (the Velo ids sit ~1.7 MB into the page),
|
|
257
|
+
and sharing the same deadline bounded to 6 seconds of it. The ordinary full business-book flow is unchanged.
|
|
243
258
|
|
|
244
259
|
`HIMI_ANALYZE_REQUEST` optionally supplies request text for backend source selection.
|
|
245
260
|
The response inventories backend paths and exports; selected entry points, matching
|
|
@@ -52,7 +52,10 @@ Export names: `get_dogs` → `GET /_functions/dogs`. Same for `post_` / `put_` /
|
|
|
52
52
|
|
|
53
53
|
`himi site analyze` / MCP `himi_site_analyze` / `POST /v1/site/analyze` **read**
|
|
54
54
|
`http-functions.js` into `book.velo` and **refuse** UpdateFiles and invoke.
|
|
55
|
-
Writes belong here.
|
|
55
|
+
Writes belong here. In `--read-only` mode, pass `--url <published site>` when the
|
|
56
|
+
owner code-app probe 404s: the page's own `codeAppId` finds the listing that has
|
|
57
|
+
`backend/http-functions.js` (fetched credential-free; see
|
|
58
|
+
[wix-business-context](wix-business-context.md)).
|
|
56
59
|
|
|
57
60
|
## App workers
|
|
58
61
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wix/himalaya-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1331.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": [
|
|
@@ -71,5 +71,5 @@
|
|
|
71
71
|
"artifactId": "himalaya-cli"
|
|
72
72
|
}
|
|
73
73
|
},
|
|
74
|
-
"falconPackageHash": "
|
|
74
|
+
"falconPackageHash": "28d2553b9a570cc58de5b6254b0a231741d5cc5f5de6ea1baecd358a"
|
|
75
75
|
}
|