@wix/himalaya-cli 0.1126.0 → 0.1128.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 +825 -593
- package/guides/headless-loop.md +12 -4
- package/guides/himi-test.md +8 -0
- package/guides/skill.md +2 -2
- package/package.json +2 -2
package/dist/cli.mjs
CHANGED
|
@@ -7988,6 +7988,16 @@ function isInteractive(node) {
|
|
|
7988
7988
|
(k) => !!mods[k] && typeof mods[k] === "object" || !!node[k] && typeof node[k] === "object"
|
|
7989
7989
|
);
|
|
7990
7990
|
}
|
|
7991
|
+
function hasPropHandler(node) {
|
|
7992
|
+
if (componentTypeOf(node) === null) return false;
|
|
7993
|
+
const bag = (v) => v && typeof v === "object" && !Array.isArray(v) ? v : {};
|
|
7994
|
+
const body = bag(node.body);
|
|
7995
|
+
const bags = [bag(body.props), bag(node.props), node];
|
|
7996
|
+
return bags.some((b) => PROP_HANDLER_KEYS.some((k) => {
|
|
7997
|
+
const v = own(b, k);
|
|
7998
|
+
return !!v && typeof v === "object" && !Array.isArray(v);
|
|
7999
|
+
}));
|
|
8000
|
+
}
|
|
7991
8001
|
function ownName(n) {
|
|
7992
8002
|
for (const k of ACCESSIBLE_NAME_KEYS) {
|
|
7993
8003
|
const v = own(n, k);
|
|
@@ -8042,6 +8052,7 @@ function componentTypeOf(node) {
|
|
|
8042
8052
|
function accessibleNameGaps(descriptor, options = {}) {
|
|
8043
8053
|
const out2 = [];
|
|
8044
8054
|
const interactiveOnly = options.interactiveOnly === true;
|
|
8055
|
+
const propHandlers = options.propHandlers === true;
|
|
8045
8056
|
const walk2 = (node, path2, key2, suppressed = false) => {
|
|
8046
8057
|
if (!node || typeof node !== "object") return;
|
|
8047
8058
|
if (Array.isArray(node)) {
|
|
@@ -8057,9 +8068,9 @@ function accessibleNameGaps(descriptor, options = {}) {
|
|
|
8057
8068
|
const viaBody = componentTypeOf(n) !== null && typeof n.body?.type === "string";
|
|
8058
8069
|
const bodyMods = viaBody ? n.body.modifiers : void 0;
|
|
8059
8070
|
const deferToBody = viaBody && !hasMods && !!bodyMods && typeof bodyMods === "object" && !Array.isArray(bodyMods);
|
|
8060
|
-
if (key2 !== "modifiers" && !suppressed && !deferToBody) {
|
|
8071
|
+
if (key2 !== "modifiers" && !suppressed && !deferToBody && !(propHandlers && key2 === "props")) {
|
|
8061
8072
|
if (exposure !== "decorative") {
|
|
8062
|
-
const tappable = isInteractive(n);
|
|
8073
|
+
const tappable = isInteractive(n) || propHandlers && hasPropHandler(n);
|
|
8063
8074
|
const type = componentTypeOf(n);
|
|
8064
8075
|
const row = type ? own(A11Y_CONTRACT, type) : void 0;
|
|
8065
8076
|
const contractual = !interactiveOnly && row?.name === "required";
|
|
@@ -8081,7 +8092,7 @@ function accessibleNameGaps(descriptor, options = {}) {
|
|
|
8081
8092
|
walk2(descriptor, "", "");
|
|
8082
8093
|
return out2;
|
|
8083
8094
|
}
|
|
8084
|
-
var A11Y_CONTRACT, ACCESSIBLE_NAME_KEYS, CONTENT_ARM_KEYS, TAP_MODIFIER_KEYS;
|
|
8095
|
+
var A11Y_CONTRACT, ACCESSIBLE_NAME_KEYS, CONTENT_ARM_KEYS, TAP_MODIFIER_KEYS, PROP_HANDLER_KEYS;
|
|
8085
8096
|
var init_a11y_contract = __esm({
|
|
8086
8097
|
"../../core/dev-server/src/a11y-contract.ts"() {
|
|
8087
8098
|
A11Y_CONTRACT = {
|
|
@@ -8220,6 +8231,7 @@ var init_a11y_contract = __esm({
|
|
|
8220
8231
|
];
|
|
8221
8232
|
CONTENT_ARM_KEYS = ["template", "timeRef"];
|
|
8222
8233
|
TAP_MODIFIER_KEYS = ["onTap", "onLongPress", "onDoubleTap"];
|
|
8234
|
+
PROP_HANDLER_KEYS = ["action", ...TAP_MODIFIER_KEYS];
|
|
8223
8235
|
}
|
|
8224
8236
|
});
|
|
8225
8237
|
|
|
@@ -15843,6 +15855,24 @@ async function localDbEntities(dir) {
|
|
|
15843
15855
|
const names = (schema?.entities ?? []).map((e) => e?.name).filter((n) => typeof n === "string" && n.length > 0);
|
|
15844
15856
|
return names.length ? names.map((name) => ({ name })) : null;
|
|
15845
15857
|
}
|
|
15858
|
+
function renderMocksFrom(rules, bad2, seeded) {
|
|
15859
|
+
if (rules || seeded) {
|
|
15860
|
+
return {
|
|
15861
|
+
mocks: {
|
|
15862
|
+
rules: rules?.rules ?? null,
|
|
15863
|
+
cmsSeed: seeded,
|
|
15864
|
+
from: [...rules ? [rules.from] : [], ...seeded ? ["config cms seed rows"] : []],
|
|
15865
|
+
...bad2 ? { ignored: `the net mocks could not be read (${bad2}), so only the cms seed rows were used` } : {}
|
|
15866
|
+
}
|
|
15867
|
+
};
|
|
15868
|
+
}
|
|
15869
|
+
return {
|
|
15870
|
+
note: {
|
|
15871
|
+
used: false,
|
|
15872
|
+
reason: bad2 ? `the net mocks could not be read (${bad2}), and the config declares no cms seed rows \u2014 the initial state was rendered` : "no recorded data: no dev/net-mocks.json (`himi test --record` writes it) and no cms seed rows in config.ts \u2014 the initial state was rendered"
|
|
15873
|
+
}
|
|
15874
|
+
};
|
|
15875
|
+
}
|
|
15846
15876
|
var init_crawl_layers = __esm({
|
|
15847
15877
|
"src/crawl-layers.ts"() {
|
|
15848
15878
|
init_app_config_loader();
|
|
@@ -24719,7 +24749,6 @@ function shouldRevealError(args) {
|
|
|
24719
24749
|
var EMAIL_RE, URL_HOST_RE, INTEGER_RE, NUMBER_RE;
|
|
24720
24750
|
var init_validation = __esm({
|
|
24721
24751
|
"../../core/ts/src/validation.ts"() {
|
|
24722
|
-
"use strict";
|
|
24723
24752
|
EMAIL_RE = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9-]+(\.[A-Za-z0-9-]+)*\.[A-Za-z]{2,}$/;
|
|
24724
24753
|
URL_HOST_RE = /^[A-Za-z0-9-]+(\.[A-Za-z0-9-]+)*(:[0-9]+)?$/;
|
|
24725
24754
|
INTEGER_RE = /^[+-]?[0-9]+$/;
|
|
@@ -29120,7 +29149,6 @@ function validateWorkerSubtree(raw, slotId, policy, actionCatalog) {
|
|
|
29120
29149
|
var WORKER_SUBTREE_CEILINGS, ID_PART, ACTION_ID;
|
|
29121
29150
|
var init_worker_subtree = __esm({
|
|
29122
29151
|
"../../core/ts/src/worker-subtree.ts"() {
|
|
29123
|
-
"use strict";
|
|
29124
29152
|
init_worker_subtree_safe_types_generated();
|
|
29125
29153
|
WORKER_SUBTREE_CEILINGS = Object.freeze({
|
|
29126
29154
|
maxEncodedBytes: 65536,
|
|
@@ -42075,6 +42103,7 @@ __export(screen_crawl_exports, {
|
|
|
42075
42103
|
emptiedArrays: () => emptiedArrays,
|
|
42076
42104
|
findingKey: () => findingKey,
|
|
42077
42105
|
hostDispatch: () => hostDispatch,
|
|
42106
|
+
isUnservedRequest: () => isUnservedRequest,
|
|
42078
42107
|
layeredTransport: () => layeredTransport,
|
|
42079
42108
|
mapBundleFrame: () => mapBundleFrame,
|
|
42080
42109
|
netTransport: () => netTransport,
|
|
@@ -42171,6 +42200,10 @@ function rootOnAppear(descriptor) {
|
|
|
42171
42200
|
function isTransportReject(message) {
|
|
42172
42201
|
return message === REJECT_ALL_MESSAGE || message.startsWith("no data layer served ");
|
|
42173
42202
|
}
|
|
42203
|
+
function isUnservedRequest(e) {
|
|
42204
|
+
const m = e instanceof Error ? e.message : typeof e === "string" ? e : "";
|
|
42205
|
+
return isTransportReject(m) || m.startsWith("wix-data is served only from the config's declared cms") || m.startsWith("the cms seed layer answers queries only") || m.startsWith("no net fixture rule matched ") || m.startsWith("net fixture rule ") && m.includes("`transform` rule");
|
|
42206
|
+
}
|
|
42174
42207
|
function mapBundleFrame(mapJson, genLine, genCol) {
|
|
42175
42208
|
try {
|
|
42176
42209
|
const map = JSON.parse(mapJson);
|
|
@@ -43573,7 +43606,8 @@ async function bootAndSnapshotState(opts) {
|
|
|
43573
43606
|
const { screenId, descriptor, bundleJs } = opts;
|
|
43574
43607
|
if (!bundleJs) return null;
|
|
43575
43608
|
return withSwallowedRejections(async () => {
|
|
43576
|
-
const
|
|
43609
|
+
const layered = layeredTransport({ rules: opts.rules, cmsSeed: opts.cmsSeed, canned: opts.canned });
|
|
43610
|
+
const wixCall = opts.transportWrap ? opts.transportWrap(layered.call) : layered.call;
|
|
43577
43611
|
let w;
|
|
43578
43612
|
try {
|
|
43579
43613
|
w = await bootScreenWorker({ bundleJs, descriptor, wixCall, screenId, budgetMs: opts.budgetMs, localDbSchema: opts.localDbSchema, capabilitySuccess: opts.capabilitySuccess === true, ...opts.evaluator ? { evaluator: opts.evaluator } : {} });
|
|
@@ -44818,7 +44852,7 @@ async function buildFromLoaded(preloaded, dest, opts = {}) {
|
|
|
44818
44852
|
};
|
|
44819
44853
|
return liveWixCall;
|
|
44820
44854
|
};
|
|
44821
|
-
const renderServicesFor = async (bundleName, descriptor) => {
|
|
44855
|
+
const renderServicesFor = async (bundleName, descriptor, renderedScreenId) => {
|
|
44822
44856
|
const bakedInput = descriptor?.input;
|
|
44823
44857
|
const input = {
|
|
44824
44858
|
...bakedInput && typeof bakedInput === "object" && !Array.isArray(bakedInput) ? bakedInput : {},
|
|
@@ -44828,6 +44862,9 @@ async function buildFromLoaded(preloaded, dest, opts = {}) {
|
|
|
44828
44862
|
let liveCalls;
|
|
44829
44863
|
let liveReturned;
|
|
44830
44864
|
let liveErrors;
|
|
44865
|
+
let mockCalls;
|
|
44866
|
+
let mockBootFailed;
|
|
44867
|
+
let mockUnserved;
|
|
44831
44868
|
if (collect2?.render?.live) {
|
|
44832
44869
|
const bundleJs = bundleName ? readBuiltBundle(tier5Dir, bundleName) : void 0;
|
|
44833
44870
|
if (!bundleJs) {
|
|
@@ -44846,6 +44883,38 @@ async function buildFromLoaded(preloaded, dest, opts = {}) {
|
|
|
44846
44883
|
liveReturned = counting.returned();
|
|
44847
44884
|
liveErrors = live2.errors;
|
|
44848
44885
|
}
|
|
44886
|
+
} else if (collect2?.render?.mocks && collect2.render.hydrate !== false && bundleName && readBuiltBundle(tier5Dir, bundleName)) {
|
|
44887
|
+
const calls = [];
|
|
44888
|
+
const unserved = [];
|
|
44889
|
+
const snap = await bootAndSnapshotState({
|
|
44890
|
+
screenId: renderedScreenId ?? String(descriptor?.screenId ?? bundleName),
|
|
44891
|
+
// `--input` rides on the descriptor's own input, as a navigator hands it over.
|
|
44892
|
+
descriptor: collect2.render.input && descriptor && typeof descriptor === "object" ? { ...descriptor, input } : descriptor,
|
|
44893
|
+
bundleJs: readBuiltBundle(tier5Dir, bundleName),
|
|
44894
|
+
knownScreenIds: /* @__PURE__ */ new Set(),
|
|
44895
|
+
rules: collect2.render.mocks.rules ?? null,
|
|
44896
|
+
...collect2.render.mocks.cmsSeed ? { cmsSeed: collect2.render.mocks.cmsSeed } : {},
|
|
44897
|
+
localDbSchema: appLocalDbSchema,
|
|
44898
|
+
...collect2.render.loadBudgetMs ? { budgetMs: collect2.render.loadBudgetMs } : {},
|
|
44899
|
+
transportWrap: (call) => async (method, path2, body) => {
|
|
44900
|
+
const label2 = `${String(method).toUpperCase()} ${String(path2).split("?")[0]}`;
|
|
44901
|
+
calls.push(label2);
|
|
44902
|
+
try {
|
|
44903
|
+
return await call(method, path2, body);
|
|
44904
|
+
} catch (e) {
|
|
44905
|
+
if (isUnservedRequest(e)) unserved.push(label2);
|
|
44906
|
+
throw e;
|
|
44907
|
+
}
|
|
44908
|
+
}
|
|
44909
|
+
});
|
|
44910
|
+
if (snap && Object.keys(snap).length > 0) {
|
|
44911
|
+
base2 = snap;
|
|
44912
|
+
mockCalls = calls;
|
|
44913
|
+
mockUnserved = unserved;
|
|
44914
|
+
} else {
|
|
44915
|
+
mockBootFailed = true;
|
|
44916
|
+
base2 = hydrationServices(tier5Dir, bundleName, input);
|
|
44917
|
+
}
|
|
44849
44918
|
} else {
|
|
44850
44919
|
base2 = collect2?.render?.hydrate === false ? null : hydrationServices(
|
|
44851
44920
|
tier5Dir,
|
|
@@ -44860,7 +44929,7 @@ async function buildFromLoaded(preloaded, dest, opts = {}) {
|
|
|
44860
44929
|
services[svc] = { ...services[svc] ?? {}, ...st };
|
|
44861
44930
|
}
|
|
44862
44931
|
}
|
|
44863
|
-
return { services, ...liveCalls ? { liveCalls } : {}, ...liveReturned !== void 0 ? { liveReturned } : {}, ...liveErrors ? { liveErrors } : {} };
|
|
44932
|
+
return { services, ...liveCalls ? { liveCalls } : {}, ...liveReturned !== void 0 ? { liveReturned } : {}, ...liveErrors ? { liveErrors } : {}, ...mockCalls ? { mockCalls, mockUnserved: mockUnserved ?? [] } : {}, ...mockBootFailed ? { mockBootFailed } : {} };
|
|
44864
44933
|
};
|
|
44865
44934
|
checkRegistry(preloaded.module.actions);
|
|
44866
44935
|
await store.reset(app);
|
|
@@ -45130,9 +45199,9 @@ async function buildFromLoaded(preloaded, dest, opts = {}) {
|
|
|
45130
45199
|
lint.push({ screenId, ok: report3.ok, issues: report3.issues });
|
|
45131
45200
|
}
|
|
45132
45201
|
if (collect2?.render && (!collect2.render.screenId || collect2.render.screenId === screenId)) {
|
|
45133
|
-
const { services, liveCalls, liveReturned, liveErrors } = await renderServicesFor(bundleName, descriptor);
|
|
45202
|
+
const { services, liveCalls, liveReturned, liveErrors, mockCalls, mockUnserved, mockBootFailed } = await renderServicesFor(bundleName, descriptor, screenId);
|
|
45134
45203
|
const rr = await renderDescriptorWeb(descriptor, renderTokens, renderScheme, collect2.render.png === true, services, collect2.render.width, collect2.render.rtl === true);
|
|
45135
|
-
renders.push({ screenId, scheme: renderScheme, ...rr, ...await renderCompare(descriptor, services), ...collect2.render.dumpState && services ? { serviceState: services } : {}, ...liveCalls ? { liveCalls } : {}, ...liveReturned !== void 0 ? { liveReturned } : {}, ...liveErrors ? { liveErrors } : {} });
|
|
45204
|
+
renders.push({ screenId, scheme: renderScheme, ...rr, ...await renderCompare(descriptor, services), ...collect2.render.dumpState && services ? { serviceState: services } : {}, ...liveCalls ? { liveCalls } : {}, ...liveReturned !== void 0 ? { liveReturned } : {}, ...liveErrors ? { liveErrors } : {}, ...mockCalls ? { mockCalls, mockUnserved } : {}, ...mockBootFailed ? { mockBootFailed } : {} });
|
|
45136
45205
|
}
|
|
45137
45206
|
}
|
|
45138
45207
|
const wantedRender = collect2?.render?.screenId;
|
|
@@ -45158,9 +45227,9 @@ async function buildFromLoaded(preloaded, dest, opts = {}) {
|
|
|
45158
45227
|
});
|
|
45159
45228
|
lint.push({ screenId: wantedRender, ok: report3.ok, issues: report3.issues });
|
|
45160
45229
|
}
|
|
45161
|
-
const { services, liveCalls, liveReturned, liveErrors } = await renderServicesFor(bundleName, descriptor);
|
|
45230
|
+
const { services, liveCalls, liveReturned, liveErrors, mockCalls, mockUnserved, mockBootFailed } = await renderServicesFor(bundleName, descriptor, wantedRender);
|
|
45162
45231
|
const rr = await renderDescriptorWeb(descriptor, renderTokens, renderScheme, collect2.render.png === true, services, collect2.render.width, collect2.render.rtl === true);
|
|
45163
|
-
renders.push({ screenId: wantedRender, scheme: renderScheme, ...rr, ...await renderCompare(descriptor, services), ...collect2.render.dumpState && services ? { serviceState: services } : {}, ...liveCalls ? { liveCalls } : {}, ...liveReturned !== void 0 ? { liveReturned } : {}, ...liveErrors ? { liveErrors } : {} });
|
|
45232
|
+
renders.push({ screenId: wantedRender, scheme: renderScheme, ...rr, ...await renderCompare(descriptor, services), ...collect2.render.dumpState && services ? { serviceState: services } : {}, ...liveCalls ? { liveCalls } : {}, ...liveReturned !== void 0 ? { liveReturned } : {}, ...liveErrors ? { liveErrors } : {}, ...mockCalls ? { mockCalls, mockUnserved } : {}, ...mockBootFailed ? { mockBootFailed } : {} });
|
|
45164
45233
|
}
|
|
45165
45234
|
}
|
|
45166
45235
|
for (const t of preloaded.module.screens.templated ?? []) {
|
|
@@ -48573,19 +48642,300 @@ var init_app_source = __esm({
|
|
|
48573
48642
|
}
|
|
48574
48643
|
});
|
|
48575
48644
|
|
|
48645
|
+
// src/run-watch.ts
|
|
48646
|
+
var run_watch_exports = {};
|
|
48647
|
+
__export(run_watch_exports, {
|
|
48648
|
+
ReloadBus: () => ReloadBus,
|
|
48649
|
+
boardFile: () => boardFile,
|
|
48650
|
+
diffReleases: () => diffReleases,
|
|
48651
|
+
himiDir: () => himiDir,
|
|
48652
|
+
ignoredChange: () => ignoredChange,
|
|
48653
|
+
isEmptyDiff: () => isEmptyDiff,
|
|
48654
|
+
rebuildBlocker: () => rebuildBlocker,
|
|
48655
|
+
reloadEvents: () => reloadEvents,
|
|
48656
|
+
removeInitWatchRecord: () => removeInitWatchRecord,
|
|
48657
|
+
removeRunRecord: () => removeRunRecord,
|
|
48658
|
+
serialize: () => serialize,
|
|
48659
|
+
watchContent: () => watchContent,
|
|
48660
|
+
watchFlagConflict: () => watchFlagConflict,
|
|
48661
|
+
watchedContentDir: () => watchedContentDir,
|
|
48662
|
+
writeRunRecord: () => writeRunRecord
|
|
48663
|
+
});
|
|
48664
|
+
import { closeSync as closeSync3, constants, existsSync as existsSync47, fstatSync, lstatSync as lstatSync10, statSync as statSync11, mkdirSync as mkdirSync23, openSync as openSync3, readFileSync as readFileSync51, realpathSync as realpathSync7, renameSync as renameSync5, rmSync as rmSync11, writeFileSync as writeFileSync18, watch as fsWatch } from "node:fs";
|
|
48665
|
+
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
48666
|
+
import { extname as extname4, join as join45, resolve as resolve41, sep as sep12 } from "node:path";
|
|
48667
|
+
function diffReleases(prev, next) {
|
|
48668
|
+
const bundles = Object.keys(next.bundles).filter((b) => prev.bundles[b]?.sha256 !== next.bundles[b].sha256).sort();
|
|
48669
|
+
const changedBundles = new Set(bundles);
|
|
48670
|
+
const screens = Object.entries(next.screens).filter(([id, s]) => {
|
|
48671
|
+
const before = prev.screens[id];
|
|
48672
|
+
return !before || before.descriptorHash !== s.descriptorHash || before.bundle !== s.bundle || s.bundle !== null && changedBundles.has(s.bundle);
|
|
48673
|
+
}).map(([id]) => id).sort();
|
|
48674
|
+
const prevIds = Object.keys(prev.screens).sort().join("\n");
|
|
48675
|
+
const nextIds = Object.keys(next.screens).sort().join("\n");
|
|
48676
|
+
return {
|
|
48677
|
+
screens,
|
|
48678
|
+
bundles,
|
|
48679
|
+
tokens: prev.tokensHash !== next.tokensHash,
|
|
48680
|
+
config: prev.appConfigHash !== next.appConfigHash || prevIds !== nextIds
|
|
48681
|
+
};
|
|
48682
|
+
}
|
|
48683
|
+
function reloadEvents(d) {
|
|
48684
|
+
const out2 = [];
|
|
48685
|
+
if (d.bundles.length) out2.push(["bundle.invalidated", { bundles: d.bundles }]);
|
|
48686
|
+
if (d.screens.length) out2.push(["screen.invalidated", { screenIds: d.screens }]);
|
|
48687
|
+
if (d.tokens) out2.push(["tokens.changed", {}]);
|
|
48688
|
+
if (d.config) out2.push(["config.invalidated", {}]);
|
|
48689
|
+
return out2;
|
|
48690
|
+
}
|
|
48691
|
+
function ignoredChange(relPath, isDirectory = () => false) {
|
|
48692
|
+
const parts = relPath.split(/[\\/]/).filter(Boolean);
|
|
48693
|
+
if (!parts.length) return true;
|
|
48694
|
+
const [top] = parts;
|
|
48695
|
+
if ([".himi", "dist", "node_modules", ".git", ".build", ...AGENT_TOOLING_DIRS].includes(top)) return true;
|
|
48696
|
+
if (parts.length === 1 && AGENT_TOOLING_ROOT_FILES.includes(top)) return true;
|
|
48697
|
+
if ((top === "shots" || top === "evidence") && (parts.length > 1 || isDirectory(top))) return true;
|
|
48698
|
+
const name = parts[parts.length - 1];
|
|
48699
|
+
return /(^\.#|~$|\.sw[a-p]$|^\.DS_Store$|\.tmp$)/.test(name);
|
|
48700
|
+
}
|
|
48701
|
+
function watchContent(dir, onChange, opts = {}) {
|
|
48702
|
+
const debounceMs = opts.debounceMs ?? 200;
|
|
48703
|
+
const watch2 = opts.watch ?? ((p, o, l) => fsWatch(p, o, (event, file) => l(event, file === null ? null : String(file))));
|
|
48704
|
+
let timer = null;
|
|
48705
|
+
const pending = /* @__PURE__ */ new Set();
|
|
48706
|
+
const watcher2 = watch2(dir, { recursive: true }, (_event, file) => {
|
|
48707
|
+
if (!file || ignoredChange(file, (rel) => isDir(join45(dir, rel)))) return;
|
|
48708
|
+
pending.add(file);
|
|
48709
|
+
if (timer) clearTimeout(timer);
|
|
48710
|
+
timer = setTimeout(() => {
|
|
48711
|
+
timer = null;
|
|
48712
|
+
const paths = [...pending];
|
|
48713
|
+
pending.clear();
|
|
48714
|
+
onChange(paths);
|
|
48715
|
+
}, debounceMs);
|
|
48716
|
+
});
|
|
48717
|
+
return {
|
|
48718
|
+
close() {
|
|
48719
|
+
if (timer) clearTimeout(timer);
|
|
48720
|
+
watcher2.close();
|
|
48721
|
+
}
|
|
48722
|
+
};
|
|
48723
|
+
}
|
|
48724
|
+
function watchedContentDir(source) {
|
|
48725
|
+
if (!source || /^https?:\/\//i.test(source)) return null;
|
|
48726
|
+
const dir = resolve41(source);
|
|
48727
|
+
try {
|
|
48728
|
+
return statSync11(dir).isDirectory() && existsSync47(join45(dir, "himalaya.content.json")) ? dir : null;
|
|
48729
|
+
} catch {
|
|
48730
|
+
return null;
|
|
48731
|
+
}
|
|
48732
|
+
}
|
|
48733
|
+
function watchFlagConflict(opts) {
|
|
48734
|
+
if (opts.source) {
|
|
48735
|
+
return { error: "watch_needs_source", message: "--watch rebuilds a content package on every save, so it cannot watch a packaged release or a URL. Pass the app's directory (the one with himalaya.content.json), --content <dir>, or run it from there with no argument." };
|
|
48736
|
+
}
|
|
48737
|
+
if (opts.browser) {
|
|
48738
|
+
return { error: "watch_with_browser", message: "--watch already opens the app in your browser, so drop --browser. (--browser is a different mode: it runs the whole serve plane inside the tab, leaving no process here to rebuild on save.) Under --watch a site-bound app shows the site's live data on its own." };
|
|
48739
|
+
}
|
|
48740
|
+
if (opts.spa) {
|
|
48741
|
+
return { error: "watch_with_spa", message: "--watch pushes each rebuild to the open tab over this server's own origin, and a hosted SPA (--spa) listens on its origin instead, so it would never repaint. Drop --spa to watch with the bundled web host, or drop --watch." };
|
|
48742
|
+
}
|
|
48743
|
+
return null;
|
|
48744
|
+
}
|
|
48745
|
+
function rebuildBlocker(outcome, servedMissing) {
|
|
48746
|
+
const failed2 = outcome.failedToBuild[0];
|
|
48747
|
+
if (failed2) return `${failed2.screenId}: ${failed2.error.split("\n")[0]}`;
|
|
48748
|
+
const had = new Set(servedMissing.map((m) => `${m.screen}\0${m.bundle}`));
|
|
48749
|
+
const lost = outcome.missingBundles.find((m) => !had.has(`${m.screen}\0${m.bundle}`));
|
|
48750
|
+
if (lost) return `${lost.screen}: its worker bundle "${lost.bundle}" is referenced but was not built`;
|
|
48751
|
+
return null;
|
|
48752
|
+
}
|
|
48753
|
+
function serialize(task) {
|
|
48754
|
+
let running = null;
|
|
48755
|
+
let again = false;
|
|
48756
|
+
let stopped = false;
|
|
48757
|
+
const go = () => {
|
|
48758
|
+
if (stopped) return;
|
|
48759
|
+
if (running) {
|
|
48760
|
+
again = true;
|
|
48761
|
+
return;
|
|
48762
|
+
}
|
|
48763
|
+
running = (async () => {
|
|
48764
|
+
try {
|
|
48765
|
+
await task();
|
|
48766
|
+
} finally {
|
|
48767
|
+
running = null;
|
|
48768
|
+
if (again && !stopped) {
|
|
48769
|
+
again = false;
|
|
48770
|
+
go();
|
|
48771
|
+
}
|
|
48772
|
+
}
|
|
48773
|
+
})();
|
|
48774
|
+
};
|
|
48775
|
+
return Object.assign(() => go(), {
|
|
48776
|
+
async stop() {
|
|
48777
|
+
stopped = true;
|
|
48778
|
+
again = false;
|
|
48779
|
+
await running?.catch(() => {
|
|
48780
|
+
});
|
|
48781
|
+
}
|
|
48782
|
+
});
|
|
48783
|
+
}
|
|
48784
|
+
function himiDir(dir) {
|
|
48785
|
+
const root = join45(dir, ".himi");
|
|
48786
|
+
try {
|
|
48787
|
+
return lstatSync10(root).isDirectory() ? root : null;
|
|
48788
|
+
} catch (e) {
|
|
48789
|
+
if (e.code !== "ENOENT") return null;
|
|
48790
|
+
mkdirSync23(root, { recursive: true });
|
|
48791
|
+
return lstatSync10(root).isDirectory() ? root : null;
|
|
48792
|
+
}
|
|
48793
|
+
}
|
|
48794
|
+
function writeRunRecord(dir, record2) {
|
|
48795
|
+
const root = himiDir(dir);
|
|
48796
|
+
if (!root) return false;
|
|
48797
|
+
const tmp = join45(root, `.run.json.${process.pid}.${randomUUID3()}.tmp`);
|
|
48798
|
+
try {
|
|
48799
|
+
writeFileSync18(tmp, `${JSON.stringify(record2, null, 2)}
|
|
48800
|
+
`, { flag: "wx" });
|
|
48801
|
+
renameSync5(tmp, join45(root, "run.json"));
|
|
48802
|
+
} catch (e) {
|
|
48803
|
+
rmSync11(tmp, { force: true });
|
|
48804
|
+
throw e;
|
|
48805
|
+
}
|
|
48806
|
+
return true;
|
|
48807
|
+
}
|
|
48808
|
+
function removeRunRecord(dir, owner) {
|
|
48809
|
+
const root = join45(dir, ".himi");
|
|
48810
|
+
try {
|
|
48811
|
+
if (!lstatSync10(root).isDirectory()) return;
|
|
48812
|
+
} catch {
|
|
48813
|
+
return;
|
|
48814
|
+
}
|
|
48815
|
+
if (owner !== void 0) {
|
|
48816
|
+
const bytes2 = readConfined(root, "run.json");
|
|
48817
|
+
let pid;
|
|
48818
|
+
try {
|
|
48819
|
+
pid = bytes2 ? JSON.parse(bytes2.toString("utf8")).pid : void 0;
|
|
48820
|
+
} catch {
|
|
48821
|
+
}
|
|
48822
|
+
if (typeof pid === "number" && pid !== owner) return;
|
|
48823
|
+
}
|
|
48824
|
+
rmSync11(join45(root, "run.json"), { force: true });
|
|
48825
|
+
}
|
|
48826
|
+
function removeInitWatchRecord(dir, owner) {
|
|
48827
|
+
const root = join45(dir, ".himi");
|
|
48828
|
+
try {
|
|
48829
|
+
if (!lstatSync10(root).isDirectory()) return;
|
|
48830
|
+
} catch {
|
|
48831
|
+
return;
|
|
48832
|
+
}
|
|
48833
|
+
const bytes2 = readConfined(root, "watch.json");
|
|
48834
|
+
if (!bytes2) return;
|
|
48835
|
+
let pid;
|
|
48836
|
+
try {
|
|
48837
|
+
pid = JSON.parse(bytes2.toString("utf8")).pid;
|
|
48838
|
+
} catch {
|
|
48839
|
+
return;
|
|
48840
|
+
}
|
|
48841
|
+
if (pid !== owner) return;
|
|
48842
|
+
rmSync11(join45(root, "watch.json"), { force: true });
|
|
48843
|
+
}
|
|
48844
|
+
function boardFile(dir, pathname) {
|
|
48845
|
+
if (pathname === RUN_IDENTITY_PATH) {
|
|
48846
|
+
return new Response(JSON.stringify(runIdentity(dir)), { status: 200, headers: { "content-type": "application/json", "cache-control": "no-store" } });
|
|
48847
|
+
}
|
|
48848
|
+
let rel = null;
|
|
48849
|
+
if (pathname === "/__himi/studio/" || pathname === "/__himi/studio/index.html") rel = join45("studio", "index.html");
|
|
48850
|
+
else if (pathname === "/__himi/studio/state.js") rel = join45("studio", "state.js");
|
|
48851
|
+
else if (pathname === "/__himi/studio/timeline.js") rel = join45("studio", "timeline.js");
|
|
48852
|
+
else {
|
|
48853
|
+
const m = /^\/__himi\/history\/([0-9a-f]{64}\.(png|jpg|gif|webp))$/.exec(pathname);
|
|
48854
|
+
if (m) rel = join45("history", m[1]);
|
|
48855
|
+
}
|
|
48856
|
+
if (!rel) return null;
|
|
48857
|
+
const bytes2 = readConfined(resolve41(dir, ".himi"), rel);
|
|
48858
|
+
if (!bytes2) return new Response("not found", { status: 404 });
|
|
48859
|
+
return new Response(bytes2, {
|
|
48860
|
+
status: 200,
|
|
48861
|
+
headers: { "content-type": BOARD_TYPES[extname4(rel)] ?? "application/octet-stream", "cache-control": rel.startsWith("history") ? "public, max-age=31536000, immutable" : "no-store" }
|
|
48862
|
+
});
|
|
48863
|
+
}
|
|
48864
|
+
function readConfined(root, rel) {
|
|
48865
|
+
try {
|
|
48866
|
+
if (!lstatSync10(root).isDirectory()) return null;
|
|
48867
|
+
const parts = rel.split(sep12);
|
|
48868
|
+
let at2 = root;
|
|
48869
|
+
for (let i = 0; i < parts.length; i++) {
|
|
48870
|
+
at2 = join45(at2, parts[i]);
|
|
48871
|
+
const st = lstatSync10(at2);
|
|
48872
|
+
if (st.isSymbolicLink() || !(i === parts.length - 1 ? st.isFile() : st.isDirectory())) return null;
|
|
48873
|
+
}
|
|
48874
|
+
const canonicalRoot = realpathSync7(root);
|
|
48875
|
+
if (!realpathSync7(at2).startsWith(canonicalRoot + sep12)) return null;
|
|
48876
|
+
const fd = openSync3(at2, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
|
|
48877
|
+
try {
|
|
48878
|
+
if (!fstatSync(fd).isFile()) return null;
|
|
48879
|
+
return readFileSync51(fd);
|
|
48880
|
+
} finally {
|
|
48881
|
+
closeSync3(fd);
|
|
48882
|
+
}
|
|
48883
|
+
} catch {
|
|
48884
|
+
return null;
|
|
48885
|
+
}
|
|
48886
|
+
}
|
|
48887
|
+
var isEmptyDiff, ReloadBus, isDir, BOARD_TYPES;
|
|
48888
|
+
var init_run_watch = __esm({
|
|
48889
|
+
"src/run-watch.ts"() {
|
|
48890
|
+
init_source_deny();
|
|
48891
|
+
init_run_identity();
|
|
48892
|
+
isEmptyDiff = (d) => !d.screens.length && !d.bundles.length && !d.tokens && !d.config;
|
|
48893
|
+
ReloadBus = class {
|
|
48894
|
+
subscribers = /* @__PURE__ */ new Set();
|
|
48895
|
+
subscribe(send2) {
|
|
48896
|
+
this.subscribers.add(send2);
|
|
48897
|
+
return () => this.subscribers.delete(send2);
|
|
48898
|
+
}
|
|
48899
|
+
publish(event, data) {
|
|
48900
|
+
for (const send2 of [...this.subscribers]) send2(event, data);
|
|
48901
|
+
}
|
|
48902
|
+
get listeners() {
|
|
48903
|
+
return this.subscribers.size;
|
|
48904
|
+
}
|
|
48905
|
+
};
|
|
48906
|
+
isDir = (path2) => {
|
|
48907
|
+
try {
|
|
48908
|
+
return statSync11(path2).isDirectory();
|
|
48909
|
+
} catch {
|
|
48910
|
+
return false;
|
|
48911
|
+
}
|
|
48912
|
+
};
|
|
48913
|
+
BOARD_TYPES = {
|
|
48914
|
+
".html": "text/html; charset=utf-8",
|
|
48915
|
+
".js": "text/javascript; charset=utf-8",
|
|
48916
|
+
".png": "image/png",
|
|
48917
|
+
".jpg": "image/jpeg",
|
|
48918
|
+
".gif": "image/gif",
|
|
48919
|
+
".webp": "image/webp"
|
|
48920
|
+
};
|
|
48921
|
+
}
|
|
48922
|
+
});
|
|
48923
|
+
|
|
48576
48924
|
// src/screen-state.ts
|
|
48577
48925
|
var screen_state_exports = {};
|
|
48578
48926
|
__export(screen_state_exports, {
|
|
48579
48927
|
homeStateCaveat: () => homeStateCaveat,
|
|
48928
|
+
offlineArtifact: () => offlineArtifact,
|
|
48580
48929
|
quotableLine: () => quotableLine,
|
|
48581
48930
|
recordShotState: () => recordShotState,
|
|
48931
|
+
recordedShots: () => recordedShots,
|
|
48582
48932
|
screenStateOf: () => screenStateOf,
|
|
48583
48933
|
shotStateFor: () => shotStateFor,
|
|
48584
48934
|
withHomeCaveat: () => withHomeCaveat
|
|
48585
48935
|
});
|
|
48586
48936
|
import { createHash as createHash25 } from "node:crypto";
|
|
48587
|
-
import { lstatSync as
|
|
48588
|
-
import { join as
|
|
48937
|
+
import { lstatSync as lstatSync11, readFileSync as readFileSync52 } from "node:fs";
|
|
48938
|
+
import { join as join46 } from "node:path";
|
|
48589
48939
|
function quotableLine(text8) {
|
|
48590
48940
|
if (!text8) return void 0;
|
|
48591
48941
|
const first = text8.split(/\r?\n/).map((l) => l.trim()).find(Boolean);
|
|
@@ -48627,7 +48977,7 @@ function screenStateOf(services, html, opts = {}) {
|
|
|
48627
48977
|
const flagged = status === "error" || status === "failed" || s.hasError === true || s.isError === true || s.isErrored === true || s.errored === true || s.noError === false || s.notErrored === false;
|
|
48628
48978
|
if (flagged && !(message && page !== void 0 && !painted(message, page)) || message && page !== void 0 && painted(message, page)) {
|
|
48629
48979
|
const line = message && painted(message, page) ? quotableLine(message) : void 0;
|
|
48630
|
-
return { state: "error", ...line ? { line } : {} };
|
|
48980
|
+
return { state: "error", ...line ? { line } : {}, ...opts.offline ? { offline: true } : {} };
|
|
48631
48981
|
}
|
|
48632
48982
|
if (status === "loading" || s.isLoading === true || s.noLoading === false) loading = true;
|
|
48633
48983
|
const emptyText = str3(s.emptyText) ?? str3(s.emptySourceText);
|
|
@@ -48636,14 +48986,15 @@ function screenStateOf(services, html, opts = {}) {
|
|
|
48636
48986
|
empty = { state: "empty", ...line ? { line } : {} };
|
|
48637
48987
|
}
|
|
48638
48988
|
}
|
|
48639
|
-
|
|
48640
|
-
|
|
48989
|
+
const why = { ...opts.initial ? { initial: true } : {}, ...opts.offline ? { offline: true } : {} };
|
|
48990
|
+
if (loading) return { state: "loading", ...why };
|
|
48991
|
+
return empty ? { ...empty, ...why } : { state: "content" };
|
|
48641
48992
|
}
|
|
48642
48993
|
function readRecord(dir) {
|
|
48643
|
-
const file =
|
|
48994
|
+
const file = join46(dir, SHOT_STATES_FILE);
|
|
48644
48995
|
try {
|
|
48645
|
-
if (!
|
|
48646
|
-
const r = JSON.parse(
|
|
48996
|
+
if (!lstatSync11(file).isFile()) return {};
|
|
48997
|
+
const r = JSON.parse(readFileSync52(file, "utf8"));
|
|
48647
48998
|
return r.v === 1 && r.shots && typeof r.shots === "object" ? r.shots : {};
|
|
48648
48999
|
} catch {
|
|
48649
49000
|
return {};
|
|
@@ -48653,9 +49004,9 @@ function recordShotState(dir, screenId, png, state, now = /* @__PURE__ */ new Da
|
|
|
48653
49004
|
try {
|
|
48654
49005
|
if (!state || !realDirIn(dir, ".himi")) return;
|
|
48655
49006
|
const shots = readRecord(dir);
|
|
48656
|
-
shots[sha256Of(png)] = { screen: screenId, state: state.state, ...state.line ? { line: state.line } : {}, ...state.initial ? { initial: true } : {}, at: now.toISOString() };
|
|
49007
|
+
shots[sha256Of(png)] = { screen: screenId, state: state.state, ...state.line ? { line: state.line } : {}, ...state.initial ? { initial: true } : {}, ...state.offline ? { offline: true } : {}, at: now.toISOString() };
|
|
48657
49008
|
const kept = Object.entries(shots).sort(([, a], [, b]) => a.at < b.at ? 1 : a.at > b.at ? -1 : 0).slice(0, KEEP);
|
|
48658
|
-
writeFileAtomic(
|
|
49009
|
+
writeFileAtomic(join46(dir, SHOT_STATES_FILE), `${JSON.stringify({ v: 1, shots: Object.fromEntries(kept) }, null, 2)}
|
|
48659
49010
|
`);
|
|
48660
49011
|
} catch {
|
|
48661
49012
|
}
|
|
@@ -48666,18 +49017,27 @@ function shotStateFor(dir, image) {
|
|
|
48666
49017
|
const r = readRecord(dir)[sha256Of(image)];
|
|
48667
49018
|
if (!r || !["content", "error", "loading", "empty"].includes(r.state)) return null;
|
|
48668
49019
|
const line = typeof r.line === "string" ? quotableLine(r.line) : void 0;
|
|
48669
|
-
return { state: r.state, ...line ? { line } : {}, ...r.initial === true ? { initial: true } : {} };
|
|
49020
|
+
return { state: r.state, ...line ? { line } : {}, ...r.initial === true ? { initial: true } : {}, ...r.offline === true ? { offline: true } : {} };
|
|
48670
49021
|
} catch {
|
|
48671
49022
|
return null;
|
|
48672
49023
|
}
|
|
48673
49024
|
}
|
|
49025
|
+
function recordedShots(dir, screenId) {
|
|
49026
|
+
return Object.entries(readRecord(dir)).filter(([sha, r]) => /^[0-9a-f]{64}$/.test(sha) && !!r && r.screen === screenId && typeof r.at === "string" && ["content", "error", "loading", "empty"].includes(r.state)).map(([sha, r]) => ({ sha256: sha, at: r.at, state: { state: r.state, ...r.initial === true ? { initial: true } : {}, ...r.offline === true ? { offline: true } : {} } })).sort((a, b) => a.at < b.at ? 1 : a.at > b.at ? -1 : 0);
|
|
49027
|
+
}
|
|
49028
|
+
function offlineArtifact(state) {
|
|
49029
|
+
if (!state || state.state === "content") return false;
|
|
49030
|
+
return state.offline === true || state.initial === true && state.state !== "error";
|
|
49031
|
+
}
|
|
48674
49032
|
function homeStateCaveat(state) {
|
|
48675
49033
|
if (!state || state.state === "content") return void 0;
|
|
49034
|
+
if (offlineArtifact(state)) {
|
|
49035
|
+
const shows = state.state === "loading" ? "its loading placeholders" : state.state === "empty" ? "its empty state" : "what it paints when its data can't load";
|
|
49036
|
+
return `it is an offline preview, without the site's data, so the home screen shows ${shows}`;
|
|
49037
|
+
}
|
|
48676
49038
|
const quote = state.line ? `: \u201C${state.line}\u201D` : "";
|
|
48677
49039
|
if (state.state === "error") return `the home screen shows an error${quote}`;
|
|
48678
|
-
if (state.state === "loading")
|
|
48679
|
-
return state.initial ? "the home screen shows its loading placeholders (an offline render loads no data)" : "the home screen is still on its loading state (its data never finished loading)";
|
|
48680
|
-
}
|
|
49040
|
+
if (state.state === "loading") return "the home screen is still on its loading state (its data never finished loading)";
|
|
48681
49041
|
return `the home screen shows its empty state${quote}`;
|
|
48682
49042
|
}
|
|
48683
49043
|
function withHomeCaveat(message, caveat) {
|
|
@@ -48689,11 +49049,11 @@ var SHOT_STATES_FILE, KEEP, LINE_MAX, str3, sha256Of;
|
|
|
48689
49049
|
var init_screen_state = __esm({
|
|
48690
49050
|
"src/screen-state.ts"() {
|
|
48691
49051
|
init_progress();
|
|
48692
|
-
SHOT_STATES_FILE =
|
|
49052
|
+
SHOT_STATES_FILE = join46(".himi", "shot-states.json");
|
|
48693
49053
|
KEEP = 40;
|
|
48694
49054
|
LINE_MAX = 120;
|
|
48695
49055
|
str3 = (v) => typeof v === "string" && v.trim() ? v : void 0;
|
|
48696
|
-
sha256Of = (file) => createHash25("sha256").update(
|
|
49056
|
+
sha256Of = (file) => createHash25("sha256").update(readFileSync52(file)).digest("hex");
|
|
48697
49057
|
}
|
|
48698
49058
|
});
|
|
48699
49059
|
|
|
@@ -48709,10 +49069,10 @@ __export(studio_reel_exports, {
|
|
|
48709
49069
|
utcDay: () => utcDay
|
|
48710
49070
|
});
|
|
48711
49071
|
import { createHash as createHash26 } from "node:crypto";
|
|
48712
|
-
import { lstatSync as
|
|
48713
|
-
import { basename as basename13, join as
|
|
49072
|
+
import { lstatSync as lstatSync12, readFileSync as readFileSync53 } from "node:fs";
|
|
49073
|
+
import { basename as basename13, join as join47 } from "node:path";
|
|
48714
49074
|
function loadMemory2(dir) {
|
|
48715
|
-
const m = readJson5(
|
|
49075
|
+
const m = readJson5(join47(dir, ".himi", "studio", "memory.json"));
|
|
48716
49076
|
if (!isObj3(m) || m.version !== 1 || !isObj3(m.shots) || !Array.isArray(m.timeline)) return null;
|
|
48717
49077
|
const shots = [];
|
|
48718
49078
|
for (const [id, s] of Object.entries(m.shots)) {
|
|
@@ -48749,8 +49109,8 @@ function planReel(dir) {
|
|
|
48749
49109
|
const memory = loadMemory2(dir);
|
|
48750
49110
|
const { entries } = readProgress(dir);
|
|
48751
49111
|
const summary2 = summarizeProgress(entries);
|
|
48752
|
-
const manifest = readJson5(
|
|
48753
|
-
const tokens = readJson5(
|
|
49112
|
+
const manifest = readJson5(join47(dir, "himalaya.content.json"));
|
|
49113
|
+
const tokens = readJson5(join47(dir, "tokens.json"));
|
|
48754
49114
|
const accentToken = tokens?.colors?.accentPrimary;
|
|
48755
49115
|
const accentRaw = typeof accentToken === "string" ? accentToken : accentToken?.light;
|
|
48756
49116
|
const accent = typeof accentRaw === "string" && ACCENT.test(accentRaw) ? accentRaw : DEFAULT_ACCENT;
|
|
@@ -48793,10 +49153,10 @@ function inlineImages(dir, plan, budget = REEL_BUDGET_BYTES) {
|
|
|
48793
49153
|
const verified = (f) => {
|
|
48794
49154
|
if (!HISTORY_NAME.test(f)) return null;
|
|
48795
49155
|
try {
|
|
48796
|
-
const path2 =
|
|
48797
|
-
const st =
|
|
49156
|
+
const path2 = join47(dir, HISTORY_DIR, f);
|
|
49157
|
+
const st = lstatSync12(path2);
|
|
48798
49158
|
if (!st.isFile() || st.nlink !== 1) return null;
|
|
48799
|
-
const bytes2 =
|
|
49159
|
+
const bytes2 = readFileSync53(path2);
|
|
48800
49160
|
return createHash26("sha256").update(bytes2).digest("hex") === f.slice(0, 64) && imageExtension(bytes2) === f.slice(65) ? bytes2 : null;
|
|
48801
49161
|
} catch {
|
|
48802
49162
|
return null;
|
|
@@ -48930,7 +49290,7 @@ var init_studio_reel = __esm({
|
|
|
48930
49290
|
mimeOf = (file) => MIME2[file.slice(65)] ?? "image/png";
|
|
48931
49291
|
readJson5 = (file) => {
|
|
48932
49292
|
try {
|
|
48933
|
-
return JSON.parse(
|
|
49293
|
+
return JSON.parse(readFileSync53(file, "utf8"));
|
|
48934
49294
|
} catch {
|
|
48935
49295
|
return null;
|
|
48936
49296
|
}
|
|
@@ -48954,7 +49314,7 @@ __export(milestones_exports, {
|
|
|
48954
49314
|
FIRST_LOOK_BUDGET_MS: () => FIRST_LOOK_BUDGET_MS,
|
|
48955
49315
|
INIT_SOURCE_FILE: () => INIT_SOURCE_FILE,
|
|
48956
49316
|
INIT_WATCH_IDLE_MINUTES: () => INIT_WATCH_IDLE_MINUTES,
|
|
48957
|
-
|
|
49317
|
+
LEGACY_NO_WATCH_ENV: () => LEGACY_NO_WATCH_ENV,
|
|
48958
49318
|
NO_WATCH_IGNORED_NOTE: () => NO_WATCH_IGNORED_NOTE,
|
|
48959
49319
|
REMOTE_FIRST_LOOK_SKIP: () => REMOTE_FIRST_LOOK_SKIP,
|
|
48960
49320
|
REMOTE_WATCH_SKIP: () => REMOTE_WATCH_SKIP,
|
|
@@ -48974,6 +49334,7 @@ __export(milestones_exports, {
|
|
|
48974
49334
|
defaultStartWatchDeps: () => defaultStartWatchDeps,
|
|
48975
49335
|
displayPath: () => displayPath,
|
|
48976
49336
|
doNowBlock: () => doNowBlock,
|
|
49337
|
+
draftChangeNote: () => draftChangeNote,
|
|
48977
49338
|
draftPictureGap: () => draftPictureGap,
|
|
48978
49339
|
firstLookAtInit: () => firstLookAtInit,
|
|
48979
49340
|
homePictureAtValidate: () => homePictureAtValidate,
|
|
@@ -48993,19 +49354,20 @@ __export(milestones_exports, {
|
|
|
48993
49354
|
testChangeNote: () => testChangeNote,
|
|
48994
49355
|
testPostcardCounts: () => testPostcardCounts,
|
|
48995
49356
|
testPostcardMessage: () => testPostcardMessage,
|
|
49357
|
+
watchOptOutIgnoredNote: () => watchOptOutIgnoredNote,
|
|
48996
49358
|
watchReminder: () => watchReminder,
|
|
48997
49359
|
watchUrlFor: () => watchUrlFor,
|
|
48998
49360
|
writeHandoverReel: () => writeHandoverReel
|
|
48999
49361
|
});
|
|
49000
|
-
import { createHash as createHash27, randomUUID as
|
|
49362
|
+
import { createHash as createHash27, randomUUID as randomUUID4 } from "node:crypto";
|
|
49001
49363
|
import { spawn as spawn5 } from "node:child_process";
|
|
49002
|
-
import { closeSync as
|
|
49364
|
+
import { closeSync as closeSync4, existsSync as existsSync48, lstatSync as lstatSync13, readFileSync as readFileSync54, statSync as statSync12 } from "node:fs";
|
|
49003
49365
|
import { createServer as createServer4 } from "node:net";
|
|
49004
|
-
import { basename as basename14, dirname as dirname27, isAbsolute as isAbsolute4, join as
|
|
49366
|
+
import { basename as basename14, dirname as dirname27, isAbsolute as isAbsolute4, join as join48, relative as relative9, resolve as resolve42, sep as sep13 } from "node:path";
|
|
49005
49367
|
function homeScreenIdOf(dir) {
|
|
49006
49368
|
let src;
|
|
49007
49369
|
try {
|
|
49008
|
-
src =
|
|
49370
|
+
src = readFileSync54(join48(dir, "dev", "index.ts"), "utf8");
|
|
49009
49371
|
} catch {
|
|
49010
49372
|
return null;
|
|
49011
49373
|
}
|
|
@@ -49014,13 +49376,13 @@ function homeScreenIdOf(dir) {
|
|
|
49014
49376
|
return id && /^[A-Za-z0-9_.\-/{}]+$/.test(id) ? id : null;
|
|
49015
49377
|
}
|
|
49016
49378
|
function homeShotPath(dir, home) {
|
|
49017
|
-
const root =
|
|
49018
|
-
const base =
|
|
49019
|
-
const shot =
|
|
49020
|
-
if (!shot.startsWith(base +
|
|
49021
|
-
for (let p = shot; p !== root && p.startsWith(root +
|
|
49379
|
+
const root = resolve42(dir);
|
|
49380
|
+
const base = resolve42(root, ".himi", "shots");
|
|
49381
|
+
const shot = resolve42(base, `${home}.png`);
|
|
49382
|
+
if (!shot.startsWith(base + sep13)) return null;
|
|
49383
|
+
for (let p = shot; p !== root && p.startsWith(root + sep13); p = dirname27(p)) {
|
|
49022
49384
|
try {
|
|
49023
|
-
if (
|
|
49385
|
+
if (lstatSync13(p).isSymbolicLink()) return null;
|
|
49024
49386
|
} catch {
|
|
49025
49387
|
}
|
|
49026
49388
|
}
|
|
@@ -49057,7 +49419,7 @@ async function renderHomeInChild(dir, home, opts) {
|
|
|
49057
49419
|
};
|
|
49058
49420
|
let child;
|
|
49059
49421
|
try {
|
|
49060
|
-
child = spawn5(process.execPath, [...opts.loader ?? [], opts.entry, "render", home, "--png", "--device", "iphone", "--content", dir, "--skip-version-check"], {
|
|
49422
|
+
child = spawn5(process.execPath, [...opts.loader ?? [], opts.entry, "render", home, "--png", "--mocks", "--device", "iphone", "--content", dir, "--skip-version-check"], {
|
|
49061
49423
|
cwd: dir,
|
|
49062
49424
|
// Which command ran it: validate's refresh is the one whose loading skeletons must not be
|
|
49063
49425
|
// journaled; init's first look is the template's honest "Starting point" and always is.
|
|
@@ -49089,7 +49451,7 @@ async function renderHomeInChild(dir, home, opts) {
|
|
|
49089
49451
|
}
|
|
49090
49452
|
let fresh = false;
|
|
49091
49453
|
try {
|
|
49092
|
-
fresh =
|
|
49454
|
+
fresh = statSync12(shot).mtimeMs >= started - 1e3;
|
|
49093
49455
|
} catch {
|
|
49094
49456
|
}
|
|
49095
49457
|
if (code === 0 && fresh) {
|
|
@@ -49123,7 +49485,7 @@ function autoPictureGate(dir) {
|
|
|
49123
49485
|
}
|
|
49124
49486
|
async function homePictureAtValidate(dir, opts) {
|
|
49125
49487
|
if (!opts.ok) return { skipped: "validate found errors, so the picture was left as it is" };
|
|
49126
|
-
if (!
|
|
49488
|
+
if (!existsSync48(join48(dir, "himalaya.content.json"))) return { skipped: "not a content package" };
|
|
49127
49489
|
const env = opts.env ?? process.env;
|
|
49128
49490
|
if (ciSet(env)) return { skipped: "the CI environment variable is set" };
|
|
49129
49491
|
if (env.NODE_TEST_CONTEXT) return { skipped: "running under `node --test`" };
|
|
@@ -49193,7 +49555,7 @@ function stillBuilding(dir, image, now) {
|
|
|
49193
49555
|
}
|
|
49194
49556
|
}
|
|
49195
49557
|
function firstLookBeforeOwnPostcard(dir, image, now) {
|
|
49196
|
-
if (!image || !
|
|
49558
|
+
if (!image || !existsSync48(image) || initDivergence(dir).state !== "diverged") return void 0;
|
|
49197
49559
|
return journal(dir, "first-look", withHomeCaveat(yourFirstLookMessage(dir), homeCaveat(dir, image)), { image, ...now ? { now } : {} });
|
|
49198
49560
|
}
|
|
49199
49561
|
function afterRenderPng(dir, input) {
|
|
@@ -49258,13 +49620,13 @@ function testChangeNote(headline2, change) {
|
|
|
49258
49620
|
}
|
|
49259
49621
|
function afterTestRun(dir, input) {
|
|
49260
49622
|
const firstLook = firstLookBeforeOwnPostcard(dir, input.homeShot, input.now);
|
|
49261
|
-
const shot = input.homeShot &&
|
|
49623
|
+
const shot = input.homeShot && existsSync48(input.homeShot) ? input.homeShot : void 0;
|
|
49262
49624
|
const caveat = shot ? homeCaveat(dir, shot) : void 0;
|
|
49263
49625
|
const counts = testPostcardMessage(input);
|
|
49264
49626
|
const headline2 = caveat ? `${counts} But ${caveat}.` : counts;
|
|
49265
49627
|
const postcard = journal(dir, "test", headline2, {
|
|
49266
49628
|
ref: input.runId,
|
|
49267
|
-
...input.homeShot &&
|
|
49629
|
+
...input.homeShot && existsSync48(input.homeShot) ? { image: input.homeShot } : {},
|
|
49268
49630
|
...input.now ? { now: input.now } : {},
|
|
49269
49631
|
unlessUnchanged: true,
|
|
49270
49632
|
changeNote: (change) => testChangeNote(headline2, change)
|
|
@@ -49285,7 +49647,7 @@ function afterBrowserTestRun(dir, input) {
|
|
|
49285
49647
|
const now = input.now ?? /* @__PURE__ */ new Date();
|
|
49286
49648
|
const failedSteps = input.report.ok === false ? (input.report.steps ?? []).filter((s) => s.ok === false && typeof s.step === "string" && s.step.length <= 40 && !looksSecret(s.step)).map((s) => s.step).slice(0, 3) : [];
|
|
49287
49649
|
return afterTestRun(dir, {
|
|
49288
|
-
runId: `browser-${now.toISOString()}-${
|
|
49650
|
+
runId: `browser-${now.toISOString()}-${randomUUID4().slice(0, 8)}`,
|
|
49289
49651
|
screens: 1,
|
|
49290
49652
|
errors: Array.isArray(input.report.findings) ? input.report.findings.length : 0,
|
|
49291
49653
|
browserScreen: input.screen,
|
|
@@ -49298,7 +49660,7 @@ function afterBrowserTestRun(dir, input) {
|
|
|
49298
49660
|
function shotIsCurrent(shot, sourceChangedAtMs) {
|
|
49299
49661
|
if (sourceChangedAtMs === void 0) return false;
|
|
49300
49662
|
try {
|
|
49301
|
-
return
|
|
49663
|
+
return statSync12(shot).mtimeMs >= sourceChangedAtMs;
|
|
49302
49664
|
} catch {
|
|
49303
49665
|
return false;
|
|
49304
49666
|
}
|
|
@@ -49307,7 +49669,7 @@ function draftPictureGap(input) {
|
|
|
49307
49669
|
if (!input.home) return { reason: "the app's dev/index.ts names no literal homeScreenId, so there is no home screen to picture" };
|
|
49308
49670
|
if (!input.homeShot) return { reason: "the homeScreenId does not name a file under .himi/shots" };
|
|
49309
49671
|
const render = `himi render ${shellQuote(input.home)} --png${contentArg(input.content)}`;
|
|
49310
|
-
if (!
|
|
49672
|
+
if (!existsSync48(input.homeShot)) return { reason: "there is no home shot yet", render };
|
|
49311
49673
|
if (input.sourceChangedAtMs === void 0) return { reason: "the source's last change could not be read, so the home shot cannot be shown to be current", render };
|
|
49312
49674
|
if (!shotIsCurrent(input.homeShot, input.sourceChangedAtMs)) return { reason: "the home shot is older than the last source edit, so it is not a picture of this draft", render };
|
|
49313
49675
|
return null;
|
|
@@ -49318,11 +49680,14 @@ function afterDraftPush(dir, input) {
|
|
|
49318
49680
|
const gap = current || input.home === void 0 ? null : draftPictureGap(input);
|
|
49319
49681
|
const why = gap ? ` No picture: ${gap.reason}${gap.render ? ` \u2014 \`himi render ${shellQuote(input.home)} --png\` makes a current one` : ""}.` : "";
|
|
49320
49682
|
const draftLine = withHomeCaveat(`Draft pushed: release ${input.releaseId.slice(0, 12)} of ${input.app} \u2014 owner-private, not live.`, current ? homeCaveat(dir, input.homeShot) : void 0);
|
|
49321
|
-
const
|
|
49683
|
+
const note = draftChangeNote(dir, { home: input.home ?? null, manifest: input.manifest, picture: current ? input.homeShot : void 0 });
|
|
49684
|
+
const withNote = note && `${draftLine} ${note}${why}`.length <= MAX_MESSAGE_CHARS ? `${draftLine} ${note}` : draftLine;
|
|
49685
|
+
const postcard = journal(dir, "draft", `${withNote}${why}`, {
|
|
49322
49686
|
ref: input.releaseId,
|
|
49323
49687
|
...current ? { image: input.homeShot } : {},
|
|
49324
49688
|
...input.now ? { now: input.now } : {}
|
|
49325
49689
|
});
|
|
49690
|
+
if (input.manifest) rememberDraft(dir, input.releaseId, input.manifest);
|
|
49326
49691
|
const reel = input.reel === false ? void 0 : writeHandoverReel(dir);
|
|
49327
49692
|
const handover = reel && "file" in reel ? `At the handover, send the owner ${shellQuote(displayPath(reel.file))} (the making-of, rebuilt by every draft push) beside that link` : `At the handover, \`himi studio reel${input.content ? ` --content ${shellQuote(input.content)}` : ""}\` writes the making-of to hand the owner beside that link`;
|
|
49328
49693
|
const postcardLine = `${progressCommand({ what: "what the owner can review in this draft", step: "review", content: input.content })} \u2014 your postcard for the draft; share the preview link in chat, never in the journal. ${handover}`;
|
|
@@ -49335,6 +49700,63 @@ function afterDraftPush(dir, input) {
|
|
|
49335
49700
|
next: gap?.render ? `\`${gap.render}\` \u2014 this draft's postcard has no picture: ${gap.reason}; then ${postcardLine} (given no --image, it attaches that picture)` : postcardLine
|
|
49336
49701
|
};
|
|
49337
49702
|
}
|
|
49703
|
+
function rememberDraft(dir, releaseId, m) {
|
|
49704
|
+
try {
|
|
49705
|
+
if (!realDirIn(dir, ".himi")) return;
|
|
49706
|
+
const manifest = {
|
|
49707
|
+
screens: Object.fromEntries(Object.entries(m.screens).map(([id, x]) => [id, { descriptorHash: x.descriptorHash, bundle: x.bundle }])),
|
|
49708
|
+
bundles: Object.fromEntries(Object.entries(m.bundles).map(([id, x]) => [id, { sha256: x.sha256 }])),
|
|
49709
|
+
...m.tokensHash ? { tokensHash: m.tokensHash } : {},
|
|
49710
|
+
...m.appConfigHash ? { appConfigHash: m.appConfigHash } : {}
|
|
49711
|
+
};
|
|
49712
|
+
writeFileAtomic(join48(dir, LAST_DRAFT_FILE), `${JSON.stringify({ v: 1, releaseId, manifest })}
|
|
49713
|
+
`);
|
|
49714
|
+
} catch {
|
|
49715
|
+
}
|
|
49716
|
+
}
|
|
49717
|
+
function lastDraft(dir) {
|
|
49718
|
+
try {
|
|
49719
|
+
const file = join48(dir, LAST_DRAFT_FILE);
|
|
49720
|
+
if (!lstatSync13(file).isFile()) return null;
|
|
49721
|
+
const r = JSON.parse(readFileSync54(file, "utf8"));
|
|
49722
|
+
const m = r.manifest;
|
|
49723
|
+
if (r.v !== 1 || typeof r.releaseId !== "string" || !m || typeof m.screens !== "object" || !m.screens || typeof m.bundles !== "object" || !m.bundles) return null;
|
|
49724
|
+
return { releaseId: r.releaseId, manifest: m };
|
|
49725
|
+
} catch {
|
|
49726
|
+
return null;
|
|
49727
|
+
}
|
|
49728
|
+
}
|
|
49729
|
+
function draftChangeNote(dir, input) {
|
|
49730
|
+
try {
|
|
49731
|
+
const entries = readProgress(dir, { all: true }).entries;
|
|
49732
|
+
const previous = [...entries].reverse().find((e) => e.by === "himi" && e.milestone === "draft");
|
|
49733
|
+
const was = lastDraft(dir);
|
|
49734
|
+
const parts = [];
|
|
49735
|
+
if (input.manifest && was) {
|
|
49736
|
+
const d = diffReleases(was.manifest, input.manifest);
|
|
49737
|
+
const added = Object.keys(input.manifest.screens).filter((id) => !(id in was.manifest.screens));
|
|
49738
|
+
const removed = Object.keys(was.manifest.screens).filter((id) => !(id in input.manifest.screens));
|
|
49739
|
+
const changed = d.screens.filter((id) => !added.includes(id));
|
|
49740
|
+
const homeChanged = input.home !== null && changed.includes(input.home);
|
|
49741
|
+
if (changed.length) parts.push(homeChanged ? changed.length === 1 ? "the home screen changed" : `${plural(changed.length, "screen")} changed, the home screen among them` : `${plural(changed.length, "screen")} changed`);
|
|
49742
|
+
if (added.length) parts.push(`${plural(added.length, "screen")} added`);
|
|
49743
|
+
if (removed.length) parts.push(`${plural(removed.length, "screen")} removed`);
|
|
49744
|
+
if (d.tokens) parts.push("the design tokens changed");
|
|
49745
|
+
if (!parts.length) parts.push(d.config ? "no screen changed, the app's settings did" : "no screen changed");
|
|
49746
|
+
} else {
|
|
49747
|
+
if (!previous) return void 0;
|
|
49748
|
+
const shaOf = (i) => i && typeof i === "object" && typeof i.sha256 === "string" ? i.sha256 : void 0;
|
|
49749
|
+
const had = Array.isArray(previous.images) ? previous.images.map(shaOf).find(Boolean) : void 0;
|
|
49750
|
+
const has = input.picture && existsSync48(input.picture) ? sha256Of2(input.picture) : void 0;
|
|
49751
|
+
if (had && has) parts.push(had === has ? "the home screen looks the same" : "the home screen changed");
|
|
49752
|
+
else if (has) parts.push("a home picture this time");
|
|
49753
|
+
else return void 0;
|
|
49754
|
+
}
|
|
49755
|
+
return `Since the last draft: ${parts.join(", ")}.`;
|
|
49756
|
+
} catch {
|
|
49757
|
+
return void 0;
|
|
49758
|
+
}
|
|
49759
|
+
}
|
|
49338
49760
|
function recordInitSource(dir, opts = {}) {
|
|
49339
49761
|
try {
|
|
49340
49762
|
const at2 = realDirIn(dir, ".himi");
|
|
@@ -49347,7 +49769,7 @@ function recordInitSource(dir, opts = {}) {
|
|
|
49347
49769
|
template: opts.template && TEMPLATE_NAME.test(opts.template) ? opts.template : null,
|
|
49348
49770
|
...opts.source ? { source: opts.source } : {}
|
|
49349
49771
|
};
|
|
49350
|
-
writeFileAtomic(
|
|
49772
|
+
writeFileAtomic(join48(dir, INIT_SOURCE_FILE), `${JSON.stringify(record2, null, 2)}
|
|
49351
49773
|
`);
|
|
49352
49774
|
return true;
|
|
49353
49775
|
} catch {
|
|
@@ -49355,10 +49777,10 @@ function recordInitSource(dir, opts = {}) {
|
|
|
49355
49777
|
}
|
|
49356
49778
|
}
|
|
49357
49779
|
function readInitSource(dir) {
|
|
49358
|
-
const file =
|
|
49780
|
+
const file = join48(dir, INIT_SOURCE_FILE);
|
|
49359
49781
|
try {
|
|
49360
|
-
if (!
|
|
49361
|
-
const r = JSON.parse(
|
|
49782
|
+
if (!lstatSync13(file).isFile()) return null;
|
|
49783
|
+
const r = JSON.parse(readFileSync54(file, "utf8"));
|
|
49362
49784
|
if (r.v !== 1 || typeof r.fingerprint !== "string" || !/^[0-9a-f]{64}$/.test(r.fingerprint) || typeof r.newestMs !== "number") return null;
|
|
49363
49785
|
return {
|
|
49364
49786
|
v: 1,
|
|
@@ -49391,7 +49813,7 @@ function homeShotForPostcard(dir, opts = {}) {
|
|
|
49391
49813
|
const shot = homeShotPath(dir, home);
|
|
49392
49814
|
if (!shot) return { skipped: "the homeScreenId does not name a file under .himi/shots" };
|
|
49393
49815
|
const render = `\`himi render ${shellQuote(home)} --png\``;
|
|
49394
|
-
if (!
|
|
49816
|
+
if (!existsSync48(shot)) return { skipped: `no home shot yet \u2014 ${render} makes one, and the next postcard carries it` };
|
|
49395
49817
|
let changed;
|
|
49396
49818
|
try {
|
|
49397
49819
|
changed = newestSourceChangeMs(dir);
|
|
@@ -49399,9 +49821,22 @@ function homeShotForPostcard(dir, opts = {}) {
|
|
|
49399
49821
|
changed = void 0;
|
|
49400
49822
|
}
|
|
49401
49823
|
if (!shotIsCurrent(shot, changed)) return { skipped: `the home shot is older than the last source change \u2014 ${render} makes a current one` };
|
|
49402
|
-
|
|
49824
|
+
let pick2 = { image: shot };
|
|
49825
|
+
const landed = shotStateFor(dir, shot);
|
|
49826
|
+
if (landed && landed.state !== "content") {
|
|
49827
|
+
const shows = homeStateCaveat(landed) ?? `the home screen shows its ${landed.state} state`;
|
|
49828
|
+
const content = contentHomePictures(dir, home);
|
|
49829
|
+
const current = content.find((c) => changed !== void 0 && Date.parse(c.at) >= changed);
|
|
49830
|
+
if (!current) {
|
|
49831
|
+
const older = content[0] ? `; the newest home picture with content, ${shellQuote(displayPath(content[0].file))}, predates the last source change` : "";
|
|
49832
|
+
const withData = `\`himi render ${shellQuote(home)} --png --mocks\` paints it from the app's recorded data (dev/net-mocks.json \u2014 \`himi test --record\` writes it), \`himi render ${shellQuote(home)} --png --live\` from the site's`;
|
|
49833
|
+
return { skipped: `the current home picture would contradict a postcard about the app working \u2014 ${shows}${older}. ${withData}; or pass --image to attach it on purpose` };
|
|
49834
|
+
}
|
|
49835
|
+
pick2 = { image: current.file, note: `attached the newest home picture that shows content (recorded ${current.at}), not the current home shot \u2014 in that one ${shows}` };
|
|
49836
|
+
}
|
|
49837
|
+
if (opts.repeatOk) return pick2;
|
|
49403
49838
|
try {
|
|
49404
|
-
const sha = sha256Of2(
|
|
49839
|
+
const sha = sha256Of2(pick2.image);
|
|
49405
49840
|
const entries = readProgress(dir, { all: true }).entries;
|
|
49406
49841
|
const latest = entries[entries.length - 1];
|
|
49407
49842
|
const shown2 = Array.isArray(latest?.images) && latest.images.some((i) => !!i && typeof i === "object" && i.sha256 === sha);
|
|
@@ -49409,13 +49844,31 @@ function homeShotForPostcard(dir, opts = {}) {
|
|
|
49409
49844
|
} catch {
|
|
49410
49845
|
return { skipped: "the home shot could not be read" };
|
|
49411
49846
|
}
|
|
49412
|
-
return
|
|
49847
|
+
return pick2;
|
|
49848
|
+
}
|
|
49849
|
+
function contentHomePictures(dir, home) {
|
|
49850
|
+
const out2 = [];
|
|
49851
|
+
const shot = homeShotPath(dir, home);
|
|
49852
|
+
const hist = realDirIn(dir, HISTORY_DIR) ? join48(dir, HISTORY_DIR) : null;
|
|
49853
|
+
for (const r of recordedShots(dir, home)) {
|
|
49854
|
+
if (r.state.state !== "content") continue;
|
|
49855
|
+
for (const file of [shot, hist && join48(hist, `${r.sha256}.png`)]) {
|
|
49856
|
+
try {
|
|
49857
|
+
if (!file || !lstatSync13(file).isFile() || sha256Of2(file) !== r.sha256) continue;
|
|
49858
|
+
} catch {
|
|
49859
|
+
continue;
|
|
49860
|
+
}
|
|
49861
|
+
out2.push({ file, at: r.at });
|
|
49862
|
+
break;
|
|
49863
|
+
}
|
|
49864
|
+
}
|
|
49865
|
+
return out2;
|
|
49413
49866
|
}
|
|
49414
49867
|
function readInitWatch(dir) {
|
|
49415
|
-
const file =
|
|
49868
|
+
const file = join48(dir, INIT_WATCH_FILE);
|
|
49416
49869
|
try {
|
|
49417
|
-
if (!
|
|
49418
|
-
const r = JSON.parse(
|
|
49870
|
+
if (!lstatSync13(file).isFile()) return null;
|
|
49871
|
+
const r = JSON.parse(readFileSync54(file, "utf8"));
|
|
49419
49872
|
if (r.v !== 1 || typeof r.pid !== "number" || !Number.isInteger(r.pid) || r.pid < 1) return null;
|
|
49420
49873
|
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;
|
|
49421
49874
|
if (r.root !== runRootId(dir)) return null;
|
|
@@ -49440,14 +49893,14 @@ function liveWatchRunning(dir, isAlive = pidAlive, now) {
|
|
|
49440
49893
|
}
|
|
49441
49894
|
function watchReminder(dir, opts = {}) {
|
|
49442
49895
|
try {
|
|
49443
|
-
if (!
|
|
49896
|
+
if (!existsSync48(join48(dir, "himalaya.content.json"))) return void 0;
|
|
49444
49897
|
const nowDate = opts.now ?? /* @__PURE__ */ new Date();
|
|
49445
49898
|
const live2 = liveWatch(dir, opts.isAlive, nowDate);
|
|
49446
49899
|
const now = nowDate.getTime();
|
|
49447
|
-
const file =
|
|
49900
|
+
const file = join48(dir, REMINDERS_FILE);
|
|
49448
49901
|
let last = Number.NaN;
|
|
49449
49902
|
try {
|
|
49450
|
-
if (
|
|
49903
|
+
if (lstatSync13(file).isFile()) last = Date.parse(JSON.parse(readFileSync54(file, "utf8")).watch ?? "");
|
|
49451
49904
|
} catch {
|
|
49452
49905
|
}
|
|
49453
49906
|
if (Number.isFinite(last) && now - last >= 0 && now - last < WATCH_REMINDER_EVERY_MS) return void 0;
|
|
@@ -49458,11 +49911,14 @@ function watchReminder(dir, opts = {}) {
|
|
|
49458
49911
|
return `the live preview is running at ${live2.url} (pid ${live2.pid}${live2.by === "init" ? ", started by himi init" : ""}) \u2014 it repaints on every save, and the build board is ${live2.board}; give the owner that link to click around while you work, and do not start another`;
|
|
49459
49912
|
}
|
|
49460
49913
|
const content = opts.content ?? displayPath(dir);
|
|
49461
|
-
return `start the live preview and leave it running: \`himi run --watch --content ${shellQuote(content)} &\` (the consent preflight first, if a worker or config calls Wix at boot) \u2014 it repaints on every save; the build board is ${shellQuote(
|
|
49914
|
+
return `start the live preview and leave it running: \`himi run --watch --content ${shellQuote(content)} &\` (the consent preflight first, if a worker or config calls Wix at boot) \u2014 it repaints on every save; the build board is ${shellQuote(join48(content, ".himi", "studio", "index.html"))}, or /__himi/studio/ on the preview`;
|
|
49462
49915
|
} catch {
|
|
49463
49916
|
return void 0;
|
|
49464
49917
|
}
|
|
49465
49918
|
}
|
|
49919
|
+
function watchOptOutIgnoredNote(given) {
|
|
49920
|
+
return `${given.join(" and ")} ${given.length > 1 ? "were" : "was"} ignored: the live preview is how the owner watches the build, so it stays running while you work`;
|
|
49921
|
+
}
|
|
49466
49922
|
function watchUrlFor(port, app) {
|
|
49467
49923
|
const origin = `http://127.0.0.1:${port}`;
|
|
49468
49924
|
return { url: `${origin}/preview/a/${encodeURIComponent(app)}`, board: `${origin}/__himi/studio/` };
|
|
@@ -49489,13 +49945,13 @@ function absoluteLoaderArgs(args, resolveSpec = (spec) => import.meta.resolve(sp
|
|
|
49489
49945
|
async function startWatchAtInit(dir, opts) {
|
|
49490
49946
|
if (opts.source !== "bundled") return { skipped: REMOTE_WATCH_SKIP };
|
|
49491
49947
|
const env = opts.env ?? process.env;
|
|
49492
|
-
if (envOn(env[NO_WATCH_ENV])) return { skipped: `${NO_WATCH_ENV} is set, so nothing is left running after init` };
|
|
49493
49948
|
const ci = ciSet(env);
|
|
49494
49949
|
const underTest = !!env.NODE_TEST_CONTEXT;
|
|
49495
49950
|
if (opts.noWatch && (ci || underTest)) return { skipped: "--no-watch" };
|
|
49496
49951
|
if (ci) return { skipped: "the CI environment variable is set, so nothing is left running after init" };
|
|
49497
49952
|
if (underTest) return { skipped: "running under `node --test`, so nothing is left running after init" };
|
|
49498
|
-
const
|
|
49953
|
+
const given = [...opts.noWatch ? ["--no-watch"] : [], ...envOn(env[LEGACY_NO_WATCH_ENV]) ? [LEGACY_NO_WATCH_ENV] : []];
|
|
49954
|
+
const note = given.length ? { note: watchOptOutIgnoredNote(given) } : {};
|
|
49499
49955
|
const deps = { ...defaultStartWatchDeps, ...opts.deps };
|
|
49500
49956
|
const now = opts.now ?? /* @__PURE__ */ new Date();
|
|
49501
49957
|
const live2 = liveWatch(dir, deps.isAlive, now);
|
|
@@ -49514,14 +49970,14 @@ async function startWatchAtInit(dir, opts) {
|
|
|
49514
49970
|
{ cwd: dir, detached: true, stdio: ["ignore", fd, fd], env: { ...env, HIMI_NO_UPDATE_CHECK: "1" } }
|
|
49515
49971
|
);
|
|
49516
49972
|
} finally {
|
|
49517
|
-
|
|
49973
|
+
closeSync4(fd);
|
|
49518
49974
|
}
|
|
49519
49975
|
child.once("error", () => {
|
|
49520
49976
|
});
|
|
49521
49977
|
child.unref();
|
|
49522
49978
|
if (!child.pid) return { skipped: "the live preview did not start (no process) \u2014 start it yourself: `himi run --watch --content <dir> &`", ...note };
|
|
49523
49979
|
const record2 = { v: 1, pid: child.pid, root: runRootId(dir), url, board, log: WATCH_LOG, startedAt: now.toISOString() };
|
|
49524
|
-
if (realDirIn(dir, ".himi")) writeFileAtomic(
|
|
49980
|
+
if (realDirIn(dir, ".himi")) writeFileAtomic(join48(dir, INIT_WATCH_FILE), `${JSON.stringify(record2, null, 2)}
|
|
49525
49981
|
`);
|
|
49526
49982
|
return { started: true, pid: child.pid, url, board, log: WATCH_LOG, ...note };
|
|
49527
49983
|
} catch (e) {
|
|
@@ -49536,7 +49992,7 @@ function watchRunning(w) {
|
|
|
49536
49992
|
function doNowBlock(opts) {
|
|
49537
49993
|
const content = displayPath(opts.dir, opts.cwd);
|
|
49538
49994
|
const home = opts.home ?? "<homeScreenId>";
|
|
49539
|
-
const board =
|
|
49995
|
+
const board = join48(content, ".himi", "studio", "index.html");
|
|
49540
49996
|
const running = watchRunning(opts.watch);
|
|
49541
49997
|
const who = running?.by === "init" ? "init started it" : "already running";
|
|
49542
49998
|
const watch2 = running ? `${running.url} (${who}; the board: ${running.board}) \u2014 leave it running, and do not start another` : `\`himi run --watch${contentArg(content)} &\` and leave it running (the board: ${shellQuote(board)}, or /__himi/studio/ on the preview)`;
|
|
@@ -49555,7 +50011,7 @@ function writeHandoverReel(dir) {
|
|
|
49555
50011
|
try {
|
|
49556
50012
|
if (!realDirIn(dir, ".himi")) return { skipped: ".himi is not a real directory inside the package (a symlink?), so no making-of was written" };
|
|
49557
50013
|
const { html } = buildReel(dir);
|
|
49558
|
-
const file =
|
|
50014
|
+
const file = join48(dir, ".himi", "making-of.html");
|
|
49559
50015
|
writeFileAtomic(file, html);
|
|
49560
50016
|
return { file, hint: `hand ${basename14(file)} to the owner at the handover \u2014 one self-contained page of their app taking shape; send it beside the preview link, never instead of it` };
|
|
49561
50017
|
} catch (e) {
|
|
@@ -49563,11 +50019,11 @@ function writeHandoverReel(dir) {
|
|
|
49563
50019
|
}
|
|
49564
50020
|
}
|
|
49565
50021
|
function afterHandoverPostcard(dir, opts = {}) {
|
|
49566
|
-
const file =
|
|
50022
|
+
const file = join48(dir, ".himi", "making-of.html");
|
|
49567
50023
|
let fresh = false;
|
|
49568
50024
|
try {
|
|
49569
|
-
const st =
|
|
49570
|
-
fresh = st.isFile() && !st.isSymbolicLink() && st.mtimeMs >=
|
|
50025
|
+
const st = lstatSync13(file);
|
|
50026
|
+
fresh = st.isFile() && !st.isSymbolicLink() && st.mtimeMs >= statSync12(join48(dir, ".himi", "progress.jsonl")).mtimeMs;
|
|
49571
50027
|
} catch {
|
|
49572
50028
|
fresh = false;
|
|
49573
50029
|
}
|
|
@@ -49575,13 +50031,14 @@ function afterHandoverPostcard(dir, opts = {}) {
|
|
|
49575
50031
|
const next = "file" in makingOf ? [`hand the owner the making-of, ${shellQuote(displayPath(makingOf.file))}, beside the preview link \u2014 in your final message and report, never instead of the link`] : [`\`himi studio reel${contentArg(opts.content)}\` writes the making-of to hand the owner beside the preview link (${makingOf.skipped})`];
|
|
49576
50032
|
return { makingOf, next };
|
|
49577
50033
|
}
|
|
49578
|
-
var contentArg, CONSENT_PREFLIGHT, CONSENT_PREFLIGHT_STEP, REMOTE_FIRST_LOOK_SKIP, FIRST_LOOK_BUDGET_MS, VALIDATE_PICTURE_BUDGET_MS, plural, appName, STILL_BUILDING_AFTER_MS, STILL_BUILDING_MESSAGE, yourFirstLookMessage, homeCaveat, INIT_SOURCE_FILE, STARTING_POINT_REF, TEMPLATE_NAME, sha256Of2, WATCH_REMINDER_EVERY_MS, REMINDERS_FILE, pidAlive, INIT_WATCH_FILE, INIT_WATCH_STARTING_MS, INIT_WATCH_IDLE_MINUTES, WATCH_LOG, NO_WATCH_IGNORED_NOTE,
|
|
50034
|
+
var contentArg, CONSENT_PREFLIGHT, CONSENT_PREFLIGHT_STEP, REMOTE_FIRST_LOOK_SKIP, FIRST_LOOK_BUDGET_MS, VALIDATE_PICTURE_BUDGET_MS, plural, appName, STILL_BUILDING_AFTER_MS, STILL_BUILDING_MESSAGE, yourFirstLookMessage, homeCaveat, LAST_DRAFT_FILE, INIT_SOURCE_FILE, STARTING_POINT_REF, TEMPLATE_NAME, sha256Of2, WATCH_REMINDER_EVERY_MS, REMINDERS_FILE, pidAlive, INIT_WATCH_FILE, INIT_WATCH_STARTING_MS, INIT_WATCH_IDLE_MINUTES, WATCH_LOG, NO_WATCH_IGNORED_NOTE, LEGACY_NO_WATCH_ENV, REMOTE_WATCH_SKIP, listenOn, defaultStartWatchDeps, envOn, ciSet;
|
|
49579
50035
|
var init_milestones = __esm({
|
|
49580
50036
|
"src/milestones.ts"() {
|
|
49581
50037
|
init_app_source();
|
|
49582
50038
|
init_preview();
|
|
49583
50039
|
init_run_identity();
|
|
49584
50040
|
init_progress();
|
|
50041
|
+
init_run_watch();
|
|
49585
50042
|
init_screen_state();
|
|
49586
50043
|
init_shell_quote();
|
|
49587
50044
|
init_studio_reel();
|
|
@@ -49597,12 +50054,13 @@ var init_milestones = __esm({
|
|
|
49597
50054
|
STILL_BUILDING_MESSAGE = "Still building: here's the home screen now.";
|
|
49598
50055
|
yourFirstLookMessage = (dir) => `First look at your app: ${appName(dir)}'s home screen, with your changes.`;
|
|
49599
50056
|
homeCaveat = (dir, image) => homeStateCaveat(shotStateFor(dir, image));
|
|
49600
|
-
|
|
50057
|
+
LAST_DRAFT_FILE = join48(".himi", "last-draft.json");
|
|
50058
|
+
INIT_SOURCE_FILE = join48(".himi", "init-source.json");
|
|
49601
50059
|
STARTING_POINT_REF = "starting-point";
|
|
49602
50060
|
TEMPLATE_NAME = /^[A-Za-z0-9._-]{1,64}$/;
|
|
49603
|
-
sha256Of2 = (file) => createHash27("sha256").update(
|
|
50061
|
+
sha256Of2 = (file) => createHash27("sha256").update(readFileSync54(file)).digest("hex");
|
|
49604
50062
|
WATCH_REMINDER_EVERY_MS = 10 * 60 * 1e3;
|
|
49605
|
-
REMINDERS_FILE =
|
|
50063
|
+
REMINDERS_FILE = join48(".himi", "reminders.json");
|
|
49606
50064
|
pidAlive = (pid) => {
|
|
49607
50065
|
try {
|
|
49608
50066
|
process.kill(pid, 0);
|
|
@@ -49611,12 +50069,12 @@ var init_milestones = __esm({
|
|
|
49611
50069
|
return e.code === "EPERM";
|
|
49612
50070
|
}
|
|
49613
50071
|
};
|
|
49614
|
-
INIT_WATCH_FILE =
|
|
50072
|
+
INIT_WATCH_FILE = join48(".himi", "watch.json");
|
|
49615
50073
|
INIT_WATCH_STARTING_MS = 5 * 60 * 1e3;
|
|
49616
50074
|
INIT_WATCH_IDLE_MINUTES = 30;
|
|
49617
50075
|
WATCH_LOG = ".himi/watch.log";
|
|
49618
|
-
NO_WATCH_IGNORED_NOTE = "--no-watch
|
|
49619
|
-
|
|
50076
|
+
NO_WATCH_IGNORED_NOTE = watchOptOutIgnoredNote(["--no-watch"]);
|
|
50077
|
+
LEGACY_NO_WATCH_ENV = "HIMI_NO_WATCH";
|
|
49620
50078
|
REMOTE_WATCH_SKIP = "the template came off the serve plane: the live preview builds and serves its publisher's code on this machine, so init never starts it for one \u2014 read its dev/, config.ts and tier5-src/ first, then start it yourself";
|
|
49621
50079
|
listenOn = (port) => new Promise((done) => {
|
|
49622
50080
|
const srv = createServer4();
|
|
@@ -50078,8 +50536,8 @@ __export(template_trim_exports, {
|
|
|
50078
50536
|
routeMatcher: () => routeMatcher,
|
|
50079
50537
|
runTemplateTrim: () => runTemplateTrim
|
|
50080
50538
|
});
|
|
50081
|
-
import { cpSync, existsSync as
|
|
50082
|
-
import { dirname as dirname28, join as
|
|
50539
|
+
import { cpSync, existsSync as existsSync49, mkdirSync as mkdirSync24, readdirSync as readdirSync26, readFileSync as readFileSync55, renameSync as renameSync6, rmSync as rmSync12, statSync as statSync13, writeFileSync as writeFileSync19 } from "node:fs";
|
|
50540
|
+
import { dirname as dirname28, join as join49 } from "node:path";
|
|
50083
50541
|
function routeMatcher(pattern) {
|
|
50084
50542
|
const detail = /^\^([A-Za-z0-9_]+)\(\?:\[_\/\]\(\.\+\)\)\?\$$/.exec(pattern);
|
|
50085
50543
|
if (detail) {
|
|
@@ -50773,20 +51231,20 @@ function homeModulesPass(app, kept, edited) {
|
|
|
50773
51231
|
return { text: patched.src, dropped: patched.droppedModules };
|
|
50774
51232
|
}
|
|
50775
51233
|
function listWorkers(dir) {
|
|
50776
|
-
const root =
|
|
50777
|
-
if (!
|
|
50778
|
-
const dirs = readdirSync26(root).filter((n) =>
|
|
51234
|
+
const root = join49(dir, "tier5-src");
|
|
51235
|
+
if (!existsSync49(root)) return { dirs: [], files: {} };
|
|
51236
|
+
const dirs = readdirSync26(root).filter((n) => statSync13(join49(root, n)).isDirectory()).sort();
|
|
50779
51237
|
const files = {};
|
|
50780
51238
|
const walk2 = (abs, rel) => {
|
|
50781
51239
|
for (const name of readdirSync26(abs)) {
|
|
50782
51240
|
if (name === "node_modules") continue;
|
|
50783
|
-
const p =
|
|
51241
|
+
const p = join49(abs, name);
|
|
50784
51242
|
const r = `${rel}/${name}`;
|
|
50785
|
-
if (
|
|
50786
|
-
else if (/\.(?:[cm]?[jt]sx?|json)$/.test(name)) files[r] =
|
|
51243
|
+
if (statSync13(p).isDirectory()) walk2(p, r);
|
|
51244
|
+
else if (/\.(?:[cm]?[jt]sx?|json)$/.test(name)) files[r] = readFileSync55(p, "utf8");
|
|
50787
51245
|
}
|
|
50788
51246
|
};
|
|
50789
|
-
for (const d of dirs) walk2(
|
|
51247
|
+
for (const d of dirs) walk2(join49(root, d), `tier5-src/${d}`);
|
|
50790
51248
|
return { dirs, files };
|
|
50791
51249
|
}
|
|
50792
51250
|
function csv(v) {
|
|
@@ -50809,16 +51267,16 @@ async function runTemplateTrim(req, deps) {
|
|
|
50809
51267
|
links = req.links;
|
|
50810
51268
|
}
|
|
50811
51269
|
const dir = req.dir;
|
|
50812
|
-
const devPath =
|
|
50813
|
-
const configPath =
|
|
50814
|
-
if (!
|
|
51270
|
+
const devPath = join49(dir, "dev/index.ts");
|
|
51271
|
+
const configPath = join49(dir, "config.ts");
|
|
51272
|
+
if (!existsSync49(devPath) || !existsSync49(configPath)) {
|
|
50815
51273
|
return refuse3(new TrimRefusal(`${dir} has no dev/index.ts + config.ts \u2014 point --content at an app scaffolded with \`himi init --template\``));
|
|
50816
51274
|
}
|
|
50817
51275
|
if (isTemplateSourceTree(dir)) {
|
|
50818
51276
|
return refuse3(new TrimRefusal(`${dir} is a template source tree (its identity is still __HIMI_APP_*__ sentinels) \u2014 trim the app \`himi init --template\` scaffolds from it, not the template itself`));
|
|
50819
51277
|
}
|
|
50820
|
-
const dev =
|
|
50821
|
-
const config =
|
|
51278
|
+
const dev = readFileSync55(devPath, "utf8");
|
|
51279
|
+
const config = readFileSync55(configPath, "utf8");
|
|
50822
51280
|
const workers = listWorkers(dir);
|
|
50823
51281
|
let plan;
|
|
50824
51282
|
try {
|
|
@@ -50828,34 +51286,34 @@ async function runTemplateTrim(req, deps) {
|
|
|
50828
51286
|
throw e;
|
|
50829
51287
|
}
|
|
50830
51288
|
if (req.dryRun) return { code: 0, result: { ok: true, dryRun: true, ...plan.report, validate: null } };
|
|
50831
|
-
const hadHimi =
|
|
50832
|
-
const backupRel =
|
|
50833
|
-
const backup =
|
|
51289
|
+
const hadHimi = existsSync49(join49(dir, ".himi"));
|
|
51290
|
+
const backupRel = join49(".himi", "trim-backup", (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-"));
|
|
51291
|
+
const backup = join49(dir, backupRel);
|
|
50834
51292
|
const originals = [[devPath, "dev/index.ts"], [configPath, "config.ts"]];
|
|
50835
51293
|
const moved = [];
|
|
50836
51294
|
const restore = () => {
|
|
50837
51295
|
for (const [path2, rel] of originals) {
|
|
50838
|
-
const original =
|
|
50839
|
-
if (!
|
|
51296
|
+
const original = readFileSync55(join49(backup, rel));
|
|
51297
|
+
if (!existsSync49(path2) || !readFileSync55(path2).equals(original)) writeFileSync19(path2, original);
|
|
50840
51298
|
}
|
|
50841
|
-
for (const name of [...moved].reverse())
|
|
51299
|
+
for (const name of [...moved].reverse()) renameSync6(join49(backup, "tier5-src", name), join49(dir, "tier5-src", name));
|
|
50842
51300
|
moved.length = 0;
|
|
50843
51301
|
};
|
|
50844
51302
|
const apply = () => {
|
|
50845
|
-
const moveDir = deps.moveDir ??
|
|
51303
|
+
const moveDir = deps.moveDir ?? renameSync6;
|
|
50846
51304
|
for (const name of plan.removedWorkers) {
|
|
50847
|
-
moveDir(
|
|
51305
|
+
moveDir(join49(dir, "tier5-src", name), join49(backup, "tier5-src", name));
|
|
50848
51306
|
moved.push(name);
|
|
50849
51307
|
}
|
|
50850
|
-
if (plan.dev !== dev)
|
|
50851
|
-
if (plan.config !== config)
|
|
51308
|
+
if (plan.dev !== dev) writeFileSync19(devPath, plan.dev);
|
|
51309
|
+
if (plan.config !== config) writeFileSync19(configPath, plan.config);
|
|
50852
51310
|
};
|
|
50853
51311
|
const dropBackup = () => {
|
|
50854
|
-
|
|
50855
|
-
for (const d of [dirname28(backup),
|
|
50856
|
-
if (d ===
|
|
51312
|
+
rmSync12(backup, { recursive: true, force: true });
|
|
51313
|
+
for (const d of [dirname28(backup), join49(dir, ".himi")]) {
|
|
51314
|
+
if (d === join49(dir, ".himi") && hadHimi) break;
|
|
50857
51315
|
try {
|
|
50858
|
-
if (!readdirSync26(d).length)
|
|
51316
|
+
if (!readdirSync26(d).length) rmSync12(d, { recursive: true });
|
|
50859
51317
|
} catch {
|
|
50860
51318
|
}
|
|
50861
51319
|
}
|
|
@@ -50879,9 +51337,9 @@ async function runTemplateTrim(req, deps) {
|
|
|
50879
51337
|
return { code: 1, result: { ok: false, error: `${error} (${message}); every file was restored`, rolledBack: true, ...base(validate4) } };
|
|
50880
51338
|
};
|
|
50881
51339
|
try {
|
|
50882
|
-
|
|
50883
|
-
for (const [path2, rel] of originals) cpSync(path2,
|
|
50884
|
-
if (plan.removedWorkers.length)
|
|
51340
|
+
mkdirSync24(join49(backup, "dev"), { recursive: true });
|
|
51341
|
+
for (const [path2, rel] of originals) cpSync(path2, join49(backup, rel));
|
|
51342
|
+
if (plan.removedWorkers.length) mkdirSync24(join49(backup, "tier5-src"), { recursive: true });
|
|
50885
51343
|
} catch (e) {
|
|
50886
51344
|
dropBackup();
|
|
50887
51345
|
return { code: 1, result: { ok: false, error: `could not back up the app before trimming it (${e.message}); nothing was changed`, rolledBack: true, ...base() } };
|
|
@@ -50894,7 +51352,7 @@ async function runTemplateTrim(req, deps) {
|
|
|
50894
51352
|
let failure;
|
|
50895
51353
|
let validate3;
|
|
50896
51354
|
try {
|
|
50897
|
-
const written = parseApp(
|
|
51355
|
+
const written = parseApp(readFileSync55(devPath, "utf8"), readFileSync55(configPath, "utf8"));
|
|
50898
51356
|
const leftovers = leftoverNavRefs(written.nav.value, plan.removedIds);
|
|
50899
51357
|
if (leftovers.length) failure = { error: "navigation still names screens this trim removed", leftovers };
|
|
50900
51358
|
else {
|
|
@@ -51217,13 +51675,13 @@ __export(eject_exports, {
|
|
|
51217
51675
|
EJECTABLE: () => EJECTABLE,
|
|
51218
51676
|
ejectModule: () => ejectModule
|
|
51219
51677
|
});
|
|
51220
|
-
import { existsSync as
|
|
51221
|
-
import { dirname as dirname29, join as
|
|
51678
|
+
import { existsSync as existsSync50, mkdirSync as mkdirSync25, readFileSync as readFileSync56, writeFileSync as writeFileSync20 } from "node:fs";
|
|
51679
|
+
import { dirname as dirname29, join as join50, relative as relative10, resolve as resolve43, sep as sep14 } from "node:path";
|
|
51222
51680
|
function locate(repoRootOrCwd, mod) {
|
|
51223
|
-
const inRepo =
|
|
51224
|
-
if (
|
|
51225
|
-
const dist =
|
|
51226
|
-
if (
|
|
51681
|
+
const inRepo = resolve43(repoRootOrCwd, `stdlib/flows/${mod}/src/index.ts`);
|
|
51682
|
+
if (existsSync50(inRepo)) return { path: inRepo, form: "source" };
|
|
51683
|
+
const dist = resolve43(repoRootOrCwd, `node_modules/@wix/himalaya/dist/${mod}.mjs`);
|
|
51684
|
+
if (existsSync50(dist)) return { path: dist, form: "bundle" };
|
|
51227
51685
|
return null;
|
|
51228
51686
|
}
|
|
51229
51687
|
function specifiersFor(mod) {
|
|
@@ -51247,8 +51705,8 @@ function ejectModule(opts) {
|
|
|
51247
51705
|
};
|
|
51248
51706
|
}
|
|
51249
51707
|
const ext = found.form === "source" ? "ts" : "mjs";
|
|
51250
|
-
const target =
|
|
51251
|
-
|
|
51708
|
+
const target = join50(contentDir2, "tier5-src", "_ejected", `${mod}.${ext}`);
|
|
51709
|
+
mkdirSync25(dirname29(target), { recursive: true });
|
|
51252
51710
|
const banner = `// EJECTED from @wix/himalaya/${mod}. This app owns this file now.
|
|
51253
51711
|
//
|
|
51254
51712
|
// Central fixes to @wix/himalaya/${mod} NO LONGER REACH THIS APP \u2014 including any
|
|
@@ -51261,17 +51719,17 @@ function ejectModule(opts) {
|
|
|
51261
51719
|
// gate still applies. Ejecting buys layout and flow, never the floor.
|
|
51262
51720
|
|
|
51263
51721
|
`;
|
|
51264
|
-
const normalized =
|
|
51722
|
+
const normalized = readFileSync56(found.path, "utf8").replace(
|
|
51265
51723
|
/(["'])@himalaya\/([a-z0-9-]+(?:\/[a-z0-9-]+)?)\1/g,
|
|
51266
51724
|
(_m, q, sub) => `${q}@wix/himalaya/${sub}${q}`
|
|
51267
51725
|
);
|
|
51268
|
-
|
|
51726
|
+
writeFileSync20(target, banner + normalized);
|
|
51269
51727
|
const rewired = [];
|
|
51270
51728
|
for (const f of files) {
|
|
51271
|
-
if (!
|
|
51272
|
-
const before =
|
|
51729
|
+
if (!existsSync50(f)) continue;
|
|
51730
|
+
const before = readFileSync56(f, "utf8");
|
|
51273
51731
|
let after = before;
|
|
51274
|
-
let rel = relative10(dirname29(f), target).split(
|
|
51732
|
+
let rel = relative10(dirname29(f), target).split(sep14).join("/").replace(/\.tsx?$/, ".js");
|
|
51275
51733
|
if (!rel.startsWith(".")) rel = `./${rel}`;
|
|
51276
51734
|
for (const spec of specifiersFor(mod)) {
|
|
51277
51735
|
const q = spec.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
@@ -51281,7 +51739,7 @@ function ejectModule(opts) {
|
|
|
51281
51739
|
);
|
|
51282
51740
|
}
|
|
51283
51741
|
if (after !== before) {
|
|
51284
|
-
|
|
51742
|
+
writeFileSync20(f, after);
|
|
51285
51743
|
rewired.push(f);
|
|
51286
51744
|
}
|
|
51287
51745
|
}
|
|
@@ -51389,8 +51847,8 @@ var init_app_map = __esm({
|
|
|
51389
51847
|
});
|
|
51390
51848
|
|
|
51391
51849
|
// src/wix-writes.ts
|
|
51392
|
-
import { existsSync as
|
|
51393
|
-
import { join as
|
|
51850
|
+
import { existsSync as existsSync51, readFileSync as readFileSync57 } from "node:fs";
|
|
51851
|
+
import { join as join51 } from "node:path";
|
|
51394
51852
|
function isProvenRead(method, path2) {
|
|
51395
51853
|
if (/[#\x00-\x1F\x7F]/.test(path2)) return false;
|
|
51396
51854
|
const verb = method.toUpperCase();
|
|
@@ -51508,11 +51966,11 @@ function parseDataAccess(text8, from) {
|
|
|
51508
51966
|
}
|
|
51509
51967
|
function readDataAccess(dir) {
|
|
51510
51968
|
for (const name of ["SPEC.md", "MOBILE-UX.md"]) {
|
|
51511
|
-
const p =
|
|
51512
|
-
if (!
|
|
51969
|
+
const p = join51(dir, name);
|
|
51970
|
+
if (!existsSync51(p)) continue;
|
|
51513
51971
|
let text8;
|
|
51514
51972
|
try {
|
|
51515
|
-
text8 =
|
|
51973
|
+
text8 = readFileSync57(p, "utf8");
|
|
51516
51974
|
} catch {
|
|
51517
51975
|
continue;
|
|
51518
51976
|
}
|
|
@@ -51625,14 +52083,14 @@ __export(test_exports, {
|
|
|
51625
52083
|
scopeDescriptors: () => scopeDescriptors,
|
|
51626
52084
|
writeRecordedMocks: () => writeRecordedMocks
|
|
51627
52085
|
});
|
|
51628
|
-
import { existsSync as
|
|
51629
|
-
import { join as
|
|
52086
|
+
import { existsSync as existsSync52, mkdirSync as mkdirSync26, readFileSync as readFileSync58, writeFileSync as writeFileSync21 } from "node:fs";
|
|
52087
|
+
import { join as join52, resolve as resolve44 } from "node:path";
|
|
51630
52088
|
function readTestConfig(dir) {
|
|
51631
|
-
const p =
|
|
51632
|
-
if (!
|
|
52089
|
+
const p = resolve44(dir, TEST_CONFIG_FILE);
|
|
52090
|
+
if (!existsSync52(p)) return {};
|
|
51633
52091
|
let parsed;
|
|
51634
52092
|
try {
|
|
51635
|
-
parsed = JSON.parse(
|
|
52093
|
+
parsed = JSON.parse(readFileSync58(p, "utf8"));
|
|
51636
52094
|
} catch (e) {
|
|
51637
52095
|
throw new Error(`${TEST_CONFIG_FILE} is not valid JSON \u2014 ${e.message}`);
|
|
51638
52096
|
}
|
|
@@ -51679,7 +52137,7 @@ function readTestConfig(dir) {
|
|
|
51679
52137
|
function readNetRules(path2) {
|
|
51680
52138
|
let parsed;
|
|
51681
52139
|
try {
|
|
51682
|
-
parsed = JSON.parse(
|
|
52140
|
+
parsed = JSON.parse(readFileSync58(path2, "utf8"));
|
|
51683
52141
|
} catch (e) {
|
|
51684
52142
|
throw new Error(`--net ${path2} could not be read as JSON \u2014 ${e.message}`);
|
|
51685
52143
|
}
|
|
@@ -51699,8 +52157,8 @@ function readNetRules(path2) {
|
|
|
51699
52157
|
}
|
|
51700
52158
|
function defaultNetRules(dir) {
|
|
51701
52159
|
for (const rel of ["dev/net-mocks.json", "tests/net-mocks.json"]) {
|
|
51702
|
-
const p =
|
|
51703
|
-
if (
|
|
52160
|
+
const p = resolve44(dir, rel);
|
|
52161
|
+
if (existsSync52(p)) {
|
|
51704
52162
|
const rules = readNetRules(p);
|
|
51705
52163
|
if (rules.length) return { rules, from: rel };
|
|
51706
52164
|
}
|
|
@@ -51716,7 +52174,7 @@ function readCannedResponses() {
|
|
|
51716
52174
|
];
|
|
51717
52175
|
for (const url of candidates) {
|
|
51718
52176
|
try {
|
|
51719
|
-
const parsed = JSON.parse(
|
|
52177
|
+
const parsed = JSON.parse(readFileSync58(url, "utf8"));
|
|
51720
52178
|
const byKey = {};
|
|
51721
52179
|
for (const [k, v] of Object.entries(parsed)) {
|
|
51722
52180
|
if (!k.startsWith("//")) byKey[k] = v;
|
|
@@ -51803,11 +52261,11 @@ function writeRecordedMocks(opts) {
|
|
|
51803
52261
|
};
|
|
51804
52262
|
}
|
|
51805
52263
|
const fs = opts.io ?? {
|
|
51806
|
-
mkdir: (p) =>
|
|
51807
|
-
write: (p, s) =>
|
|
52264
|
+
mkdir: (p) => mkdirSync26(p, { recursive: true }),
|
|
52265
|
+
write: (p, s) => writeFileSync21(p, s)
|
|
51808
52266
|
};
|
|
51809
|
-
fs.mkdir(
|
|
51810
|
-
fs.write(
|
|
52267
|
+
fs.mkdir(join52(opts.dir, "dev"));
|
|
52268
|
+
fs.write(join52(opts.dir, "dev", "net-mocks.json"), JSON.stringify(opts.mocks, null, 2) + "\n");
|
|
51811
52269
|
return { ok: true, rules, to: "dev/net-mocks.json" };
|
|
51812
52270
|
}
|
|
51813
52271
|
function scopeDescriptors(descriptors, requestedIds, invalidScreens) {
|
|
@@ -51818,6 +52276,8 @@ async function runShotsPass(opts) {
|
|
|
51818
52276
|
const scheme = opts.scheme ?? "light";
|
|
51819
52277
|
const shots = [];
|
|
51820
52278
|
for (const baked of opts.descriptors) {
|
|
52279
|
+
let unserved = 0;
|
|
52280
|
+
const isUnserved = opts.deps.isUnserved;
|
|
51821
52281
|
const state = baked.bundleJs ? await opts.deps.bootAndSnapshotState({
|
|
51822
52282
|
screenId: baked.screenId,
|
|
51823
52283
|
descriptor: baked.descriptor,
|
|
@@ -51826,10 +52286,20 @@ async function runShotsPass(opts) {
|
|
|
51826
52286
|
rules: opts.layers.rules ?? null,
|
|
51827
52287
|
...opts.layers.cmsSeed ? { cmsSeed: opts.layers.cmsSeed } : {},
|
|
51828
52288
|
...opts.layers.canned ? { canned: opts.layers.canned } : {},
|
|
51829
|
-
...opts.budgetMs ? { budgetMs: opts.budgetMs } : {}
|
|
52289
|
+
...opts.budgetMs ? { budgetMs: opts.budgetMs } : {},
|
|
52290
|
+
...isUnserved ? {
|
|
52291
|
+
transportWrap: (call) => async (method, path2, body) => {
|
|
52292
|
+
try {
|
|
52293
|
+
return await call(method, path2, body);
|
|
52294
|
+
} catch (e) {
|
|
52295
|
+
if (isUnserved(e)) unserved++;
|
|
52296
|
+
throw e;
|
|
52297
|
+
}
|
|
52298
|
+
}
|
|
52299
|
+
} : {}
|
|
51830
52300
|
}) : null;
|
|
51831
52301
|
const rr = await opts.deps.renderDescriptorWeb(baked.descriptor, opts.tokens, scheme, false, state, opts.width);
|
|
51832
|
-
if (rr.renderable && rr.html) shots.push({ screenId: baked.screenId, html: rr.html, state: screenStateOf(state, rr.html) });
|
|
52302
|
+
if (rr.renderable && rr.html) shots.push({ screenId: baked.screenId, html: rr.html, state: screenStateOf(state, rr.html, { offline: unserved > 0 }) });
|
|
51833
52303
|
}
|
|
51834
52304
|
const pngs = await opts.deps.renderPngBatch(shots.map((x) => ({ html: x.html, scheme, tokens: opts.tokens, ...opts.width ? { width: opts.width } : {} })));
|
|
51835
52305
|
if (pngs === null) return { dir: opts.outDir, written: [], unavailable: true };
|
|
@@ -51883,7 +52353,7 @@ async function runCrawl(opts) {
|
|
|
51883
52353
|
for (const d of list) if (d.screenId !== rep.screenId) coveredBy.set(d.screenId, rep.screenId);
|
|
51884
52354
|
}
|
|
51885
52355
|
}
|
|
51886
|
-
const net = opts.offline ? null : opts.netPath ? { rules: readNetRules(
|
|
52356
|
+
const net = opts.offline ? null : opts.netPath ? { rules: readNetRules(resolve44(opts.netPath)), from: opts.netPath } : defaultNetRules(opts.dir);
|
|
51887
52357
|
const suppressions = opts.config?.skip ?? [];
|
|
51888
52358
|
const suppressed = [];
|
|
51889
52359
|
const matchSuppression = (screen, actionId, componentId, invariant) => suppressions.find((s) => (s.screen === void 0 || s.screen === screen) && (s.action === void 0 || s.action === actionId) && (s.component === void 0 || s.component === componentId) && (s.invariant === void 0 || s.invariant === invariant));
|
|
@@ -52313,14 +52783,14 @@ var mobile_ux_lint_exports = {};
|
|
|
52313
52783
|
__export(mobile_ux_lint_exports, {
|
|
52314
52784
|
mobileUxIssues: () => mobileUxIssues
|
|
52315
52785
|
});
|
|
52316
|
-
import { existsSync as
|
|
52317
|
-
import { join as
|
|
52786
|
+
import { existsSync as existsSync53, readFileSync as readFileSync59 } from "node:fs";
|
|
52787
|
+
import { join as join53 } from "node:path";
|
|
52318
52788
|
function mobileUxIssues(dir, strict) {
|
|
52319
|
-
const file =
|
|
52320
|
-
if (!
|
|
52789
|
+
const file = join53(dir, "MOBILE-UX.md");
|
|
52790
|
+
if (!existsSync53(file)) return [];
|
|
52321
52791
|
let text8;
|
|
52322
52792
|
try {
|
|
52323
|
-
text8 =
|
|
52793
|
+
text8 = readFileSync59(file, "utf8");
|
|
52324
52794
|
} catch {
|
|
52325
52795
|
return [];
|
|
52326
52796
|
}
|
|
@@ -54001,8 +54471,8 @@ __export(functions_exports, {
|
|
|
54001
54471
|
resolveFunctionsTarget: () => resolveFunctionsTarget,
|
|
54002
54472
|
runRemoteFunctionOperation: () => runRemoteFunctionOperation
|
|
54003
54473
|
});
|
|
54004
|
-
import { readFileSync as
|
|
54005
|
-
import { join as
|
|
54474
|
+
import { readFileSync as readFileSync60, existsSync as existsSync54 } from "node:fs";
|
|
54475
|
+
import { join as join54, resolve as resolve45 } from "node:path";
|
|
54006
54476
|
function resolveFunctionsTarget(input) {
|
|
54007
54477
|
const explicitUrl = input.functionsUrl?.trim();
|
|
54008
54478
|
if (explicitUrl) return { mode: "remote", baseUrl: explicitUrl };
|
|
@@ -54014,16 +54484,16 @@ function resolveFunctionsTarget(input) {
|
|
|
54014
54484
|
return { mode: "local" };
|
|
54015
54485
|
}
|
|
54016
54486
|
async function loadFunctionsApp(dir) {
|
|
54017
|
-
const loaded = await loadContent(
|
|
54487
|
+
const loaded = await loadContent(resolve45(dir));
|
|
54018
54488
|
const config = loaded.module.config;
|
|
54019
54489
|
if (config.kind !== "functions" || !Array.isArray(config.functions?.functions)) {
|
|
54020
54490
|
throw new Error(`content package "${dir}" is not a functions-only app`);
|
|
54021
54491
|
}
|
|
54022
54492
|
const sources = {};
|
|
54023
54493
|
for (const definition of config.functions.functions) {
|
|
54024
|
-
const source = [".ts", ".js", ".mts", ".mjs"].map((extension) =>
|
|
54494
|
+
const source = [".ts", ".js", ".mts", ".mjs"].map((extension) => join54(loaded.dir, "functions", `${definition.name}${extension}`)).find(existsSync54);
|
|
54025
54495
|
if (!source) throw new Error(`missing source for function ${definition.name}; expected functions/${definition.name}.ts`);
|
|
54026
|
-
sources[definition.name] =
|
|
54496
|
+
sources[definition.name] = readFileSync60(source, "utf8");
|
|
54027
54497
|
}
|
|
54028
54498
|
const data = config.cms || config.data ? mergeDataResources(config) : void 0;
|
|
54029
54499
|
const dataErrors = validateDataResources(data, loaded.name).filter((issue2) => issue2.severity === "error");
|
|
@@ -54211,30 +54681,30 @@ var init_cli_bridge = __esm({
|
|
|
54211
54681
|
});
|
|
54212
54682
|
|
|
54213
54683
|
// src/mcp/file-tools.ts
|
|
54214
|
-
import { closeSync as
|
|
54684
|
+
import { closeSync as closeSync5, constants as constants2, lstatSync as lstatSync14, mkdirSync as mkdirSync27, openSync as openSync4, readdirSync as readdirSync27, readFileSync as readFileSync61, realpathSync as realpathSync8, renameSync as renameSync7, rmSync as rmSync13, statSync as statSync14, writeSync, existsSync as existsSync55 } from "node:fs";
|
|
54215
54685
|
import { randomBytes as randomBytes4 } from "node:crypto";
|
|
54216
|
-
import { dirname as dirname30, isAbsolute as isAbsolute5, join as
|
|
54686
|
+
import { dirname as dirname30, isAbsolute as isAbsolute5, join as join55, relative as relative11, resolve as resolve46, sep as sep15 } from "node:path";
|
|
54217
54687
|
import * as z from "zod";
|
|
54218
54688
|
function within(root, p) {
|
|
54219
|
-
return p === root || p.startsWith(root.endsWith(
|
|
54689
|
+
return p === root || p.startsWith(root.endsWith(sep15) ? root : root + sep15);
|
|
54220
54690
|
}
|
|
54221
54691
|
function realish(p) {
|
|
54222
54692
|
let cur = p;
|
|
54223
54693
|
const tail = [];
|
|
54224
|
-
while (!
|
|
54694
|
+
while (!existsSync55(cur)) {
|
|
54225
54695
|
const up = dirname30(cur);
|
|
54226
54696
|
if (up === cur) break;
|
|
54227
|
-
tail.unshift(cur.slice(up.length + (up.endsWith(
|
|
54697
|
+
tail.unshift(cur.slice(up.length + (up.endsWith(sep15) ? 0 : 1)));
|
|
54228
54698
|
cur = up;
|
|
54229
54699
|
}
|
|
54230
|
-
return
|
|
54700
|
+
return join55(realpathSync8(cur), ...tail);
|
|
54231
54701
|
}
|
|
54232
54702
|
function symlinkComponent(base, abs) {
|
|
54233
54703
|
let cur = base;
|
|
54234
|
-
for (const part of relative11(base, abs).split(
|
|
54235
|
-
cur =
|
|
54704
|
+
for (const part of relative11(base, abs).split(sep15).filter(Boolean)) {
|
|
54705
|
+
cur = join55(cur, part);
|
|
54236
54706
|
try {
|
|
54237
|
-
if (
|
|
54707
|
+
if (lstatSync14(cur).isSymbolicLink()) return relative11(base, cur);
|
|
54238
54708
|
} catch {
|
|
54239
54709
|
return void 0;
|
|
54240
54710
|
}
|
|
@@ -54242,24 +54712,24 @@ function symlinkComponent(base, abs) {
|
|
|
54242
54712
|
return void 0;
|
|
54243
54713
|
}
|
|
54244
54714
|
function workspaceRefusal(root, target) {
|
|
54245
|
-
const r =
|
|
54246
|
-
const t =
|
|
54247
|
-
if (!
|
|
54248
|
-
if (!within(r, t) || !within(
|
|
54715
|
+
const r = resolve46(root);
|
|
54716
|
+
const t = resolve46(r, target);
|
|
54717
|
+
if (!existsSync55(r)) return `the workspace folder ${r} does not exist`;
|
|
54718
|
+
if (!within(r, t) || !within(realpathSync8(r), realish(t))) return `"${target}" is outside the workspace folder ${r}`;
|
|
54249
54719
|
const link = symlinkComponent(r, t);
|
|
54250
54720
|
if (link) return `"${link}" is a symlink \u2014 paths must stay in real folders inside the workspace`;
|
|
54251
54721
|
return null;
|
|
54252
54722
|
}
|
|
54253
54723
|
async function confinedAppDir(args, deps) {
|
|
54254
|
-
const root =
|
|
54255
|
-
const dir = typeof args.dir === "string" && args.dir ?
|
|
54256
|
-
if (!
|
|
54257
|
-
if (!within(root, dir) || !within(
|
|
54724
|
+
const root = resolve46(await deps.defaultDir());
|
|
54725
|
+
const dir = typeof args.dir === "string" && args.dir ? resolve46(root, args.dir) : root;
|
|
54726
|
+
if (!existsSync55(root)) return refuse4("no_workspace", `the workspace folder ${root} does not exist`);
|
|
54727
|
+
if (!within(root, dir) || !within(realpathSync8(root), realish(dir))) return refuse4("outside_workspace", `"${String(args.dir)}" is outside the workspace folder`);
|
|
54258
54728
|
const link = symlinkComponent(root, dir);
|
|
54259
54729
|
if (link) return refuse4("outside_workspace", `"${link}" is a symlink \u2014 the app folder must be a real folder in the workspace`);
|
|
54260
54730
|
const rel = relative11(root, dir);
|
|
54261
54731
|
if (rel && (isDeniedSourcePath(`${rel}/x`) || isDeniedSourcePath(`${rel.toLowerCase()}/x`))) return refuse4("denied", `"${rel}" is a folder this tool does not touch`);
|
|
54262
|
-
if (!
|
|
54732
|
+
if (!existsSync55(join55(dir, CONTENT_MARKER))) {
|
|
54263
54733
|
return refuse4("not_an_app", `"${rel || "."}" is not an app folder (no ${CONTENT_MARKER}) \u2014 see himi_list_apps, or create one with himi_init`);
|
|
54264
54734
|
}
|
|
54265
54735
|
return dir;
|
|
@@ -54267,19 +54737,19 @@ async function confinedAppDir(args, deps) {
|
|
|
54267
54737
|
function onDiskSpelling(abs) {
|
|
54268
54738
|
let cur = abs;
|
|
54269
54739
|
const tail = [];
|
|
54270
|
-
while (!
|
|
54740
|
+
while (!existsSync55(cur)) {
|
|
54271
54741
|
const up = dirname30(cur);
|
|
54272
54742
|
if (up === cur) break;
|
|
54273
|
-
tail.unshift(cur.slice(up.length + (up.endsWith(
|
|
54743
|
+
tail.unshift(cur.slice(up.length + (up.endsWith(sep15) ? 0 : 1)));
|
|
54274
54744
|
cur = up;
|
|
54275
54745
|
}
|
|
54276
|
-
return
|
|
54746
|
+
return join55(realpathSync8.native(cur), ...tail);
|
|
54277
54747
|
}
|
|
54278
54748
|
function denied(appDir3, abs) {
|
|
54279
54749
|
const rel = relative11(appDir3, abs);
|
|
54280
54750
|
if (isDeniedSourcePath(rel) || isDeniedSourcePath(rel.toLowerCase())) return true;
|
|
54281
54751
|
try {
|
|
54282
|
-
return isDeniedSourcePath(relative11(
|
|
54752
|
+
return isDeniedSourcePath(relative11(realpathSync8.native(appDir3), onDiskSpelling(abs)));
|
|
54283
54753
|
} catch {
|
|
54284
54754
|
return true;
|
|
54285
54755
|
}
|
|
@@ -54288,10 +54758,10 @@ function confinedPath(appDir3, rel) {
|
|
|
54288
54758
|
if (typeof rel !== "string" || !rel.trim()) return { error: "a path is required" };
|
|
54289
54759
|
if (rel.includes("\0")) return { error: "a path may not contain NUL" };
|
|
54290
54760
|
if (isAbsolute5(rel)) return { error: `"${rel}" is absolute \u2014 give a path inside the app folder` };
|
|
54291
|
-
const abs =
|
|
54761
|
+
const abs = resolve46(appDir3, rel);
|
|
54292
54762
|
if (!within(appDir3, abs) || abs === appDir3) return { error: `"${rel}" is outside the app folder` };
|
|
54293
54763
|
const real = realish(abs);
|
|
54294
|
-
const base =
|
|
54764
|
+
const base = existsSync55(appDir3) ? realpathSync8(appDir3) : realish(appDir3);
|
|
54295
54765
|
if (!within(base, real) || real === base) return { error: `"${rel}" is outside the app folder` };
|
|
54296
54766
|
const link = symlinkComponent(appDir3, abs);
|
|
54297
54767
|
if (link) return { error: `"${link}" is a symlink, which this tool does not follow` };
|
|
@@ -54299,22 +54769,22 @@ function confinedPath(appDir3, rel) {
|
|
|
54299
54769
|
return abs;
|
|
54300
54770
|
}
|
|
54301
54771
|
function writeNoFollow(appDir3, abs, content) {
|
|
54302
|
-
|
|
54772
|
+
mkdirSync27(dirname30(abs), { recursive: true });
|
|
54303
54773
|
const before = symlinkComponent(appDir3, abs);
|
|
54304
54774
|
if (before) throw new Error(`"${before}" became a symlink`);
|
|
54305
|
-
const tmp =
|
|
54306
|
-
const fd =
|
|
54775
|
+
const tmp = join55(dirname30(abs), `.himi-write-${process.pid}-${randomBytes4(6).toString("hex")}.tmp`);
|
|
54776
|
+
const fd = openSync4(tmp, constants2.O_WRONLY | constants2.O_CREAT | constants2.O_EXCL | (constants2.O_NOFOLLOW ?? 0), 420);
|
|
54307
54777
|
try {
|
|
54308
54778
|
writeSync(fd, content);
|
|
54309
54779
|
} finally {
|
|
54310
|
-
|
|
54780
|
+
closeSync5(fd);
|
|
54311
54781
|
}
|
|
54312
54782
|
const link = symlinkComponent(appDir3, abs);
|
|
54313
|
-
if (link || !within(
|
|
54314
|
-
|
|
54783
|
+
if (link || !within(realpathSync8(appDir3), realpathSync8(dirname30(abs)))) {
|
|
54784
|
+
rmSync13(tmp, { force: true });
|
|
54315
54785
|
throw new Error(`"${link ?? relative11(appDir3, dirname30(abs))}" changed into a link while writing`);
|
|
54316
54786
|
}
|
|
54317
|
-
|
|
54787
|
+
renameSync7(tmp, abs);
|
|
54318
54788
|
}
|
|
54319
54789
|
function listFiles(appDir3) {
|
|
54320
54790
|
const files = [];
|
|
@@ -54322,7 +54792,7 @@ function listFiles(appDir3) {
|
|
|
54322
54792
|
const parentRel = relative11(appDir3, d);
|
|
54323
54793
|
for (const e of readdirSync27(d, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
54324
54794
|
if (e.isSymbolicLink()) continue;
|
|
54325
|
-
const p =
|
|
54795
|
+
const p = join55(d, e.name);
|
|
54326
54796
|
if (e.isDirectory()) {
|
|
54327
54797
|
if (isDeniedSourceDir(e.name, parentRel) || isDeniedSourceDir(e.name.toLowerCase(), parentRel)) continue;
|
|
54328
54798
|
if (!walk2(p)) return false;
|
|
@@ -54330,7 +54800,7 @@ function listFiles(appDir3) {
|
|
|
54330
54800
|
const rel = relative11(appDir3, p);
|
|
54331
54801
|
if (isDeniedSourcePath(rel) || isDeniedSourcePath(rel.toLowerCase())) continue;
|
|
54332
54802
|
if (files.length >= MAX_LISTED) return false;
|
|
54333
|
-
files.push({ path: rel.split(
|
|
54803
|
+
files.push({ path: rel.split(sep15).join("/"), bytes: statSync14(p).size });
|
|
54334
54804
|
}
|
|
54335
54805
|
}
|
|
54336
54806
|
return true;
|
|
@@ -54347,12 +54817,12 @@ function fileTools() {
|
|
|
54347
54817
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
54348
54818
|
inputSchema: z.object({}),
|
|
54349
54819
|
handler: async (_a, d) => {
|
|
54350
|
-
const root =
|
|
54351
|
-
if (!
|
|
54820
|
+
const root = resolve46(await d.defaultDir());
|
|
54821
|
+
if (!existsSync55(root)) return ok({ ok: true, workspace: root, apps: [] });
|
|
54352
54822
|
const apps = [];
|
|
54353
|
-
if (
|
|
54823
|
+
if (existsSync55(join55(root, CONTENT_MARKER))) apps.push(".");
|
|
54354
54824
|
for (const e of readdirSync27(root, { withFileTypes: true })) {
|
|
54355
|
-
if (e.isDirectory() && !e.name.startsWith(".") &&
|
|
54825
|
+
if (e.isDirectory() && !e.name.startsWith(".") && existsSync55(join55(root, e.name, CONTENT_MARKER))) apps.push(e.name);
|
|
54356
54826
|
}
|
|
54357
54827
|
return ok({ ok: true, workspace: root, apps: apps.sort() });
|
|
54358
54828
|
}
|
|
@@ -54366,7 +54836,7 @@ function fileTools() {
|
|
|
54366
54836
|
handler: async (a, d) => {
|
|
54367
54837
|
const app = await confinedAppDir(a, d);
|
|
54368
54838
|
if (typeof app !== "string") return app;
|
|
54369
|
-
if (!
|
|
54839
|
+
if (!existsSync55(app) || !statSync14(app).isDirectory()) return refuse4("no_app", `no folder at ${app}`);
|
|
54370
54840
|
return ok({ ok: true, dir: app, ...listFiles(app) });
|
|
54371
54841
|
}
|
|
54372
54842
|
},
|
|
@@ -54381,10 +54851,10 @@ function fileTools() {
|
|
|
54381
54851
|
if (typeof app !== "string") return app;
|
|
54382
54852
|
const p = confinedPath(app, a.path);
|
|
54383
54853
|
if (typeof p !== "string") return refuse4("bad_path", p.error);
|
|
54384
|
-
if (!
|
|
54385
|
-
const size =
|
|
54854
|
+
if (!existsSync55(p) || !lstatSync14(p).isFile()) return refuse4("not_found", `no file "${String(a.path)}"`);
|
|
54855
|
+
const size = statSync14(p).size;
|
|
54386
54856
|
if (size > MAX_FILE_BYTES) return refuse4("too_large", `"${String(a.path)}" is ${size} bytes \u2014 over the ${MAX_FILE_BYTES}-byte read limit`);
|
|
54387
|
-
return ok({ ok: true, path: a.path, bytes: size, content:
|
|
54857
|
+
return ok({ ok: true, path: a.path, bytes: size, content: readFileSync61(p, "utf8") });
|
|
54388
54858
|
}
|
|
54389
54859
|
},
|
|
54390
54860
|
{
|
|
@@ -54408,7 +54878,7 @@ function fileTools() {
|
|
|
54408
54878
|
if (typeof p !== "string") refused2.push({ path: f.path, error: p.error });
|
|
54409
54879
|
else if (typeof f.content !== "string") refused2.push({ path: f.path, error: "content must be text" });
|
|
54410
54880
|
else if (Buffer.byteLength(f.content) > MAX_FILE_BYTES) refused2.push({ path: f.path, error: `over the ${MAX_FILE_BYTES}-byte limit` });
|
|
54411
|
-
else if (
|
|
54881
|
+
else if (existsSync55(p) && !lstatSync14(p).isFile()) refused2.push({ path: f.path, error: "not a regular file" });
|
|
54412
54882
|
else if (planned.some((q) => q.abs === p)) refused2.push({ path: f.path, error: "the same file twice in one call" });
|
|
54413
54883
|
else planned.push({ abs: p, rel: String(f.path), content: f.content });
|
|
54414
54884
|
}
|
|
@@ -54441,15 +54911,15 @@ function fileTools() {
|
|
|
54441
54911
|
for (const rel of paths) {
|
|
54442
54912
|
const p = confinedPath(app, rel);
|
|
54443
54913
|
if (typeof p !== "string") refused2.push({ path: rel, error: p.error });
|
|
54444
|
-
else if (!
|
|
54445
|
-
else if (!
|
|
54914
|
+
else if (!existsSync55(p)) refused2.push({ path: rel, error: "no such file" });
|
|
54915
|
+
else if (!lstatSync14(p).isFile()) refused2.push({ path: rel, error: "not a regular file" });
|
|
54446
54916
|
else if (!planned.includes(p)) {
|
|
54447
54917
|
planned.push(p);
|
|
54448
54918
|
deleted.push(rel);
|
|
54449
54919
|
}
|
|
54450
54920
|
}
|
|
54451
54921
|
if (refused2.length) return { ...ok({ ok: false, error: "refused", deleted: [], refused: refused2 }), isError: true };
|
|
54452
|
-
for (const p of planned)
|
|
54922
|
+
for (const p of planned) rmSync13(p);
|
|
54453
54923
|
return ok({ ok: true, dir: app, deleted });
|
|
54454
54924
|
}
|
|
54455
54925
|
}
|
|
@@ -54472,7 +54942,7 @@ var init_file_tools = __esm({
|
|
|
54472
54942
|
|
|
54473
54943
|
// src/mcp/tools.ts
|
|
54474
54944
|
import * as z2 from "zod";
|
|
54475
|
-
import { resolve as
|
|
54945
|
+
import { resolve as resolve47 } from "node:path";
|
|
54476
54946
|
function fromCli(r, extra = []) {
|
|
54477
54947
|
const body = r.json !== void 0 ? JSON.stringify(r.json, null, 2) : r.text.trim();
|
|
54478
54948
|
const content = [];
|
|
@@ -54484,7 +54954,7 @@ ${r.stderr.trim()}`) });
|
|
|
54484
54954
|
}
|
|
54485
54955
|
async function appDir2(args, deps) {
|
|
54486
54956
|
const base = await deps.defaultDir();
|
|
54487
|
-
return typeof args.dir === "string" && args.dir ?
|
|
54957
|
+
return typeof args.dir === "string" && args.dir ? resolve47(base, args.dir) : base;
|
|
54488
54958
|
}
|
|
54489
54959
|
function loginResult(s) {
|
|
54490
54960
|
switch (s.state) {
|
|
@@ -54525,7 +54995,7 @@ function guarded(h) {
|
|
|
54525
54995
|
for (const k of PATH_ARGS) {
|
|
54526
54996
|
const v = args[k];
|
|
54527
54997
|
if (typeof v !== "string" || !v) continue;
|
|
54528
|
-
const why = workspaceRefusal(deps.workspaceBoundary,
|
|
54998
|
+
const why = workspaceRefusal(deps.workspaceBoundary, resolve47(base, v));
|
|
54529
54999
|
if (why) return text6(`refused: ${k} ${why}`, true);
|
|
54530
55000
|
}
|
|
54531
55001
|
}
|
|
@@ -54581,7 +55051,7 @@ function rawTools() {
|
|
|
54581
55051
|
}),
|
|
54582
55052
|
handler: async (a, d) => {
|
|
54583
55053
|
const base = await d.defaultDir();
|
|
54584
|
-
const parent = typeof a.parentDir === "string" && a.parentDir ?
|
|
55054
|
+
const parent = typeof a.parentDir === "string" && a.parentDir ? resolve47(base, a.parentDir) : base;
|
|
54585
55055
|
return fromCli(await d.runCli(["init", String(a.name), "--dir", parent, ...opt("--template", a.template)]));
|
|
54586
55056
|
}
|
|
54587
55057
|
},
|
|
@@ -54761,7 +55231,7 @@ ${log}` : ""}`, true);
|
|
|
54761
55231
|
handler: async (a, d) => {
|
|
54762
55232
|
const gate = await requireLogin(d);
|
|
54763
55233
|
if (gate) return gate;
|
|
54764
|
-
return fromCli(await d.runCli(["share", "--file",
|
|
55234
|
+
return fromCli(await d.runCli(["share", "--file", resolve47(await d.defaultDir(), String(a.file)), ...opt("--expires-at", a.expiresAt), ...opt("--target", d.login.target())]));
|
|
54765
55235
|
}
|
|
54766
55236
|
},
|
|
54767
55237
|
{
|
|
@@ -54886,7 +55356,7 @@ __export(server_exports, {
|
|
|
54886
55356
|
protectStdout: () => protectStdout,
|
|
54887
55357
|
startMcpServer: () => startMcpServer
|
|
54888
55358
|
});
|
|
54889
|
-
import { mkdirSync as
|
|
55359
|
+
import { mkdirSync as mkdirSync28, readFileSync as readFileSync62 } from "node:fs";
|
|
54890
55360
|
import { fileURLToPath as fileURLToPath21 } from "node:url";
|
|
54891
55361
|
import { McpServer } from "@modelcontextprotocol/server";
|
|
54892
55362
|
import { serveStdio, StdioServerTransport } from "@modelcontextprotocol/server/stdio";
|
|
@@ -54925,7 +55395,7 @@ async function startMcpServer(opts) {
|
|
|
54925
55395
|
const runCli = createCliBridge(opts.run);
|
|
54926
55396
|
if (opts.workspace) {
|
|
54927
55397
|
try {
|
|
54928
|
-
|
|
55398
|
+
mkdirSync28(opts.workspace, { recursive: true });
|
|
54929
55399
|
} catch (e) {
|
|
54930
55400
|
console.error(`[himi mcp] could not create the workspace folder ${opts.workspace}: ${e.message}`);
|
|
54931
55401
|
}
|
|
@@ -54946,7 +55416,7 @@ async function startMcpServer(opts) {
|
|
|
54946
55416
|
status: (dir) => previewStatus(dir, previewDeps),
|
|
54947
55417
|
stop: (dir) => stopPreview(dir, previewDeps)
|
|
54948
55418
|
},
|
|
54949
|
-
readFile: (p) =>
|
|
55419
|
+
readFile: (p) => readFileSync62(p),
|
|
54950
55420
|
login: createLoginSession({ ...defaultLoginDeps, selfCommand: previewDeps.selfCommand, ...opts.login }),
|
|
54951
55421
|
...opts.workspace ? { workspaceBoundary: opts.workspace } : {}
|
|
54952
55422
|
};
|
|
@@ -54990,9 +55460,9 @@ __export(native_decode_exports, {
|
|
|
54990
55460
|
runNativeDecode: () => runNativeDecode
|
|
54991
55461
|
});
|
|
54992
55462
|
import { execFileSync as execFileSync5, spawnSync as spawnSync2 } from "node:child_process";
|
|
54993
|
-
import { existsSync as
|
|
55463
|
+
import { existsSync as existsSync56, mkdtempSync as mkdtempSync2, readFileSync as readFileSync63, rmSync as rmSync14, writeFileSync as writeFileSync22 } from "node:fs";
|
|
54994
55464
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
54995
|
-
import { join as
|
|
55465
|
+
import { join as join56 } from "node:path";
|
|
54996
55466
|
import { fileURLToPath as fileURLToPath22 } from "node:url";
|
|
54997
55467
|
function nativeDecodePackageDir() {
|
|
54998
55468
|
return fileURLToPath22(new URL("../../../core/runtime/ios/HimalayaCore/", import.meta.url));
|
|
@@ -55002,8 +55472,8 @@ function nativeDecodeAndroidHarnessDir() {
|
|
|
55002
55472
|
}
|
|
55003
55473
|
function nativeOracleShipped() {
|
|
55004
55474
|
return {
|
|
55005
|
-
ios:
|
|
55006
|
-
android:
|
|
55475
|
+
ios: existsSync56(join56(nativeDecodePackageDir(), "Package.swift")),
|
|
55476
|
+
android: existsSync56(join56(nativeDecodeAndroidHarnessDir(), "gradlew"))
|
|
55007
55477
|
};
|
|
55008
55478
|
}
|
|
55009
55479
|
function detectNativeDecode(options = {}) {
|
|
@@ -55014,7 +55484,7 @@ function detectNativeDecode(options = {}) {
|
|
|
55014
55484
|
return { platform: "ios", available: false, reason: `the iOS decoder oracle requires macOS (darwin); this host is ${hostPlatform}` };
|
|
55015
55485
|
}
|
|
55016
55486
|
if (!swiftAvailable) return { platform: "ios", available: false, reason: "swift is not on PATH" };
|
|
55017
|
-
const packageAvailable = options.packageAvailable ??
|
|
55487
|
+
const packageAvailable = options.packageAvailable ?? existsSync56(join56(packageDir, "Package.swift"));
|
|
55018
55488
|
if (!packageAvailable) {
|
|
55019
55489
|
return {
|
|
55020
55490
|
platform: "ios",
|
|
@@ -55026,7 +55496,7 @@ function detectNativeDecode(options = {}) {
|
|
|
55026
55496
|
}
|
|
55027
55497
|
function localPropertiesSdk(harnessDir) {
|
|
55028
55498
|
try {
|
|
55029
|
-
const match =
|
|
55499
|
+
const match = readFileSync63(join56(harnessDir, "local.properties"), "utf8").match(/^sdk\.dir\s*=\s*(.+)$/m);
|
|
55030
55500
|
return match?.[1]?.trim().replace(/\\([ :\\])/g, "$1") ?? null;
|
|
55031
55501
|
} catch {
|
|
55032
55502
|
return null;
|
|
@@ -55034,8 +55504,8 @@ function localPropertiesSdk(harnessDir) {
|
|
|
55034
55504
|
}
|
|
55035
55505
|
function detectAndroidNativeDecode(options = {}) {
|
|
55036
55506
|
const harnessDir = options.harnessDir ?? nativeDecodeAndroidHarnessDir();
|
|
55037
|
-
const gradlew =
|
|
55038
|
-
const gradlewAvailable = options.gradlewAvailable ??
|
|
55507
|
+
const gradlew = join56(harnessDir, "gradlew");
|
|
55508
|
+
const gradlewAvailable = options.gradlewAvailable ?? existsSync56(gradlew);
|
|
55039
55509
|
if (!gradlewAvailable) {
|
|
55040
55510
|
return {
|
|
55041
55511
|
platform: "android",
|
|
@@ -55045,7 +55515,7 @@ function detectAndroidNativeDecode(options = {}) {
|
|
|
55045
55515
|
}
|
|
55046
55516
|
const androidHome = options.androidHome ?? process.env.ANDROID_HOME ?? "";
|
|
55047
55517
|
const sdk = androidHome.trim() || (options.localPropertiesSdk !== void 0 ? options.localPropertiesSdk : localPropertiesSdk(harnessDir));
|
|
55048
|
-
const sdkAvailable = options.sdkAvailable ?? (typeof sdk === "string" && sdk.length > 0 &&
|
|
55518
|
+
const sdkAvailable = options.sdkAvailable ?? (typeof sdk === "string" && sdk.length > 0 && existsSync56(sdk));
|
|
55049
55519
|
if (!sdkAvailable) {
|
|
55050
55520
|
return {
|
|
55051
55521
|
platform: "android",
|
|
@@ -55117,14 +55587,14 @@ function parseNativeDecodeOutput(stdout, expectedScreenIds, platform = "ios") {
|
|
|
55117
55587
|
return verdicts;
|
|
55118
55588
|
}
|
|
55119
55589
|
function runNativeDecode(options) {
|
|
55120
|
-
const descriptorDir = mkdtempSync2(
|
|
55590
|
+
const descriptorDir = mkdtempSync2(join56(tmpdir3(), "himi-native-decode-"));
|
|
55121
55591
|
const startedAt = Date.now();
|
|
55122
55592
|
try {
|
|
55123
55593
|
for (const descriptor of options.descriptors) {
|
|
55124
55594
|
if (typeof descriptor.descriptorJson !== "string") {
|
|
55125
55595
|
throw new Error(`native decode cannot judge ${descriptor.screenId}: the release builder did not return its exact baked JSON bytes`);
|
|
55126
55596
|
}
|
|
55127
|
-
|
|
55597
|
+
writeFileSync22(join56(descriptorDir, `${encodeURIComponent(descriptor.screenId)}.json`), descriptor.descriptorJson);
|
|
55128
55598
|
}
|
|
55129
55599
|
options.progress?.("building cached Swift oracle");
|
|
55130
55600
|
execFileSync5("swift", ["build", "--package-path", options.packageDir, "--product", "himi-decode-oracle"], {
|
|
@@ -55136,7 +55606,7 @@ function runNativeDecode(options) {
|
|
|
55136
55606
|
encoding: "utf8"
|
|
55137
55607
|
}).trim();
|
|
55138
55608
|
options.progress?.(`running real Swift decoder over ${options.descriptors.length} screen${options.descriptors.length === 1 ? "" : "s"}`);
|
|
55139
|
-
const stdout = execFileSync5(
|
|
55609
|
+
const stdout = execFileSync5(join56(binPath, "himi-decode-oracle"), ["--dir", descriptorDir], {
|
|
55140
55610
|
stdio: ["ignore", "pipe", "pipe"],
|
|
55141
55611
|
encoding: "utf8"
|
|
55142
55612
|
});
|
|
@@ -55150,19 +55620,19 @@ function runNativeDecode(options) {
|
|
|
55150
55620
|
const stderr = failure.stderr ? ` \u2014 ${String(failure.stderr).trim().slice(0, 2e3)}` : "";
|
|
55151
55621
|
throw new Error(`iOS decoder oracle failed: ${failure.message}${stderr}`);
|
|
55152
55622
|
} finally {
|
|
55153
|
-
|
|
55623
|
+
rmSync14(descriptorDir, { recursive: true, force: true });
|
|
55154
55624
|
}
|
|
55155
55625
|
}
|
|
55156
55626
|
function runAndroidNativeDecode(options) {
|
|
55157
|
-
const descriptorDir = mkdtempSync2(
|
|
55158
|
-
const outputPath =
|
|
55627
|
+
const descriptorDir = mkdtempSync2(join56(tmpdir3(), "himi-native-decode-android-"));
|
|
55628
|
+
const outputPath = join56(descriptorDir, "verdicts.ndjson");
|
|
55159
55629
|
const startedAt = Date.now();
|
|
55160
55630
|
try {
|
|
55161
55631
|
for (const descriptor of options.descriptors) {
|
|
55162
55632
|
if (typeof descriptor.descriptorJson !== "string") {
|
|
55163
55633
|
throw new Error(`native decode cannot judge ${descriptor.screenId}: the release builder did not return its exact baked JSON bytes`);
|
|
55164
55634
|
}
|
|
55165
|
-
|
|
55635
|
+
writeFileSync22(join56(descriptorDir, `${encodeURIComponent(descriptor.screenId)}.json`), descriptor.descriptorJson);
|
|
55166
55636
|
}
|
|
55167
55637
|
options.progress?.(`running real Kotlin decoder over ${options.descriptors.length} screen${options.descriptors.length === 1 ? "" : "s"}`);
|
|
55168
55638
|
execFileSync5(options.gradlew, [
|
|
@@ -55177,7 +55647,7 @@ function runAndroidNativeDecode(options) {
|
|
|
55177
55647
|
stdio: ["ignore", "ignore", "pipe"],
|
|
55178
55648
|
encoding: "utf8"
|
|
55179
55649
|
});
|
|
55180
|
-
const stdout =
|
|
55650
|
+
const stdout = readFileSync63(outputPath, "utf8");
|
|
55181
55651
|
return {
|
|
55182
55652
|
platform: "android",
|
|
55183
55653
|
verdicts: parseNativeDecodeOutput(stdout, options.descriptors.map((descriptor) => descriptor.screenId), "android"),
|
|
@@ -55188,7 +55658,7 @@ function runAndroidNativeDecode(options) {
|
|
|
55188
55658
|
const stderr = failure.stderr ? ` \u2014 ${String(failure.stderr).trim().slice(0, 2e3)}` : "";
|
|
55189
55659
|
throw new Error(`Android decoder oracle failed: ${failure.message}${stderr}`);
|
|
55190
55660
|
} finally {
|
|
55191
|
-
|
|
55661
|
+
rmSync14(descriptorDir, { recursive: true, force: true });
|
|
55192
55662
|
}
|
|
55193
55663
|
}
|
|
55194
55664
|
function fieldAt(path2) {
|
|
@@ -55247,30 +55717,30 @@ __export(browser_authoring_exports, {
|
|
|
55247
55717
|
});
|
|
55248
55718
|
import { createServer as createServer5 } from "node:http";
|
|
55249
55719
|
import { createHash as createHash30, randomBytes as randomBytes5 } from "node:crypto";
|
|
55250
|
-
import { existsSync as
|
|
55251
|
-
import { join as
|
|
55720
|
+
import { existsSync as existsSync57, readdirSync as readdirSync28, readFileSync as readFileSync64, realpathSync as realpathSync9, statSync as statSync15 } from "node:fs";
|
|
55721
|
+
import { join as join57, posix as posix2, relative as relative12, sep as sep16 } from "node:path";
|
|
55252
55722
|
function humanMs(ms) {
|
|
55253
55723
|
return ms < 1e3 ? `${ms}ms` : `${Math.round(ms / 1e3)}s`;
|
|
55254
55724
|
}
|
|
55255
55725
|
function collectWorkerSources(tier5SrcDir, bundleName) {
|
|
55256
|
-
const root =
|
|
55726
|
+
const root = join57(tier5SrcDir, bundleName);
|
|
55257
55727
|
const files = {};
|
|
55258
55728
|
const visitedDirs = /* @__PURE__ */ new Set();
|
|
55259
55729
|
const walk2 = (abs, rel) => {
|
|
55260
|
-
const real =
|
|
55730
|
+
const real = realpathSync9(abs);
|
|
55261
55731
|
if (visitedDirs.has(real)) return;
|
|
55262
55732
|
visitedDirs.add(real);
|
|
55263
55733
|
for (const name of readdirSync28(abs).sort()) {
|
|
55264
|
-
const childAbs =
|
|
55734
|
+
const childAbs = join57(abs, name);
|
|
55265
55735
|
const childRel = posix2.join(rel, name);
|
|
55266
|
-
if (
|
|
55736
|
+
if (statSync15(childAbs).isDirectory()) {
|
|
55267
55737
|
walk2(childAbs, childRel);
|
|
55268
55738
|
} else if (/\.(ts|tsx|js|mjs|json)$/.test(name)) {
|
|
55269
|
-
files[childRel] =
|
|
55739
|
+
files[childRel] = readFileSync64(childAbs, "utf8");
|
|
55270
55740
|
}
|
|
55271
55741
|
}
|
|
55272
55742
|
};
|
|
55273
|
-
if (!
|
|
55743
|
+
if (!existsSync57(root)) return null;
|
|
55274
55744
|
walk2(root, `/tier5-src/${bundleName}`);
|
|
55275
55745
|
const entry = [`/tier5-src/${bundleName}/index.ts`, `/tier5-src/${bundleName}/index.js`].find((p) => p in files);
|
|
55276
55746
|
if (!entry) return null;
|
|
@@ -55287,20 +55757,20 @@ function collectRelativeSiblings(files, tier5SrcDir) {
|
|
|
55287
55757
|
const spec = m[1];
|
|
55288
55758
|
const virtual = posix2.normalize(posix2.join(dir, spec));
|
|
55289
55759
|
if (!virtual.startsWith("/tier5-src/")) continue;
|
|
55290
|
-
const onDisk =
|
|
55760
|
+
const onDisk = join57(tier5SrcDir, virtual.slice("/tier5-src/".length));
|
|
55291
55761
|
const candidates = [
|
|
55292
55762
|
onDisk,
|
|
55293
55763
|
`${onDisk}.ts`,
|
|
55294
55764
|
`${onDisk}.js`,
|
|
55295
55765
|
onDisk.replace(/\.js$/, ".ts"),
|
|
55296
|
-
|
|
55297
|
-
|
|
55766
|
+
join57(onDisk, "index.ts"),
|
|
55767
|
+
join57(onDisk, "index.js")
|
|
55298
55768
|
];
|
|
55299
55769
|
for (const abs of candidates) {
|
|
55300
|
-
if (!
|
|
55301
|
-
const key2 = `/tier5-src/${relative12(tier5SrcDir, abs).split(
|
|
55770
|
+
if (!existsSync57(abs) || statSync15(abs).isDirectory()) continue;
|
|
55771
|
+
const key2 = `/tier5-src/${relative12(tier5SrcDir, abs).split(sep16).join(posix2.sep)}`;
|
|
55302
55772
|
if (key2 in files) break;
|
|
55303
|
-
files[key2] =
|
|
55773
|
+
files[key2] = readFileSync64(abs, "utf8");
|
|
55304
55774
|
queue.push(key2);
|
|
55305
55775
|
break;
|
|
55306
55776
|
}
|
|
@@ -55335,7 +55805,7 @@ function collectSdkModules(files, resolveSubpath) {
|
|
|
55335
55805
|
function sdkSubpathReader(resolve54) {
|
|
55336
55806
|
return (sub) => {
|
|
55337
55807
|
try {
|
|
55338
|
-
return
|
|
55808
|
+
return readFileSync64(resolve54(`@wix/himalaya/${sub}`), "utf8");
|
|
55339
55809
|
} catch {
|
|
55340
55810
|
return null;
|
|
55341
55811
|
}
|
|
@@ -55507,8 +55977,8 @@ __export(coverage_exports, {
|
|
|
55507
55977
|
suppressedNativeKeys: () => suppressedNativeKeys,
|
|
55508
55978
|
trendAndStore: () => trendAndStore
|
|
55509
55979
|
});
|
|
55510
|
-
import { existsSync as
|
|
55511
|
-
import { dirname as dirname31, join as
|
|
55980
|
+
import { existsSync as existsSync58, mkdirSync as mkdirSync29, readFileSync as readFileSync65, writeFileSync as writeFileSync23 } from "node:fs";
|
|
55981
|
+
import { dirname as dirname31, join as join58 } from "node:path";
|
|
55512
55982
|
function suppressedNativeKeys(oracle) {
|
|
55513
55983
|
return [...oracle.ios ? [] : ["native-decode|ios"], ...oracle.android ? [] : ["native-decode|android"]];
|
|
55514
55984
|
}
|
|
@@ -55688,11 +56158,11 @@ function assembleLedger(r, options = {}) {
|
|
|
55688
56158
|
return out2;
|
|
55689
56159
|
}
|
|
55690
56160
|
function trendAndStore(dir, entries, suppressed) {
|
|
55691
|
-
const p =
|
|
56161
|
+
const p = join58(dir, LEDGER_FILE);
|
|
55692
56162
|
let previous = null;
|
|
55693
56163
|
try {
|
|
55694
|
-
if (
|
|
55695
|
-
const parsed = JSON.parse(
|
|
56164
|
+
if (existsSync58(p)) {
|
|
56165
|
+
const parsed = JSON.parse(readFileSync65(p, "utf8"));
|
|
55696
56166
|
if (Array.isArray(parsed.keys)) previous = parsed.keys.filter((k) => typeof k === "string");
|
|
55697
56167
|
}
|
|
55698
56168
|
} catch {
|
|
@@ -55700,8 +56170,8 @@ function trendAndStore(dir, entries, suppressed) {
|
|
|
55700
56170
|
const current = entries.map((e) => e.key);
|
|
55701
56171
|
const stored = [.../* @__PURE__ */ new Set([...current, ...(previous ?? []).filter((key2) => (suppressed ?? []).includes(key2))])];
|
|
55702
56172
|
try {
|
|
55703
|
-
|
|
55704
|
-
|
|
56173
|
+
mkdirSync29(dirname31(p), { recursive: true });
|
|
56174
|
+
writeFileSync23(p, JSON.stringify({ keys: stored, ts: (/* @__PURE__ */ new Date()).toISOString() }, null, 2));
|
|
55705
56175
|
} catch {
|
|
55706
56176
|
return null;
|
|
55707
56177
|
}
|
|
@@ -55731,11 +56201,11 @@ function formatLedger(entries, trend) {
|
|
|
55731
56201
|
return out2.join("\n");
|
|
55732
56202
|
}
|
|
55733
56203
|
function diffAndStoreObservations(dir, current) {
|
|
55734
|
-
const p =
|
|
56204
|
+
const p = join58(dir, OBS_FILE);
|
|
55735
56205
|
let previous = null;
|
|
55736
56206
|
try {
|
|
55737
|
-
if (
|
|
55738
|
-
const parsed = JSON.parse(
|
|
56207
|
+
if (existsSync58(p)) {
|
|
56208
|
+
const parsed = JSON.parse(readFileSync65(p, "utf8"));
|
|
55739
56209
|
if (parsed.screens && typeof parsed.screens === "object" && !Array.isArray(parsed.screens) && Object.values(parsed.screens).every(
|
|
55740
56210
|
(v) => Array.isArray(v) && v.every((l) => typeof l === "string")
|
|
55741
56211
|
)) {
|
|
@@ -55745,8 +56215,8 @@ function diffAndStoreObservations(dir, current) {
|
|
|
55745
56215
|
} catch {
|
|
55746
56216
|
}
|
|
55747
56217
|
try {
|
|
55748
|
-
|
|
55749
|
-
|
|
56218
|
+
mkdirSync29(dirname31(p), { recursive: true });
|
|
56219
|
+
writeFileSync23(p, JSON.stringify({ screens: current, ts: (/* @__PURE__ */ new Date()).toISOString() }, null, 2));
|
|
55750
56220
|
} catch {
|
|
55751
56221
|
return null;
|
|
55752
56222
|
}
|
|
@@ -55784,8 +56254,8 @@ var LEDGER_FILE, OBS_FILE;
|
|
|
55784
56254
|
var init_coverage = __esm({
|
|
55785
56255
|
"src/coverage.ts"() {
|
|
55786
56256
|
init_native_decode();
|
|
55787
|
-
LEDGER_FILE =
|
|
55788
|
-
OBS_FILE =
|
|
56257
|
+
LEDGER_FILE = join58(".himi", "test-ledger.json");
|
|
56258
|
+
OBS_FILE = join58(".himi", "test-observations.json");
|
|
55789
56259
|
}
|
|
55790
56260
|
});
|
|
55791
56261
|
|
|
@@ -55796,11 +56266,11 @@ __export(design_kits_exports, {
|
|
|
55796
56266
|
kitsDir: () => kitsDir,
|
|
55797
56267
|
listKits: () => listKits2
|
|
55798
56268
|
});
|
|
55799
|
-
import { existsSync as
|
|
55800
|
-
import { join as
|
|
56269
|
+
import { existsSync as existsSync59, readFileSync as readFileSync66, readdirSync as readdirSync29 } from "node:fs";
|
|
56270
|
+
import { join as join59, basename as basename15 } from "node:path";
|
|
55801
56271
|
import { fileURLToPath as fileURLToPath23 } from "node:url";
|
|
55802
56272
|
function kitsDir() {
|
|
55803
|
-
return KIT_DIR_CANDIDATES.find((p) =>
|
|
56273
|
+
return KIT_DIR_CANDIDATES.find((p) => existsSync59(p));
|
|
55804
56274
|
}
|
|
55805
56275
|
function listKits2() {
|
|
55806
56276
|
const dir = kitsDir();
|
|
@@ -55824,7 +56294,7 @@ In a published @wix/himalaya-cli this is a packaging bug \u2014 the kits should
|
|
|
55824
56294
|
if (!kits.includes(name)) {
|
|
55825
56295
|
return { ok: false, kit: name, kits, problems: [`no design kit named "${name}". Available: ${kits.join(", ")}`] };
|
|
55826
56296
|
}
|
|
55827
|
-
const raw =
|
|
56297
|
+
const raw = readFileSync66(join59(dir, `${name}.json`), "utf8");
|
|
55828
56298
|
let parsed;
|
|
55829
56299
|
try {
|
|
55830
56300
|
parsed = JSON.parse(raw);
|
|
@@ -55848,9 +56318,9 @@ var init_design_kits = __esm({
|
|
|
55848
56318
|
"src/design-kits.ts"() {
|
|
55849
56319
|
KIT_DIR_CANDIDATES = [
|
|
55850
56320
|
// in-repo: tools/himi-cli/src/design-kits.ts → <repo>/stdlib/design-kits/
|
|
55851
|
-
|
|
56321
|
+
join59(fileURLToPath23(new URL("../../..", import.meta.url)), "stdlib", "design-kits"),
|
|
55852
56322
|
// published CLI: <pkg>/dist/cli.mjs → <pkg>/stdlib/design-kits/
|
|
55853
|
-
|
|
56323
|
+
join59(fileURLToPath23(new URL("..", import.meta.url)), "stdlib", "design-kits")
|
|
55854
56324
|
];
|
|
55855
56325
|
}
|
|
55856
56326
|
});
|
|
@@ -55864,11 +56334,11 @@ __export(assets_exports, {
|
|
|
55864
56334
|
removeAssetPayload: () => removeAssetPayload,
|
|
55865
56335
|
verifyReport: () => verifyReport
|
|
55866
56336
|
});
|
|
55867
|
-
import { readFileSync as
|
|
56337
|
+
import { readFileSync as readFileSync67 } from "node:fs";
|
|
55868
56338
|
import { basename as basename16 } from "node:path";
|
|
55869
56339
|
function declaredAssets(dir) {
|
|
55870
56340
|
return collectAssets(dir).map((a) => {
|
|
55871
|
-
const bytes2 =
|
|
56341
|
+
const bytes2 = readFileSync67(a.path);
|
|
55872
56342
|
return {
|
|
55873
56343
|
name: a.name,
|
|
55874
56344
|
sha256: assetSha256(bytes2),
|
|
@@ -55945,7 +56415,7 @@ function verifyReport(dir, app, manifest) {
|
|
|
55945
56415
|
};
|
|
55946
56416
|
}
|
|
55947
56417
|
function addAssetPayload(manifest, filePath, opts) {
|
|
55948
|
-
const bytes2 =
|
|
56418
|
+
const bytes2 = readFileSync67(filePath);
|
|
55949
56419
|
const name = opts.name ?? basename16(filePath);
|
|
55950
56420
|
const sha2565 = assetSha256(bytes2);
|
|
55951
56421
|
const record2 = {
|
|
@@ -57669,9 +58139,9 @@ __export(run_exports, {
|
|
|
57669
58139
|
runPackage: () => runPackage,
|
|
57670
58140
|
withoutProdServe: () => withoutProdServe
|
|
57671
58141
|
});
|
|
57672
|
-
import { cpSync as cpSync2, existsSync as
|
|
58142
|
+
import { cpSync as cpSync2, existsSync as existsSync60, mkdirSync as mkdirSync30, mkdtempSync as mkdtempSync3, readFileSync as readFileSync68, rmSync as rmSync15, statSync as statSync16 } from "node:fs";
|
|
57673
58143
|
import { networkInterfaces as osNetworkInterfaces, tmpdir as tmpdir4 } from "node:os";
|
|
57674
|
-
import { dirname as dirname32, join as
|
|
58144
|
+
import { dirname as dirname32, join as join60 } from "node:path";
|
|
57675
58145
|
import { fileURLToPath as fileURLToPath24 } from "node:url";
|
|
57676
58146
|
function makePackageRunHandler(opts) {
|
|
57677
58147
|
const store = new FileReleaseStore(opts.releaseRoot);
|
|
@@ -57699,9 +58169,9 @@ async function fetchBytes(url) {
|
|
|
57699
58169
|
}
|
|
57700
58170
|
async function resolvePackage(source, workDir) {
|
|
57701
58171
|
const isUrl2 = /^https?:\/\//i.test(source);
|
|
57702
|
-
if (!isUrl2 &&
|
|
57703
|
-
const appDirManifest =
|
|
57704
|
-
if (
|
|
58172
|
+
if (!isUrl2 && existsSync60(source) && statSync16(source).isDirectory()) {
|
|
58173
|
+
const appDirManifest = join60(source, "release-manifest.json");
|
|
58174
|
+
if (existsSync60(appDirManifest)) {
|
|
57705
58175
|
const opened2 = openPackageDir(source);
|
|
57706
58176
|
if (!opened2.validation.ok) {
|
|
57707
58177
|
throw new Error(`package is not valid:
|
|
@@ -57710,11 +58180,11 @@ async function resolvePackage(source, workDir) {
|
|
|
57710
58180
|
const app2 = opened2.header?.app ?? opened2.manifest?.app;
|
|
57711
58181
|
if (!app2) throw new Error(`${source} has no app id (no himi-package.json and no manifest.app)`);
|
|
57712
58182
|
if (!isSafeAppId(app2)) throw new Error(`${source} declares an unsafe app id ${JSON.stringify(app2)} \u2014 refusing to open it`);
|
|
57713
|
-
const root2 = workDir ?? mkdtempSync3(
|
|
57714
|
-
const target =
|
|
57715
|
-
if (
|
|
57716
|
-
|
|
57717
|
-
|
|
58183
|
+
const root2 = workDir ?? mkdtempSync3(join60(tmpdir4(), "himi-run-"));
|
|
58184
|
+
const target = join60(root2, app2);
|
|
58185
|
+
if (join60(source) !== target) {
|
|
58186
|
+
mkdirSync30(root2, { recursive: true });
|
|
58187
|
+
rmSync15(target, { recursive: true, force: true });
|
|
57718
58188
|
cpSync2(source, target, { recursive: true });
|
|
57719
58189
|
}
|
|
57720
58190
|
return {
|
|
@@ -57729,12 +58199,12 @@ async function resolvePackage(source, workDir) {
|
|
|
57729
58199
|
}
|
|
57730
58200
|
throw new Error(`${source} is a directory but has no release-manifest.json \u2014 is it a package?`);
|
|
57731
58201
|
}
|
|
57732
|
-
const bytes2 = isUrl2 ? await fetchBytes(source) :
|
|
57733
|
-
const root = workDir ?? mkdtempSync3(
|
|
58202
|
+
const bytes2 = isUrl2 ? await fetchBytes(source) : readFileSync68(source);
|
|
58203
|
+
const root = workDir ?? mkdtempSync3(join60(tmpdir4(), "himi-run-"));
|
|
57734
58204
|
const { app, appDir: appDir3 } = unpackTo(bytes2, root);
|
|
57735
58205
|
const opened = openPackageDir(appDir3);
|
|
57736
58206
|
if (!opened.validation.ok) {
|
|
57737
|
-
|
|
58207
|
+
rmSync15(workDir ? appDir3 : root, { recursive: true, force: true });
|
|
57738
58208
|
throw new Error(`package is not valid:
|
|
57739
58209
|
${opened.validation.errors.join("\n ")}`);
|
|
57740
58210
|
}
|
|
@@ -57753,14 +58223,14 @@ async function loadPreviewAssets() {
|
|
|
57753
58223
|
const mod = await Promise.resolve().then(() => (init_preview_assets_generated(), preview_assets_generated_exports));
|
|
57754
58224
|
const here = dirname32(fileURLToPath24(import.meta.url));
|
|
57755
58225
|
const candidates = [
|
|
57756
|
-
|
|
58226
|
+
join60(here, "preview-assets.generated.bin"),
|
|
57757
58227
|
// published: copied next to the bundled CLI
|
|
57758
|
-
|
|
58228
|
+
join60(here, "../../../packages/serve/src/preview-assets.generated.bin")
|
|
57759
58229
|
// in-repo checkout
|
|
57760
58230
|
];
|
|
57761
58231
|
for (const path2 of candidates) {
|
|
57762
|
-
if (!
|
|
57763
|
-
const blob =
|
|
58232
|
+
if (!existsSync60(path2)) continue;
|
|
58233
|
+
const blob = readFileSync68(path2);
|
|
57764
58234
|
return mod.assemblePreviewAssetsFromBlob(new Uint8Array(blob.buffer, blob.byteOffset, blob.byteLength));
|
|
57765
58235
|
}
|
|
57766
58236
|
return /* @__PURE__ */ new Map();
|
|
@@ -57808,7 +58278,7 @@ async function runPackage(source, opts = {}) {
|
|
|
57808
58278
|
const resolved2 = await resolvePackage(source, opts.workDir);
|
|
57809
58279
|
const assets = opts.assets ?? (opts.spa ? /* @__PURE__ */ new Map() : await loadPreviewAssets());
|
|
57810
58280
|
if (!opts.spa && assets.size === 0) {
|
|
57811
|
-
if (resolved2.temp)
|
|
58281
|
+
if (resolved2.temp) rmSync15(resolved2.temp, { recursive: true, force: true });
|
|
57812
58282
|
throw new Error(NO_SPA_MESSAGE);
|
|
57813
58283
|
}
|
|
57814
58284
|
const plane = makePackageRunHandler({
|
|
@@ -57846,7 +58316,7 @@ async function runPackage(source, opts = {}) {
|
|
|
57846
58316
|
lanBound = (await listen(lanServer, port, lanHost)).address;
|
|
57847
58317
|
} catch (e) {
|
|
57848
58318
|
await closeServer(server);
|
|
57849
|
-
if (resolved2.temp)
|
|
58319
|
+
if (resolved2.temp) rmSync15(resolved2.temp, { recursive: true, force: true });
|
|
57850
58320
|
throw new Error(`himi run --lan could not listen on ${lanHost}:${port} (${e.message}); drop --lan, or pass --port for a free one`);
|
|
57851
58321
|
}
|
|
57852
58322
|
bound.push(lanBound);
|
|
@@ -57868,7 +58338,7 @@ async function runPackage(source, opts = {}) {
|
|
|
57868
58338
|
warnings: resolved2.warnings,
|
|
57869
58339
|
close: async () => {
|
|
57870
58340
|
await Promise.all(servers.map(closeServer));
|
|
57871
|
-
if (resolved2.temp)
|
|
58341
|
+
if (resolved2.temp) rmSync15(resolved2.temp, { recursive: true, force: true });
|
|
57872
58342
|
}
|
|
57873
58343
|
};
|
|
57874
58344
|
}
|
|
@@ -57889,285 +58359,6 @@ var init_run = __esm({
|
|
|
57889
58359
|
}
|
|
57890
58360
|
});
|
|
57891
58361
|
|
|
57892
|
-
// src/run-watch.ts
|
|
57893
|
-
var run_watch_exports = {};
|
|
57894
|
-
__export(run_watch_exports, {
|
|
57895
|
-
ReloadBus: () => ReloadBus,
|
|
57896
|
-
boardFile: () => boardFile,
|
|
57897
|
-
diffReleases: () => diffReleases,
|
|
57898
|
-
himiDir: () => himiDir,
|
|
57899
|
-
ignoredChange: () => ignoredChange,
|
|
57900
|
-
isEmptyDiff: () => isEmptyDiff,
|
|
57901
|
-
rebuildBlocker: () => rebuildBlocker,
|
|
57902
|
-
reloadEvents: () => reloadEvents,
|
|
57903
|
-
removeInitWatchRecord: () => removeInitWatchRecord,
|
|
57904
|
-
removeRunRecord: () => removeRunRecord,
|
|
57905
|
-
serialize: () => serialize,
|
|
57906
|
-
watchContent: () => watchContent,
|
|
57907
|
-
watchFlagConflict: () => watchFlagConflict,
|
|
57908
|
-
watchedContentDir: () => watchedContentDir,
|
|
57909
|
-
writeRunRecord: () => writeRunRecord
|
|
57910
|
-
});
|
|
57911
|
-
import { closeSync as closeSync5, constants as constants2, existsSync as existsSync60, fstatSync, lstatSync as lstatSync14, statSync as statSync16, mkdirSync as mkdirSync30, openSync as openSync4, readFileSync as readFileSync68, realpathSync as realpathSync9, renameSync as renameSync7, rmSync as rmSync15, writeFileSync as writeFileSync23, watch as fsWatch } from "node:fs";
|
|
57912
|
-
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
57913
|
-
import { extname as extname4, join as join60, resolve as resolve47, sep as sep16 } from "node:path";
|
|
57914
|
-
function diffReleases(prev, next) {
|
|
57915
|
-
const bundles = Object.keys(next.bundles).filter((b) => prev.bundles[b]?.sha256 !== next.bundles[b].sha256).sort();
|
|
57916
|
-
const changedBundles = new Set(bundles);
|
|
57917
|
-
const screens = Object.entries(next.screens).filter(([id, s]) => {
|
|
57918
|
-
const before = prev.screens[id];
|
|
57919
|
-
return !before || before.descriptorHash !== s.descriptorHash || before.bundle !== s.bundle || s.bundle !== null && changedBundles.has(s.bundle);
|
|
57920
|
-
}).map(([id]) => id).sort();
|
|
57921
|
-
const prevIds = Object.keys(prev.screens).sort().join("\n");
|
|
57922
|
-
const nextIds = Object.keys(next.screens).sort().join("\n");
|
|
57923
|
-
return {
|
|
57924
|
-
screens,
|
|
57925
|
-
bundles,
|
|
57926
|
-
tokens: prev.tokensHash !== next.tokensHash,
|
|
57927
|
-
config: prev.appConfigHash !== next.appConfigHash || prevIds !== nextIds
|
|
57928
|
-
};
|
|
57929
|
-
}
|
|
57930
|
-
function reloadEvents(d) {
|
|
57931
|
-
const out2 = [];
|
|
57932
|
-
if (d.bundles.length) out2.push(["bundle.invalidated", { bundles: d.bundles }]);
|
|
57933
|
-
if (d.screens.length) out2.push(["screen.invalidated", { screenIds: d.screens }]);
|
|
57934
|
-
if (d.tokens) out2.push(["tokens.changed", {}]);
|
|
57935
|
-
if (d.config) out2.push(["config.invalidated", {}]);
|
|
57936
|
-
return out2;
|
|
57937
|
-
}
|
|
57938
|
-
function ignoredChange(relPath, isDirectory = () => false) {
|
|
57939
|
-
const parts = relPath.split(/[\\/]/).filter(Boolean);
|
|
57940
|
-
if (!parts.length) return true;
|
|
57941
|
-
const [top] = parts;
|
|
57942
|
-
if ([".himi", "dist", "node_modules", ".git", ".build", ...AGENT_TOOLING_DIRS].includes(top)) return true;
|
|
57943
|
-
if (parts.length === 1 && AGENT_TOOLING_ROOT_FILES.includes(top)) return true;
|
|
57944
|
-
if ((top === "shots" || top === "evidence") && (parts.length > 1 || isDirectory(top))) return true;
|
|
57945
|
-
const name = parts[parts.length - 1];
|
|
57946
|
-
return /(^\.#|~$|\.sw[a-p]$|^\.DS_Store$|\.tmp$)/.test(name);
|
|
57947
|
-
}
|
|
57948
|
-
function watchContent(dir, onChange, opts = {}) {
|
|
57949
|
-
const debounceMs = opts.debounceMs ?? 200;
|
|
57950
|
-
const watch2 = opts.watch ?? ((p, o, l) => fsWatch(p, o, (event, file) => l(event, file === null ? null : String(file))));
|
|
57951
|
-
let timer = null;
|
|
57952
|
-
const pending = /* @__PURE__ */ new Set();
|
|
57953
|
-
const watcher2 = watch2(dir, { recursive: true }, (_event, file) => {
|
|
57954
|
-
if (!file || ignoredChange(file, (rel) => isDir(join60(dir, rel)))) return;
|
|
57955
|
-
pending.add(file);
|
|
57956
|
-
if (timer) clearTimeout(timer);
|
|
57957
|
-
timer = setTimeout(() => {
|
|
57958
|
-
timer = null;
|
|
57959
|
-
const paths = [...pending];
|
|
57960
|
-
pending.clear();
|
|
57961
|
-
onChange(paths);
|
|
57962
|
-
}, debounceMs);
|
|
57963
|
-
});
|
|
57964
|
-
return {
|
|
57965
|
-
close() {
|
|
57966
|
-
if (timer) clearTimeout(timer);
|
|
57967
|
-
watcher2.close();
|
|
57968
|
-
}
|
|
57969
|
-
};
|
|
57970
|
-
}
|
|
57971
|
-
function watchedContentDir(source) {
|
|
57972
|
-
if (!source || /^https?:\/\//i.test(source)) return null;
|
|
57973
|
-
const dir = resolve47(source);
|
|
57974
|
-
try {
|
|
57975
|
-
return statSync16(dir).isDirectory() && existsSync60(join60(dir, "himalaya.content.json")) ? dir : null;
|
|
57976
|
-
} catch {
|
|
57977
|
-
return null;
|
|
57978
|
-
}
|
|
57979
|
-
}
|
|
57980
|
-
function watchFlagConflict(opts) {
|
|
57981
|
-
if (opts.source) {
|
|
57982
|
-
return { error: "watch_needs_source", message: "--watch rebuilds a content package on every save, so it cannot watch a packaged release or a URL. Pass the app's directory (the one with himalaya.content.json), --content <dir>, or run it from there with no argument." };
|
|
57983
|
-
}
|
|
57984
|
-
if (opts.browser) {
|
|
57985
|
-
return { error: "watch_with_browser", message: "--watch already opens the app in your browser, so drop --browser. (--browser is a different mode: it runs the whole serve plane inside the tab, leaving no process here to rebuild on save.) Under --watch a site-bound app shows the site's live data on its own." };
|
|
57986
|
-
}
|
|
57987
|
-
if (opts.spa) {
|
|
57988
|
-
return { error: "watch_with_spa", message: "--watch pushes each rebuild to the open tab over this server's own origin, and a hosted SPA (--spa) listens on its origin instead, so it would never repaint. Drop --spa to watch with the bundled web host, or drop --watch." };
|
|
57989
|
-
}
|
|
57990
|
-
return null;
|
|
57991
|
-
}
|
|
57992
|
-
function rebuildBlocker(outcome, servedMissing) {
|
|
57993
|
-
const failed2 = outcome.failedToBuild[0];
|
|
57994
|
-
if (failed2) return `${failed2.screenId}: ${failed2.error.split("\n")[0]}`;
|
|
57995
|
-
const had = new Set(servedMissing.map((m) => `${m.screen}\0${m.bundle}`));
|
|
57996
|
-
const lost = outcome.missingBundles.find((m) => !had.has(`${m.screen}\0${m.bundle}`));
|
|
57997
|
-
if (lost) return `${lost.screen}: its worker bundle "${lost.bundle}" is referenced but was not built`;
|
|
57998
|
-
return null;
|
|
57999
|
-
}
|
|
58000
|
-
function serialize(task) {
|
|
58001
|
-
let running = null;
|
|
58002
|
-
let again = false;
|
|
58003
|
-
let stopped = false;
|
|
58004
|
-
const go = () => {
|
|
58005
|
-
if (stopped) return;
|
|
58006
|
-
if (running) {
|
|
58007
|
-
again = true;
|
|
58008
|
-
return;
|
|
58009
|
-
}
|
|
58010
|
-
running = (async () => {
|
|
58011
|
-
try {
|
|
58012
|
-
await task();
|
|
58013
|
-
} finally {
|
|
58014
|
-
running = null;
|
|
58015
|
-
if (again && !stopped) {
|
|
58016
|
-
again = false;
|
|
58017
|
-
go();
|
|
58018
|
-
}
|
|
58019
|
-
}
|
|
58020
|
-
})();
|
|
58021
|
-
};
|
|
58022
|
-
return Object.assign(() => go(), {
|
|
58023
|
-
async stop() {
|
|
58024
|
-
stopped = true;
|
|
58025
|
-
again = false;
|
|
58026
|
-
await running?.catch(() => {
|
|
58027
|
-
});
|
|
58028
|
-
}
|
|
58029
|
-
});
|
|
58030
|
-
}
|
|
58031
|
-
function himiDir(dir) {
|
|
58032
|
-
const root = join60(dir, ".himi");
|
|
58033
|
-
try {
|
|
58034
|
-
return lstatSync14(root).isDirectory() ? root : null;
|
|
58035
|
-
} catch (e) {
|
|
58036
|
-
if (e.code !== "ENOENT") return null;
|
|
58037
|
-
mkdirSync30(root, { recursive: true });
|
|
58038
|
-
return lstatSync14(root).isDirectory() ? root : null;
|
|
58039
|
-
}
|
|
58040
|
-
}
|
|
58041
|
-
function writeRunRecord(dir, record2) {
|
|
58042
|
-
const root = himiDir(dir);
|
|
58043
|
-
if (!root) return false;
|
|
58044
|
-
const tmp = join60(root, `.run.json.${process.pid}.${randomUUID4()}.tmp`);
|
|
58045
|
-
try {
|
|
58046
|
-
writeFileSync23(tmp, `${JSON.stringify(record2, null, 2)}
|
|
58047
|
-
`, { flag: "wx" });
|
|
58048
|
-
renameSync7(tmp, join60(root, "run.json"));
|
|
58049
|
-
} catch (e) {
|
|
58050
|
-
rmSync15(tmp, { force: true });
|
|
58051
|
-
throw e;
|
|
58052
|
-
}
|
|
58053
|
-
return true;
|
|
58054
|
-
}
|
|
58055
|
-
function removeRunRecord(dir, owner) {
|
|
58056
|
-
const root = join60(dir, ".himi");
|
|
58057
|
-
try {
|
|
58058
|
-
if (!lstatSync14(root).isDirectory()) return;
|
|
58059
|
-
} catch {
|
|
58060
|
-
return;
|
|
58061
|
-
}
|
|
58062
|
-
if (owner !== void 0) {
|
|
58063
|
-
const bytes2 = readConfined(root, "run.json");
|
|
58064
|
-
let pid;
|
|
58065
|
-
try {
|
|
58066
|
-
pid = bytes2 ? JSON.parse(bytes2.toString("utf8")).pid : void 0;
|
|
58067
|
-
} catch {
|
|
58068
|
-
}
|
|
58069
|
-
if (typeof pid === "number" && pid !== owner) return;
|
|
58070
|
-
}
|
|
58071
|
-
rmSync15(join60(root, "run.json"), { force: true });
|
|
58072
|
-
}
|
|
58073
|
-
function removeInitWatchRecord(dir, owner) {
|
|
58074
|
-
const root = join60(dir, ".himi");
|
|
58075
|
-
try {
|
|
58076
|
-
if (!lstatSync14(root).isDirectory()) return;
|
|
58077
|
-
} catch {
|
|
58078
|
-
return;
|
|
58079
|
-
}
|
|
58080
|
-
const bytes2 = readConfined(root, "watch.json");
|
|
58081
|
-
if (!bytes2) return;
|
|
58082
|
-
let pid;
|
|
58083
|
-
try {
|
|
58084
|
-
pid = JSON.parse(bytes2.toString("utf8")).pid;
|
|
58085
|
-
} catch {
|
|
58086
|
-
return;
|
|
58087
|
-
}
|
|
58088
|
-
if (pid !== owner) return;
|
|
58089
|
-
rmSync15(join60(root, "watch.json"), { force: true });
|
|
58090
|
-
}
|
|
58091
|
-
function boardFile(dir, pathname) {
|
|
58092
|
-
if (pathname === RUN_IDENTITY_PATH) {
|
|
58093
|
-
return new Response(JSON.stringify(runIdentity(dir)), { status: 200, headers: { "content-type": "application/json", "cache-control": "no-store" } });
|
|
58094
|
-
}
|
|
58095
|
-
let rel = null;
|
|
58096
|
-
if (pathname === "/__himi/studio/" || pathname === "/__himi/studio/index.html") rel = join60("studio", "index.html");
|
|
58097
|
-
else if (pathname === "/__himi/studio/state.js") rel = join60("studio", "state.js");
|
|
58098
|
-
else if (pathname === "/__himi/studio/timeline.js") rel = join60("studio", "timeline.js");
|
|
58099
|
-
else {
|
|
58100
|
-
const m = /^\/__himi\/history\/([0-9a-f]{64}\.(png|jpg|gif|webp))$/.exec(pathname);
|
|
58101
|
-
if (m) rel = join60("history", m[1]);
|
|
58102
|
-
}
|
|
58103
|
-
if (!rel) return null;
|
|
58104
|
-
const bytes2 = readConfined(resolve47(dir, ".himi"), rel);
|
|
58105
|
-
if (!bytes2) return new Response("not found", { status: 404 });
|
|
58106
|
-
return new Response(bytes2, {
|
|
58107
|
-
status: 200,
|
|
58108
|
-
headers: { "content-type": BOARD_TYPES[extname4(rel)] ?? "application/octet-stream", "cache-control": rel.startsWith("history") ? "public, max-age=31536000, immutable" : "no-store" }
|
|
58109
|
-
});
|
|
58110
|
-
}
|
|
58111
|
-
function readConfined(root, rel) {
|
|
58112
|
-
try {
|
|
58113
|
-
if (!lstatSync14(root).isDirectory()) return null;
|
|
58114
|
-
const parts = rel.split(sep16);
|
|
58115
|
-
let at2 = root;
|
|
58116
|
-
for (let i = 0; i < parts.length; i++) {
|
|
58117
|
-
at2 = join60(at2, parts[i]);
|
|
58118
|
-
const st = lstatSync14(at2);
|
|
58119
|
-
if (st.isSymbolicLink() || !(i === parts.length - 1 ? st.isFile() : st.isDirectory())) return null;
|
|
58120
|
-
}
|
|
58121
|
-
const canonicalRoot = realpathSync9(root);
|
|
58122
|
-
if (!realpathSync9(at2).startsWith(canonicalRoot + sep16)) return null;
|
|
58123
|
-
const fd = openSync4(at2, constants2.O_RDONLY | (constants2.O_NOFOLLOW ?? 0));
|
|
58124
|
-
try {
|
|
58125
|
-
if (!fstatSync(fd).isFile()) return null;
|
|
58126
|
-
return readFileSync68(fd);
|
|
58127
|
-
} finally {
|
|
58128
|
-
closeSync5(fd);
|
|
58129
|
-
}
|
|
58130
|
-
} catch {
|
|
58131
|
-
return null;
|
|
58132
|
-
}
|
|
58133
|
-
}
|
|
58134
|
-
var isEmptyDiff, ReloadBus, isDir, BOARD_TYPES;
|
|
58135
|
-
var init_run_watch = __esm({
|
|
58136
|
-
"src/run-watch.ts"() {
|
|
58137
|
-
init_source_deny();
|
|
58138
|
-
init_run_identity();
|
|
58139
|
-
isEmptyDiff = (d) => !d.screens.length && !d.bundles.length && !d.tokens && !d.config;
|
|
58140
|
-
ReloadBus = class {
|
|
58141
|
-
subscribers = /* @__PURE__ */ new Set();
|
|
58142
|
-
subscribe(send2) {
|
|
58143
|
-
this.subscribers.add(send2);
|
|
58144
|
-
return () => this.subscribers.delete(send2);
|
|
58145
|
-
}
|
|
58146
|
-
publish(event, data) {
|
|
58147
|
-
for (const send2 of [...this.subscribers]) send2(event, data);
|
|
58148
|
-
}
|
|
58149
|
-
get listeners() {
|
|
58150
|
-
return this.subscribers.size;
|
|
58151
|
-
}
|
|
58152
|
-
};
|
|
58153
|
-
isDir = (path2) => {
|
|
58154
|
-
try {
|
|
58155
|
-
return statSync16(path2).isDirectory();
|
|
58156
|
-
} catch {
|
|
58157
|
-
return false;
|
|
58158
|
-
}
|
|
58159
|
-
};
|
|
58160
|
-
BOARD_TYPES = {
|
|
58161
|
-
".html": "text/html; charset=utf-8",
|
|
58162
|
-
".js": "text/javascript; charset=utf-8",
|
|
58163
|
-
".png": "image/png",
|
|
58164
|
-
".jpg": "image/jpeg",
|
|
58165
|
-
".gif": "image/gif",
|
|
58166
|
-
".webp": "image/webp"
|
|
58167
|
-
};
|
|
58168
|
-
}
|
|
58169
|
-
});
|
|
58170
|
-
|
|
58171
58362
|
// src/run-browser.ts
|
|
58172
58363
|
var run_browser_exports = {};
|
|
58173
58364
|
__export(run_browser_exports, {
|
|
@@ -72214,7 +72405,8 @@ var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
|
|
|
72214
72405
|
"no-agent",
|
|
72215
72406
|
"no-first-look",
|
|
72216
72407
|
"no-image",
|
|
72217
|
-
"no-watch"
|
|
72408
|
+
"no-watch",
|
|
72409
|
+
"mocks"
|
|
72218
72410
|
]);
|
|
72219
72411
|
var SHELL_ENUM = SHELL_NAMES2.join("|");
|
|
72220
72412
|
var SWITCH_FLAGS = new Set([...BOOLEAN_FLAGS].filter((name) => name !== "deep"));
|
|
@@ -72329,7 +72521,8 @@ var USAGE = `himi \u2014 the Himalaya authoring CLI (remote dev loop)
|
|
|
72329
72521
|
preview itself (\`himi run --watch\`, detached, --no-open, log
|
|
72330
72522
|
.himi/watch.log, stops after 30 idle minutes) and reports
|
|
72331
72523
|
watch: {started, pid, url, board, log} \u2014 or {skipped} for a
|
|
72332
|
-
--remote template
|
|
72524
|
+
--remote template, a CI environment or a test runner. Nothing
|
|
72525
|
+
else skips it: outside CI it starts, and watch.note says why
|
|
72333
72526
|
himi template list [--remote] list starter templates + metadata (title/description/tags) + each one's start prompt
|
|
72334
72527
|
himi template prompt <name> print the prompt that starts an app from that template
|
|
72335
72528
|
himi template publish [--dir <app>] [--source-dir <dir>] [--name <n>] [--icon <png>] [--visibility public|unlisted]
|
|
@@ -72375,7 +72568,9 @@ var USAGE = `himi \u2014 the Himalaya authoring CLI (remote dev loop)
|
|
|
72375
72568
|
journal a postcard for the owner (.himi/progress.jsonl): what
|
|
72376
72569
|
changed, which journey step, and the image that shows it (copied
|
|
72377
72570
|
into .himi/history/ so later renders cannot replace it). With no
|
|
72378
|
-
--image the current home shot is attached (never a stale one
|
|
72571
|
+
--image the current home shot is attached (never a stale one,
|
|
72572
|
+
never placeholders or an error screen \u2014 then the newest home
|
|
72573
|
+
picture with content, or none, imageNote says which;
|
|
72379
72574
|
--no-image opts out; imageAttached says which; on the
|
|
72380
72575
|
--step review handover card it attaches even as a repeat,
|
|
72381
72576
|
and the output names the making-of to hand over). Bare
|
|
@@ -72419,7 +72614,7 @@ var USAGE = `himi \u2014 the Himalaya authoring CLI (remote dev loop)
|
|
|
72419
72614
|
(--ai writes rows for you to review first)
|
|
72420
72615
|
himi secrets set|list|rotate --app <app> --name <name> [--environment preview|production]
|
|
72421
72616
|
encrypted secret metadata; set/rotate read the value from stdin
|
|
72422
|
-
himi render <screenId> [--content <dir>] [--scheme dark] [--png [<file>]] [--tree] [--dump-state] [--static] [--live] [--state <json|file>] [--input <json|file>] [--width <px> | --device <name>] [--rtl] [--load-budget <ms>]
|
|
72617
|
+
himi render <screenId> [--content <dir>] [--scheme dark] [--png [<file>]] [--tree] [--dump-state] [--static] [--live] [--mocks] [--state <json|file>] [--input <json|file>] [--width <px> | --device <name>] [--rtl] [--load-budget <ms>]
|
|
72423
72618
|
[--remote --app <app> --release <id>]
|
|
72424
72619
|
headless web render + diagnostics (no sim). Default: local content dir.
|
|
72425
72620
|
A bare --png writes .himi/shots/<screenId>.png (what the build board shows).
|
|
@@ -72429,6 +72624,10 @@ var USAGE = `himi \u2014 the Himalaya authoring CLI (remote dev loop)
|
|
|
72429
72624
|
render LOCALLY with --live (+ --png), not --remote.
|
|
72430
72625
|
--live runs the worker's fetches against REAL Wix (minted visitor token) and awaits
|
|
72431
72626
|
load(), so the render shows LIVE data (needs network + a Wix clientId in config.ts);
|
|
72627
|
+
--mocks runs load() against the app's OWN recorded data instead \u2014 dev/net-mocks.json
|
|
72628
|
+
(what \`himi test --record\` writes) and the config's cms seed rows, as \`himi test\`'s
|
|
72629
|
+
shots do; no request leaves this machine. With none, it renders the initial state.
|
|
72630
|
+
The automatic pictures (init's first look, validate's refresh) use it;
|
|
72432
72631
|
--tree adds stable raw structure plus a resolvedRenderTree when hydrated;
|
|
72433
72632
|
--dump-state explicitly includes the full worker snapshot;
|
|
72434
72633
|
--state '{"<svc>":{\u2026}}' injects worker state (render loaded/error states);
|
|
@@ -72952,17 +73151,17 @@ WHAT A PATCH DOES NOT CARRY \u2014 all reported in the output, never silent
|
|
|
72952
73151
|
himi docs releasing-with-himi-serve`;
|
|
72953
73152
|
var COMMANDS = [
|
|
72954
73153
|
{ name: "captcha", summary: "create and store native CAPTCHA keys in Serve, or inspect setup status; customer setup verifies Wix site ownership; does not enable member CAPTCHA SDKs", required: "setup|status --app <app> [--content <dir>] [--site <msid>] [--ios-bundle-id <id>] [--android-package-name <id>] [--target <t>]" },
|
|
72955
|
-
{ name: "init", summary: "scaffold a starter content package (OTA-only; no native shells). --template <t> starts from a full reference app (branded-lite = consumer storefront over a Wix site; owner-lite = owner CRUD/management) instead of the minimal counter; --templates lists bundled ones; --remote fetches the template off the serve plane (falls back to the bundled copy offline). The new app also gets its agent files (the same set `himi agent-init` writes: AGENTS.md, CLAUDE.md, the himalaya skill, HIMALAYA.md, a SessionStart hook) so the next session started inside it picks the work up; --no-agent skips them. It also makes the build board (.himi/studio/, exactly as `himi studio --no-open` does \u2014 it never opens a browser), so every later render, test, validate, push and progress entry refreshes it; and, for a template bundled with this CLI, it makes the FIRST PICTURE itself (never for a --remote one: that is its publisher's code, and a render runs it on this machine, so next[] leaves the render to the agent after it has read the code): exactly `himi render <homeScreenId> --png --device iphone` (offline \u2014 no request; never --live), run as a child it kills after a time budget, writing .himi/shots/<home>.png and journaling it (by: himi) as the STARTING POINT (\"Starting point: the <template> template, before any of your changes\") so the board and the journal hold a picture when init returns. init also records the source it wrote (.himi/init-source.json: a content fingerprint, Markdown left out), and the first home PNG render after the source diverges from it is journaled once as \"First look at your app\" \u2014 reported as `firstLook: {image}` or `{skipped: <reason>}`, never a failed init; `--no-first-look` skips it (for scripts and CI that render themselves). For a bundled template (never a --remote one \u2014 the preview would build and serve its publisher's code) init also STARTS the live preview itself: `himi run --watch --content <dir> --port <p> --no-open --idle-exit 30`, detached in its own process group with its output in .himi/watch.log, never waited for (the url is chosen before it builds), recorded in .himi/watch.json, and reported as `watch: {started, pid, url, board, log}` \u2014 or `{skipped: <reason>}` for a `CI` environment, a test runner, a watch already running, or a start that failed (never a failed init). The envelope LEADS with `doNow`: three lines to run now (the consent preflight, the live preview \u2014 its url and board when init started it, else `himi run --watch --content <dir> &` with the board's path \u2014 and the postcard line), and ENDS with the same lines as `doNowAgain`, so an agent that reads only the head or only the tail (`| tail`) gets them; at a terminal they are also printed as plain text on stderr, before the JSON and again after it. Its `next[]` LEADS with the steps for THIS session, in this order for every app \u2014 the offline first render and that picture's `himi progress` postcard (only when init could not make the picture), the skill's consent preflight (authorize or mock any Wix or external startup request; with none, go straight on), and only then the live preview (the running one's url, or `himi run --watch &` when init did not start it) \u2014 ahead of the next-session guidance", required: "<name> [--dir] [--template <t>] [--remote] [--no-agent] [--no-first-look] | --templates" },
|
|
73154
|
+
{ name: "init", summary: "scaffold a starter content package (OTA-only; no native shells). --template <t> starts from a full reference app (branded-lite = consumer storefront over a Wix site; owner-lite = owner CRUD/management) instead of the minimal counter; --templates lists bundled ones; --remote fetches the template off the serve plane (falls back to the bundled copy offline). The new app also gets its agent files (the same set `himi agent-init` writes: AGENTS.md, CLAUDE.md, the himalaya skill, HIMALAYA.md, a SessionStart hook) so the next session started inside it picks the work up; --no-agent skips them. It also makes the build board (.himi/studio/, exactly as `himi studio --no-open` does \u2014 it never opens a browser), so every later render, test, validate, push and progress entry refreshes it; and, for a template bundled with this CLI, it makes the FIRST PICTURE itself (never for a --remote one: that is its publisher's code, and a render runs it on this machine, so next[] leaves the render to the agent after it has read the code): exactly `himi render <homeScreenId> --png --mocks --device iphone` (offline \u2014 no request; never --live; --mocks loads the app's recorded data when it has any), run as a child it kills after a time budget, writing .himi/shots/<home>.png and journaling it (by: himi) as the STARTING POINT (\"Starting point: the <template> template, before any of your changes\") so the board and the journal hold a picture when init returns. init also records the source it wrote (.himi/init-source.json: a content fingerprint, Markdown left out), and the first home PNG render after the source diverges from it is journaled once as \"First look at your app\" \u2014 reported as `firstLook: {image}` or `{skipped: <reason>}`, never a failed init; `--no-first-look` skips it (for scripts and CI that render themselves). For a bundled template (never a --remote one \u2014 the preview would build and serve its publisher's code) init also STARTS the live preview itself: `himi run --watch --content <dir> --port <p> --no-open --idle-exit 30`, detached in its own process group with its output in .himi/watch.log, never waited for (the url is chosen before it builds), recorded in .himi/watch.json, and reported as `watch: {started, pid, url, board, log}` \u2014 or `{skipped: <reason>}` for a `CI` environment, a test runner, a watch already running, or a start that failed (never a failed init). Nothing else skips it: outside CI it is started, and `watch.note` says an opt-out was ignored. The envelope LEADS with `doNow`: three lines to run now (the consent preflight, the live preview \u2014 its url and board when init started it, else `himi run --watch --content <dir> &` with the board's path \u2014 and the postcard line), and ENDS with the same lines as `doNowAgain`, so an agent that reads only the head or only the tail (`| tail`) gets them; at a terminal they are also printed as plain text on stderr, before the JSON and again after it. Its `next[]` LEADS with the steps for THIS session, in this order for every app \u2014 the offline first render and that picture's `himi progress` postcard (only when init could not make the picture), the skill's consent preflight (authorize or mock any Wix or external startup request; with none, go straight on), and only then the live preview (the running one's url, or `himi run --watch &` when init did not start it) \u2014 ahead of the next-session guidance", required: "<name> [--dir] [--template <t>] [--remote] [--no-agent] [--no-first-look] | --templates" },
|
|
72956
73155
|
{ name: "template", summary: "starter-template catalog + publishing. Every template is a SHOWCASE APP \u2014 a real published release you can open running before you start from it. `list` prints available templates with metadata (title/description/tags) plus each one's start prompt \u2014 pass --remote to include the serve-plane catalog so an agent can pick one; `prompt <name>` prints just that sentence for one template (the skill URL is the public door, or the plane named by --target/--serve-url); `publish` publishes an app AS a `himi init --template` (needs a himalaya.template.json; builds a previewable release + attaches the scaffold source; --source-dir overrides which tree becomes the source, --name sets the template name; bakes a launcher icon \u2014 --icon <png>, or the committed template-icons/<name>.png \u2014 and extracts accent/palette gallery meta from the scaffold's tokens.json; --visibility unlisted keeps it out of the /v1/templates catalog \u2014 templates default to public); `trim --keep <ids>` cuts an app scaffolded from a template down to those screens (the home, and any screen the navigation pins such as a drawer body, always stays) \u2014 it deletes the other SCREENS/TEMPLATED entries whole, prunes navigation (linkableScreens, tabBar, drawer, deep links, surfaces) and drops tier5-src workers no kept screen uses, then runs validate --strict and rolls everything back if it fails (--force keeps it, --dry-run writes nothing). A kept screen that links to a removed one is refused unless --links follow (keep those too) or --links drop (remove the taps)", required: "list [--remote] | prompt <name> | publish [--dir <app>] [--source-dir <dir>] [--name <n>] [--icon <png>] [--visibility public|unlisted] [--target <t>] | trim --keep <id,id,\u2026> [--content <dir>] [--links follow|drop] [--dry-run] [--force]" },
|
|
72957
73156
|
{ name: "build", summary: "compile tier5-src/<name>/index.ts -> dist/bundles/<name>.bundle.js with esbuild (--binary = resolve published @himalaya/*); semantic TypeScript checking belongs to himi validate", required: "[--content <dir>] [--binary]" },
|
|
72958
73157
|
{ name: "functions", summary: "inspect/invoke server functions and operate durable jobs or schedules", required: "list|describe|openapi|invoke | jobs list|get|cancel|retry|dlq | schedule describe|invoke [--app <a>] [--environment preview|production] [--release <id>]" },
|
|
72959
73158
|
{ name: "eject", summary: "take ownership of a shared flow module (auth|checkout|screens): copies its source into tier5-src/_ejected/ and repoints this package's imports at the copy. The ladder's escape hatch \u2014 for when the factory options cannot express what you need. AFTER EJECTING, CENTRAL FIXES NO LONGER REACH THIS APP, including future corrections to the fail-closed entitlement and confirm-the-session rules. The floor is unaffected: credentials still go only through the native auth actions and money only through the cashier rail.", required: "<auth|checkout|screens> [--content <dir>]" },
|
|
72960
|
-
{ name: "validate", summary: 'lint the content package (no push): fixed-contract TypeScript checking for tier5-src workers + schema/proto + token refs + component allowlist + $service bindings + strict-enum foot-guns + capability decls + worker boot dry-run + build/compile failures. Type issues are advisory warnings normally; --strict promotes them and hygiene warnings to errors. Exit 1 on any error. Off-repo, the CLI supplies its compiler and SDK declarations. A PASSING validate after a source change also refreshes the home picture: exactly `himi render <homeScreenId> --png --device iphone` (offline, never --live), run as a child it kills after 30 seconds, writing .himi/shots/<home>.png \u2014 whose own postcard logic then journals "First look at your app" or, after ten minutes of postcard silence, "Still building" \u2014 reported as `homePicture: {image}` (with `postcard` when one was journaled) or `{skipped: <reason>}`, never a failed validate. Skipped for a validate with errors, a home shot already current, CI or a test runner, and code nobody asked to run: a --remote template still unchanged since init.', required: "[--content <dir>] [--strict]" },
|
|
73159
|
+
{ name: "validate", summary: 'lint the content package (no push): fixed-contract TypeScript checking for tier5-src workers + schema/proto + token refs + component allowlist + $service bindings + strict-enum foot-guns + capability decls + worker boot dry-run + build/compile failures. Type issues are advisory warnings normally; --strict promotes them and hygiene warnings to errors. Exit 1 on any error. Off-repo, the CLI supplies its compiler and SDK declarations. A PASSING validate after a source change also refreshes the home picture: exactly `himi render <homeScreenId> --png --mocks --device iphone` (offline, never --live; with recorded data \u2014 dev/net-mocks.json or cms seed rows \u2014 the picture shows it, and without, its postcard calls it an offline preview), run as a child it kills after 30 seconds, writing .himi/shots/<home>.png \u2014 whose own postcard logic then journals "First look at your app" or, after ten minutes of postcard silence, "Still building" \u2014 reported as `homePicture: {image}` (with `postcard` when one was journaled) or `{skipped: <reason>}`, never a failed validate. Skipped for a validate with errors, a home shot already current, CI or a test runner, and code nobody asked to run: a --remote template still unchanged since init.', required: "[--content <dir>] [--strict]" },
|
|
72961
73160
|
{ name: "test", summary: "DERIVED behavioral crawl \u2014 no test files to write. Boots each screen's real worker, replays its onAppear, then dispatches every action site the descriptor already declares (each from a fresh boot) and checks a narrow set of unambiguous invariants: no uncaught throw, never stuck on `loading`, no `$service:` token or NaN/undefined/[object Object] reaching visible text, no binding aimed at state the worker never defines, no nav param silently dropped, no dead nav target. Catches what validate/render cannot \u2014 a WRONG REDUCER passes both with ok:true. Runs the worker in a DEVICE-PARITY sandbox (no setTimeout/setInterval, as on JSC/WebView) so a bundle that would die on device fails here. Crawls on HERMETIC DATA by default \u2014 zero network: the app's own dev/net-mocks.json first (--net <file> overrides), then the config's CMS `seed:` rows, then a canned fixture of frozen real vertical responses shipped with the CLI (exercises code paths, not API-contract truth) \u2014 so the success/mapping path runs, not just the failure UI. Two boot-only HOSTILE passes (every list empty; every request 500) then check the failure/empty day \u2014 corpus-proven, so their findings are errors. NAV EDGES are followed too: every himi.open site ($item params resolved against row 0 of its list, $service against worker state) and worker _nav directive is harvested, and each baked target is booted WITH the params the source sends \u2014 detail screens finally run their content arm; edge findings are errors tagged edge:<source>. --native runs both real native decoders over the exact baked JSON bytes; --native ios or --native android selects one. It is explicit, runs only from a Himalaya SOURCE CHECKOUT (the decoder oracles are that tree's own HimalayaCore Swift package and Gradle harness, which a published install does not ship), and needs the selected local toolchain. From a source checkout, default runs name missing native verdicts in the coverage ledger; elsewhere the ledger stays silent about them rather than offering a remedy you cannot run. --offline strips all data layers (the old reject-all axis); --no-hostile skips the hostile passes. --record (EXPLICIT, never automatic) refreshes dev/net-mocks.json from live reads first: it boots each screen (load + root onAppear ONLY \u2014 nothing the app's own launch would not fire; the tap/action crawl never sees the live transport, and only proven reads \u2014 GET, or read-shaped POST (/query, /search, /list*, \u2026) \u2014 ever reach the wire), freezes the round-trips, then crawls hermetically on the fresh fixture; record against a QA site, not production. Nodes it cannot judge headless are named, never silently passed. Exit 1 on any error. The CLI carries the authoring runtime off-repo; no per-app SDK install is required. --browser runs the whole loop (build \u2192 extract \u2192 validate \u2192 render \u2192 test) in YOUR browser instead of here, so the source never reaches a pod: it opens one screen's worker + descriptor in a tab, blocks on a loopback callback, and prints the report it posts back. One screen per tab \u2014 name it. --no-open prints the URL rather than launching a browser (SSH); --timeout <ms> bounds the wait, which matters because the page has no execution deadline of its own and an infinite loop in the worker hangs the tab.", required: "[<screenId>\u2026] [--content <dir>] [--net <file>] [--budget <ms>] [--deep [n]] [--record] [--native [ios|android]] [--no-shots] [--offline] [--no-hostile] [--all-screens] [--browser] [--no-open] [--timeout <ms>] [--json]" },
|
|
72962
73161
|
{ name: "ux", summary: "audit or record mobile UX evidence. `audit` checks the public-safe contract, seeded blank acceptance rows, placeholder source copy, app-specific mocks, and iOS/Android audit records. It warns while existing templates migrate; `--strict` promotes missing evidence. `record` stores checks performed on an installed iOS/Android build: `--checks` is required for passed|failed and empty for unavailable, which drove nothing and needs a `--reason` instead. `record` also STAMPS the release it audited (it resolves the id from a build, the same one a push would publish), because an unforced `himi push` of a release with screens refuses when no drive is recorded for THAT release, or when the one that is recorded says `failed` \u2014 the artifact stopped being advice nobody reads (#2955). `unavailable` still satisfies it; only `failed` refuses.", required: "audit [--content <dir>] [--strict] [--json] | record --content <dir> --platform ios|android --status passed|failed|unavailable [--checks back,keyboard,...] [--reason <text>]" },
|
|
72963
|
-
{ name: "progress", summary: 'the build JOURNAL an agent keeps for the owner while it works. `himi progress "<what changed>" --step <1-8|name> --image <png>` appends one postcard to .himi/progress.jsonl \u2014 the step is one of the customer journey\'s eight (discover, classify, plan, implement, brand, validate, review, download) \u2014 and COPIES each image, content-addressed, into .himi/history/, because .himi/shots/ is overwritten by every render and cleared by every full `himi test`, so a path into it would later show a different picture than the one the owner was sent. --image repeats. With NO --image, the home screen\'s current shot is attached automatically, only when it is no older than the last source change and not the picture the latest journaled postcard already shows \u2014 except on the HANDOVER card (`--step review`), where a repeat is the point and it attaches anyway; that card\'s output also names the making-of (`makingOf`, .himi/making-of.html, rebuilt when missing or older than the journal so it ends on this card) and its `next` says to hand it to the owner beside the preview link; `imageAttached` in the output says whether the postcard carries a picture (with `imageNote` saying why not), and `--no-image` sends a text-only postcard. --notify also raises a best-effort desktop notification for an owner who stepped away. A bare `himi progress` prints the journal with the current step and elapsed time \u2014 also how an agent recovers its place after its context was compacted. APPROVALS: `--approve <gate> "<what>"` records one the customer just gave (gates: site-create, connect, private-data, draft, promote, push-signing, qa-build) in .himi/approvals.jsonl, and `--done <gate>` / `--drop <gate>` closes it \u2014 an approval is owed until then, and the chat that held it does not survive a new session. `--brief` prints plain text for a session start: the current step, the last postcard, every open approval, and whether this app\'s agent files are stale \u2014 the SessionStart hook `himi agent-init` installs runs exactly this. The CLI also journals milestones by itself \u2014 the first PNG render of the home screen (for an app a current `himi init` started: once as the template\'s "Starting point" while the source is what init wrote, then once as "First look at your app" after it diverges, Markdown edits not counting; once per app otherwise), a `himi test` crawl whose outcome changed since the last one (its counts or its home picture; an identical re-run writes none, and a repeat names what changed: "The home screen changed since the last crawl." or the changed counts, was \u2192 now), every draft push (with the home picture only when that shot is newer than the last source edit \u2014 otherwise the postcard says why and names `himi render <home> --png`), and ONE rule for a long silence: a successful home `render --png` more than ten minutes after the newest postcard of any kind, with a picture the latest postcard does not show, journals "Still building: here\'s the home screen now." with it (never twice within the window). When a crawl\'s or a draft\'s home picture is the first of an app whose source diverged, "First look at your app" is journaled first, from that shot, once. All are marked `"by": "himi"`; they never move the journey step, carry no link or credential, and step aside (folded out of every reader) when the agent journals the same step or picture within five minutes. Local only: nothing is sent anywhere.', required: '["<message>" [--step <1-8|name>] [--image <png>]\u2026 [--no-image] [--notify]] [--content <dir>] | --approve|--done|--drop <gate> ["<note>"] | --brief' },
|
|
73162
|
+
{ name: "progress", summary: 'the build JOURNAL an agent keeps for the owner while it works. `himi progress "<what changed>" --step <1-8|name> --image <png>` appends one postcard to .himi/progress.jsonl \u2014 the step is one of the customer journey\'s eight (discover, classify, plan, implement, brand, validate, review, download) \u2014 and COPIES each image, content-addressed, into .himi/history/, because .himi/shots/ is overwritten by every render and cleared by every full `himi test`, so a path into it would later show a different picture than the one the owner was sent. --image repeats. With NO --image, the home screen\'s current shot is attached automatically, only when it is no older than the last source change and not the picture the latest journaled postcard already shows \u2014 and never a shot that landed on loading placeholders, an empty or an error state (.himi/shot-states.json): then the newest home picture of the current source that shows content goes on instead (its .himi/history copy, `imageNote` saying so), or none \u2014 except on the HANDOVER card (`--step review`), where a repeat is the point and it attaches anyway; that card\'s output also names the making-of (`makingOf`, .himi/making-of.html, rebuilt when missing or older than the journal so it ends on this card) and its `next` says to hand it to the owner beside the preview link; `imageAttached` in the output says whether the postcard carries a picture (with `imageNote` saying why not, or which picture went on instead), and `--no-image` sends a text-only postcard. --notify also raises a best-effort desktop notification for an owner who stepped away. A bare `himi progress` prints the journal with the current step and elapsed time \u2014 also how an agent recovers its place after its context was compacted. APPROVALS: `--approve <gate> "<what>"` records one the customer just gave (gates: site-create, connect, private-data, draft, promote, push-signing, qa-build) in .himi/approvals.jsonl, and `--done <gate>` / `--drop <gate>` closes it \u2014 an approval is owed until then, and the chat that held it does not survive a new session. `--brief` prints plain text for a session start: the current step, the last postcard, every open approval, and whether this app\'s agent files are stale \u2014 the SessionStart hook `himi agent-init` installs runs exactly this. The CLI also journals milestones by itself \u2014 the first PNG render of the home screen (for an app a current `himi init` started: once as the template\'s "Starting point" while the source is what init wrote, then once as "First look at your app" after it diverges, Markdown edits not counting; once per app otherwise), a `himi test` crawl whose outcome changed since the last one (its counts or its home picture; an identical re-run writes none, and a repeat names what changed: "The home screen changed since the last crawl." or the changed counts, was \u2192 now), every draft push (with the home picture only when that shot is newer than the last source edit \u2014 otherwise the postcard says why and names `himi render <home> --png`), and ONE rule for a long silence: a successful home `render --png` more than ten minutes after the newest postcard of any kind, with a picture the latest postcard does not show, journals "Still building: here\'s the home screen now." with it (never twice within the window). When a crawl\'s or a draft\'s home picture is the first of an app whose source diverged, "First look at your app" is journaled first, from that shot, once. All are marked `"by": "himi"`; they never move the journey step, carry no link or credential, and step aside (folded out of every reader) when the agent journals the same step or picture within five minutes. Local only: nothing is sent anywhere.', required: '["<message>" [--step <1-8|name>] [--image <png>]\u2026 [--no-image] [--notify]] [--content <dir>] | --approve|--done|--drop <gate> ["<note>"] | --brief' },
|
|
72964
73163
|
{ name: "studio", summary: "open the BUILD BOARD \u2014 one local page the owner can leave open while an agent builds their app, which fills itself in: every screen as it renders (NEW/CHANGED badges, and each earlier version kept for a filmstrip), the journey stepper and the latest postcard from `himi progress`, the validate/test/UX/draft checks as they run, the site next to the app's home screen with its palette, and the navigation the test crawl followed. Writes .himi/studio/index.html and opens it; the page re-reads its state every two seconds, so nothing needs to be served. OPT-IN BY EXISTENCE: until this command (or `himi init`, which makes the board for every app it creates, without opening a browser) has run, no other command touches .himi/studio/ \u2014 after it, render/test/validate/push/icon/site/ux/progress each refresh the board as they finish (best-effort; never fails a command). Every image is a content-addressed copy in .himi/history/, never a live path into .himi/shots/. Local only, no secrets: it never reads a preview token, handover code or control token. --no-open prints the path instead (SSH, CI). `himi studio reel` writes the MAKING-OF: one self-contained HTML file (every image inlined, nothing fetched) with a flipbook of the home screen version by version, captioned by the postcard in effect at the time, the final screens, and every postcard \u2014 sized to stay attachable, thinning frames first. Defaults to .himi/making-of.html (never archived by push), which every successful `himi push --draft` also rebuilds and names as `reel` in its output; --out to put it elsewhere. The output names that file as the one to hand the owner (`handover: {file, hint}` in JSON; two plain lines at a terminal) \u2014 beside the preview link, never instead of it.", required: "[--content <dir>] [--no-open] | reel [--out <file>] [--no-open]" },
|
|
72965
|
-
{ name: "render", summary: "headless-render a screen with the web renderer: render-tree + diagnostics, optional --png [<file>] (bare: .himi/shots/<id>.png, what the build board shows), --scheme dark, --tree. Local by default (content dir). Pass --remote --app --release to hit GET /v1/preview/\u2026/render on the serve plane after a push. Boots the worker so $service bindings resolve; --static raw bindings; --live real Wix fetches; --state injects worker state; --input supplies pre-boot navigation params; --width/--device for PNG viewport; --rtl for dir=rtl + $env:isRTL.", required: "<screenId> [--content <dir>] [--scheme dark] [--png [<file>]] [--tree] [--static] [--live] [--state <json|file>] [--input <json|file>] [--width <px> | --device <name>] [--rtl] [--remote --app --release]" },
|
|
73164
|
+
{ name: "render", summary: "headless-render a screen with the web renderer: render-tree + diagnostics, optional --png [<file>] (bare: .himi/shots/<id>.png, what the build board shows), --scheme dark, --tree. Local by default (content dir). Pass --remote --app --release to hit GET /v1/preview/\u2026/render on the serve plane after a push. Boots the worker so $service bindings resolve; --static raw bindings; --live real Wix fetches; --mocks runs load() against the app's own recorded data (dev/net-mocks.json from `himi test --record`, then the config's cms seed rows \u2014 hermetic, never a request; with neither it paints the initial state, and `mocks: {used: false}` says why), which is what the automatic pictures use, so they show the app's content instead of its offline placeholders; --state injects worker state; --input supplies pre-boot navigation params; --width/--device for PNG viewport; --rtl for dir=rtl + $env:isRTL.", required: "<screenId> [--content <dir>] [--scheme dark] [--png [<file>]] [--tree] [--static] [--live] [--mocks] [--state <json|file>] [--input <json|file>] [--width <px> | --device <name>] [--rtl] [--remote --app --release]" },
|
|
72966
73165
|
{ name: "cms", summary: "inspect, provision, and seed the app's CMS (Wix Data) collections, declared as a `cms` block in config.ts. `query` is READ-ONLY \u2014 by default it fetches ONLY the physical ids `config.ts` declares (item counts + schema drift) and reports `siteScanned: false`; it never lists the site, so an UNDECLARED `himi_<app>_` collection this app owns is not reached without `--all`. `--all` SCANS the site \u2014 every collection it lists is labeled `owned`/`adopted`/`foreign`, and it describes every foreign one \u2014 id, non-system field keys + types, and the site's read role \u2014 so an app with no `cms` block at all can DISCOVER a collection to adopt (`--all` is the one case where a missing `cms` block is not 'nothing to do'). Foreign collections are described up to a cap and are never item-counted (`itemCount: null`), because a row count is one query each; `foreign.count` is how many the scan LISTED \u2014 it follows the list endpoint's pages, and `listingIncomplete: true` says when the walk stopped short of the end, with `unlistedCollections` counting what it left out when Wix gave a paging total \u2014 and `foreign.truncated` says how many of those went undescribed, each still NAMED in `foreign.others` (id + displayName), so a collection the site already has is never hidden behind the cap. The scan is OWNER-scoped \u2014 it needs --site <msid> (or WIX_SITE_ID) plus owner auth via `wix token`; a visitor token can read a public collection's ITEMS but can never enumerate collections, so there is no tokenless discovery path. `create` reconciles declared collections additively and idempotently, and REFUSES any id outside the app's `himi_<app>_` namespace, so it is safe against a production site. It reports declared-vs-site `permissionDrift` but never writes permissions unless asked with --apply-permissions, so a field reconcile can't silently revert access control changed in the dashboard. `seed` inserts the git-tracked `seed:` rows into EMPTY owned collections (--force to add to a non-empty one; --ai writes placeholder rows plus a refinement prompt for you to review). Adopted collections (declared by `wixId`) are read-only and never created, updated, or seeded. Site from --site <msid> or WIX_SITE_ID; owner auth via `wix token`.", required: "query|create|seed [--site <msid>] [--content <dir>] [--all] [--dry-run] [--force] [--allow-destructive] [--apply-permissions] [--only <id>] [--ai] [--count <n>]" },
|
|
72967
73166
|
{ name: "data", summary: "read-only plan or explicitly provision/seed declared KV and Wix Data resources. Preview and production are separate scopes. KV administration uses the operator data plane from --data-url/HIMI_DATA_ADMIN_URL and HIMI_DATA_ADMIN_TOKEN; draft push never calls it.", required: "plan|create|seed [--site <msid>] [--environment preview|production] [--content <dir>] [--data-url <url>] [--dry-run] [--force]" },
|
|
72968
73167
|
{ name: "icons", summary: "list the valid Icon.name vocabulary (an unknown name renders a fallback glyph; validate warns on unknown names) (offline)", required: "[--json]" },
|
|
@@ -72973,7 +73172,7 @@ var COMMANDS = [
|
|
|
72973
73172
|
{ 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 URL/browser and never uploads or applies anything. `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>] [--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]" },
|
|
72974
73173
|
{ 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>]" },
|
|
72975
73174
|
{ 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]" },
|
|
72976
|
-
{ name: "push", summary: "lint + build + publish a release (default --draft: servable via ?release= but NOT live). REFUSES to publish on any error unless --force; --strict promotes warnings. Prints preview + agent-context links. A successful draft push also rebuilds the making-of (.himi/making-of.html, local and offline) and names it as `reel` \u2014 the file to hand the owner beside the preview link. Its postcard carries the home shot only when that shot is newer than the last source edit; otherwise the postcard says why and names `himi render <home> --png`, and the envelope's `postcardPicture: {skipped, render}` leads next[] with that render (the push never renders it itself). The published slug is NAMESPACED per owner \u2014 `<ownerPrefix>.<name>`, composed offline from the control token's ownerPrefix claim, so the CLI and the server derive the identical string and himalaya.content.json is NOT rewritten (a legacy token with no ownerPrefix publishes the declared name unchanged). An app claimed BEFORE namespacing is stored under its FLAT slug, so the claim route answers `alreadyYours` naming that slug and the push adopts it \u2014 reported as `adoptedSlug`, since it is the one case where the published `app` differs from what the declared name composes to. `--no-claim` skips the route that knows this, so a pre-namespacing app pushed with it still fails 403. --visibility public|unlisted decides whether /v1/apps + the Himi Player picker LIST the app (also declarable as `visibility` in himalaya.content.json; omitted, a brand-new app defaults to unlisted \u2014 share by link \u2014 and an existing setting is kept). --account-id names which account owns the app when your Wix session resolves to several (the auto-claim of a brand-new slug cannot guess \u2014 without it a multi-account caller is told to name one). An unforced push of a release that carries SCREENS also refuses when `.himi/ux-audit.json` holds no mobile-UX drive recorded against that release id, or holds one that FAILED (#2955) \u2014 a published draft IS the owner-private preview a customer is handed; `himi ux record` clears it and `--force` overrides it, which the success envelope then says out loud. `--only <selector>` publishes ONLY the named items as a release derived from the app's live release (or `--from-release <id>`), running the identical build + lint gate and simply publishing less; it requires an explicit `--draft` or `--live` because a partial publish has no safe default, never claims a slug, and carries neither attestation nor a source archive (both named in the output). Selectors are comma-separated in ONE `--only`: `changed` (every item whose bytes differ from the base), `screen:<id|glob>`, `bundle:<name>`, `tokens`, `app-config` \u2014 a selected screen drags its logic bundle and a selected bundle drags the screens that reference it, or the bytes land under a key nothing reads.", required: "[--content <dir>] [--draft] [--live] [--force] [--strict] [--label] [--notes] [--visibility public|unlisted] [--no-claim] [--account-id <id>] [--only <selector>] [--from-release <id>] [--accept-regressions] [--accept-partial] [--app <slug>] (--control-token)" },
|
|
73175
|
+
{ name: "push", summary: "lint + build + publish a release (default --draft: servable via ?release= but NOT live). REFUSES to publish on any error unless --force; --strict promotes warnings. Prints preview + agent-context links. A successful draft push also rebuilds the making-of (.himi/making-of.html, local and offline) and names it as `reel` \u2014 the file to hand the owner beside the preview link. Its postcard carries the home shot only when that shot is newer than the last source edit; a repeat draft's postcard adds what changed since the last one (\"Since the last draft: the home screen changed.\", from the release manifests' screen hashes, remembered in .himi/last-draft.json); otherwise the postcard says why and names `himi render <home> --png`, and the envelope's `postcardPicture: {skipped, render}` leads next[] with that render (the push never renders it itself). The published slug is NAMESPACED per owner \u2014 `<ownerPrefix>.<name>`, composed offline from the control token's ownerPrefix claim, so the CLI and the server derive the identical string and himalaya.content.json is NOT rewritten (a legacy token with no ownerPrefix publishes the declared name unchanged). An app claimed BEFORE namespacing is stored under its FLAT slug, so the claim route answers `alreadyYours` naming that slug and the push adopts it \u2014 reported as `adoptedSlug`, since it is the one case where the published `app` differs from what the declared name composes to. `--no-claim` skips the route that knows this, so a pre-namespacing app pushed with it still fails 403. --visibility public|unlisted decides whether /v1/apps + the Himi Player picker LIST the app (also declarable as `visibility` in himalaya.content.json; omitted, a brand-new app defaults to unlisted \u2014 share by link \u2014 and an existing setting is kept). --account-id names which account owns the app when your Wix session resolves to several (the auto-claim of a brand-new slug cannot guess \u2014 without it a multi-account caller is told to name one). An unforced push of a release that carries SCREENS also refuses when `.himi/ux-audit.json` holds no mobile-UX drive recorded against that release id, or holds one that FAILED (#2955) \u2014 a published draft IS the owner-private preview a customer is handed; `himi ux record` clears it and `--force` overrides it, which the success envelope then says out loud. `--only <selector>` publishes ONLY the named items as a release derived from the app's live release (or `--from-release <id>`), running the identical build + lint gate and simply publishing less; it requires an explicit `--draft` or `--live` because a partial publish has no safe default, never claims a slug, and carries neither attestation nor a source archive (both named in the output). Selectors are comma-separated in ONE `--only`: `changed` (every item whose bytes differ from the base), `screen:<id|glob>`, `bundle:<name>`, `tokens`, `app-config` \u2014 a selected screen drags its logic bundle and a selected bundle drags the screens that reference it, or the bytes land under a key nothing reads.", required: "[--content <dir>] [--draft] [--live] [--force] [--strict] [--label] [--notes] [--visibility public|unlisted] [--no-claim] [--account-id <id>] [--only <selector>] [--from-release <id>] [--accept-regressions] [--accept-partial] [--app <slug>] (--control-token)" },
|
|
72977
73176
|
{ name: "pack", summary: 'write the app as a PORTABLE .himi package \u2014 one self-contained file holding every screen, Tier-5 bundle, token, asset and the app-config. Packages are live by default: they boot bundled content offline, then Himi Player follows its baked-in production ring. `--snapshot` makes a frozen reproducible artifact. Package-supplied origins are never trusted. --format dir writes the same tree unpacked, for static hosting. --release-dir packs a release that is already built on disk (needs --app) instead of rebuilding from source. `--dev` ALSO packs the app\'s own source archive into the container (`source.zip`, header `contents: "dev"`), so the package can be handed to someone who then keeps building \u2014 collected through the same deny-list and secret scan `himi push` uses, and refused if a credential is found. A dev package IS the codebase: it is never the default, it is always labelled in the output, the web host will not open one, and it is archive-only (`--dev --format dir` is refused, because a reader that predates the source entry drops it before hashing a directory and would call the package tampered). Restore one with `himi import`.', required: "[--content <dir>] [--out <file|dir>] [--format zip|dir] [--name <title>] [--snapshot] [--dev] [--strict] [--force] | --release-dir <dir> --app <a>" },
|
|
72978
73177
|
{ name: "import", summary: "turn a `himi pack --dev` package back into an EDITABLE content package \u2014 the file counterpart of `himi pull`, needing no serve plane, no account and no network. Refuses an invalid package before trusting its source (the content hash is recomputed over every entry on open, so the archive is provably the one that was packed), and refuses a RELEASE package by naming the absence \u2014 it carries built output and no source \u2014 rather than reporting a missing file. `--name` re-substitutes the app identity through the same machinery `himi init --template` and `himi pull --name` use, so the restored tree is a NEW app rather than a second copy of the same one, and a name that also appears inside a screen id (`feed` in `feed_home`) is left alone. A non-empty destination needs `--force` and a merge NAMES what it kept (`left: [...]`), the same contract `himi pull --force` has; a shape clash is refused whole before anything is written. There is no `--replace`: a file you were handed is not the plane's per-release archive.", required: "<package.himi> [--out <dir>] [--name <new>] [--force]" },
|
|
72979
73178
|
{ name: "pull", summary: "restore an app's SOURCE from the serve plane into an editable content package \u2014 the other half of `himi push`, which archives the content package on every publish. Owner or editor only: a viewer may download the RUNNING app (`/v1/owner/package`), not the codebase it was built from. A PAST release is restorable, not only the live one \u2014 the archive is content-addressed per release, so it has none of the live-single-slot problem that makes `himi pack` current-release-only. Two limits: pruning keeps the current release plus the newest ~20 ledger entries, and a release id identifies OTA content rather than source, so a source-only change republishes under the same id and replaces that id's archive. A release published before source archiving shipped, or pushed with `--no-source`, answers `app_source_not_found` and says to push again. `--name` re-substitutes the app identity through the same machinery `himi init --template` uses, so the restored tree is a NEW app rather than a second copy of the same one, and a name that also appears inside a screen id (`feed` in `feed_home`) is left alone. A NON-EMPTY destination is refused unless you say which you mean: `--force` merges and reports \u2014 it writes the archive over what is there, keeps files the release does not carry, and NAMES every one of them as `left: [...]` so a tree that matches no release is never silent (#2437); `--replace` restores exactly what was archived, removing those files first (`removed: [...]`) and implying `--force`. `--replace` refuses a directory holding no `himalaya.content.json`, so a mistyped `--out` cannot clear an unrelated tree, and the only paths it can remove are the ones a push would have archived \u2014 `.git/`, `node_modules/`, `dist/`, `.env*` and key material are out of its reach by construction.", required: "<app> [--release <id>] [--out <dir>] [--name <new>] [--force|--replace] [--target <t>]" },
|
|
@@ -74163,7 +74362,7 @@ function defaultTargetForCommand(command, authoringDir) {
|
|
|
74163
74362
|
return isHimalayaMonorepoCheckout(authoringDir) ? void 0 : "wix";
|
|
74164
74363
|
}
|
|
74165
74364
|
function renderDataSource(input) {
|
|
74166
|
-
if (!input.live) return input.hydrate ? "worker-initial-state" : "static";
|
|
74365
|
+
if (!input.live) return input.mocked ? "app-mocks" : input.hydrate ? "worker-initial-state" : "static";
|
|
74167
74366
|
if (!input.liveCalls) return "static";
|
|
74168
74367
|
if (input.liveCalls.length === 0) return "worker-load-no-network";
|
|
74169
74368
|
return (input.liveReturned ?? 0) > 0 ? "wix-network" : "wix-network-failed";
|
|
@@ -74645,6 +74844,7 @@ async function loadAuthoring() {
|
|
|
74645
74844
|
webWixCall: wml.webWixCall,
|
|
74646
74845
|
freezeTrafficToMocks: nf.freezeTrafficToMocks,
|
|
74647
74846
|
bootAndSnapshotState: crawl.bootAndSnapshotState,
|
|
74847
|
+
isUnservedRequest: crawl.isUnservedRequest,
|
|
74648
74848
|
renderDescriptorWeb: rd.renderDescriptorWeb,
|
|
74649
74849
|
renderPngBatch: rd.renderPngBatch,
|
|
74650
74850
|
pngAvailable: rd.pngAvailable,
|
|
@@ -75868,8 +76068,10 @@ ${handover.hint[0].toUpperCase()}${handover.hint.slice(1)}.`);
|
|
|
75868
76068
|
const handover = step === "review";
|
|
75869
76069
|
if (!images.length && flags["no-image"] !== true && isContentPackage) {
|
|
75870
76070
|
const auto = (await Promise.resolve().then(() => (init_milestones(), milestones_exports))).homeShotForPostcard(dir, { repeatOk: handover });
|
|
75871
|
-
if ("image" in auto)
|
|
75872
|
-
|
|
76071
|
+
if ("image" in auto) {
|
|
76072
|
+
images.push(auto.image);
|
|
76073
|
+
if (auto.note) imageNote = auto.note;
|
|
76074
|
+
} else imageNote = `no picture attached: ${auto.skipped}`;
|
|
75873
76075
|
}
|
|
75874
76076
|
try {
|
|
75875
76077
|
const entry = appendProgress2(dir, { message, ...step ? { step } : {}, images });
|
|
@@ -75877,7 +76079,7 @@ ${handover.hint[0].toUpperCase()}${handover.hint.slice(1)}.`);
|
|
|
75877
76079
|
const { count: count2 } = summarizeProgress2(readProgress2(dir).entries);
|
|
75878
76080
|
const imageAttached = (entry.images?.length ?? 0) > 0;
|
|
75879
76081
|
const handed = handover && isContentPackage ? (await Promise.resolve().then(() => (init_milestones(), milestones_exports))).afterHandoverPostcard(dir, { content: str9(flags.content) }) : null;
|
|
75880
|
-
print({ ok: true, entry, journal: journal2, count: count2, imageAttached,
|
|
76082
|
+
print({ ok: true, entry, journal: journal2, count: count2, imageAttached, ...imageNote ? { imageNote } : {}, ...notified === void 0 ? {} : { notified }, ...handed ? { makingOf: handed.makingOf, next: handed.next } : {}, ...hint });
|
|
75881
76083
|
return 0;
|
|
75882
76084
|
} catch (e) {
|
|
75883
76085
|
if (e instanceof ProgressError2) {
|
|
@@ -75954,7 +76156,7 @@ ${handover.hint[0].toUpperCase()}${handover.hint.slice(1)}.`);
|
|
|
75954
76156
|
return results.every((result2) => result2.ok) ? 0 : 1;
|
|
75955
76157
|
}
|
|
75956
76158
|
const app = await appId(dir);
|
|
75957
|
-
const { buildContentBundles: buildContentBundles2, buildReleaseFromContent: buildReleaseFromContent2, bundleOutDir: bundleOutDir2, crawlScreen: crawlScreen2, crawlScreenJourney: crawlScreenJourney2, crawlScreenHostile: crawlScreenHostile2, crawlScreenRelations: crawlScreenRelations2, crawlScreenCapabilitySuccess: crawlScreenCapabilitySuccess2, findingKey: findingKey2, startWorkerCoverage: startWorkerCoverage2, uncoveredFunctions: uncoveredFunctions2, crawlScreenDeep: crawlScreenDeep2, crawlSignature: crawlSignature2, recordScreenTraffic: recordScreenTraffic2, mintVisitor: mintVisitor2, webWixCall: webWixCall2, freezeTrafficToMocks: freezeTrafficToMocks2, bootAndSnapshotState: bootAndSnapshotState2, renderDescriptorWeb: renderDescriptorWeb2, renderPngBatch: renderPngBatch2, pngAvailable: pngAvailable2, crawlBlockingPasses } = await loadAuthoring();
|
|
76159
|
+
const { buildContentBundles: buildContentBundles2, buildReleaseFromContent: buildReleaseFromContent2, bundleOutDir: bundleOutDir2, crawlScreen: crawlScreen2, crawlScreenJourney: crawlScreenJourney2, crawlScreenHostile: crawlScreenHostile2, crawlScreenRelations: crawlScreenRelations2, crawlScreenCapabilitySuccess: crawlScreenCapabilitySuccess2, findingKey: findingKey2, startWorkerCoverage: startWorkerCoverage2, uncoveredFunctions: uncoveredFunctions2, crawlScreenDeep: crawlScreenDeep2, crawlSignature: crawlSignature2, recordScreenTraffic: recordScreenTraffic2, mintVisitor: mintVisitor2, webWixCall: webWixCall2, freezeTrafficToMocks: freezeTrafficToMocks2, bootAndSnapshotState: bootAndSnapshotState2, isUnservedRequest: isUnservedRequest2, renderDescriptorWeb: renderDescriptorWeb2, renderPngBatch: renderPngBatch2, pngAvailable: pngAvailable2, crawlBlockingPasses } = await loadAuthoring();
|
|
75958
76160
|
const { readTestConfig: readTestConfig2, runCrawl: runCrawl2, formatReport: formatReport2, readCannedResponses: readCannedResponses2, makeRecordingWixCall: makeRecordingWixCall2, runRecordPass: runRecordPass2, runShotsPass: runShotsPass2, defaultNetRules: defaultNetRules2, readNetRules: readNetRules2, scopeDescriptors: scopeDescriptors2, writeRecordedMocks: writeRecordedMocks2 } = await Promise.resolve().then(() => (init_test(), test_exports));
|
|
75959
76161
|
let config;
|
|
75960
76162
|
try {
|
|
@@ -76259,6 +76461,7 @@ ${handover.hint[0].toUpperCase()}${handover.hint.slice(1)}.`);
|
|
|
76259
76461
|
bootAndSnapshotState: bootAndSnapshotState2,
|
|
76260
76462
|
renderDescriptorWeb: renderDescriptorWeb2,
|
|
76261
76463
|
renderPngBatch: renderPngBatch2,
|
|
76464
|
+
isUnserved: isUnservedRequest2,
|
|
76262
76465
|
writePng: (path2, base64) => writePngFile(path2, base64),
|
|
76263
76466
|
clearDir: (d) => rmSync16(d, { recursive: true, force: true })
|
|
76264
76467
|
}
|
|
@@ -76394,6 +76597,14 @@ ${[...afterRun.next, ...testReminder ? [testReminder] : []].join("\n")}`);
|
|
|
76394
76597
|
io.error(JSON.stringify({ error: "--static and --live are mutually exclusive: --static renders raw bindings, --live renders real Wix data" }));
|
|
76395
76598
|
return 2;
|
|
76396
76599
|
}
|
|
76600
|
+
if (flags.mocks !== void 0 && flags.mocks !== true) {
|
|
76601
|
+
io.error(JSON.stringify({ error: `--mocks takes no value (got "${String(flags.mocks)}") \u2014 pass it bare` }));
|
|
76602
|
+
return 2;
|
|
76603
|
+
}
|
|
76604
|
+
if (flags.mocks === true && (live2 || flags.static === true)) {
|
|
76605
|
+
io.error(JSON.stringify({ error: `--mocks and ${live2 ? "--live" : "--static"} are mutually exclusive: --mocks loads the app's recorded data, ${live2 ? "--live real Wix data" : "--static no data at all"}` }));
|
|
76606
|
+
return 2;
|
|
76607
|
+
}
|
|
76397
76608
|
let injectedState;
|
|
76398
76609
|
if (flags.state !== void 0) {
|
|
76399
76610
|
const stateArg = str9(flags.state);
|
|
@@ -76464,6 +76675,22 @@ ${[...afterRun.next, ...testReminder ? [testReminder] : []].join("\n")}`);
|
|
|
76464
76675
|
return 2;
|
|
76465
76676
|
}
|
|
76466
76677
|
}
|
|
76678
|
+
let mocks;
|
|
76679
|
+
let mocksNote;
|
|
76680
|
+
if (flags.mocks === true) {
|
|
76681
|
+
let rules = null;
|
|
76682
|
+
let bad2;
|
|
76683
|
+
try {
|
|
76684
|
+
rules = (await Promise.resolve().then(() => (init_test(), test_exports))).defaultNetRules(dir);
|
|
76685
|
+
} catch (e) {
|
|
76686
|
+
bad2 = e.message.split("\n")[0].slice(0, 200);
|
|
76687
|
+
}
|
|
76688
|
+
const cmsSeed = await cmsSeedRows(dir, app).catch(() => null);
|
|
76689
|
+
const seeded = cmsSeed && Object.keys(cmsSeed).length ? cmsSeed : null;
|
|
76690
|
+
const picked = renderMocksFrom(rules, bad2, seeded);
|
|
76691
|
+
mocks = picked.mocks;
|
|
76692
|
+
mocksNote = picked.note;
|
|
76693
|
+
}
|
|
76467
76694
|
const { buildContentBundles: buildContentBundles2, buildReleaseFromContent: buildReleaseFromContent2, bundleOutDir: bundleOutDir2 } = await loadAuthoring();
|
|
76468
76695
|
const bres = await buildContentBundles2(dir, { resolution: resolutionFlag(flags) });
|
|
76469
76696
|
const out2 = commandScratchDir("himi-render-");
|
|
@@ -76472,7 +76699,7 @@ ${[...afterRun.next, ...testReminder ? [testReminder] : []].join("\n")}`);
|
|
|
76472
76699
|
...hasWorkers ? { tier5Dir: bundleOutDir2(dir) } : {},
|
|
76473
76700
|
// The settled state is always kept (printed only under --dump-state): it is what says whether
|
|
76474
76701
|
// the picture landed on the screen's content or on its error, loading or empty state.
|
|
76475
|
-
collect: { lint: true, strict, render: { screenId, scheme, png: wantPng, hydrate, dumpState: true, ...live2 ? { live: true } : {}, ...injectedState ? { state: injectedState } : {}, ...injectedInput ? { input: injectedInput } : {}, ...renderWidth ? { width: renderWidth } : {}, ...flags.rtl === true ? { rtl: true } : {}, ...loadBudgetMs ? { loadBudgetMs } : {} } }
|
|
76702
|
+
collect: { lint: true, strict, render: { screenId, scheme, png: wantPng, hydrate, dumpState: true, ...live2 ? { live: true } : {}, ...injectedState ? { state: injectedState } : {}, ...injectedInput ? { input: injectedInput } : {}, ...renderWidth ? { width: renderWidth } : {}, ...flags.rtl === true ? { rtl: true } : {}, ...loadBudgetMs ? { loadBudgetMs } : {}, ...mocks ? { mocks: { rules: mocks.rules, cmsSeed: mocks.cmsSeed } } : {} } }
|
|
76476
76703
|
});
|
|
76477
76704
|
const r = (outcome.renders ?? [])[0];
|
|
76478
76705
|
if (!r) {
|
|
@@ -76502,7 +76729,9 @@ ${[...afterRun.next, ...testReminder ? [testReminder] : []].join("\n")}`);
|
|
|
76502
76729
|
scheme,
|
|
76503
76730
|
renderable: r.renderable,
|
|
76504
76731
|
hydrated: live2 ? "live" : hydrate,
|
|
76505
|
-
dataSource: renderDataSource({ live: live2, hydrate, liveCalls: r.liveCalls, liveReturned: r.liveReturned }),
|
|
76732
|
+
dataSource: renderDataSource({ live: live2, hydrate, liveCalls: r.liveCalls, liveReturned: r.liveReturned, mocked: !!r.mockCalls }),
|
|
76733
|
+
// What --mocks loaded from, how many requests the load made, and the ones no layer held.
|
|
76734
|
+
...r.mockCalls && mocks ? { mocks: { used: true, from: mocks.from, calls: r.mockCalls.length, ...r.mockUnserved?.length ? { unserved: r.mockUnserved } : {}, ...mocks.ignored ? { ignored: mocks.ignored } : {} } } : mocksNote ? { mocks: mocksNote } : mocks ? { mocks: { used: false, reason: r.mockBootFailed ? "the screen's worker failed to boot against the recorded data, so its initial state was rendered" : "the screen has no worker bundle to load data into", ...mocks.ignored ? { ignored: mocks.ignored } : {} } } : {},
|
|
76506
76735
|
...live2 ? {
|
|
76507
76736
|
liveCalls: r.liveCalls?.length ?? 0,
|
|
76508
76737
|
// Beside the attempt count, because the two coming apart IS the signal (#1883).
|
|
@@ -76554,10 +76783,11 @@ ${[...afterRun.next, ...testReminder ? [testReminder] : []].join("\n")}`);
|
|
|
76554
76783
|
let landed = null;
|
|
76555
76784
|
if (pngFile && r.renderable) {
|
|
76556
76785
|
const { recordShotState: recordShotState2, screenStateOf: screenStateOf2 } = await Promise.resolve().then(() => (init_screen_state(), screen_state_exports));
|
|
76557
|
-
|
|
76786
|
+
const mocked = !!r.mockCalls;
|
|
76787
|
+
landed = screenStateOf2(r.serviceState, r.html, { initial: !live2 && !mocked, offline: mocked && (r.mockUnserved?.length ?? 0) > 0 });
|
|
76558
76788
|
recordShotState2(dir, screenId, pngFile, landed);
|
|
76559
76789
|
}
|
|
76560
|
-
const autoSkip = process.env.HIMI_AUTO_RENDER === "validate" && landed !== null && (landed.state === "loading" || landed.state === "empty") ? `the picture shows the home screen's ${landed.state} state${landed.initial ? " (an offline
|
|
76790
|
+
const autoSkip = process.env.HIMI_AUTO_RENDER === "validate" && landed !== null && (landed.state === "loading" || landed.state === "empty" || landed.state === "error" && landed.offline === true) ? `the picture shows the home screen's ${landed.state} state${landed.initial || landed.offline ? " (an offline preview: the site's data is not loaded offline)" : ""}, so no postcard was journaled from it` : void 0;
|
|
76561
76791
|
const afterPng = pngFile && !autoSkip ? milestoneKit.afterRenderPng(dir, { screenId, homeScreenId: outcome.homeScreenId ?? null, pngFile, renderable: r.renderable, content: str9(flags.content) }) : null;
|
|
76562
76792
|
const reminder = process.env.HIMI_AUTO_RENDER ? void 0 : milestoneKit.watchReminder(dir, { content: str9(flags.content) });
|
|
76563
76793
|
const renderNext = [...reminder ? [reminder] : [], ...afterPng?.next ?? []];
|
|
@@ -77320,7 +77550,9 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
|
|
|
77320
77550
|
releaseId: release,
|
|
77321
77551
|
content: str9(flags.content),
|
|
77322
77552
|
home: outcome.homeScreenId ?? null,
|
|
77323
|
-
...draftShot ? { homeShot: draftShot, sourceChangedAtMs } : {}
|
|
77553
|
+
...draftShot ? { homeShot: draftShot, sourceChangedAtMs } : {},
|
|
77554
|
+
// Its screen hashes say what changed since the last draft, on the postcard.
|
|
77555
|
+
manifest
|
|
77324
77556
|
}) : null;
|
|
77325
77557
|
print({
|
|
77326
77558
|
ok: true,
|