@wix/himalaya-cli 0.1187.0 → 0.1188.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/core/ts/src/site-shell.d.ts +10 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/core.mjs +4 -0
- package/dist/cli.mjs +270 -68
- package/guides/capabilities.md +26 -3
- package/guides/himi-test.md +31 -0
- package/guides/qa-builds.md +1 -1
- package/package.json +2 -2
package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/ts/src/site-shell.d.ts
CHANGED
|
@@ -66,6 +66,16 @@ export interface SiteShellConfig {
|
|
|
66
66
|
* site's own cookie endpoint signs the WebView in). Set by the shell from its member binding;
|
|
67
67
|
* a Base44 site always bridges. */
|
|
68
68
|
sessionBridge?: boolean;
|
|
69
|
+
/** The site's own Members Area pages a member notification can open — My Orders and My Bookings
|
|
70
|
+
* — as paths relative to {@link origin}. They are never navigation rows (a signed-in utility,
|
|
71
|
+
* not a destination), so they live here rather than in {@link pages}; the site analyzer fills
|
|
72
|
+
* them from the pages it actually found. Absent ⇒ that notification opens the home page. */
|
|
73
|
+
memberPages?: SiteMemberPages;
|
|
74
|
+
}
|
|
75
|
+
/** Members Area pages, by what a notification is about. */
|
|
76
|
+
export interface SiteMemberPages {
|
|
77
|
+
orders?: string;
|
|
78
|
+
bookings?: string;
|
|
69
79
|
}
|
|
70
80
|
/** At most four site tabs; the rest belong in the drawer. Four, not the usual phone-bar five,
|
|
71
81
|
* because every tab keeps its WebView warm (`keepAlive`) and the native keep-alive stores
|
|
@@ -13373,6 +13373,10 @@ function assertSiteShellConfig(site) {
|
|
|
13373
13373
|
seen.add(page.id);
|
|
13374
13374
|
sitePageUrl(site.origin, page.path);
|
|
13375
13375
|
}
|
|
13376
|
+
for (const [key, path] of Object.entries(site.memberPages ?? {})) {
|
|
13377
|
+
if (typeof path !== "string" || !path) throw new Error(`site memberPages.${key} must be a path`);
|
|
13378
|
+
sitePageUrl(site.origin, path);
|
|
13379
|
+
}
|
|
13376
13380
|
if (site.layout !== void 0 && !SITE_LAYOUTS.includes(site.layout)) {
|
|
13377
13381
|
throw new Error(`site layout ${JSON.stringify(site.layout)} must be one of ${SITE_LAYOUTS.join(", ")}`);
|
|
13378
13382
|
}
|
package/dist/cli.mjs
CHANGED
|
@@ -17406,12 +17406,20 @@ function needsNativeBuild(id) {
|
|
|
17406
17406
|
}
|
|
17407
17407
|
function capabilitiesUsedBy(bundleJs) {
|
|
17408
17408
|
const out2 = /* @__PURE__ */ new Set();
|
|
17409
|
-
for (const m of bundleJs
|
|
17410
|
-
if (m
|
|
17409
|
+
for (const m of capabilityLiterals(bundleJs)) {
|
|
17410
|
+
if (m.verb || isKnownCapability(m.id)) out2.add(m.id);
|
|
17411
17411
|
}
|
|
17412
17412
|
for (const [id, marker] of BRIDGE_MARKERS) if (marker.test(bundleJs)) out2.add(id);
|
|
17413
17413
|
return [...out2].sort();
|
|
17414
17414
|
}
|
|
17415
|
+
function capabilityDispatchesIn(bundleJs) {
|
|
17416
|
+
const out2 = /* @__PURE__ */ new Set();
|
|
17417
|
+
for (const m of capabilityLiterals(bundleJs)) if (m.verb) out2.add(`capability.${m.id}.${m.verb}`);
|
|
17418
|
+
return [...out2].sort();
|
|
17419
|
+
}
|
|
17420
|
+
function capabilityLiterals(bundleJs) {
|
|
17421
|
+
return [...bundleJs.matchAll(CAPABILITY_LITERAL)].map((m) => ({ id: m[1], ...m[2] ? { verb: m[2] } : {} }));
|
|
17422
|
+
}
|
|
17415
17423
|
function distAddRefusal(rawId) {
|
|
17416
17424
|
const id = catalogKey(rawId);
|
|
17417
17425
|
if (!isKnownCapability(id)) return "is not a capability id the platform knows";
|
|
@@ -17438,7 +17446,7 @@ function usageStringCopyFor(permissionCopy) {
|
|
|
17438
17446
|
}
|
|
17439
17447
|
return out2;
|
|
17440
17448
|
}
|
|
17441
|
-
var BRIDGE_CAPABILITY_IDS, KNOWN_CAPABILITY_IDS, BRIDGE_MARKERS, NOT_DIST_ADDABLE;
|
|
17449
|
+
var BRIDGE_CAPABILITY_IDS, KNOWN_CAPABILITY_IDS, CAPABILITY_LITERAL, BRIDGE_MARKERS, NOT_DIST_ADDABLE;
|
|
17442
17450
|
var init_known = __esm({
|
|
17443
17451
|
"../../core/sdk/capability-catalog/src/known.ts"() {
|
|
17444
17452
|
init_builtins();
|
|
@@ -17456,6 +17464,7 @@ var init_known = __esm({
|
|
|
17456
17464
|
])
|
|
17457
17465
|
].sort()
|
|
17458
17466
|
);
|
|
17467
|
+
CAPABILITY_LITERAL = /["'`]capability\.([a-z0-9_]+)(?:\.([A-Za-z0-9_]+))?["'`]/g;
|
|
17459
17468
|
BRIDGE_MARKERS = [
|
|
17460
17469
|
["wix_api", /himalaya\.wixApi\b|\bwixApi\.call\b/],
|
|
17461
17470
|
["webview", /himalaya\.webview\b/]
|
|
@@ -22425,8 +22434,8 @@ var init_shell_capabilities_generated = __esm({
|
|
|
22425
22434
|
appDir: "apps/himi-player",
|
|
22426
22435
|
shared: true,
|
|
22427
22436
|
extensible: false,
|
|
22428
|
-
built: ["audio_record_background", "calls", "clipboard", "speech"],
|
|
22429
|
-
serves: ["app_info", "audio_record", "audio_record_background", "browser_auth", "calls", "clipboard", "file_picker", "files", "himi_apps", "iap", "keyboard", "local_database", "on_device_ai", "open_url", "pdf", "presigned_upload", "release_control", "secure_storage", "share", "speaker_diarization", "speech", "stream", "wix_api", "wix_cashier", "wix_platform"]
|
|
22437
|
+
built: ["audio_record_background", "biometrics", "calendar", "calls", "camera", "clipboard", "contacts", "haptic", "location", "media_playback", "motion", "photos", "speech"],
|
|
22438
|
+
serves: ["app_info", "audio_record", "audio_record_background", "biometrics", "browser_auth", "calendar", "calls", "camera", "clipboard", "contacts", "file_picker", "files", "haptic", "himi_apps", "iap", "keyboard", "local_database", "location", "media_playback", "motion", "on_device_ai", "open_url", "pdf", "photos", "presigned_upload", "push", "release_control", "secure_storage", "share", "speaker_diarization", "speech", "stream", "wix_api", "wix_cashier", "wix_platform"]
|
|
22430
22439
|
}
|
|
22431
22440
|
};
|
|
22432
22441
|
}
|
|
@@ -22436,6 +22445,7 @@ var init_shell_capabilities_generated = __esm({
|
|
|
22436
22445
|
var capabilities_exports = {};
|
|
22437
22446
|
__export(capabilities_exports, {
|
|
22438
22447
|
DECLARE_HINT: () => DECLARE_HINT,
|
|
22448
|
+
LAST_DIST_FILE: () => LAST_DIST_FILE,
|
|
22439
22449
|
SHELLS: () => SHELLS,
|
|
22440
22450
|
allCapabilities: () => allCapabilities,
|
|
22441
22451
|
capabilitiesOfPackage: () => capabilitiesOfPackage,
|
|
@@ -22447,11 +22457,15 @@ __export(capabilities_exports, {
|
|
|
22447
22457
|
formatCapabilityList: () => formatCapabilityList,
|
|
22448
22458
|
formatDistPlan: () => formatDistPlan,
|
|
22449
22459
|
isShell: () => isShell,
|
|
22460
|
+
lastDistFromJob: () => lastDistFromJob,
|
|
22461
|
+
needsBinaryNotice: () => needsBinaryNotice,
|
|
22462
|
+
readLastDist: () => readLastDist,
|
|
22450
22463
|
shellDistAdds: () => shellDistAdds,
|
|
22451
22464
|
shellServes: () => shellServes,
|
|
22452
|
-
shellSupport: () => shellSupport
|
|
22465
|
+
shellSupport: () => shellSupport,
|
|
22466
|
+
writeLastDist: () => writeLastDist
|
|
22453
22467
|
});
|
|
22454
|
-
import { existsSync as existsSync33, readFileSync as readFileSync33 } from "node:fs";
|
|
22468
|
+
import { existsSync as existsSync33, lstatSync as lstatSync9, readFileSync as readFileSync33 } from "node:fs";
|
|
22455
22469
|
import { join as join35 } from "node:path";
|
|
22456
22470
|
function isShell(name) {
|
|
22457
22471
|
return Object.hasOwn(SHELL_CAPABILITIES, name);
|
|
@@ -22515,6 +22529,47 @@ function contentShell(dir, flag) {
|
|
|
22515
22529
|
return null;
|
|
22516
22530
|
}
|
|
22517
22531
|
}
|
|
22532
|
+
async function writeLastDist(dir, rec) {
|
|
22533
|
+
const { realDirIn: realDirIn2, writeFileAtomic: writeFileAtomic2 } = await Promise.resolve().then(() => (init_progress(), progress_exports));
|
|
22534
|
+
const himi = realDirIn2(dir, ".himi");
|
|
22535
|
+
if (!himi) return null;
|
|
22536
|
+
const path2 = join35(himi, "last-dist.json");
|
|
22537
|
+
writeFileAtomic2(path2, `${JSON.stringify(rec, null, 2)}
|
|
22538
|
+
`);
|
|
22539
|
+
return path2;
|
|
22540
|
+
}
|
|
22541
|
+
function readLastDist(dir) {
|
|
22542
|
+
const path2 = join35(dir, LAST_DIST_FILE);
|
|
22543
|
+
try {
|
|
22544
|
+
if (lstatSync9(join35(dir, ".himi")).isSymbolicLink() || lstatSync9(path2).isSymbolicLink()) return null;
|
|
22545
|
+
} catch {
|
|
22546
|
+
return null;
|
|
22547
|
+
}
|
|
22548
|
+
try {
|
|
22549
|
+
const v = JSON.parse(readFileSync33(path2, "utf8"));
|
|
22550
|
+
if (typeof v.app !== "string" || typeof v.jobId !== "string" || typeof v.shell !== "string" || !Array.isArray(v.capabilities)) return null;
|
|
22551
|
+
return { app: v.app, jobId: v.jobId, shell: v.shell, capabilities: v.capabilities.filter((x) => typeof x === "string") };
|
|
22552
|
+
} catch {
|
|
22553
|
+
return null;
|
|
22554
|
+
}
|
|
22555
|
+
}
|
|
22556
|
+
function lastDistFromJob(job, now = /* @__PURE__ */ new Date()) {
|
|
22557
|
+
if (job.status !== "SUCCESS" || !Array.isArray(job.buildCapabilities)) return null;
|
|
22558
|
+
if (typeof job.app !== "string" || typeof job.jobId !== "string" || typeof job.shell !== "string") return null;
|
|
22559
|
+
const capabilities = [...new Set(job.buildCapabilities.filter((x) => typeof x === "string").map(catalogKey))].sort();
|
|
22560
|
+
return { app: job.app, jobId: job.jobId, shell: job.shell, capabilities, finishedAt: now.toISOString() };
|
|
22561
|
+
}
|
|
22562
|
+
function needsBinaryNotice(json6) {
|
|
22563
|
+
const nb = json6?.needsBinary;
|
|
22564
|
+
if (!nb || !Array.isArray(nb.missing)) return null;
|
|
22565
|
+
const missing = nb.missing.filter((x) => typeof x === "string");
|
|
22566
|
+
if (!missing.length) return null;
|
|
22567
|
+
return {
|
|
22568
|
+
missing,
|
|
22569
|
+
...typeof nb.lastDistJobId === "string" ? { lastDistJobId: nb.lastDistJobId } : {},
|
|
22570
|
+
message: `needs a new binary: ${missing.join(", ")} \u2014 run himi dist`
|
|
22571
|
+
};
|
|
22572
|
+
}
|
|
22518
22573
|
function noImportLabel(id) {
|
|
22519
22574
|
return BRIDGE_CAPABILITY_IDS.includes(id) || id === "wix_platform" ? "a bridge capability (no @himalaya import of its own)" : "declare-only (no @himalaya import of its own)";
|
|
22520
22575
|
}
|
|
@@ -22583,7 +22638,7 @@ function didYouMean(id) {
|
|
|
22583
22638
|
const best = KNOWN_CAPABILITY_IDS.map((k) => [k, editDistance2(id, k)]).sort((x, y) => x[1] - y[1])[0];
|
|
22584
22639
|
return best && best[1] <= 2 ? ` Did you mean "${best[0]}"?` : "";
|
|
22585
22640
|
}
|
|
22586
|
-
function capabilityIssues(usage, shell, strict) {
|
|
22641
|
+
function capabilityIssues(usage, shell, strict, lastDist) {
|
|
22587
22642
|
if (!usage) return [];
|
|
22588
22643
|
const warn = strict ? "error" : "warning";
|
|
22589
22644
|
const issues = [];
|
|
@@ -22662,7 +22717,19 @@ function capabilityIssues(usage, shell, strict) {
|
|
|
22662
22717
|
for (const sc of usage.screens) for (const id of [...sc.declared, ...sc.bundle ? usage.bundles[sc.bundle] ?? [] : []]) required3.add(catalogKey(id));
|
|
22663
22718
|
const unserved = [...required3].filter((id) => isKnownCapability(id) && !shellServes(shell, id)).sort();
|
|
22664
22719
|
const addable = s.extensible ? unserved.filter((id) => shellDistAdds(shell, id)) : [];
|
|
22665
|
-
|
|
22720
|
+
const installed = lastDist && lastDist.shell === shell ? expandCapabilities(lastDist.capabilities.map(catalogKey)) : null;
|
|
22721
|
+
if (installed) {
|
|
22722
|
+
const stale = [...required3].filter((id) => isKnownCapability(id) && !installed.has(id) && (shellServes(shell, id) || s.extensible && shellDistAdds(shell, id))).sort();
|
|
22723
|
+
if (stale.length) {
|
|
22724
|
+
issues.push({
|
|
22725
|
+
screen: null,
|
|
22726
|
+
pass: "capabilities",
|
|
22727
|
+
severity: "warning",
|
|
22728
|
+
capability: stale.join(","),
|
|
22729
|
+
message: `needs a new binary: ${stale.join(", ")} \u2014 your last \`himi dist\` (${lastDist.jobId}) was not built with ${stale.length === 1 ? "it" : "them"}, so that install cannot call ${stale.length === 1 ? "it" : "them"} until you run \`himi dist\` again. Preview with \`himi dist --plan\`.`
|
|
22730
|
+
});
|
|
22731
|
+
}
|
|
22732
|
+
} else if (addable.length) {
|
|
22666
22733
|
issues.push({
|
|
22667
22734
|
screen: null,
|
|
22668
22735
|
pass: "capabilities",
|
|
@@ -22722,7 +22789,7 @@ function capabilityConfigFindings(sourceDir, builtDir) {
|
|
|
22722
22789
|
}
|
|
22723
22790
|
return out2;
|
|
22724
22791
|
}
|
|
22725
|
-
var DECLARE_HINT, REGISTRY, PACKAGES, SHELLS, servedSets, MAX_SUGGEST_LEN2, clip;
|
|
22792
|
+
var DECLARE_HINT, REGISTRY, PACKAGES, SHELLS, servedSets, LAST_DIST_FILE, MAX_SUGGEST_LEN2, clip;
|
|
22726
22793
|
var init_capabilities = __esm({
|
|
22727
22794
|
"src/capabilities.ts"() {
|
|
22728
22795
|
init_src2();
|
|
@@ -22734,6 +22801,7 @@ var init_capabilities = __esm({
|
|
|
22734
22801
|
PACKAGES = CAPABILITY_PACKAGES;
|
|
22735
22802
|
SHELLS = Object.keys(SHELL_CAPABILITIES).sort();
|
|
22736
22803
|
servedSets = /* @__PURE__ */ new Map();
|
|
22804
|
+
LAST_DIST_FILE = join35(".himi", "last-dist.json");
|
|
22737
22805
|
MAX_SUGGEST_LEN2 = 64;
|
|
22738
22806
|
clip = (id) => id.length > MAX_SUGGEST_LEN2 ? `${id.slice(0, MAX_SUGGEST_LEN2)}\u2026` : id;
|
|
22739
22807
|
}
|
|
@@ -22828,7 +22896,7 @@ var init_provisioning_facts = __esm({
|
|
|
22828
22896
|
|
|
22829
22897
|
// ../../core/dev-server/src/app-assets.ts
|
|
22830
22898
|
import { createHash as createHash10 } from "node:crypto";
|
|
22831
|
-
import { existsSync as existsSync34, lstatSync as
|
|
22899
|
+
import { existsSync as existsSync34, lstatSync as lstatSync10, readFileSync as readFileSync35, readdirSync as readdirSync19, realpathSync as realpathSync5, statSync as statSync8 } from "node:fs";
|
|
22832
22900
|
import { join as join36, resolve as resolve24, sep as sep8 } from "node:path";
|
|
22833
22901
|
function contentTypeFor(name) {
|
|
22834
22902
|
const ext = name.includes(".") ? name.slice(name.lastIndexOf(".") + 1).toLowerCase() : "";
|
|
@@ -22906,7 +22974,7 @@ function collectBrandFonts(appDir3) {
|
|
|
22906
22974
|
if (rel.startsWith("/") || rel.split("/").includes("..")) continue;
|
|
22907
22975
|
const path2 = join36(appDir3, rel);
|
|
22908
22976
|
try {
|
|
22909
|
-
if (!
|
|
22977
|
+
if (!lstatSync10(path2).isFile()) continue;
|
|
22910
22978
|
const root = realpathSync5(appDir3);
|
|
22911
22979
|
if (!realpathSync5(path2).startsWith(root + sep8)) continue;
|
|
22912
22980
|
} catch {
|
|
@@ -28204,6 +28272,10 @@ function assertSiteShellConfig(site) {
|
|
|
28204
28272
|
seen.add(page.id);
|
|
28205
28273
|
sitePageUrl(site.origin, page.path);
|
|
28206
28274
|
}
|
|
28275
|
+
for (const [key2, path2] of Object.entries(site.memberPages ?? {})) {
|
|
28276
|
+
if (typeof path2 !== "string" || !path2) throw new Error(`site memberPages.${key2} must be a path`);
|
|
28277
|
+
sitePageUrl(site.origin, path2);
|
|
28278
|
+
}
|
|
28207
28279
|
if (site.layout !== void 0 && !SITE_LAYOUTS.includes(site.layout)) {
|
|
28208
28280
|
throw new Error(`site layout ${JSON.stringify(site.layout)} must be one of ${SITE_LAYOUTS.join(", ")}`);
|
|
28209
28281
|
}
|
|
@@ -44696,17 +44768,21 @@ var init_net_scenario = __esm({
|
|
|
44696
44768
|
});
|
|
44697
44769
|
|
|
44698
44770
|
// ../../core/dev-server/src/capability-fixtures.ts
|
|
44699
|
-
function
|
|
44700
|
-
const out2 =
|
|
44701
|
-
for (const
|
|
44702
|
-
|
|
44703
|
-
out2.
|
|
44771
|
+
function capabilityCoverageFor(bundleJs, granted) {
|
|
44772
|
+
const out2 = {};
|
|
44773
|
+
for (const action of capabilityDispatchesIn(bundleJs)) {
|
|
44774
|
+
const id = action.split(".")[1];
|
|
44775
|
+
out2[action] = !granted(id) ? "denied" : STORE_BACKED.has(id) ? "store" : CAPABILITY_SUCCESS[action] ? "success" : "unavailable-only";
|
|
44704
44776
|
}
|
|
44705
|
-
return
|
|
44777
|
+
return out2;
|
|
44778
|
+
}
|
|
44779
|
+
function capabilityIdsIn(bundleJs) {
|
|
44780
|
+
return capabilityDispatchesIn(bundleJs).filter((action) => !STORE_BACKED.has(action.split(".")[1]));
|
|
44706
44781
|
}
|
|
44707
44782
|
var decodeItem, MEDIA_EXT, MEDIA_MIME, FILE, CALL, MEDIA_STATUS, CAPABILITY_SUCCESS, STORE_BACKED;
|
|
44708
44783
|
var init_capability_fixtures = __esm({
|
|
44709
44784
|
"../../core/dev-server/src/capability-fixtures.ts"() {
|
|
44785
|
+
init_known();
|
|
44710
44786
|
decodeItem = (raw) => {
|
|
44711
44787
|
if (typeof raw !== "string") return {};
|
|
44712
44788
|
try {
|
|
@@ -44889,7 +44965,34 @@ var init_capability_fixtures = __esm({
|
|
|
44889
44965
|
const name = path2.split("/").pop() || path2;
|
|
44890
44966
|
return { uri: `file:///${path2.replace(/^\/+/, "")}`, name, mimeType: typeof p.mimeType === "string" ? p.mimeType : "application/octet-stream", sizeBytes: 1024, modifiedAt: ctx.clock };
|
|
44891
44967
|
},
|
|
44892
|
-
"capability.files.remove": () => ({ removed: true })
|
|
44968
|
+
"capability.files.remove": () => ({ removed: true }),
|
|
44969
|
+
// --- camera -----------------------------------------------------------------------------------
|
|
44970
|
+
// `capturePhoto` THROWS on an envelope with no `uri` ("native handler returned no uri") and maps
|
|
44971
|
+
// `cancelled` to null, so the answer is a bare CapturedAsset — not wrapped, not cancelled.
|
|
44972
|
+
"capability.camera.capture": () => ({
|
|
44973
|
+
uri: "file:///crawl/photo-1.jpg",
|
|
44974
|
+
mime: "image/jpeg",
|
|
44975
|
+
width: 1024,
|
|
44976
|
+
height: 768,
|
|
44977
|
+
byteSize: 204800
|
|
44978
|
+
}),
|
|
44979
|
+
"capability.camera.pickDocument": () => ({
|
|
44980
|
+
uri: "file:///crawl/document-1.pdf",
|
|
44981
|
+
name: "document-1.pdf",
|
|
44982
|
+
mime: "application/pdf",
|
|
44983
|
+
byteSize: 40960
|
|
44984
|
+
}),
|
|
44985
|
+
"capability.camera.scanQR": () => ({ code: "crawl-qr-code" }),
|
|
44986
|
+
// --- location ---------------------------------------------------------------------------------
|
|
44987
|
+
// `getCurrent` reads `r.position` and returns null without it — the very "no fix" arm the main
|
|
44988
|
+
// axis already covers. The watch verbs are deliberately absent: `watchNext` is a long poll the
|
|
44989
|
+
// binding loops on until `stopped`, and a stateless fixture that always answered a position would
|
|
44990
|
+
// spin a `for await` for the crawl's whole budget.
|
|
44991
|
+
"capability.location.getPermissionStatus": () => ({ status: "granted" }),
|
|
44992
|
+
"capability.location.requestPermission": () => ({ status: "granted" }),
|
|
44993
|
+
"capability.location.getCurrent": (_p, ctx) => ({
|
|
44994
|
+
position: { lat: 32.0853, lng: 34.7818, accuracy: 10, timestamp: ctx.clock }
|
|
44995
|
+
})
|
|
44893
44996
|
};
|
|
44894
44997
|
STORE_BACKED = /* @__PURE__ */ new Set(["local_database", "secure_storage"]);
|
|
44895
44998
|
}
|
|
@@ -45642,7 +45745,7 @@ function declaredCapabilitiesOf(descriptor) {
|
|
|
45642
45745
|
const list = Array.isArray(raw) ? raw : [];
|
|
45643
45746
|
return new Set(list.filter((c) => typeof c === "string"));
|
|
45644
45747
|
}
|
|
45645
|
-
function hostDispatch(localDbSchema, declaredCapabilities = /* @__PURE__ */ new Set(), capabilitySuccess = false, clock = 0) {
|
|
45748
|
+
function hostDispatch(localDbSchema, declaredCapabilities = /* @__PURE__ */ new Set(), capabilitySuccess = false, clock = 0, onDenied) {
|
|
45646
45749
|
const localDb = /* @__PURE__ */ new Map();
|
|
45647
45750
|
const writeQueue = [];
|
|
45648
45751
|
const secureKv = /* @__PURE__ */ new Map();
|
|
@@ -45653,6 +45756,7 @@ function hostDispatch(localDbSchema, declaredCapabilities = /* @__PURE__ */ new
|
|
|
45653
45756
|
const cap = CAPABILITY_ACTION.exec(action);
|
|
45654
45757
|
if (cap) {
|
|
45655
45758
|
if (!grants(declaredCapabilities, cap[1])) {
|
|
45759
|
+
onDenied?.(cap[1], action);
|
|
45656
45760
|
throw new Error(
|
|
45657
45761
|
`${cap[1]}: capability not declared \u2014 add "${cap[1]}" to this screen's logic_bundle.declaredCapabilities. An undeclared capability call is blocked on device.`
|
|
45658
45762
|
);
|
|
@@ -46035,7 +46139,16 @@ async function bootScreenWorker(opts) {
|
|
|
46035
46139
|
};
|
|
46036
46140
|
const appear = opts.skipAppear ? null : rootOnAppear(opts.descriptor);
|
|
46037
46141
|
const hostCalls = [];
|
|
46038
|
-
const
|
|
46142
|
+
const deniedCapabilities = [];
|
|
46143
|
+
const host = hostDispatch(
|
|
46144
|
+
opts.localDbSchema,
|
|
46145
|
+
declaredCapabilitiesOf(opts.descriptor),
|
|
46146
|
+
opts.capabilitySuccess === true,
|
|
46147
|
+
opts.clock ?? CRAWL_CLOCK,
|
|
46148
|
+
(_id, action) => {
|
|
46149
|
+
deniedCapabilities.push(action);
|
|
46150
|
+
}
|
|
46151
|
+
);
|
|
46039
46152
|
const w = await bootBundleWorker(opts.bundleJs, {
|
|
46040
46153
|
wixCall: opts.wixCall,
|
|
46041
46154
|
input,
|
|
@@ -46070,7 +46183,7 @@ async function bootScreenWorker(opts) {
|
|
|
46070
46183
|
}
|
|
46071
46184
|
for (let i = 0; i < 20; i++) await yieldMacrotask();
|
|
46072
46185
|
const { state, defects } = snapshot(w);
|
|
46073
|
-
return { ...w, hostCalls, state: Object.keys(state).length ? state : w.services, defects, ...deadLifecycle ? { deadLifecycle } : {} };
|
|
46186
|
+
return { ...w, hostCalls, deniedCapabilities, state: Object.keys(state).length ? state : w.services, defects, ...deadLifecycle ? { deadLifecycle } : {} };
|
|
46074
46187
|
}
|
|
46075
46188
|
function effectSince(w, mark, action, componentId) {
|
|
46076
46189
|
const wix = w.calls.slice(mark.wix);
|
|
@@ -46131,12 +46244,14 @@ async function withSwallowedRejections(fn) {
|
|
|
46131
46244
|
async function crawlScreenCapabilitySuccess(opts) {
|
|
46132
46245
|
return withSwallowedRejections(async () => {
|
|
46133
46246
|
const declared = declaredCapabilitiesOf(opts.descriptor);
|
|
46247
|
+
const coverage = opts.bundleJs ? capabilityCoverageFor(opts.bundleJs, (id) => grants(declared, id)) : {};
|
|
46134
46248
|
const modelled = opts.bundleJs ? capabilityIdsIn(opts.bundleJs).filter((id) => CAPABILITY_SUCCESS[id] && grants(declared, id.split(".")[1])) : [];
|
|
46135
|
-
if (!modelled.length) return { findings: [], modelled: 0 };
|
|
46249
|
+
if (!modelled.length) return { findings: [], modelled: 0, coverage };
|
|
46136
46250
|
const r = await crawlScreenInner({ ...opts, capabilitySuccess: true });
|
|
46137
46251
|
return {
|
|
46138
46252
|
findings: r.findings.map((f) => ({ ...f, axis: f.axis ? `capability:success+${f.axis}` : "capability:success" })),
|
|
46139
46253
|
modelled: modelled.length,
|
|
46254
|
+
coverage,
|
|
46140
46255
|
// The success arm is where a paid flow goes on past `isAvailable` to `proceedToPayment`, so
|
|
46141
46256
|
// its effects are the write inventory's too — unlike its counts, they cannot double anything.
|
|
46142
46257
|
...r.effects?.length ? { effects: r.effects } : {}
|
|
@@ -46530,11 +46645,29 @@ async function crawlScreenInner(opts) {
|
|
|
46530
46645
|
hint: `${d.reason === "no-service" ? "services defined" : "actions declared"}: ${d.declared.join(", ") || "none"}. The host fires onAppear on device, so this screen stays on its initial state forever.`
|
|
46531
46646
|
});
|
|
46532
46647
|
};
|
|
46648
|
+
const deniedReported = /* @__PURE__ */ new Set();
|
|
46649
|
+
const reportDenied = (actions, trigger) => {
|
|
46650
|
+
for (const action of actions) {
|
|
46651
|
+
const id = CAPABILITY_ACTION.exec(action)?.[1] ?? action;
|
|
46652
|
+
if (deniedReported.has(id)) continue;
|
|
46653
|
+
deniedReported.add(id);
|
|
46654
|
+
findings.push({
|
|
46655
|
+
screen: screenId,
|
|
46656
|
+
trigger,
|
|
46657
|
+
invariant: "capability-undeclared",
|
|
46658
|
+
severity: "error",
|
|
46659
|
+
actual: action,
|
|
46660
|
+
message: `${action} was denied: "${id}" is not in this screen's logicBundle.declaredCapabilities \u2014 the call is blocked on device`,
|
|
46661
|
+
hint: `add "${id}" to this screen's logicBundle.declaredCapabilities (the runtime gate) and to config.ts capabilities (what the binary is built with). If this screen should never make the call, it is reaching a shared worker action meant for another screen. A try/catch around the call hides this on device: the control just does nothing.`
|
|
46662
|
+
});
|
|
46663
|
+
}
|
|
46664
|
+
};
|
|
46533
46665
|
const first = await boot();
|
|
46534
46666
|
if (first) {
|
|
46535
46667
|
try {
|
|
46536
46668
|
if (first.deadLifecycle) reportDeadLifecycle(first.deadLifecycle);
|
|
46537
46669
|
check("boot", first.state, first.errors, first.defects);
|
|
46670
|
+
reportDenied(first.deniedCapabilities, "boot");
|
|
46538
46671
|
observations = collectObservations(descriptor, first.state);
|
|
46539
46672
|
seedWords = seedTokens(first.state);
|
|
46540
46673
|
harvestStaticEdges(first.state);
|
|
@@ -46628,6 +46761,7 @@ async function crawlScreenInner(opts) {
|
|
|
46628
46761
|
const before = JSON.stringify(preState);
|
|
46629
46762
|
const callsBefore = w.calls.length;
|
|
46630
46763
|
const hostBefore = w.hostCalls.length;
|
|
46764
|
+
const deniedBefore = w.deniedCapabilities.length;
|
|
46631
46765
|
const errors = [];
|
|
46632
46766
|
try {
|
|
46633
46767
|
await handle.dispatch(parsed.action, ...workerArgs(dispatched));
|
|
@@ -46637,6 +46771,7 @@ async function crawlScreenInner(opts) {
|
|
|
46637
46771
|
exercised += 1;
|
|
46638
46772
|
const { state, defects } = snapshot(w);
|
|
46639
46773
|
check(trigger, state, errors, defects);
|
|
46774
|
+
reportDenied(w.deniedCapabilities.slice(deniedBefore), trigger);
|
|
46640
46775
|
const navCountBefore = navEdges.length;
|
|
46641
46776
|
drainWorkerEdges(w, `worker:${site.actionId}`);
|
|
46642
46777
|
drainPollDirectives(w, trigger);
|
|
@@ -51098,7 +51233,7 @@ __export(config_push_edit_exports, {
|
|
|
51098
51233
|
enablePushInConfig: () => enablePushInConfig,
|
|
51099
51234
|
previewPushConfigEdit: () => previewPushConfigEdit
|
|
51100
51235
|
});
|
|
51101
|
-
import { lstatSync as
|
|
51236
|
+
import { lstatSync as lstatSync11, readFileSync as readFileSync53 } from "node:fs";
|
|
51102
51237
|
import { basename as basename13, join as join46 } from "node:path";
|
|
51103
51238
|
import ts from "typescript";
|
|
51104
51239
|
function enablePushInConfig(source, options) {
|
|
@@ -51328,7 +51463,7 @@ function backUp(dir, fileName, original) {
|
|
|
51328
51463
|
}
|
|
51329
51464
|
function isSymlink2(path2) {
|
|
51330
51465
|
try {
|
|
51331
|
-
return
|
|
51466
|
+
return lstatSync11(path2).isSymbolicLink();
|
|
51332
51467
|
} catch {
|
|
51333
51468
|
return false;
|
|
51334
51469
|
}
|
|
@@ -51991,7 +52126,7 @@ __export(app_source_exports, {
|
|
|
51991
52126
|
zipAppSource: () => zipAppSource
|
|
51992
52127
|
});
|
|
51993
52128
|
import { createHash as createHash24 } from "node:crypto";
|
|
51994
|
-
import { readdirSync as readdirSync27, lstatSync as
|
|
52129
|
+
import { readdirSync as readdirSync27, lstatSync as lstatSync12, readFileSync as readFileSync55, mkdirSync as mkdirSync24, writeFileSync as writeFileSync19, existsSync as existsSync49, rmSync as rmSync11, rmdirSync as rmdirSync2, unlinkSync as unlinkSync3 } from "node:fs";
|
|
51995
52130
|
import { join as join48, resolve as resolve41, sep as sep11 } from "node:path";
|
|
51996
52131
|
import { createRequire as createRequire3 } from "node:module";
|
|
51997
52132
|
import { deflateRawSync as deflateRawSync2 } from "node:zlib";
|
|
@@ -52001,7 +52136,7 @@ function walkSourceTree(dir, file, skip, enter = () => {
|
|
|
52001
52136
|
enter(relDir);
|
|
52002
52137
|
for (const name of readdirSync27(join48(dir, relDir))) {
|
|
52003
52138
|
const rel = relDir ? join48(relDir, name) : name;
|
|
52004
|
-
const st =
|
|
52139
|
+
const st = lstatSync12(join48(dir, rel));
|
|
52005
52140
|
if (st.isSymbolicLink()) {
|
|
52006
52141
|
skip(rel);
|
|
52007
52142
|
continue;
|
|
@@ -52082,7 +52217,7 @@ function newestSourceChangeMs(dir) {
|
|
|
52082
52217
|
dir,
|
|
52083
52218
|
(archivePath, hostRel) => {
|
|
52084
52219
|
if (/\.md$/i.test(archivePath) || !counts(hostRel)) return;
|
|
52085
|
-
const m =
|
|
52220
|
+
const m = lstatSync12(join48(dir, hostRel)).mtimeMs;
|
|
52086
52221
|
if (m > newest) newest = m;
|
|
52087
52222
|
},
|
|
52088
52223
|
() => {
|
|
@@ -52094,7 +52229,7 @@ function newestSourceChangeMs(dir) {
|
|
|
52094
52229
|
// Nor a root directory the render does not read: creating a file in `_qa/` moves `_qa/`.
|
|
52095
52230
|
(hostRel) => {
|
|
52096
52231
|
if (hostRel === "" || !inRead(hostRel)) return;
|
|
52097
|
-
const m =
|
|
52232
|
+
const m = lstatSync12(join48(dir, hostRel)).mtimeMs;
|
|
52098
52233
|
if (m > newest) newest = m;
|
|
52099
52234
|
}
|
|
52100
52235
|
);
|
|
@@ -52142,7 +52277,7 @@ function readAppSource(zip) {
|
|
|
52142
52277
|
}
|
|
52143
52278
|
function pathKind(p) {
|
|
52144
52279
|
try {
|
|
52145
|
-
return
|
|
52280
|
+
return lstatSync12(p);
|
|
52146
52281
|
} catch {
|
|
52147
52282
|
return null;
|
|
52148
52283
|
}
|
|
@@ -52239,7 +52374,7 @@ function pruneEmptyDirs(root, rel, needed, removed) {
|
|
|
52239
52374
|
for (const name of readdirSync27(rel ? join48(root, rel) : root)) {
|
|
52240
52375
|
const childRel = rel ? join48(rel, name) : name;
|
|
52241
52376
|
const posix5 = childRel.split(sep11).join("/");
|
|
52242
|
-
const kind =
|
|
52377
|
+
const kind = lstatSync12(join48(root, childRel));
|
|
52243
52378
|
if (kind.isSymbolicLink() || !kind.isDirectory() || isDeniedSourceDir(name, rel)) {
|
|
52244
52379
|
empty = false;
|
|
52245
52380
|
continue;
|
|
@@ -52319,7 +52454,7 @@ __export(run_watch_exports, {
|
|
|
52319
52454
|
watchedContentDir: () => watchedContentDir,
|
|
52320
52455
|
writeRunRecord: () => writeRunRecord
|
|
52321
52456
|
});
|
|
52322
|
-
import { closeSync as closeSync3, constants, existsSync as existsSync50, fstatSync, lstatSync as
|
|
52457
|
+
import { closeSync as closeSync3, constants, existsSync as existsSync50, fstatSync, lstatSync as lstatSync13, statSync as statSync13, mkdirSync as mkdirSync25, openSync as openSync3, readFileSync as readFileSync56, realpathSync as realpathSync7, renameSync as renameSync7, rmSync as rmSync12, writeFileSync as writeFileSync20, watch as fsWatch } from "node:fs";
|
|
52323
52458
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
52324
52459
|
import { extname as extname4, join as join49, resolve as resolve42, sep as sep12 } from "node:path";
|
|
52325
52460
|
function diffReleases(prev, next) {
|
|
@@ -52442,11 +52577,11 @@ function serialize(task) {
|
|
|
52442
52577
|
function himiDir(dir) {
|
|
52443
52578
|
const root = join49(dir, ".himi");
|
|
52444
52579
|
try {
|
|
52445
|
-
return
|
|
52580
|
+
return lstatSync13(root).isDirectory() ? root : null;
|
|
52446
52581
|
} catch (e) {
|
|
52447
52582
|
if (e.code !== "ENOENT") return null;
|
|
52448
52583
|
mkdirSync25(root, { recursive: true });
|
|
52449
|
-
return
|
|
52584
|
+
return lstatSync13(root).isDirectory() ? root : null;
|
|
52450
52585
|
}
|
|
52451
52586
|
}
|
|
52452
52587
|
function writeRunRecord(dir, record2) {
|
|
@@ -52466,7 +52601,7 @@ function writeRunRecord(dir, record2) {
|
|
|
52466
52601
|
function removeRunRecord(dir, owner) {
|
|
52467
52602
|
const root = join49(dir, ".himi");
|
|
52468
52603
|
try {
|
|
52469
|
-
if (!
|
|
52604
|
+
if (!lstatSync13(root).isDirectory()) return;
|
|
52470
52605
|
} catch {
|
|
52471
52606
|
return;
|
|
52472
52607
|
}
|
|
@@ -52484,7 +52619,7 @@ function removeRunRecord(dir, owner) {
|
|
|
52484
52619
|
function removeInitWatchRecord(dir, owner) {
|
|
52485
52620
|
const root = join49(dir, ".himi");
|
|
52486
52621
|
try {
|
|
52487
|
-
if (!
|
|
52622
|
+
if (!lstatSync13(root).isDirectory()) return;
|
|
52488
52623
|
} catch {
|
|
52489
52624
|
return;
|
|
52490
52625
|
}
|
|
@@ -52521,12 +52656,12 @@ function boardFile(dir, pathname) {
|
|
|
52521
52656
|
}
|
|
52522
52657
|
function readConfined(root, rel) {
|
|
52523
52658
|
try {
|
|
52524
|
-
if (!
|
|
52659
|
+
if (!lstatSync13(root).isDirectory()) return null;
|
|
52525
52660
|
const parts = rel.split(sep12);
|
|
52526
52661
|
let at2 = root;
|
|
52527
52662
|
for (let i = 0; i < parts.length; i++) {
|
|
52528
52663
|
at2 = join49(at2, parts[i]);
|
|
52529
|
-
const st =
|
|
52664
|
+
const st = lstatSync13(at2);
|
|
52530
52665
|
if (st.isSymbolicLink() || !(i === parts.length - 1 ? st.isFile() : st.isDirectory())) return null;
|
|
52531
52666
|
}
|
|
52532
52667
|
const canonicalRoot = realpathSync7(root);
|
|
@@ -52592,7 +52727,7 @@ __export(screen_state_exports, {
|
|
|
52592
52727
|
withHomeCaveat: () => withHomeCaveat
|
|
52593
52728
|
});
|
|
52594
52729
|
import { createHash as createHash25 } from "node:crypto";
|
|
52595
|
-
import { lstatSync as
|
|
52730
|
+
import { lstatSync as lstatSync14, readFileSync as readFileSync57 } from "node:fs";
|
|
52596
52731
|
import { join as join50 } from "node:path";
|
|
52597
52732
|
function quotableLine(text8) {
|
|
52598
52733
|
if (!text8) return void 0;
|
|
@@ -52651,7 +52786,7 @@ function screenStateOf(services, html, opts = {}) {
|
|
|
52651
52786
|
function readRecord(dir) {
|
|
52652
52787
|
const file = join50(dir, SHOT_STATES_FILE);
|
|
52653
52788
|
try {
|
|
52654
|
-
if (!
|
|
52789
|
+
if (!lstatSync14(file).isFile()) return {};
|
|
52655
52790
|
const r = JSON.parse(readFileSync57(file, "utf8"));
|
|
52656
52791
|
return r.v === 1 && r.shots && typeof r.shots === "object" ? r.shots : {};
|
|
52657
52792
|
} catch {
|
|
@@ -52727,7 +52862,7 @@ __export(studio_reel_exports, {
|
|
|
52727
52862
|
utcDay: () => utcDay
|
|
52728
52863
|
});
|
|
52729
52864
|
import { createHash as createHash26 } from "node:crypto";
|
|
52730
|
-
import { lstatSync as
|
|
52865
|
+
import { lstatSync as lstatSync15, readFileSync as readFileSync58 } from "node:fs";
|
|
52731
52866
|
import { basename as basename14, join as join51 } from "node:path";
|
|
52732
52867
|
function loadMemory2(dir) {
|
|
52733
52868
|
const m = readJson5(join51(dir, ".himi", "studio", "memory.json"));
|
|
@@ -52812,7 +52947,7 @@ function inlineImages(dir, plan, budget = REEL_BUDGET_BYTES) {
|
|
|
52812
52947
|
if (!HISTORY_NAME.test(f)) return null;
|
|
52813
52948
|
try {
|
|
52814
52949
|
const path2 = join51(dir, HISTORY_DIR, f);
|
|
52815
|
-
const st =
|
|
52950
|
+
const st = lstatSync15(path2);
|
|
52816
52951
|
if (!st.isFile() || st.nlink !== 1) return null;
|
|
52817
52952
|
const bytes2 = readFileSync58(path2);
|
|
52818
52953
|
return createHash26("sha256").update(bytes2).digest("hex") === f.slice(0, 64) && imageExtension(bytes2) === f.slice(65) ? bytes2 : null;
|
|
@@ -53019,7 +53154,7 @@ __export(milestones_exports, {
|
|
|
53019
53154
|
});
|
|
53020
53155
|
import { createHash as createHash27, randomUUID as randomUUID4 } from "node:crypto";
|
|
53021
53156
|
import { spawn as spawn5 } from "node:child_process";
|
|
53022
|
-
import { closeSync as closeSync4, existsSync as existsSync51, lstatSync as
|
|
53157
|
+
import { closeSync as closeSync4, existsSync as existsSync51, lstatSync as lstatSync16, readFileSync as readFileSync59, statSync as statSync14 } from "node:fs";
|
|
53023
53158
|
import { createServer as createServer4 } from "node:net";
|
|
53024
53159
|
import { basename as basename15, dirname as dirname29, isAbsolute as isAbsolute4, join as join52, relative as relative9, resolve as resolve43, sep as sep13 } from "node:path";
|
|
53025
53160
|
function homeScreenIdOf(dir) {
|
|
@@ -53040,7 +53175,7 @@ function homeShotPath(dir, home) {
|
|
|
53040
53175
|
if (!shot.startsWith(base + sep13)) return null;
|
|
53041
53176
|
for (let p = shot; p !== root && p.startsWith(root + sep13); p = dirname29(p)) {
|
|
53042
53177
|
try {
|
|
53043
|
-
if (
|
|
53178
|
+
if (lstatSync16(p).isSymbolicLink()) return null;
|
|
53044
53179
|
} catch {
|
|
53045
53180
|
}
|
|
53046
53181
|
}
|
|
@@ -53375,7 +53510,7 @@ function rememberDraft(dir, releaseId, m) {
|
|
|
53375
53510
|
function lastDraft(dir) {
|
|
53376
53511
|
try {
|
|
53377
53512
|
const file = join52(dir, LAST_DRAFT_FILE);
|
|
53378
|
-
if (!
|
|
53513
|
+
if (!lstatSync16(file).isFile()) return null;
|
|
53379
53514
|
const r = JSON.parse(readFileSync59(file, "utf8"));
|
|
53380
53515
|
const m = r.manifest;
|
|
53381
53516
|
if (r.v !== 1 || typeof r.releaseId !== "string" || !m || typeof m.screens !== "object" || !m.screens || typeof m.bundles !== "object" || !m.bundles) return null;
|
|
@@ -53437,7 +53572,7 @@ function recordInitSource(dir, opts = {}) {
|
|
|
53437
53572
|
function readInitSource(dir) {
|
|
53438
53573
|
const file = join52(dir, INIT_SOURCE_FILE);
|
|
53439
53574
|
try {
|
|
53440
|
-
if (!
|
|
53575
|
+
if (!lstatSync16(file).isFile()) return null;
|
|
53441
53576
|
const r = JSON.parse(readFileSync59(file, "utf8"));
|
|
53442
53577
|
if (r.v !== 1 || typeof r.fingerprint !== "string" || !/^[0-9a-f]{64}$/.test(r.fingerprint) || typeof r.newestMs !== "number") return null;
|
|
53443
53578
|
return {
|
|
@@ -53512,7 +53647,7 @@ function contentHomePictures(dir, home) {
|
|
|
53512
53647
|
if (r.state.state !== "content") continue;
|
|
53513
53648
|
for (const file of [shot, hist && join52(hist, `${r.sha256}.png`)]) {
|
|
53514
53649
|
try {
|
|
53515
|
-
if (!file || !
|
|
53650
|
+
if (!file || !lstatSync16(file).isFile() || sha256Of2(file) !== r.sha256) continue;
|
|
53516
53651
|
} catch {
|
|
53517
53652
|
continue;
|
|
53518
53653
|
}
|
|
@@ -53525,7 +53660,7 @@ function contentHomePictures(dir, home) {
|
|
|
53525
53660
|
function readInitWatch(dir) {
|
|
53526
53661
|
const file = join52(dir, INIT_WATCH_FILE);
|
|
53527
53662
|
try {
|
|
53528
|
-
if (!
|
|
53663
|
+
if (!lstatSync16(file).isFile()) return null;
|
|
53529
53664
|
const r = JSON.parse(readFileSync59(file, "utf8"));
|
|
53530
53665
|
if (r.v !== 1 || typeof r.pid !== "number" || !Number.isInteger(r.pid) || r.pid < 1) return null;
|
|
53531
53666
|
if (typeof r.url !== "string" || !/^http:\/\/127\.0\.0\.1:\d+\//.test(r.url) || typeof r.board !== "string" || typeof r.log !== "string" || typeof r.startedAt !== "string") return null;
|
|
@@ -53558,7 +53693,7 @@ function watchReminder(dir, opts = {}) {
|
|
|
53558
53693
|
const file = join52(dir, REMINDERS_FILE);
|
|
53559
53694
|
let last = Number.NaN;
|
|
53560
53695
|
try {
|
|
53561
|
-
if (
|
|
53696
|
+
if (lstatSync16(file).isFile()) last = Date.parse(JSON.parse(readFileSync59(file, "utf8")).watch ?? "");
|
|
53562
53697
|
} catch {
|
|
53563
53698
|
}
|
|
53564
53699
|
if (Number.isFinite(last) && now - last >= 0 && now - last < WATCH_REMINDER_EVERY_MS) return void 0;
|
|
@@ -53681,7 +53816,7 @@ function afterHandoverPostcard(dir, opts = {}) {
|
|
|
53681
53816
|
const file = join52(dir, ".himi", "making-of.html");
|
|
53682
53817
|
let fresh = false;
|
|
53683
53818
|
try {
|
|
53684
|
-
const st =
|
|
53819
|
+
const st = lstatSync16(file);
|
|
53685
53820
|
fresh = st.isFile() && !st.isSymbolicLink() && st.mtimeMs >= statSync14(join52(dir, ".himi", "progress.jsonl")).mtimeMs;
|
|
53686
53821
|
} catch {
|
|
53687
53822
|
fresh = false;
|
|
@@ -54811,6 +54946,7 @@ var test_exports = {};
|
|
|
54811
54946
|
__export(test_exports, {
|
|
54812
54947
|
TEST_CONFIG_FILE: () => TEST_CONFIG_FILE,
|
|
54813
54948
|
defaultNetRules: () => defaultNetRules,
|
|
54949
|
+
foldCapabilityCoverage: () => foldCapabilityCoverage,
|
|
54814
54950
|
formatReport: () => formatReport,
|
|
54815
54951
|
makeRecordingWixCall: () => makeRecordingWixCall,
|
|
54816
54952
|
readCannedResponses: () => readCannedResponses,
|
|
@@ -55055,6 +55191,31 @@ async function runShotsPass(opts) {
|
|
|
55055
55191
|
}
|
|
55056
55192
|
return { dir: opts.outDir, written, ...Object.keys(states).length ? { states } : {} };
|
|
55057
55193
|
}
|
|
55194
|
+
function declaredKey(descriptor) {
|
|
55195
|
+
return [...declaredCapabilitiesOf(descriptor)].sort().join(",");
|
|
55196
|
+
}
|
|
55197
|
+
function foldCapabilityCoverage(perScreen) {
|
|
55198
|
+
const acc = /* @__PURE__ */ new Map();
|
|
55199
|
+
for (const { screen, coverage } of perScreen) {
|
|
55200
|
+
for (const [call, arm] of Object.entries(coverage)) {
|
|
55201
|
+
const e = acc.get(call) ?? { screens: [], deniedOn: [] };
|
|
55202
|
+
if (!e.screens.includes(screen)) e.screens.push(screen);
|
|
55203
|
+
if (arm === "denied") {
|
|
55204
|
+
if (!e.deniedOn.includes(screen)) e.deniedOn.push(screen);
|
|
55205
|
+
} else e.granted = arm;
|
|
55206
|
+
acc.set(call, e);
|
|
55207
|
+
}
|
|
55208
|
+
}
|
|
55209
|
+
const counts = { success: 0, store: 0, "unavailable-only": 0, denied: 0 };
|
|
55210
|
+
const calls = {};
|
|
55211
|
+
for (const call of [...acc.keys()].sort()) {
|
|
55212
|
+
const e = acc.get(call);
|
|
55213
|
+
const arm = e.granted ?? "denied";
|
|
55214
|
+
counts[arm] += 1;
|
|
55215
|
+
calls[call] = { arm, screens: e.screens.sort(), ...e.granted && e.deniedOn.length ? { deniedOn: e.deniedOn.sort() } : {} };
|
|
55216
|
+
}
|
|
55217
|
+
return { counts, calls };
|
|
55218
|
+
}
|
|
55058
55219
|
function withCovered(rows, coveredBy) {
|
|
55059
55220
|
const byScreen = new Map(rows.map((r) => [r.screen, r]));
|
|
55060
55221
|
const out2 = [...rows];
|
|
@@ -55079,7 +55240,7 @@ async function runCrawl(opts) {
|
|
|
55079
55240
|
if (!opts.allScreens) {
|
|
55080
55241
|
const groups = /* @__PURE__ */ new Map();
|
|
55081
55242
|
for (const d of requested) {
|
|
55082
|
-
const key2 = `${d.bundleName ?? "-"}|${deps.crawlSignature(d.descriptor)}`;
|
|
55243
|
+
const key2 = `${d.bundleName ?? "-"}|${deps.crawlSignature(d.descriptor)}|${declaredKey(d.descriptor)}`;
|
|
55083
55244
|
const list = groups.get(key2) ?? [];
|
|
55084
55245
|
list.push(d);
|
|
55085
55246
|
groups.set(key2, list);
|
|
@@ -55105,6 +55266,7 @@ async function runCrawl(opts) {
|
|
|
55105
55266
|
const relationFindings = [];
|
|
55106
55267
|
let relationsChecked = 0;
|
|
55107
55268
|
let capabilityModelled = 0;
|
|
55269
|
+
const capabilityCoverageByScreen = [];
|
|
55108
55270
|
const relationsByKind = { inverse: 0, refresh: 0, loadMore: 0, filter: 0 };
|
|
55109
55271
|
const deepFindings = [];
|
|
55110
55272
|
const journeyFindings = [];
|
|
@@ -55197,6 +55359,7 @@ async function runCrawl(opts) {
|
|
|
55197
55359
|
const cr = await deps.crawlScreenCapabilitySuccess(args);
|
|
55198
55360
|
writes.push(...writesFromEffects(baked.screenId, cr.effects));
|
|
55199
55361
|
capabilityModelled += cr.modelled;
|
|
55362
|
+
if (cr.coverage) capabilityCoverageByScreen.push({ screen: baked.screenId, coverage: cr.coverage });
|
|
55200
55363
|
const keyOfC = deps.findingKey ?? ((f) => `${f.invariant}|${f.at ?? ""}|${f.screen}|${f.message}`);
|
|
55201
55364
|
const seen = new Set([...r.findings, ...relationFindings].map(keyOfC));
|
|
55202
55365
|
for (const f of cr.findings) if (!seen.has(keyOfC(f))) relationFindings.push(f);
|
|
@@ -55355,7 +55518,7 @@ async function runCrawl(opts) {
|
|
|
55355
55518
|
...retired.length ? { retired } : {},
|
|
55356
55519
|
...opts.deep && opts.deep >= 2 ? { deep: { depth: opts.deep, ...deepTotals } } : {},
|
|
55357
55520
|
...deps.crawlScreenRelations ? { relations: { checked: relationsChecked, byKind: relationsByKind } } : {},
|
|
55358
|
-
...deps.crawlScreenCapabilitySuccess ? { capabilities: { modelled: capabilityModelled } } : {},
|
|
55521
|
+
...deps.crawlScreenCapabilitySuccess ? { capabilities: { modelled: capabilityModelled }, capabilityCoverage: foldCapabilityCoverage(capabilityCoverageByScreen) } : {},
|
|
55359
55522
|
...journeyResults.length ? { journeys: journeyResults } : {},
|
|
55360
55523
|
writes: inventory,
|
|
55361
55524
|
...unreachedWrites.length ? { unreachedWrites } : {},
|
|
@@ -55507,6 +55670,7 @@ var TEST_CONFIG_FILE, mapResult, triggerLabel;
|
|
|
55507
55670
|
var init_test = __esm({
|
|
55508
55671
|
"src/test.ts"() {
|
|
55509
55672
|
init_app_map();
|
|
55673
|
+
init_screen_crawl();
|
|
55510
55674
|
init_wix_gateway_rules();
|
|
55511
55675
|
init_crawl_layers();
|
|
55512
55676
|
init_screen_state();
|
|
@@ -57420,7 +57584,7 @@ var init_cli_bridge = __esm({
|
|
|
57420
57584
|
});
|
|
57421
57585
|
|
|
57422
57586
|
// src/mcp/file-tools.ts
|
|
57423
|
-
import { closeSync as closeSync5, constants as constants2, lstatSync as
|
|
57587
|
+
import { closeSync as closeSync5, constants as constants2, lstatSync as lstatSync17, mkdirSync as mkdirSync28, openSync as openSync4, readdirSync as readdirSync28, readFileSync as readFileSync67, realpathSync as realpathSync8, renameSync as renameSync8, rmSync as rmSync13, statSync as statSync15, writeSync, existsSync as existsSync58 } from "node:fs";
|
|
57424
57588
|
import { randomBytes as randomBytes4 } from "node:crypto";
|
|
57425
57589
|
import { dirname as dirname31, isAbsolute as isAbsolute5, join as join60, relative as relative11, resolve as resolve47, sep as sep15 } from "node:path";
|
|
57426
57590
|
import * as z from "zod";
|
|
@@ -57443,7 +57607,7 @@ function symlinkComponent(base, abs) {
|
|
|
57443
57607
|
for (const part of relative11(base, abs).split(sep15).filter(Boolean)) {
|
|
57444
57608
|
cur = join60(cur, part);
|
|
57445
57609
|
try {
|
|
57446
|
-
if (
|
|
57610
|
+
if (lstatSync17(cur).isSymbolicLink()) return relative11(base, cur);
|
|
57447
57611
|
} catch {
|
|
57448
57612
|
return void 0;
|
|
57449
57613
|
}
|
|
@@ -57590,7 +57754,7 @@ function fileTools() {
|
|
|
57590
57754
|
if (typeof app !== "string") return app;
|
|
57591
57755
|
const p = confinedPath(app, a.path);
|
|
57592
57756
|
if (typeof p !== "string") return refuse4("bad_path", p.error);
|
|
57593
|
-
if (!existsSync58(p) || !
|
|
57757
|
+
if (!existsSync58(p) || !lstatSync17(p).isFile()) return refuse4("not_found", `no file "${String(a.path)}"`);
|
|
57594
57758
|
const size = statSync15(p).size;
|
|
57595
57759
|
if (size > MAX_FILE_BYTES) return refuse4("too_large", `"${String(a.path)}" is ${size} bytes \u2014 over the ${MAX_FILE_BYTES}-byte read limit`);
|
|
57596
57760
|
return ok({ ok: true, path: a.path, bytes: size, content: readFileSync67(p, "utf8") });
|
|
@@ -57617,7 +57781,7 @@ function fileTools() {
|
|
|
57617
57781
|
if (typeof p !== "string") refused2.push({ path: f.path, error: p.error });
|
|
57618
57782
|
else if (typeof f.content !== "string") refused2.push({ path: f.path, error: "content must be text" });
|
|
57619
57783
|
else if (Buffer.byteLength(f.content) > MAX_FILE_BYTES) refused2.push({ path: f.path, error: `over the ${MAX_FILE_BYTES}-byte limit` });
|
|
57620
|
-
else if (existsSync58(p) && !
|
|
57784
|
+
else if (existsSync58(p) && !lstatSync17(p).isFile()) refused2.push({ path: f.path, error: "not a regular file" });
|
|
57621
57785
|
else if (planned.some((q) => q.abs === p)) refused2.push({ path: f.path, error: "the same file twice in one call" });
|
|
57622
57786
|
else planned.push({ abs: p, rel: String(f.path), content: f.content });
|
|
57623
57787
|
}
|
|
@@ -57651,7 +57815,7 @@ function fileTools() {
|
|
|
57651
57815
|
const p = confinedPath(app, rel);
|
|
57652
57816
|
if (typeof p !== "string") refused2.push({ path: rel, error: p.error });
|
|
57653
57817
|
else if (!existsSync58(p)) refused2.push({ path: rel, error: "no such file" });
|
|
57654
|
-
else if (!
|
|
57818
|
+
else if (!lstatSync17(p).isFile()) refused2.push({ path: rel, error: "not a regular file" });
|
|
57655
57819
|
else if (!planned.includes(p)) {
|
|
57656
57820
|
planned.push(p);
|
|
57657
57821
|
deleted.push(rel);
|
|
@@ -74566,6 +74730,16 @@ function isSystemPage(path2, title) {
|
|
|
74566
74730
|
const last = path2.split("/").filter(Boolean).pop() ?? "";
|
|
74567
74731
|
return SYSTEM_SEGMENT.test(last) || SYSTEM_TITLE.test(title);
|
|
74568
74732
|
}
|
|
74733
|
+
function deriveMemberPages(origin, urls) {
|
|
74734
|
+
const found = {};
|
|
74735
|
+
for (const url of urls) {
|
|
74736
|
+
const path2 = pathOnSite(origin, url);
|
|
74737
|
+
if (!path2) continue;
|
|
74738
|
+
const key2 = MEMBER_PAGE_ROUTES[path2.split("/").filter(Boolean).join("/").toLowerCase()];
|
|
74739
|
+
if (key2 && !found[key2]) found[key2] = path2;
|
|
74740
|
+
}
|
|
74741
|
+
return found.orders || found.bookings ? found : void 0;
|
|
74742
|
+
}
|
|
74569
74743
|
function boundedTitle(s) {
|
|
74570
74744
|
const t = oneLine2(s).replace(/\s*\(new\)\s*$/i, "");
|
|
74571
74745
|
return t.length <= SITE_PAGE_TITLE_MAX ? t : t.slice(0, SITE_PAGE_TITLE_MAX).trimEnd();
|
|
@@ -74698,10 +74872,15 @@ function deriveSiteShell(input) {
|
|
|
74698
74872
|
if (!published.length && !navLinks.length && !routes.length) {
|
|
74699
74873
|
notes.push("site pages: the analysis read no page list, so the map has only Home \u2014 add the site's pages to `site.pages` before --apply.");
|
|
74700
74874
|
}
|
|
74875
|
+
const memberPages = deriveMemberPages(origin, navLinks.map((l) => l.href));
|
|
74876
|
+
if (memberPages) {
|
|
74877
|
+
notes.push(`member pages: ${Object.entries(memberPages).map(([k, v]) => `${k} \u2192 ${v}`).join(", ")} \u2014 order / booking notifications open them.`);
|
|
74878
|
+
}
|
|
74701
74879
|
const site = {
|
|
74702
74880
|
origin,
|
|
74703
74881
|
kind,
|
|
74704
74882
|
pages,
|
|
74883
|
+
...memberPages ? { memberPages } : {},
|
|
74705
74884
|
// Hidden by default — the product decision for the site shell (RFC 0062 §6); the owner can
|
|
74706
74885
|
// turn any of them back on here, and the flags ride the OTA descriptor. A Base44 app has no
|
|
74707
74886
|
// Wix ads to hide, so it never carries the flag (RFC 0062 §5.8).
|
|
@@ -74728,7 +74907,7 @@ function routeTitle(r) {
|
|
|
74728
74907
|
const seg = r.path.split("/").filter(Boolean).pop() ?? r.path;
|
|
74729
74908
|
return seg.replace(/[-_]+/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
74730
74909
|
}
|
|
74731
|
-
var DERIVED_SITE_MAX_PAGES, SITE_PAGE_TITLE_MAX, ROLE_ICON, TAB_ROLES, SYSTEM_SEGMENT, SYSTEM_TITLE, HOME_WORD, oneLine2, RESERVED_PAGE_IDS;
|
|
74910
|
+
var DERIVED_SITE_MAX_PAGES, SITE_PAGE_TITLE_MAX, ROLE_ICON, TAB_ROLES, SYSTEM_SEGMENT, SYSTEM_TITLE, HOME_WORD, MEMBER_PAGE_ROUTES, oneLine2, RESERVED_PAGE_IDS;
|
|
74732
74911
|
var init_site_shell_derive = __esm({
|
|
74733
74912
|
"src/site-analyze/site-shell-derive.ts"() {
|
|
74734
74913
|
init_site_shell();
|
|
@@ -74755,6 +74934,10 @@ var init_site_shell_derive = __esm({
|
|
|
74755
74934
|
SYSTEM_SEGMENT = /^(popup-.*|lightbox.*|.*thank-?you.*|checkout|cart(-page)?|payment-request(-page)?|expired-reset-link|access-restricted|reservation|(event|experience|reservation|order|item)-details|(product|service|category|group|project|collection|reviews-collection|donation-thank-you)-page|post|project|collection|group|booking-(form|calendar)|plan-customization|notifications?|followers?|following|my-(?!account$)[a-z-]+|profile|search(-results)?|blank(-\d+)?|404)$/i;
|
|
74756
74935
|
SYSTEM_TITLE = /\b(thank you|\(popup\)|leave (a )?review|notifications|followers|side cart|order error|pending approval|my (rewards|subscriptions|bookings|orders|groups|wallet))\b|\b(group|service|product|project|collection|category|cart|payment request) pages?$|\b(event|experience|reservation|item) details\b/i;
|
|
74757
74936
|
HOME_WORD = /^(home|home ?page|main|start|accueil|inicio|início|startseite|home ?sweet ?home|בית|דף הבית|ראשי|الرئيسية|ホーム|首页|홈)$/i;
|
|
74937
|
+
MEMBER_PAGE_ROUTES = {
|
|
74938
|
+
"account/my-orders": "orders",
|
|
74939
|
+
"account/my-bookings": "bookings"
|
|
74940
|
+
};
|
|
74758
74941
|
oneLine2 = (s) => s.replace(/[\u0000-\u001F\u007F\s]+/g, " ").trim();
|
|
74759
74942
|
RESERVED_PAGE_IDS = /* @__PURE__ */ new Set(["account", "notifications"]);
|
|
74760
74943
|
}
|
|
@@ -76630,7 +76813,7 @@ var init_context = __esm({
|
|
|
76630
76813
|
});
|
|
76631
76814
|
|
|
76632
76815
|
// src/cli.ts
|
|
76633
|
-
import { mkdtempSync as mkdtempSync5, readFileSync as readFileSync91, mkdirSync as mkdirSync39, writeFileSync as writeFileSync35, existsSync as existsSync74, rmSync as rmSync20, readdirSync as readdirSync32, statSync as statSync20, lstatSync as
|
|
76816
|
+
import { mkdtempSync as mkdtempSync5, readFileSync as readFileSync91, mkdirSync as mkdirSync39, writeFileSync as writeFileSync35, existsSync as existsSync74, rmSync as rmSync20, readdirSync as readdirSync32, statSync as statSync20, lstatSync as lstatSync18, accessSync, constants as constants3, renameSync as renameSync9, cpSync as cpSync3 } from "node:fs";
|
|
76634
76817
|
import { execFile as execFile2, execFileSync as execFileSync7, spawnSync as spawnSync5, spawn as spawn7 } from "node:child_process";
|
|
76635
76818
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
76636
76819
|
import { createHash as createHash36, randomUUID as randomUUID8 } from "node:crypto";
|
|
@@ -80744,7 +80927,7 @@ function renameRestoredSource(restored, declaredApp, renamed) {
|
|
|
80744
80927
|
function symlinkDestinationIssue(dest) {
|
|
80745
80928
|
let kind;
|
|
80746
80929
|
try {
|
|
80747
|
-
kind =
|
|
80930
|
+
kind = lstatSync18(dest);
|
|
80748
80931
|
} catch {
|
|
80749
80932
|
return null;
|
|
80750
80933
|
}
|
|
@@ -82080,9 +82263,9 @@ ${doNow.map((l) => ` ${l}`).join("\n")}`);
|
|
|
82080
82263
|
message: `scaffold source (--source-dir ${sourceDir}): ${issue2.message}`
|
|
82081
82264
|
}));
|
|
82082
82265
|
const { navIssues: navIssues2 } = await Promise.resolve().then(() => (init_nav_lint(), nav_lint_exports));
|
|
82083
|
-
const { capabilityIssues: capabilityIssues2, contentShell: contentShell2 } = await Promise.resolve().then(() => (init_capabilities(), capabilities_exports));
|
|
82266
|
+
const { capabilityIssues: capabilityIssues2, contentShell: contentShell2, readLastDist: readLastDist2 } = await Promise.resolve().then(() => (init_capabilities(), capabilities_exports));
|
|
82084
82267
|
const { report: report3, hasErrors } = summarizeLint(app, bres, outcome, strict, [
|
|
82085
|
-
...capabilityIssues2(outcome.capabilityUsage, contentShell2(buildDir, str9(flags.shell)), strict),
|
|
82268
|
+
...capabilityIssues2(outcome.capabilityUsage, contentShell2(buildDir, str9(flags.shell)), strict, readLastDist2(buildDir)),
|
|
82086
82269
|
...await cmsIssues(buildDir, app, strict, { local: true }),
|
|
82087
82270
|
...surfaceIssues(buildDir, outcome.surfaceNav, strict),
|
|
82088
82271
|
...tokenColorIssues(buildDir),
|
|
@@ -82367,9 +82550,9 @@ ${doNow.map((l) => ` ${l}`).join("\n")}`);
|
|
|
82367
82550
|
...hasWorkers ? { tier5Dir: bundleOutDir2(dir) } : {},
|
|
82368
82551
|
collect: { lint: true, strict }
|
|
82369
82552
|
});
|
|
82370
|
-
const { capabilityIssues: capabilityIssues2, contentShell: contentShell2 } = await Promise.resolve().then(() => (init_capabilities(), capabilities_exports));
|
|
82553
|
+
const { capabilityIssues: capabilityIssues2, contentShell: contentShell2, readLastDist: readLastDist2 } = await Promise.resolve().then(() => (init_capabilities(), capabilities_exports));
|
|
82371
82554
|
const { report: report3, hasErrors } = summarizeLint(app, bres, outcome, strict, [
|
|
82372
|
-
...capabilityIssues2(outcome.capabilityUsage, contentShell2(dir, str9(flags.shell)), strict),
|
|
82555
|
+
...capabilityIssues2(outcome.capabilityUsage, contentShell2(dir, str9(flags.shell)), strict, readLastDist2(dir)),
|
|
82373
82556
|
...await cmsIssues(dir, app, strict, { local: true }),
|
|
82374
82557
|
...surfaceIssues(dir, outcome.surfaceNav, strict),
|
|
82375
82558
|
...tokenColorIssues(dir),
|
|
@@ -83890,9 +84073,9 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
|
|
|
83890
84073
|
collect: { lint: true, strict }
|
|
83891
84074
|
});
|
|
83892
84075
|
const { navIssues: navIssues2 } = await Promise.resolve().then(() => (init_nav_lint(), nav_lint_exports));
|
|
83893
|
-
const { capabilityIssues: capabilityIssues2, contentShell: contentShell2 } = await Promise.resolve().then(() => (init_capabilities(), capabilities_exports));
|
|
84076
|
+
const { capabilityIssues: capabilityIssues2, contentShell: contentShell2, readLastDist: readLastDist2 } = await Promise.resolve().then(() => (init_capabilities(), capabilities_exports));
|
|
83894
84077
|
const { report: report3, hasErrors } = summarizeLint(declared, bres, outcome, strict, [
|
|
83895
|
-
...capabilityIssues2(outcome.capabilityUsage, contentShell2(dir, str9(flags.shell)), strict),
|
|
84078
|
+
...capabilityIssues2(outcome.capabilityUsage, contentShell2(dir, str9(flags.shell)), strict, readLastDist2(dir)),
|
|
83896
84079
|
...await cmsIssues(dir, declared, strict, { local: true }),
|
|
83897
84080
|
...surfaceIssues(dir, outcome.surfaceNav, strict),
|
|
83898
84081
|
...tokenColorIssues(dir),
|
|
@@ -84217,11 +84400,13 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
|
|
|
84217
84400
|
// Its screen hashes say what changed since the last draft, on the postcard.
|
|
84218
84401
|
manifest
|
|
84219
84402
|
}) : null;
|
|
84403
|
+
const needsBinary = (await Promise.resolve().then(() => (init_capabilities(), capabilities_exports))).needsBinaryNotice(r.json);
|
|
84220
84404
|
print({
|
|
84221
84405
|
ok: true,
|
|
84222
84406
|
app,
|
|
84223
84407
|
releaseId: release,
|
|
84224
84408
|
draft,
|
|
84409
|
+
...needsBinary ? { needsBinary } : {},
|
|
84225
84410
|
...unchanged ? { unchanged: true, unchangedNote: "the serve plane already holds this exact release under this id, so nothing was written" } : {},
|
|
84226
84411
|
...afterDraft?.firstLook ? { firstLookPostcard: afterDraft.firstLook } : {},
|
|
84227
84412
|
...afterDraft?.postcard ? { postcard: afterDraft.postcard } : {},
|
|
@@ -84440,9 +84625,9 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
|
|
|
84440
84625
|
collect: { lint: true, strict }
|
|
84441
84626
|
});
|
|
84442
84627
|
const { navIssues: navIssues2 } = await Promise.resolve().then(() => (init_nav_lint(), nav_lint_exports));
|
|
84443
|
-
const { capabilityIssues: capabilityIssues2, contentShell: contentShell2 } = await Promise.resolve().then(() => (init_capabilities(), capabilities_exports));
|
|
84628
|
+
const { capabilityIssues: capabilityIssues2, contentShell: contentShell2, readLastDist: readLastDist2 } = await Promise.resolve().then(() => (init_capabilities(), capabilities_exports));
|
|
84444
84629
|
const { report: report3, hasErrors } = summarizeLint(app, bres, outcome, strict, [
|
|
84445
|
-
...capabilityIssues2(outcome.capabilityUsage, contentShell2(dir, str9(flags.shell)), strict),
|
|
84630
|
+
...capabilityIssues2(outcome.capabilityUsage, contentShell2(dir, str9(flags.shell)), strict, readLastDist2(dir)),
|
|
84446
84631
|
...await cmsIssues(dir, app, strict, { local: true }),
|
|
84447
84632
|
...surfaceIssues(dir, outcome.surfaceNav, strict),
|
|
84448
84633
|
...tokenColorIssues(dir),
|
|
@@ -86043,6 +86228,20 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
|
|
|
86043
86228
|
const sub = pos[1];
|
|
86044
86229
|
const c = client2();
|
|
86045
86230
|
const showDistQr = io.isTTY === true && flags.json !== true;
|
|
86231
|
+
const rememberLastDist = async (jobs2) => {
|
|
86232
|
+
try {
|
|
86233
|
+
const dir = contentDir(flags);
|
|
86234
|
+
if (!existsSync74(join75(dir, "himalaya.content.json"))) return;
|
|
86235
|
+
const { lastDistFromJob: lastDistFromJob2, writeLastDist: writeLastDist2 } = await Promise.resolve().then(() => (init_capabilities(), capabilities_exports));
|
|
86236
|
+
const own3 = await appId(dir);
|
|
86237
|
+
for (const job of jobs2) {
|
|
86238
|
+
const rec = lastDistFromJob2(job);
|
|
86239
|
+
if (!rec || rec.app !== own3) continue;
|
|
86240
|
+
await writeLastDist2(dir, rec);
|
|
86241
|
+
}
|
|
86242
|
+
} catch {
|
|
86243
|
+
}
|
|
86244
|
+
};
|
|
86046
86245
|
const humanDistJob = (job) => {
|
|
86047
86246
|
if (job.platform !== "ios" || !job.artifact || typeof job.artifact !== "object") return job;
|
|
86048
86247
|
const { installLink: _hidden, ...safeArtifact } = job.artifact;
|
|
@@ -86138,6 +86337,7 @@ ${label2}`);
|
|
|
86138
86337
|
return distUnreachable(e);
|
|
86139
86338
|
}
|
|
86140
86339
|
const body = { status: r2.status, ...r2.json ?? {} };
|
|
86340
|
+
if (r2.ok) await rememberLastDist([body]);
|
|
86141
86341
|
print(flags.json === true ? body : humanDistJob(body));
|
|
86142
86342
|
if (showDistQr && r2.ok) await printDistQrForJob(body, String(body.app ?? ""));
|
|
86143
86343
|
return r2.ok ? 0 : 1;
|
|
@@ -86178,6 +86378,7 @@ ${label2}`);
|
|
|
86178
86378
|
}
|
|
86179
86379
|
if (TERMINAL_DIST_STATUSES.has(status)) {
|
|
86180
86380
|
const body = { status: s.status, ...rec };
|
|
86381
|
+
await rememberLastDist([body]);
|
|
86181
86382
|
print(flags.json === true ? body : humanDistJob(body));
|
|
86182
86383
|
if (showDistQr && status === "SUCCESS") await printDistQrForJob(body, String(rec.app ?? ""));
|
|
86183
86384
|
return status === "SUCCESS" ? 0 : 1;
|
|
@@ -86392,6 +86593,7 @@ ${label2}`);
|
|
|
86392
86593
|
}
|
|
86393
86594
|
}
|
|
86394
86595
|
}
|
|
86596
|
+
await rememberLastDist(done);
|
|
86395
86597
|
const allOk = failures.length === 0 && done.every((d) => d.status === "SUCCESS");
|
|
86396
86598
|
print({ ok: allOk, jobs: flags.json === true ? done : done.map(humanDistJob), ...failures.length ? { failures } : {} });
|
|
86397
86599
|
await printDistQrsForJobs(done);
|
package/guides/capabilities.md
CHANGED
|
@@ -25,7 +25,7 @@ camera — import from @himalaya/camera
|
|
|
25
25
|
exports: capturePhoto, pickDocument, scanQR
|
|
26
26
|
declare: config.ts `capabilities` AND each calling screen's `logicBundle.declaredCapabilities`
|
|
27
27
|
binary: needs a NATIVE build — iOS NSCameraUsageDescription; Android android.permission.CAMERA …
|
|
28
|
-
shells: served by owner-lite;
|
|
28
|
+
shells: served by owner-lite, player; `himi dist` adds it to base44, branded-lite, brandy; NOT in the demo-app binary
|
|
29
29
|
```
|
|
30
30
|
|
|
31
31
|
Agents: the same data is the `himi_capabilities` MCP tool. `himi sdk <name>` also prints the
|
|
@@ -89,8 +89,23 @@ Name the shell your app ships in once, in `himalaya.content.json`, and both `him
|
|
|
89
89
|
|
|
90
90
|
`himi test` boots every screen's real worker and dispatches its actions. A declared capability
|
|
91
91
|
answers with its "unavailable" result there, so the crawl checks that your worker handles a
|
|
92
|
-
declined camera or a missing position without breaking
|
|
93
|
-
|
|
92
|
+
declined camera or a missing position without breaking; a second pass then answers the modelled
|
|
93
|
+
ones (camera, location, photos, speech, …) with a SUCCESS result, so the code behind a granted
|
|
94
|
+
permission runs too. Two things in the `--json` report are about capabilities:
|
|
95
|
+
|
|
96
|
+
- a **`capability-undeclared`** finding for every call the screen's gate denied — even one your
|
|
97
|
+
worker caught, which on device looks like a dead button rather than an error;
|
|
98
|
+
- **`capabilityCoverage`**: each `capability.<id>.<verb>` your workers call, and what the crawl
|
|
99
|
+
answers it with — `success` (modelled), `store` (a real session store: `local_database`,
|
|
100
|
+
`secure_storage`), `unavailable-only` (no model yet, so only the declined path runs), or `denied`.
|
|
101
|
+
See [himi-test → Capabilities](himi-test.md#capabilities).
|
|
102
|
+
|
|
103
|
+
The bundle scan only ever **reports**: it never adds a capability to a screen's declaration. The
|
|
104
|
+
per-screen list is the authorization boundary, so a worker shared by two screens cannot widen the
|
|
105
|
+
one that never makes the call.
|
|
106
|
+
|
|
107
|
+
In the web preview, camera capture and geolocation use the browser's own file picker and location
|
|
108
|
+
prompt.
|
|
94
109
|
|
|
95
110
|
## 5. Ship it
|
|
96
111
|
|
|
@@ -103,6 +118,14 @@ geolocation use the browser's own file picker and location prompt.
|
|
|
103
118
|
entitlements, Android permissions / features) and registers their handlers at boot.
|
|
104
119
|
`himi capabilities --shell <s>` shows such an id as `yes → dist`, and `himi validate` warns that
|
|
105
120
|
it "needs a new `himi dist`" — the installed binary cannot call it until you rebuild.
|
|
121
|
+
- **"needs a new binary".** A successful `himi dist` remembers what its binary was built with —
|
|
122
|
+
on the job (`buildCapabilities`) and in your package (`.himi/last-dist.json`). From then on
|
|
123
|
+
`himi push` prints `needs a new binary: <ids> — run himi dist` when the release declares an id
|
|
124
|
+
that build lacks, a following device's draft offer says the same, and `himi validate` compares
|
|
125
|
+
against that build rather than the committed shell.
|
|
126
|
+
- **Never a silent strip.** On iOS, a capability whose entitlement the signing profile cannot grant
|
|
127
|
+
fails the dist job, naming the capability — the binary would otherwise build green and fail on
|
|
128
|
+
the first call. The minted profile's Apple capabilities are derived from the build's own set.
|
|
106
129
|
- **Preview before you build:** `himi dist --plan` prints what the release declares, what the shell
|
|
107
130
|
already serves, what the build would add and the exact native delta. No job is started.
|
|
108
131
|
- **Write the permission prompt.** The OS shows a sentence when the app asks for camera, location,
|
package/guides/himi-test.md
CHANGED
|
@@ -125,6 +125,7 @@ Only **unambiguous** defects, so a green run stays worth trusting:
|
|
|
125
125
|
| `poll-action-missing` | error | A `_poll` directive every host will silently ignore — an action the bundle never declares, a malformed `action`, a missing `id`, or a non-positive `intervalMs`. Each ends the same way: a screen that never refreshes, with nothing saying why. Nothing in the descriptor names the polled action, so no static check can see it. (A merely too-small interval is legal — the host clamps it.) |
|
|
126
126
|
| `no-request-issued` | **warning** | A worker that uses the Wix transport but never fetches — "the screen loaded with sample data." A warning: a conditional or tap-triggered fetch is legitimate. |
|
|
127
127
|
| `native-decode-failed` | error | Under `--native`, the real Swift/Kotlin decoder rejected the baked descriptor bytes (one bad enum blanks the whole screen on device). |
|
|
128
|
+
| `capability-undeclared` | error | A `capability.<id>.<verb>` call the screen's gate denied — reported from the gate itself, so it is raised **even when the worker catches the rejection** (which on device leaves a control that silently does nothing). Fix: declare the id on that screen's `logicBundle.declaredCapabilities` and in `config.ts` `capabilities`. One per capability per screen. See [Capabilities](#capabilities). |
|
|
128
129
|
| `journey-failed` | error | A `himi-test.json` journey named an action the screen lacks, threw, or never rendered its `expectText`. |
|
|
129
130
|
| `inverse-pair-diverges` | **warning** | An action and the action that undoes it do not return to the state the boot settled on — `add` then `remove` leaving a different list, or a counter it forgot to put back. |
|
|
130
131
|
| `refresh-not-idempotent` | **warning** | `refresh` from a fresh boot settles somewhere other than the boot did, on identical data — usually a refresh that appends instead of replacing. |
|
|
@@ -267,6 +268,36 @@ went unproven, it names the gap and pairs it with a himi command or a source edi
|
|
|
267
268
|
derived from what actually ran, never homework. Assembly:
|
|
268
269
|
`tools/himi-cli/src/coverage.ts`.
|
|
269
270
|
|
|
271
|
+
## Capabilities
|
|
272
|
+
|
|
273
|
+
A `capability.*` call is answered twice. The main pass answers every granted one with `null` — the
|
|
274
|
+
device's "unavailable" arm — so a screen's declined-camera / no-position path is checked. The
|
|
275
|
+
capability-success pass then re-crawls each screen whose bundle calls a **modelled** capability
|
|
276
|
+
(audio-record, speech, on-device-ai, calls, media-playback, calendar, files, photos, pdf, share,
|
|
277
|
+
open-url, camera, location) with a success answer shaped exactly like the one its SDK binding
|
|
278
|
+
destructures, so the code behind a granted permission runs too. `local_database` and
|
|
279
|
+
`secure_storage` are served from a real session store on both passes.
|
|
280
|
+
|
|
281
|
+
`--json` carries what each call got as `capabilityCoverage`:
|
|
282
|
+
|
|
283
|
+
```jsonc
|
|
284
|
+
"capabilityCoverage": {
|
|
285
|
+
"counts": { "success": 2, "store": 1, "unavailable-only": 1, "denied": 0 },
|
|
286
|
+
"calls": {
|
|
287
|
+
"capability.camera.capture": { "arm": "success", "screens": ["notes"] },
|
|
288
|
+
"capability.local_database.getAll": { "arm": "store", "screens": ["notes"] },
|
|
289
|
+
"capability.location.getCurrent": { "arm": "success", "screens": ["notes"] },
|
|
290
|
+
"capability.haptic.play": { "arm": "unavailable-only", "screens": ["notes"] }
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
`unavailable-only` means no model exists yet, so only the declined path of that call has run;
|
|
296
|
+
`denied` means no screen running the call grants its id (each denial is also a
|
|
297
|
+
`capability-undeclared` finding), and `deniedOn` lists screens that deny a call others allow. The
|
|
298
|
+
map is static — it says what a call would be answered with, not that a tap reached it; the gap
|
|
299
|
+
ledger says that.
|
|
300
|
+
|
|
270
301
|
## Writes to Wix
|
|
271
302
|
|
|
272
303
|
Every run lists what the app **writes** to the Wix site, per screen and action — the inventory an
|
package/guides/qa-builds.md
CHANGED
|
@@ -326,7 +326,7 @@ one never blocks a rollout — the dist routes just `501` with the reason.
|
|
|
326
326
|
| `HIMI_DIST_ALLOWED_XCODE_VERSIONS` | comma-separated Xcode versions a caller may pin (`xcodeVersion` in the request body); unset ⇒ the built-in list. Nothing upstream constrains this field — the Job Service client validates it as `z.string().max(50)` — so without the allow-list it is caller-supplied text choosing a toolchain on a shared pool. A blank value reads as unset (the built-in list), never as "allow anything". |
|
|
327
327
|
| `HIMI_DIST_APPLE_SIGNING` | **Minted iOS signing is ON by default** — set `0`/`false`/`off` to disable it and go back to scavenging a profile off the pool. Rarely what you want: minting ships the profile and the certificate with the job, so nothing has to pre-exist on the machine. (Earlier text here cited `0 profile(s) available` on four machines as a reason to keep minting on — that reading came from a TRUNCATED survey and is retracted below; build 992367 logged 352 profiles. The recommendation is unchanged, the evidence for it was wrong.) |
|
|
328
328
|
| `HIMI_DIST_APPLE_API_KEY_NAME` | Apple account the identity is minted under; unset ⇒ `wix-enterprise` |
|
|
329
|
-
| `HIMI_DIST_APPLE_CAPABILITIES` |
|
|
329
|
+
| `HIMI_DIST_APPLE_CAPABILITIES` | Apple capability types to ADD to every minted id. The set itself is derived per job: push + associated domains (the template baseline) plus the Apple capability of every entitlement in the iOS plan of the job's `buildCapabilities` (`appleCapabilitiesForBuild`). This variable only **widens** that derived set (e.g. `APP_GROUPS` for a widget) — it can never remove a derived capability, because the sync call replaces rather than merges and a narrower list would switch off what the content asked for. Unset ⇒ the derived set alone |
|
|
330
330
|
| `QA_DIST_SLACK_CHANNEL` | Post QA build results to a channel instead of DMing the requester. Worth setting: a DM is unreadable by anything but that person, so a channel is what makes build results scriptable (and visible to the team). |
|
|
331
331
|
|
|
332
332
|
Both per-credential caps are **clamped into the range that can be enforced**, and the two ranges
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wix/himalaya-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1188.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": [
|
|
@@ -67,5 +67,5 @@
|
|
|
67
67
|
"artifactId": "himalaya-cli"
|
|
68
68
|
}
|
|
69
69
|
},
|
|
70
|
-
"falconPackageHash": "
|
|
70
|
+
"falconPackageHash": "67d96922cd1adeed2becc3bef5c4d027397fd441f83346cd018cb252"
|
|
71
71
|
}
|