@wix/himalaya-cli 0.1127.0 → 0.1129.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 +827 -591
- 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,6 +24749,7 @@ 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"() {
|
|
24752
|
+
"use strict";
|
|
24722
24753
|
EMAIL_RE = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9-]+(\.[A-Za-z0-9-]+)*\.[A-Za-z]{2,}$/;
|
|
24723
24754
|
URL_HOST_RE = /^[A-Za-z0-9-]+(\.[A-Za-z0-9-]+)*(:[0-9]+)?$/;
|
|
24724
24755
|
INTEGER_RE = /^[+-]?[0-9]+$/;
|
|
@@ -29119,6 +29150,7 @@ function validateWorkerSubtree(raw, slotId, policy, actionCatalog) {
|
|
|
29119
29150
|
var WORKER_SUBTREE_CEILINGS, ID_PART, ACTION_ID;
|
|
29120
29151
|
var init_worker_subtree = __esm({
|
|
29121
29152
|
"../../core/ts/src/worker-subtree.ts"() {
|
|
29153
|
+
"use strict";
|
|
29122
29154
|
init_worker_subtree_safe_types_generated();
|
|
29123
29155
|
WORKER_SUBTREE_CEILINGS = Object.freeze({
|
|
29124
29156
|
maxEncodedBytes: 65536,
|
|
@@ -42073,6 +42105,7 @@ __export(screen_crawl_exports, {
|
|
|
42073
42105
|
emptiedArrays: () => emptiedArrays,
|
|
42074
42106
|
findingKey: () => findingKey,
|
|
42075
42107
|
hostDispatch: () => hostDispatch,
|
|
42108
|
+
isUnservedRequest: () => isUnservedRequest,
|
|
42076
42109
|
layeredTransport: () => layeredTransport,
|
|
42077
42110
|
mapBundleFrame: () => mapBundleFrame,
|
|
42078
42111
|
netTransport: () => netTransport,
|
|
@@ -42169,6 +42202,10 @@ function rootOnAppear(descriptor) {
|
|
|
42169
42202
|
function isTransportReject(message) {
|
|
42170
42203
|
return message === REJECT_ALL_MESSAGE || message.startsWith("no data layer served ");
|
|
42171
42204
|
}
|
|
42205
|
+
function isUnservedRequest(e) {
|
|
42206
|
+
const m = e instanceof Error ? e.message : typeof e === "string" ? e : "";
|
|
42207
|
+
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");
|
|
42208
|
+
}
|
|
42172
42209
|
function mapBundleFrame(mapJson, genLine, genCol) {
|
|
42173
42210
|
try {
|
|
42174
42211
|
const map = JSON.parse(mapJson);
|
|
@@ -43571,7 +43608,8 @@ async function bootAndSnapshotState(opts) {
|
|
|
43571
43608
|
const { screenId, descriptor, bundleJs } = opts;
|
|
43572
43609
|
if (!bundleJs) return null;
|
|
43573
43610
|
return withSwallowedRejections(async () => {
|
|
43574
|
-
const
|
|
43611
|
+
const layered = layeredTransport({ rules: opts.rules, cmsSeed: opts.cmsSeed, canned: opts.canned });
|
|
43612
|
+
const wixCall = opts.transportWrap ? opts.transportWrap(layered.call) : layered.call;
|
|
43575
43613
|
let w;
|
|
43576
43614
|
try {
|
|
43577
43615
|
w = await bootScreenWorker({ bundleJs, descriptor, wixCall, screenId, budgetMs: opts.budgetMs, localDbSchema: opts.localDbSchema, capabilitySuccess: opts.capabilitySuccess === true, ...opts.evaluator ? { evaluator: opts.evaluator } : {} });
|
|
@@ -44816,7 +44854,7 @@ async function buildFromLoaded(preloaded, dest, opts = {}) {
|
|
|
44816
44854
|
};
|
|
44817
44855
|
return liveWixCall;
|
|
44818
44856
|
};
|
|
44819
|
-
const renderServicesFor = async (bundleName, descriptor) => {
|
|
44857
|
+
const renderServicesFor = async (bundleName, descriptor, renderedScreenId) => {
|
|
44820
44858
|
const bakedInput = descriptor?.input;
|
|
44821
44859
|
const input = {
|
|
44822
44860
|
...bakedInput && typeof bakedInput === "object" && !Array.isArray(bakedInput) ? bakedInput : {},
|
|
@@ -44826,6 +44864,9 @@ async function buildFromLoaded(preloaded, dest, opts = {}) {
|
|
|
44826
44864
|
let liveCalls;
|
|
44827
44865
|
let liveReturned;
|
|
44828
44866
|
let liveErrors;
|
|
44867
|
+
let mockCalls;
|
|
44868
|
+
let mockBootFailed;
|
|
44869
|
+
let mockUnserved;
|
|
44829
44870
|
if (collect2?.render?.live) {
|
|
44830
44871
|
const bundleJs = bundleName ? readBuiltBundle(tier5Dir, bundleName) : void 0;
|
|
44831
44872
|
if (!bundleJs) {
|
|
@@ -44844,6 +44885,38 @@ async function buildFromLoaded(preloaded, dest, opts = {}) {
|
|
|
44844
44885
|
liveReturned = counting.returned();
|
|
44845
44886
|
liveErrors = live2.errors;
|
|
44846
44887
|
}
|
|
44888
|
+
} else if (collect2?.render?.mocks && collect2.render.hydrate !== false && bundleName && readBuiltBundle(tier5Dir, bundleName)) {
|
|
44889
|
+
const calls = [];
|
|
44890
|
+
const unserved = [];
|
|
44891
|
+
const snap = await bootAndSnapshotState({
|
|
44892
|
+
screenId: renderedScreenId ?? String(descriptor?.screenId ?? bundleName),
|
|
44893
|
+
// `--input` rides on the descriptor's own input, as a navigator hands it over.
|
|
44894
|
+
descriptor: collect2.render.input && descriptor && typeof descriptor === "object" ? { ...descriptor, input } : descriptor,
|
|
44895
|
+
bundleJs: readBuiltBundle(tier5Dir, bundleName),
|
|
44896
|
+
knownScreenIds: /* @__PURE__ */ new Set(),
|
|
44897
|
+
rules: collect2.render.mocks.rules ?? null,
|
|
44898
|
+
...collect2.render.mocks.cmsSeed ? { cmsSeed: collect2.render.mocks.cmsSeed } : {},
|
|
44899
|
+
localDbSchema: appLocalDbSchema,
|
|
44900
|
+
...collect2.render.loadBudgetMs ? { budgetMs: collect2.render.loadBudgetMs } : {},
|
|
44901
|
+
transportWrap: (call) => async (method, path2, body) => {
|
|
44902
|
+
const label2 = `${String(method).toUpperCase()} ${String(path2).split("?")[0]}`;
|
|
44903
|
+
calls.push(label2);
|
|
44904
|
+
try {
|
|
44905
|
+
return await call(method, path2, body);
|
|
44906
|
+
} catch (e) {
|
|
44907
|
+
if (isUnservedRequest(e)) unserved.push(label2);
|
|
44908
|
+
throw e;
|
|
44909
|
+
}
|
|
44910
|
+
}
|
|
44911
|
+
});
|
|
44912
|
+
if (snap && Object.keys(snap).length > 0) {
|
|
44913
|
+
base2 = snap;
|
|
44914
|
+
mockCalls = calls;
|
|
44915
|
+
mockUnserved = unserved;
|
|
44916
|
+
} else {
|
|
44917
|
+
mockBootFailed = true;
|
|
44918
|
+
base2 = hydrationServices(tier5Dir, bundleName, input);
|
|
44919
|
+
}
|
|
44847
44920
|
} else {
|
|
44848
44921
|
base2 = collect2?.render?.hydrate === false ? null : hydrationServices(
|
|
44849
44922
|
tier5Dir,
|
|
@@ -44858,7 +44931,7 @@ async function buildFromLoaded(preloaded, dest, opts = {}) {
|
|
|
44858
44931
|
services[svc] = { ...services[svc] ?? {}, ...st };
|
|
44859
44932
|
}
|
|
44860
44933
|
}
|
|
44861
|
-
return { services, ...liveCalls ? { liveCalls } : {}, ...liveReturned !== void 0 ? { liveReturned } : {}, ...liveErrors ? { liveErrors } : {} };
|
|
44934
|
+
return { services, ...liveCalls ? { liveCalls } : {}, ...liveReturned !== void 0 ? { liveReturned } : {}, ...liveErrors ? { liveErrors } : {}, ...mockCalls ? { mockCalls, mockUnserved: mockUnserved ?? [] } : {}, ...mockBootFailed ? { mockBootFailed } : {} };
|
|
44862
44935
|
};
|
|
44863
44936
|
checkRegistry(preloaded.module.actions);
|
|
44864
44937
|
await store.reset(app);
|
|
@@ -45128,9 +45201,9 @@ async function buildFromLoaded(preloaded, dest, opts = {}) {
|
|
|
45128
45201
|
lint.push({ screenId, ok: report3.ok, issues: report3.issues });
|
|
45129
45202
|
}
|
|
45130
45203
|
if (collect2?.render && (!collect2.render.screenId || collect2.render.screenId === screenId)) {
|
|
45131
|
-
const { services, liveCalls, liveReturned, liveErrors } = await renderServicesFor(bundleName, descriptor);
|
|
45204
|
+
const { services, liveCalls, liveReturned, liveErrors, mockCalls, mockUnserved, mockBootFailed } = await renderServicesFor(bundleName, descriptor, screenId);
|
|
45132
45205
|
const rr = await renderDescriptorWeb(descriptor, renderTokens, renderScheme, collect2.render.png === true, services, collect2.render.width, collect2.render.rtl === true);
|
|
45133
|
-
renders.push({ screenId, scheme: renderScheme, ...rr, ...await renderCompare(descriptor, services), ...collect2.render.dumpState && services ? { serviceState: services } : {}, ...liveCalls ? { liveCalls } : {}, ...liveReturned !== void 0 ? { liveReturned } : {}, ...liveErrors ? { liveErrors } : {} });
|
|
45206
|
+
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 } : {} });
|
|
45134
45207
|
}
|
|
45135
45208
|
}
|
|
45136
45209
|
const wantedRender = collect2?.render?.screenId;
|
|
@@ -45156,9 +45229,9 @@ async function buildFromLoaded(preloaded, dest, opts = {}) {
|
|
|
45156
45229
|
});
|
|
45157
45230
|
lint.push({ screenId: wantedRender, ok: report3.ok, issues: report3.issues });
|
|
45158
45231
|
}
|
|
45159
|
-
const { services, liveCalls, liveReturned, liveErrors } = await renderServicesFor(bundleName, descriptor);
|
|
45232
|
+
const { services, liveCalls, liveReturned, liveErrors, mockCalls, mockUnserved, mockBootFailed } = await renderServicesFor(bundleName, descriptor, wantedRender);
|
|
45160
45233
|
const rr = await renderDescriptorWeb(descriptor, renderTokens, renderScheme, collect2.render.png === true, services, collect2.render.width, collect2.render.rtl === true);
|
|
45161
|
-
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 } : {} });
|
|
45234
|
+
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 } : {} });
|
|
45162
45235
|
}
|
|
45163
45236
|
}
|
|
45164
45237
|
for (const t of preloaded.module.screens.templated ?? []) {
|
|
@@ -48571,19 +48644,300 @@ var init_app_source = __esm({
|
|
|
48571
48644
|
}
|
|
48572
48645
|
});
|
|
48573
48646
|
|
|
48647
|
+
// src/run-watch.ts
|
|
48648
|
+
var run_watch_exports = {};
|
|
48649
|
+
__export(run_watch_exports, {
|
|
48650
|
+
ReloadBus: () => ReloadBus,
|
|
48651
|
+
boardFile: () => boardFile,
|
|
48652
|
+
diffReleases: () => diffReleases,
|
|
48653
|
+
himiDir: () => himiDir,
|
|
48654
|
+
ignoredChange: () => ignoredChange,
|
|
48655
|
+
isEmptyDiff: () => isEmptyDiff,
|
|
48656
|
+
rebuildBlocker: () => rebuildBlocker,
|
|
48657
|
+
reloadEvents: () => reloadEvents,
|
|
48658
|
+
removeInitWatchRecord: () => removeInitWatchRecord,
|
|
48659
|
+
removeRunRecord: () => removeRunRecord,
|
|
48660
|
+
serialize: () => serialize,
|
|
48661
|
+
watchContent: () => watchContent,
|
|
48662
|
+
watchFlagConflict: () => watchFlagConflict,
|
|
48663
|
+
watchedContentDir: () => watchedContentDir,
|
|
48664
|
+
writeRunRecord: () => writeRunRecord
|
|
48665
|
+
});
|
|
48666
|
+
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";
|
|
48667
|
+
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
48668
|
+
import { extname as extname4, join as join45, resolve as resolve41, sep as sep12 } from "node:path";
|
|
48669
|
+
function diffReleases(prev, next) {
|
|
48670
|
+
const bundles = Object.keys(next.bundles).filter((b) => prev.bundles[b]?.sha256 !== next.bundles[b].sha256).sort();
|
|
48671
|
+
const changedBundles = new Set(bundles);
|
|
48672
|
+
const screens = Object.entries(next.screens).filter(([id, s]) => {
|
|
48673
|
+
const before = prev.screens[id];
|
|
48674
|
+
return !before || before.descriptorHash !== s.descriptorHash || before.bundle !== s.bundle || s.bundle !== null && changedBundles.has(s.bundle);
|
|
48675
|
+
}).map(([id]) => id).sort();
|
|
48676
|
+
const prevIds = Object.keys(prev.screens).sort().join("\n");
|
|
48677
|
+
const nextIds = Object.keys(next.screens).sort().join("\n");
|
|
48678
|
+
return {
|
|
48679
|
+
screens,
|
|
48680
|
+
bundles,
|
|
48681
|
+
tokens: prev.tokensHash !== next.tokensHash,
|
|
48682
|
+
config: prev.appConfigHash !== next.appConfigHash || prevIds !== nextIds
|
|
48683
|
+
};
|
|
48684
|
+
}
|
|
48685
|
+
function reloadEvents(d) {
|
|
48686
|
+
const out2 = [];
|
|
48687
|
+
if (d.bundles.length) out2.push(["bundle.invalidated", { bundles: d.bundles }]);
|
|
48688
|
+
if (d.screens.length) out2.push(["screen.invalidated", { screenIds: d.screens }]);
|
|
48689
|
+
if (d.tokens) out2.push(["tokens.changed", {}]);
|
|
48690
|
+
if (d.config) out2.push(["config.invalidated", {}]);
|
|
48691
|
+
return out2;
|
|
48692
|
+
}
|
|
48693
|
+
function ignoredChange(relPath, isDirectory = () => false) {
|
|
48694
|
+
const parts = relPath.split(/[\\/]/).filter(Boolean);
|
|
48695
|
+
if (!parts.length) return true;
|
|
48696
|
+
const [top] = parts;
|
|
48697
|
+
if ([".himi", "dist", "node_modules", ".git", ".build", ...AGENT_TOOLING_DIRS].includes(top)) return true;
|
|
48698
|
+
if (parts.length === 1 && AGENT_TOOLING_ROOT_FILES.includes(top)) return true;
|
|
48699
|
+
if ((top === "shots" || top === "evidence") && (parts.length > 1 || isDirectory(top))) return true;
|
|
48700
|
+
const name = parts[parts.length - 1];
|
|
48701
|
+
return /(^\.#|~$|\.sw[a-p]$|^\.DS_Store$|\.tmp$)/.test(name);
|
|
48702
|
+
}
|
|
48703
|
+
function watchContent(dir, onChange, opts = {}) {
|
|
48704
|
+
const debounceMs = opts.debounceMs ?? 200;
|
|
48705
|
+
const watch2 = opts.watch ?? ((p, o, l) => fsWatch(p, o, (event, file) => l(event, file === null ? null : String(file))));
|
|
48706
|
+
let timer = null;
|
|
48707
|
+
const pending = /* @__PURE__ */ new Set();
|
|
48708
|
+
const watcher2 = watch2(dir, { recursive: true }, (_event, file) => {
|
|
48709
|
+
if (!file || ignoredChange(file, (rel) => isDir(join45(dir, rel)))) return;
|
|
48710
|
+
pending.add(file);
|
|
48711
|
+
if (timer) clearTimeout(timer);
|
|
48712
|
+
timer = setTimeout(() => {
|
|
48713
|
+
timer = null;
|
|
48714
|
+
const paths = [...pending];
|
|
48715
|
+
pending.clear();
|
|
48716
|
+
onChange(paths);
|
|
48717
|
+
}, debounceMs);
|
|
48718
|
+
});
|
|
48719
|
+
return {
|
|
48720
|
+
close() {
|
|
48721
|
+
if (timer) clearTimeout(timer);
|
|
48722
|
+
watcher2.close();
|
|
48723
|
+
}
|
|
48724
|
+
};
|
|
48725
|
+
}
|
|
48726
|
+
function watchedContentDir(source) {
|
|
48727
|
+
if (!source || /^https?:\/\//i.test(source)) return null;
|
|
48728
|
+
const dir = resolve41(source);
|
|
48729
|
+
try {
|
|
48730
|
+
return statSync11(dir).isDirectory() && existsSync47(join45(dir, "himalaya.content.json")) ? dir : null;
|
|
48731
|
+
} catch {
|
|
48732
|
+
return null;
|
|
48733
|
+
}
|
|
48734
|
+
}
|
|
48735
|
+
function watchFlagConflict(opts) {
|
|
48736
|
+
if (opts.source) {
|
|
48737
|
+
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." };
|
|
48738
|
+
}
|
|
48739
|
+
if (opts.browser) {
|
|
48740
|
+
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." };
|
|
48741
|
+
}
|
|
48742
|
+
if (opts.spa) {
|
|
48743
|
+
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." };
|
|
48744
|
+
}
|
|
48745
|
+
return null;
|
|
48746
|
+
}
|
|
48747
|
+
function rebuildBlocker(outcome, servedMissing) {
|
|
48748
|
+
const failed2 = outcome.failedToBuild[0];
|
|
48749
|
+
if (failed2) return `${failed2.screenId}: ${failed2.error.split("\n")[0]}`;
|
|
48750
|
+
const had = new Set(servedMissing.map((m) => `${m.screen}\0${m.bundle}`));
|
|
48751
|
+
const lost = outcome.missingBundles.find((m) => !had.has(`${m.screen}\0${m.bundle}`));
|
|
48752
|
+
if (lost) return `${lost.screen}: its worker bundle "${lost.bundle}" is referenced but was not built`;
|
|
48753
|
+
return null;
|
|
48754
|
+
}
|
|
48755
|
+
function serialize(task) {
|
|
48756
|
+
let running = null;
|
|
48757
|
+
let again = false;
|
|
48758
|
+
let stopped = false;
|
|
48759
|
+
const go = () => {
|
|
48760
|
+
if (stopped) return;
|
|
48761
|
+
if (running) {
|
|
48762
|
+
again = true;
|
|
48763
|
+
return;
|
|
48764
|
+
}
|
|
48765
|
+
running = (async () => {
|
|
48766
|
+
try {
|
|
48767
|
+
await task();
|
|
48768
|
+
} finally {
|
|
48769
|
+
running = null;
|
|
48770
|
+
if (again && !stopped) {
|
|
48771
|
+
again = false;
|
|
48772
|
+
go();
|
|
48773
|
+
}
|
|
48774
|
+
}
|
|
48775
|
+
})();
|
|
48776
|
+
};
|
|
48777
|
+
return Object.assign(() => go(), {
|
|
48778
|
+
async stop() {
|
|
48779
|
+
stopped = true;
|
|
48780
|
+
again = false;
|
|
48781
|
+
await running?.catch(() => {
|
|
48782
|
+
});
|
|
48783
|
+
}
|
|
48784
|
+
});
|
|
48785
|
+
}
|
|
48786
|
+
function himiDir(dir) {
|
|
48787
|
+
const root = join45(dir, ".himi");
|
|
48788
|
+
try {
|
|
48789
|
+
return lstatSync10(root).isDirectory() ? root : null;
|
|
48790
|
+
} catch (e) {
|
|
48791
|
+
if (e.code !== "ENOENT") return null;
|
|
48792
|
+
mkdirSync23(root, { recursive: true });
|
|
48793
|
+
return lstatSync10(root).isDirectory() ? root : null;
|
|
48794
|
+
}
|
|
48795
|
+
}
|
|
48796
|
+
function writeRunRecord(dir, record2) {
|
|
48797
|
+
const root = himiDir(dir);
|
|
48798
|
+
if (!root) return false;
|
|
48799
|
+
const tmp = join45(root, `.run.json.${process.pid}.${randomUUID3()}.tmp`);
|
|
48800
|
+
try {
|
|
48801
|
+
writeFileSync18(tmp, `${JSON.stringify(record2, null, 2)}
|
|
48802
|
+
`, { flag: "wx" });
|
|
48803
|
+
renameSync5(tmp, join45(root, "run.json"));
|
|
48804
|
+
} catch (e) {
|
|
48805
|
+
rmSync11(tmp, { force: true });
|
|
48806
|
+
throw e;
|
|
48807
|
+
}
|
|
48808
|
+
return true;
|
|
48809
|
+
}
|
|
48810
|
+
function removeRunRecord(dir, owner) {
|
|
48811
|
+
const root = join45(dir, ".himi");
|
|
48812
|
+
try {
|
|
48813
|
+
if (!lstatSync10(root).isDirectory()) return;
|
|
48814
|
+
} catch {
|
|
48815
|
+
return;
|
|
48816
|
+
}
|
|
48817
|
+
if (owner !== void 0) {
|
|
48818
|
+
const bytes2 = readConfined(root, "run.json");
|
|
48819
|
+
let pid;
|
|
48820
|
+
try {
|
|
48821
|
+
pid = bytes2 ? JSON.parse(bytes2.toString("utf8")).pid : void 0;
|
|
48822
|
+
} catch {
|
|
48823
|
+
}
|
|
48824
|
+
if (typeof pid === "number" && pid !== owner) return;
|
|
48825
|
+
}
|
|
48826
|
+
rmSync11(join45(root, "run.json"), { force: true });
|
|
48827
|
+
}
|
|
48828
|
+
function removeInitWatchRecord(dir, owner) {
|
|
48829
|
+
const root = join45(dir, ".himi");
|
|
48830
|
+
try {
|
|
48831
|
+
if (!lstatSync10(root).isDirectory()) return;
|
|
48832
|
+
} catch {
|
|
48833
|
+
return;
|
|
48834
|
+
}
|
|
48835
|
+
const bytes2 = readConfined(root, "watch.json");
|
|
48836
|
+
if (!bytes2) return;
|
|
48837
|
+
let pid;
|
|
48838
|
+
try {
|
|
48839
|
+
pid = JSON.parse(bytes2.toString("utf8")).pid;
|
|
48840
|
+
} catch {
|
|
48841
|
+
return;
|
|
48842
|
+
}
|
|
48843
|
+
if (pid !== owner) return;
|
|
48844
|
+
rmSync11(join45(root, "watch.json"), { force: true });
|
|
48845
|
+
}
|
|
48846
|
+
function boardFile(dir, pathname) {
|
|
48847
|
+
if (pathname === RUN_IDENTITY_PATH) {
|
|
48848
|
+
return new Response(JSON.stringify(runIdentity(dir)), { status: 200, headers: { "content-type": "application/json", "cache-control": "no-store" } });
|
|
48849
|
+
}
|
|
48850
|
+
let rel = null;
|
|
48851
|
+
if (pathname === "/__himi/studio/" || pathname === "/__himi/studio/index.html") rel = join45("studio", "index.html");
|
|
48852
|
+
else if (pathname === "/__himi/studio/state.js") rel = join45("studio", "state.js");
|
|
48853
|
+
else if (pathname === "/__himi/studio/timeline.js") rel = join45("studio", "timeline.js");
|
|
48854
|
+
else {
|
|
48855
|
+
const m = /^\/__himi\/history\/([0-9a-f]{64}\.(png|jpg|gif|webp))$/.exec(pathname);
|
|
48856
|
+
if (m) rel = join45("history", m[1]);
|
|
48857
|
+
}
|
|
48858
|
+
if (!rel) return null;
|
|
48859
|
+
const bytes2 = readConfined(resolve41(dir, ".himi"), rel);
|
|
48860
|
+
if (!bytes2) return new Response("not found", { status: 404 });
|
|
48861
|
+
return new Response(bytes2, {
|
|
48862
|
+
status: 200,
|
|
48863
|
+
headers: { "content-type": BOARD_TYPES[extname4(rel)] ?? "application/octet-stream", "cache-control": rel.startsWith("history") ? "public, max-age=31536000, immutable" : "no-store" }
|
|
48864
|
+
});
|
|
48865
|
+
}
|
|
48866
|
+
function readConfined(root, rel) {
|
|
48867
|
+
try {
|
|
48868
|
+
if (!lstatSync10(root).isDirectory()) return null;
|
|
48869
|
+
const parts = rel.split(sep12);
|
|
48870
|
+
let at2 = root;
|
|
48871
|
+
for (let i = 0; i < parts.length; i++) {
|
|
48872
|
+
at2 = join45(at2, parts[i]);
|
|
48873
|
+
const st = lstatSync10(at2);
|
|
48874
|
+
if (st.isSymbolicLink() || !(i === parts.length - 1 ? st.isFile() : st.isDirectory())) return null;
|
|
48875
|
+
}
|
|
48876
|
+
const canonicalRoot = realpathSync7(root);
|
|
48877
|
+
if (!realpathSync7(at2).startsWith(canonicalRoot + sep12)) return null;
|
|
48878
|
+
const fd = openSync3(at2, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
|
|
48879
|
+
try {
|
|
48880
|
+
if (!fstatSync(fd).isFile()) return null;
|
|
48881
|
+
return readFileSync51(fd);
|
|
48882
|
+
} finally {
|
|
48883
|
+
closeSync3(fd);
|
|
48884
|
+
}
|
|
48885
|
+
} catch {
|
|
48886
|
+
return null;
|
|
48887
|
+
}
|
|
48888
|
+
}
|
|
48889
|
+
var isEmptyDiff, ReloadBus, isDir, BOARD_TYPES;
|
|
48890
|
+
var init_run_watch = __esm({
|
|
48891
|
+
"src/run-watch.ts"() {
|
|
48892
|
+
init_source_deny();
|
|
48893
|
+
init_run_identity();
|
|
48894
|
+
isEmptyDiff = (d) => !d.screens.length && !d.bundles.length && !d.tokens && !d.config;
|
|
48895
|
+
ReloadBus = class {
|
|
48896
|
+
subscribers = /* @__PURE__ */ new Set();
|
|
48897
|
+
subscribe(send2) {
|
|
48898
|
+
this.subscribers.add(send2);
|
|
48899
|
+
return () => this.subscribers.delete(send2);
|
|
48900
|
+
}
|
|
48901
|
+
publish(event, data) {
|
|
48902
|
+
for (const send2 of [...this.subscribers]) send2(event, data);
|
|
48903
|
+
}
|
|
48904
|
+
get listeners() {
|
|
48905
|
+
return this.subscribers.size;
|
|
48906
|
+
}
|
|
48907
|
+
};
|
|
48908
|
+
isDir = (path2) => {
|
|
48909
|
+
try {
|
|
48910
|
+
return statSync11(path2).isDirectory();
|
|
48911
|
+
} catch {
|
|
48912
|
+
return false;
|
|
48913
|
+
}
|
|
48914
|
+
};
|
|
48915
|
+
BOARD_TYPES = {
|
|
48916
|
+
".html": "text/html; charset=utf-8",
|
|
48917
|
+
".js": "text/javascript; charset=utf-8",
|
|
48918
|
+
".png": "image/png",
|
|
48919
|
+
".jpg": "image/jpeg",
|
|
48920
|
+
".gif": "image/gif",
|
|
48921
|
+
".webp": "image/webp"
|
|
48922
|
+
};
|
|
48923
|
+
}
|
|
48924
|
+
});
|
|
48925
|
+
|
|
48574
48926
|
// src/screen-state.ts
|
|
48575
48927
|
var screen_state_exports = {};
|
|
48576
48928
|
__export(screen_state_exports, {
|
|
48577
48929
|
homeStateCaveat: () => homeStateCaveat,
|
|
48930
|
+
offlineArtifact: () => offlineArtifact,
|
|
48578
48931
|
quotableLine: () => quotableLine,
|
|
48579
48932
|
recordShotState: () => recordShotState,
|
|
48933
|
+
recordedShots: () => recordedShots,
|
|
48580
48934
|
screenStateOf: () => screenStateOf,
|
|
48581
48935
|
shotStateFor: () => shotStateFor,
|
|
48582
48936
|
withHomeCaveat: () => withHomeCaveat
|
|
48583
48937
|
});
|
|
48584
48938
|
import { createHash as createHash25 } from "node:crypto";
|
|
48585
|
-
import { lstatSync as
|
|
48586
|
-
import { join as
|
|
48939
|
+
import { lstatSync as lstatSync11, readFileSync as readFileSync52 } from "node:fs";
|
|
48940
|
+
import { join as join46 } from "node:path";
|
|
48587
48941
|
function quotableLine(text8) {
|
|
48588
48942
|
if (!text8) return void 0;
|
|
48589
48943
|
const first = text8.split(/\r?\n/).map((l) => l.trim()).find(Boolean);
|
|
@@ -48625,7 +48979,7 @@ function screenStateOf(services, html, opts = {}) {
|
|
|
48625
48979
|
const flagged = status === "error" || status === "failed" || s.hasError === true || s.isError === true || s.isErrored === true || s.errored === true || s.noError === false || s.notErrored === false;
|
|
48626
48980
|
if (flagged && !(message && page !== void 0 && !painted(message, page)) || message && page !== void 0 && painted(message, page)) {
|
|
48627
48981
|
const line = message && painted(message, page) ? quotableLine(message) : void 0;
|
|
48628
|
-
return { state: "error", ...line ? { line } : {} };
|
|
48982
|
+
return { state: "error", ...line ? { line } : {}, ...opts.offline ? { offline: true } : {} };
|
|
48629
48983
|
}
|
|
48630
48984
|
if (status === "loading" || s.isLoading === true || s.noLoading === false) loading = true;
|
|
48631
48985
|
const emptyText = str3(s.emptyText) ?? str3(s.emptySourceText);
|
|
@@ -48634,14 +48988,15 @@ function screenStateOf(services, html, opts = {}) {
|
|
|
48634
48988
|
empty = { state: "empty", ...line ? { line } : {} };
|
|
48635
48989
|
}
|
|
48636
48990
|
}
|
|
48637
|
-
|
|
48638
|
-
|
|
48991
|
+
const why = { ...opts.initial ? { initial: true } : {}, ...opts.offline ? { offline: true } : {} };
|
|
48992
|
+
if (loading) return { state: "loading", ...why };
|
|
48993
|
+
return empty ? { ...empty, ...why } : { state: "content" };
|
|
48639
48994
|
}
|
|
48640
48995
|
function readRecord(dir) {
|
|
48641
|
-
const file =
|
|
48996
|
+
const file = join46(dir, SHOT_STATES_FILE);
|
|
48642
48997
|
try {
|
|
48643
|
-
if (!
|
|
48644
|
-
const r = JSON.parse(
|
|
48998
|
+
if (!lstatSync11(file).isFile()) return {};
|
|
48999
|
+
const r = JSON.parse(readFileSync52(file, "utf8"));
|
|
48645
49000
|
return r.v === 1 && r.shots && typeof r.shots === "object" ? r.shots : {};
|
|
48646
49001
|
} catch {
|
|
48647
49002
|
return {};
|
|
@@ -48651,9 +49006,9 @@ function recordShotState(dir, screenId, png, state, now = /* @__PURE__ */ new Da
|
|
|
48651
49006
|
try {
|
|
48652
49007
|
if (!state || !realDirIn(dir, ".himi")) return;
|
|
48653
49008
|
const shots = readRecord(dir);
|
|
48654
|
-
shots[sha256Of(png)] = { screen: screenId, state: state.state, ...state.line ? { line: state.line } : {}, ...state.initial ? { initial: true } : {}, at: now.toISOString() };
|
|
49009
|
+
shots[sha256Of(png)] = { screen: screenId, state: state.state, ...state.line ? { line: state.line } : {}, ...state.initial ? { initial: true } : {}, ...state.offline ? { offline: true } : {}, at: now.toISOString() };
|
|
48655
49010
|
const kept = Object.entries(shots).sort(([, a], [, b]) => a.at < b.at ? 1 : a.at > b.at ? -1 : 0).slice(0, KEEP);
|
|
48656
|
-
writeFileAtomic(
|
|
49011
|
+
writeFileAtomic(join46(dir, SHOT_STATES_FILE), `${JSON.stringify({ v: 1, shots: Object.fromEntries(kept) }, null, 2)}
|
|
48657
49012
|
`);
|
|
48658
49013
|
} catch {
|
|
48659
49014
|
}
|
|
@@ -48664,18 +49019,27 @@ function shotStateFor(dir, image) {
|
|
|
48664
49019
|
const r = readRecord(dir)[sha256Of(image)];
|
|
48665
49020
|
if (!r || !["content", "error", "loading", "empty"].includes(r.state)) return null;
|
|
48666
49021
|
const line = typeof r.line === "string" ? quotableLine(r.line) : void 0;
|
|
48667
|
-
return { state: r.state, ...line ? { line } : {}, ...r.initial === true ? { initial: true } : {} };
|
|
49022
|
+
return { state: r.state, ...line ? { line } : {}, ...r.initial === true ? { initial: true } : {}, ...r.offline === true ? { offline: true } : {} };
|
|
48668
49023
|
} catch {
|
|
48669
49024
|
return null;
|
|
48670
49025
|
}
|
|
48671
49026
|
}
|
|
49027
|
+
function recordedShots(dir, screenId) {
|
|
49028
|
+
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);
|
|
49029
|
+
}
|
|
49030
|
+
function offlineArtifact(state) {
|
|
49031
|
+
if (!state || state.state === "content") return false;
|
|
49032
|
+
return state.offline === true || state.initial === true && state.state !== "error";
|
|
49033
|
+
}
|
|
48672
49034
|
function homeStateCaveat(state) {
|
|
48673
49035
|
if (!state || state.state === "content") return void 0;
|
|
49036
|
+
if (offlineArtifact(state)) {
|
|
49037
|
+
const shows = state.state === "loading" ? "its loading placeholders" : state.state === "empty" ? "its empty state" : "what it paints when its data can't load";
|
|
49038
|
+
return `it is an offline preview, without the site's data, so the home screen shows ${shows}`;
|
|
49039
|
+
}
|
|
48674
49040
|
const quote = state.line ? `: \u201C${state.line}\u201D` : "";
|
|
48675
49041
|
if (state.state === "error") return `the home screen shows an error${quote}`;
|
|
48676
|
-
if (state.state === "loading")
|
|
48677
|
-
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)";
|
|
48678
|
-
}
|
|
49042
|
+
if (state.state === "loading") return "the home screen is still on its loading state (its data never finished loading)";
|
|
48679
49043
|
return `the home screen shows its empty state${quote}`;
|
|
48680
49044
|
}
|
|
48681
49045
|
function withHomeCaveat(message, caveat) {
|
|
@@ -48687,11 +49051,11 @@ var SHOT_STATES_FILE, KEEP, LINE_MAX, str3, sha256Of;
|
|
|
48687
49051
|
var init_screen_state = __esm({
|
|
48688
49052
|
"src/screen-state.ts"() {
|
|
48689
49053
|
init_progress();
|
|
48690
|
-
SHOT_STATES_FILE =
|
|
49054
|
+
SHOT_STATES_FILE = join46(".himi", "shot-states.json");
|
|
48691
49055
|
KEEP = 40;
|
|
48692
49056
|
LINE_MAX = 120;
|
|
48693
49057
|
str3 = (v) => typeof v === "string" && v.trim() ? v : void 0;
|
|
48694
|
-
sha256Of = (file) => createHash25("sha256").update(
|
|
49058
|
+
sha256Of = (file) => createHash25("sha256").update(readFileSync52(file)).digest("hex");
|
|
48695
49059
|
}
|
|
48696
49060
|
});
|
|
48697
49061
|
|
|
@@ -48707,10 +49071,10 @@ __export(studio_reel_exports, {
|
|
|
48707
49071
|
utcDay: () => utcDay
|
|
48708
49072
|
});
|
|
48709
49073
|
import { createHash as createHash26 } from "node:crypto";
|
|
48710
|
-
import { lstatSync as
|
|
48711
|
-
import { basename as basename13, join as
|
|
49074
|
+
import { lstatSync as lstatSync12, readFileSync as readFileSync53 } from "node:fs";
|
|
49075
|
+
import { basename as basename13, join as join47 } from "node:path";
|
|
48712
49076
|
function loadMemory2(dir) {
|
|
48713
|
-
const m = readJson5(
|
|
49077
|
+
const m = readJson5(join47(dir, ".himi", "studio", "memory.json"));
|
|
48714
49078
|
if (!isObj3(m) || m.version !== 1 || !isObj3(m.shots) || !Array.isArray(m.timeline)) return null;
|
|
48715
49079
|
const shots = [];
|
|
48716
49080
|
for (const [id, s] of Object.entries(m.shots)) {
|
|
@@ -48747,8 +49111,8 @@ function planReel(dir) {
|
|
|
48747
49111
|
const memory = loadMemory2(dir);
|
|
48748
49112
|
const { entries } = readProgress(dir);
|
|
48749
49113
|
const summary2 = summarizeProgress(entries);
|
|
48750
|
-
const manifest = readJson5(
|
|
48751
|
-
const tokens = readJson5(
|
|
49114
|
+
const manifest = readJson5(join47(dir, "himalaya.content.json"));
|
|
49115
|
+
const tokens = readJson5(join47(dir, "tokens.json"));
|
|
48752
49116
|
const accentToken = tokens?.colors?.accentPrimary;
|
|
48753
49117
|
const accentRaw = typeof accentToken === "string" ? accentToken : accentToken?.light;
|
|
48754
49118
|
const accent = typeof accentRaw === "string" && ACCENT.test(accentRaw) ? accentRaw : DEFAULT_ACCENT;
|
|
@@ -48791,10 +49155,10 @@ function inlineImages(dir, plan, budget = REEL_BUDGET_BYTES) {
|
|
|
48791
49155
|
const verified = (f) => {
|
|
48792
49156
|
if (!HISTORY_NAME.test(f)) return null;
|
|
48793
49157
|
try {
|
|
48794
|
-
const path2 =
|
|
48795
|
-
const st =
|
|
49158
|
+
const path2 = join47(dir, HISTORY_DIR, f);
|
|
49159
|
+
const st = lstatSync12(path2);
|
|
48796
49160
|
if (!st.isFile() || st.nlink !== 1) return null;
|
|
48797
|
-
const bytes2 =
|
|
49161
|
+
const bytes2 = readFileSync53(path2);
|
|
48798
49162
|
return createHash26("sha256").update(bytes2).digest("hex") === f.slice(0, 64) && imageExtension(bytes2) === f.slice(65) ? bytes2 : null;
|
|
48799
49163
|
} catch {
|
|
48800
49164
|
return null;
|
|
@@ -48928,7 +49292,7 @@ var init_studio_reel = __esm({
|
|
|
48928
49292
|
mimeOf = (file) => MIME2[file.slice(65)] ?? "image/png";
|
|
48929
49293
|
readJson5 = (file) => {
|
|
48930
49294
|
try {
|
|
48931
|
-
return JSON.parse(
|
|
49295
|
+
return JSON.parse(readFileSync53(file, "utf8"));
|
|
48932
49296
|
} catch {
|
|
48933
49297
|
return null;
|
|
48934
49298
|
}
|
|
@@ -48952,7 +49316,7 @@ __export(milestones_exports, {
|
|
|
48952
49316
|
FIRST_LOOK_BUDGET_MS: () => FIRST_LOOK_BUDGET_MS,
|
|
48953
49317
|
INIT_SOURCE_FILE: () => INIT_SOURCE_FILE,
|
|
48954
49318
|
INIT_WATCH_IDLE_MINUTES: () => INIT_WATCH_IDLE_MINUTES,
|
|
48955
|
-
|
|
49319
|
+
LEGACY_NO_WATCH_ENV: () => LEGACY_NO_WATCH_ENV,
|
|
48956
49320
|
NO_WATCH_IGNORED_NOTE: () => NO_WATCH_IGNORED_NOTE,
|
|
48957
49321
|
REMOTE_FIRST_LOOK_SKIP: () => REMOTE_FIRST_LOOK_SKIP,
|
|
48958
49322
|
REMOTE_WATCH_SKIP: () => REMOTE_WATCH_SKIP,
|
|
@@ -48972,6 +49336,7 @@ __export(milestones_exports, {
|
|
|
48972
49336
|
defaultStartWatchDeps: () => defaultStartWatchDeps,
|
|
48973
49337
|
displayPath: () => displayPath,
|
|
48974
49338
|
doNowBlock: () => doNowBlock,
|
|
49339
|
+
draftChangeNote: () => draftChangeNote,
|
|
48975
49340
|
draftPictureGap: () => draftPictureGap,
|
|
48976
49341
|
firstLookAtInit: () => firstLookAtInit,
|
|
48977
49342
|
homePictureAtValidate: () => homePictureAtValidate,
|
|
@@ -48991,19 +49356,20 @@ __export(milestones_exports, {
|
|
|
48991
49356
|
testChangeNote: () => testChangeNote,
|
|
48992
49357
|
testPostcardCounts: () => testPostcardCounts,
|
|
48993
49358
|
testPostcardMessage: () => testPostcardMessage,
|
|
49359
|
+
watchOptOutIgnoredNote: () => watchOptOutIgnoredNote,
|
|
48994
49360
|
watchReminder: () => watchReminder,
|
|
48995
49361
|
watchUrlFor: () => watchUrlFor,
|
|
48996
49362
|
writeHandoverReel: () => writeHandoverReel
|
|
48997
49363
|
});
|
|
48998
|
-
import { createHash as createHash27, randomUUID as
|
|
49364
|
+
import { createHash as createHash27, randomUUID as randomUUID4 } from "node:crypto";
|
|
48999
49365
|
import { spawn as spawn5 } from "node:child_process";
|
|
49000
|
-
import { closeSync as
|
|
49366
|
+
import { closeSync as closeSync4, existsSync as existsSync48, lstatSync as lstatSync13, readFileSync as readFileSync54, statSync as statSync12 } from "node:fs";
|
|
49001
49367
|
import { createServer as createServer4 } from "node:net";
|
|
49002
|
-
import { basename as basename14, dirname as dirname27, isAbsolute as isAbsolute4, join as
|
|
49368
|
+
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";
|
|
49003
49369
|
function homeScreenIdOf(dir) {
|
|
49004
49370
|
let src;
|
|
49005
49371
|
try {
|
|
49006
|
-
src =
|
|
49372
|
+
src = readFileSync54(join48(dir, "dev", "index.ts"), "utf8");
|
|
49007
49373
|
} catch {
|
|
49008
49374
|
return null;
|
|
49009
49375
|
}
|
|
@@ -49012,13 +49378,13 @@ function homeScreenIdOf(dir) {
|
|
|
49012
49378
|
return id && /^[A-Za-z0-9_.\-/{}]+$/.test(id) ? id : null;
|
|
49013
49379
|
}
|
|
49014
49380
|
function homeShotPath(dir, home) {
|
|
49015
|
-
const root =
|
|
49016
|
-
const base =
|
|
49017
|
-
const shot =
|
|
49018
|
-
if (!shot.startsWith(base +
|
|
49019
|
-
for (let p = shot; p !== root && p.startsWith(root +
|
|
49381
|
+
const root = resolve42(dir);
|
|
49382
|
+
const base = resolve42(root, ".himi", "shots");
|
|
49383
|
+
const shot = resolve42(base, `${home}.png`);
|
|
49384
|
+
if (!shot.startsWith(base + sep13)) return null;
|
|
49385
|
+
for (let p = shot; p !== root && p.startsWith(root + sep13); p = dirname27(p)) {
|
|
49020
49386
|
try {
|
|
49021
|
-
if (
|
|
49387
|
+
if (lstatSync13(p).isSymbolicLink()) return null;
|
|
49022
49388
|
} catch {
|
|
49023
49389
|
}
|
|
49024
49390
|
}
|
|
@@ -49055,7 +49421,7 @@ async function renderHomeInChild(dir, home, opts) {
|
|
|
49055
49421
|
};
|
|
49056
49422
|
let child;
|
|
49057
49423
|
try {
|
|
49058
|
-
child = spawn5(process.execPath, [...opts.loader ?? [], opts.entry, "render", home, "--png", "--device", "iphone", "--content", dir, "--skip-version-check"], {
|
|
49424
|
+
child = spawn5(process.execPath, [...opts.loader ?? [], opts.entry, "render", home, "--png", "--mocks", "--device", "iphone", "--content", dir, "--skip-version-check"], {
|
|
49059
49425
|
cwd: dir,
|
|
49060
49426
|
// Which command ran it: validate's refresh is the one whose loading skeletons must not be
|
|
49061
49427
|
// journaled; init's first look is the template's honest "Starting point" and always is.
|
|
@@ -49087,7 +49453,7 @@ async function renderHomeInChild(dir, home, opts) {
|
|
|
49087
49453
|
}
|
|
49088
49454
|
let fresh = false;
|
|
49089
49455
|
try {
|
|
49090
|
-
fresh =
|
|
49456
|
+
fresh = statSync12(shot).mtimeMs >= started - 1e3;
|
|
49091
49457
|
} catch {
|
|
49092
49458
|
}
|
|
49093
49459
|
if (code === 0 && fresh) {
|
|
@@ -49121,7 +49487,7 @@ function autoPictureGate(dir) {
|
|
|
49121
49487
|
}
|
|
49122
49488
|
async function homePictureAtValidate(dir, opts) {
|
|
49123
49489
|
if (!opts.ok) return { skipped: "validate found errors, so the picture was left as it is" };
|
|
49124
|
-
if (!
|
|
49490
|
+
if (!existsSync48(join48(dir, "himalaya.content.json"))) return { skipped: "not a content package" };
|
|
49125
49491
|
const env = opts.env ?? process.env;
|
|
49126
49492
|
if (ciSet(env)) return { skipped: "the CI environment variable is set" };
|
|
49127
49493
|
if (env.NODE_TEST_CONTEXT) return { skipped: "running under `node --test`" };
|
|
@@ -49191,7 +49557,7 @@ function stillBuilding(dir, image, now) {
|
|
|
49191
49557
|
}
|
|
49192
49558
|
}
|
|
49193
49559
|
function firstLookBeforeOwnPostcard(dir, image, now) {
|
|
49194
|
-
if (!image || !
|
|
49560
|
+
if (!image || !existsSync48(image) || initDivergence(dir).state !== "diverged") return void 0;
|
|
49195
49561
|
return journal(dir, "first-look", withHomeCaveat(yourFirstLookMessage(dir), homeCaveat(dir, image)), { image, ...now ? { now } : {} });
|
|
49196
49562
|
}
|
|
49197
49563
|
function afterRenderPng(dir, input) {
|
|
@@ -49256,13 +49622,13 @@ function testChangeNote(headline2, change) {
|
|
|
49256
49622
|
}
|
|
49257
49623
|
function afterTestRun(dir, input) {
|
|
49258
49624
|
const firstLook = firstLookBeforeOwnPostcard(dir, input.homeShot, input.now);
|
|
49259
|
-
const shot = input.homeShot &&
|
|
49625
|
+
const shot = input.homeShot && existsSync48(input.homeShot) ? input.homeShot : void 0;
|
|
49260
49626
|
const caveat = shot ? homeCaveat(dir, shot) : void 0;
|
|
49261
49627
|
const counts = testPostcardMessage(input);
|
|
49262
49628
|
const headline2 = caveat ? `${counts} But ${caveat}.` : counts;
|
|
49263
49629
|
const postcard = journal(dir, "test", headline2, {
|
|
49264
49630
|
ref: input.runId,
|
|
49265
|
-
...input.homeShot &&
|
|
49631
|
+
...input.homeShot && existsSync48(input.homeShot) ? { image: input.homeShot } : {},
|
|
49266
49632
|
...input.now ? { now: input.now } : {},
|
|
49267
49633
|
unlessUnchanged: true,
|
|
49268
49634
|
changeNote: (change) => testChangeNote(headline2, change)
|
|
@@ -49283,7 +49649,7 @@ function afterBrowserTestRun(dir, input) {
|
|
|
49283
49649
|
const now = input.now ?? /* @__PURE__ */ new Date();
|
|
49284
49650
|
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) : [];
|
|
49285
49651
|
return afterTestRun(dir, {
|
|
49286
|
-
runId: `browser-${now.toISOString()}-${
|
|
49652
|
+
runId: `browser-${now.toISOString()}-${randomUUID4().slice(0, 8)}`,
|
|
49287
49653
|
screens: 1,
|
|
49288
49654
|
errors: Array.isArray(input.report.findings) ? input.report.findings.length : 0,
|
|
49289
49655
|
browserScreen: input.screen,
|
|
@@ -49296,7 +49662,7 @@ function afterBrowserTestRun(dir, input) {
|
|
|
49296
49662
|
function shotIsCurrent(shot, sourceChangedAtMs) {
|
|
49297
49663
|
if (sourceChangedAtMs === void 0) return false;
|
|
49298
49664
|
try {
|
|
49299
|
-
return
|
|
49665
|
+
return statSync12(shot).mtimeMs >= sourceChangedAtMs;
|
|
49300
49666
|
} catch {
|
|
49301
49667
|
return false;
|
|
49302
49668
|
}
|
|
@@ -49305,7 +49671,7 @@ function draftPictureGap(input) {
|
|
|
49305
49671
|
if (!input.home) return { reason: "the app's dev/index.ts names no literal homeScreenId, so there is no home screen to picture" };
|
|
49306
49672
|
if (!input.homeShot) return { reason: "the homeScreenId does not name a file under .himi/shots" };
|
|
49307
49673
|
const render = `himi render ${shellQuote(input.home)} --png${contentArg(input.content)}`;
|
|
49308
|
-
if (!
|
|
49674
|
+
if (!existsSync48(input.homeShot)) return { reason: "there is no home shot yet", render };
|
|
49309
49675
|
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 };
|
|
49310
49676
|
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 };
|
|
49311
49677
|
return null;
|
|
@@ -49316,11 +49682,14 @@ function afterDraftPush(dir, input) {
|
|
|
49316
49682
|
const gap = current || input.home === void 0 ? null : draftPictureGap(input);
|
|
49317
49683
|
const why = gap ? ` No picture: ${gap.reason}${gap.render ? ` \u2014 \`himi render ${shellQuote(input.home)} --png\` makes a current one` : ""}.` : "";
|
|
49318
49684
|
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);
|
|
49319
|
-
const
|
|
49685
|
+
const note = draftChangeNote(dir, { home: input.home ?? null, manifest: input.manifest, picture: current ? input.homeShot : void 0 });
|
|
49686
|
+
const withNote = note && `${draftLine} ${note}${why}`.length <= MAX_MESSAGE_CHARS ? `${draftLine} ${note}` : draftLine;
|
|
49687
|
+
const postcard = journal(dir, "draft", `${withNote}${why}`, {
|
|
49320
49688
|
ref: input.releaseId,
|
|
49321
49689
|
...current ? { image: input.homeShot } : {},
|
|
49322
49690
|
...input.now ? { now: input.now } : {}
|
|
49323
49691
|
});
|
|
49692
|
+
if (input.manifest) rememberDraft(dir, input.releaseId, input.manifest);
|
|
49324
49693
|
const reel = input.reel === false ? void 0 : writeHandoverReel(dir);
|
|
49325
49694
|
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`;
|
|
49326
49695
|
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}`;
|
|
@@ -49333,6 +49702,63 @@ function afterDraftPush(dir, input) {
|
|
|
49333
49702
|
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
|
|
49334
49703
|
};
|
|
49335
49704
|
}
|
|
49705
|
+
function rememberDraft(dir, releaseId, m) {
|
|
49706
|
+
try {
|
|
49707
|
+
if (!realDirIn(dir, ".himi")) return;
|
|
49708
|
+
const manifest = {
|
|
49709
|
+
screens: Object.fromEntries(Object.entries(m.screens).map(([id, x]) => [id, { descriptorHash: x.descriptorHash, bundle: x.bundle }])),
|
|
49710
|
+
bundles: Object.fromEntries(Object.entries(m.bundles).map(([id, x]) => [id, { sha256: x.sha256 }])),
|
|
49711
|
+
...m.tokensHash ? { tokensHash: m.tokensHash } : {},
|
|
49712
|
+
...m.appConfigHash ? { appConfigHash: m.appConfigHash } : {}
|
|
49713
|
+
};
|
|
49714
|
+
writeFileAtomic(join48(dir, LAST_DRAFT_FILE), `${JSON.stringify({ v: 1, releaseId, manifest })}
|
|
49715
|
+
`);
|
|
49716
|
+
} catch {
|
|
49717
|
+
}
|
|
49718
|
+
}
|
|
49719
|
+
function lastDraft(dir) {
|
|
49720
|
+
try {
|
|
49721
|
+
const file = join48(dir, LAST_DRAFT_FILE);
|
|
49722
|
+
if (!lstatSync13(file).isFile()) return null;
|
|
49723
|
+
const r = JSON.parse(readFileSync54(file, "utf8"));
|
|
49724
|
+
const m = r.manifest;
|
|
49725
|
+
if (r.v !== 1 || typeof r.releaseId !== "string" || !m || typeof m.screens !== "object" || !m.screens || typeof m.bundles !== "object" || !m.bundles) return null;
|
|
49726
|
+
return { releaseId: r.releaseId, manifest: m };
|
|
49727
|
+
} catch {
|
|
49728
|
+
return null;
|
|
49729
|
+
}
|
|
49730
|
+
}
|
|
49731
|
+
function draftChangeNote(dir, input) {
|
|
49732
|
+
try {
|
|
49733
|
+
const entries = readProgress(dir, { all: true }).entries;
|
|
49734
|
+
const previous = [...entries].reverse().find((e) => e.by === "himi" && e.milestone === "draft");
|
|
49735
|
+
const was = lastDraft(dir);
|
|
49736
|
+
const parts = [];
|
|
49737
|
+
if (input.manifest && was) {
|
|
49738
|
+
const d = diffReleases(was.manifest, input.manifest);
|
|
49739
|
+
const added = Object.keys(input.manifest.screens).filter((id) => !(id in was.manifest.screens));
|
|
49740
|
+
const removed = Object.keys(was.manifest.screens).filter((id) => !(id in input.manifest.screens));
|
|
49741
|
+
const changed = d.screens.filter((id) => !added.includes(id));
|
|
49742
|
+
const homeChanged = input.home !== null && changed.includes(input.home);
|
|
49743
|
+
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`);
|
|
49744
|
+
if (added.length) parts.push(`${plural(added.length, "screen")} added`);
|
|
49745
|
+
if (removed.length) parts.push(`${plural(removed.length, "screen")} removed`);
|
|
49746
|
+
if (d.tokens) parts.push("the design tokens changed");
|
|
49747
|
+
if (!parts.length) parts.push(d.config ? "no screen changed, the app's settings did" : "no screen changed");
|
|
49748
|
+
} else {
|
|
49749
|
+
if (!previous) return void 0;
|
|
49750
|
+
const shaOf = (i) => i && typeof i === "object" && typeof i.sha256 === "string" ? i.sha256 : void 0;
|
|
49751
|
+
const had = Array.isArray(previous.images) ? previous.images.map(shaOf).find(Boolean) : void 0;
|
|
49752
|
+
const has = input.picture && existsSync48(input.picture) ? sha256Of2(input.picture) : void 0;
|
|
49753
|
+
if (had && has) parts.push(had === has ? "the home screen looks the same" : "the home screen changed");
|
|
49754
|
+
else if (has) parts.push("a home picture this time");
|
|
49755
|
+
else return void 0;
|
|
49756
|
+
}
|
|
49757
|
+
return `Since the last draft: ${parts.join(", ")}.`;
|
|
49758
|
+
} catch {
|
|
49759
|
+
return void 0;
|
|
49760
|
+
}
|
|
49761
|
+
}
|
|
49336
49762
|
function recordInitSource(dir, opts = {}) {
|
|
49337
49763
|
try {
|
|
49338
49764
|
const at2 = realDirIn(dir, ".himi");
|
|
@@ -49345,7 +49771,7 @@ function recordInitSource(dir, opts = {}) {
|
|
|
49345
49771
|
template: opts.template && TEMPLATE_NAME.test(opts.template) ? opts.template : null,
|
|
49346
49772
|
...opts.source ? { source: opts.source } : {}
|
|
49347
49773
|
};
|
|
49348
|
-
writeFileAtomic(
|
|
49774
|
+
writeFileAtomic(join48(dir, INIT_SOURCE_FILE), `${JSON.stringify(record2, null, 2)}
|
|
49349
49775
|
`);
|
|
49350
49776
|
return true;
|
|
49351
49777
|
} catch {
|
|
@@ -49353,10 +49779,10 @@ function recordInitSource(dir, opts = {}) {
|
|
|
49353
49779
|
}
|
|
49354
49780
|
}
|
|
49355
49781
|
function readInitSource(dir) {
|
|
49356
|
-
const file =
|
|
49782
|
+
const file = join48(dir, INIT_SOURCE_FILE);
|
|
49357
49783
|
try {
|
|
49358
|
-
if (!
|
|
49359
|
-
const r = JSON.parse(
|
|
49784
|
+
if (!lstatSync13(file).isFile()) return null;
|
|
49785
|
+
const r = JSON.parse(readFileSync54(file, "utf8"));
|
|
49360
49786
|
if (r.v !== 1 || typeof r.fingerprint !== "string" || !/^[0-9a-f]{64}$/.test(r.fingerprint) || typeof r.newestMs !== "number") return null;
|
|
49361
49787
|
return {
|
|
49362
49788
|
v: 1,
|
|
@@ -49389,7 +49815,7 @@ function homeShotForPostcard(dir, opts = {}) {
|
|
|
49389
49815
|
const shot = homeShotPath(dir, home);
|
|
49390
49816
|
if (!shot) return { skipped: "the homeScreenId does not name a file under .himi/shots" };
|
|
49391
49817
|
const render = `\`himi render ${shellQuote(home)} --png\``;
|
|
49392
|
-
if (!
|
|
49818
|
+
if (!existsSync48(shot)) return { skipped: `no home shot yet \u2014 ${render} makes one, and the next postcard carries it` };
|
|
49393
49819
|
let changed;
|
|
49394
49820
|
try {
|
|
49395
49821
|
changed = newestSourceChangeMs(dir);
|
|
@@ -49397,9 +49823,22 @@ function homeShotForPostcard(dir, opts = {}) {
|
|
|
49397
49823
|
changed = void 0;
|
|
49398
49824
|
}
|
|
49399
49825
|
if (!shotIsCurrent(shot, changed)) return { skipped: `the home shot is older than the last source change \u2014 ${render} makes a current one` };
|
|
49400
|
-
|
|
49826
|
+
let pick2 = { image: shot };
|
|
49827
|
+
const landed = shotStateFor(dir, shot);
|
|
49828
|
+
if (landed && landed.state !== "content") {
|
|
49829
|
+
const shows = homeStateCaveat(landed) ?? `the home screen shows its ${landed.state} state`;
|
|
49830
|
+
const content = contentHomePictures(dir, home);
|
|
49831
|
+
const current = content.find((c) => changed !== void 0 && Date.parse(c.at) >= changed);
|
|
49832
|
+
if (!current) {
|
|
49833
|
+
const older = content[0] ? `; the newest home picture with content, ${shellQuote(displayPath(content[0].file))}, predates the last source change` : "";
|
|
49834
|
+
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`;
|
|
49835
|
+
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` };
|
|
49836
|
+
}
|
|
49837
|
+
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}` };
|
|
49838
|
+
}
|
|
49839
|
+
if (opts.repeatOk) return pick2;
|
|
49401
49840
|
try {
|
|
49402
|
-
const sha = sha256Of2(
|
|
49841
|
+
const sha = sha256Of2(pick2.image);
|
|
49403
49842
|
const entries = readProgress(dir, { all: true }).entries;
|
|
49404
49843
|
const latest = entries[entries.length - 1];
|
|
49405
49844
|
const shown2 = Array.isArray(latest?.images) && latest.images.some((i) => !!i && typeof i === "object" && i.sha256 === sha);
|
|
@@ -49407,13 +49846,31 @@ function homeShotForPostcard(dir, opts = {}) {
|
|
|
49407
49846
|
} catch {
|
|
49408
49847
|
return { skipped: "the home shot could not be read" };
|
|
49409
49848
|
}
|
|
49410
|
-
return
|
|
49849
|
+
return pick2;
|
|
49850
|
+
}
|
|
49851
|
+
function contentHomePictures(dir, home) {
|
|
49852
|
+
const out2 = [];
|
|
49853
|
+
const shot = homeShotPath(dir, home);
|
|
49854
|
+
const hist = realDirIn(dir, HISTORY_DIR) ? join48(dir, HISTORY_DIR) : null;
|
|
49855
|
+
for (const r of recordedShots(dir, home)) {
|
|
49856
|
+
if (r.state.state !== "content") continue;
|
|
49857
|
+
for (const file of [shot, hist && join48(hist, `${r.sha256}.png`)]) {
|
|
49858
|
+
try {
|
|
49859
|
+
if (!file || !lstatSync13(file).isFile() || sha256Of2(file) !== r.sha256) continue;
|
|
49860
|
+
} catch {
|
|
49861
|
+
continue;
|
|
49862
|
+
}
|
|
49863
|
+
out2.push({ file, at: r.at });
|
|
49864
|
+
break;
|
|
49865
|
+
}
|
|
49866
|
+
}
|
|
49867
|
+
return out2;
|
|
49411
49868
|
}
|
|
49412
49869
|
function readInitWatch(dir) {
|
|
49413
|
-
const file =
|
|
49870
|
+
const file = join48(dir, INIT_WATCH_FILE);
|
|
49414
49871
|
try {
|
|
49415
|
-
if (!
|
|
49416
|
-
const r = JSON.parse(
|
|
49872
|
+
if (!lstatSync13(file).isFile()) return null;
|
|
49873
|
+
const r = JSON.parse(readFileSync54(file, "utf8"));
|
|
49417
49874
|
if (r.v !== 1 || typeof r.pid !== "number" || !Number.isInteger(r.pid) || r.pid < 1) return null;
|
|
49418
49875
|
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;
|
|
49419
49876
|
if (r.root !== runRootId(dir)) return null;
|
|
@@ -49438,14 +49895,14 @@ function liveWatchRunning(dir, isAlive = pidAlive, now) {
|
|
|
49438
49895
|
}
|
|
49439
49896
|
function watchReminder(dir, opts = {}) {
|
|
49440
49897
|
try {
|
|
49441
|
-
if (!
|
|
49898
|
+
if (!existsSync48(join48(dir, "himalaya.content.json"))) return void 0;
|
|
49442
49899
|
const nowDate = opts.now ?? /* @__PURE__ */ new Date();
|
|
49443
49900
|
const live2 = liveWatch(dir, opts.isAlive, nowDate);
|
|
49444
49901
|
const now = nowDate.getTime();
|
|
49445
|
-
const file =
|
|
49902
|
+
const file = join48(dir, REMINDERS_FILE);
|
|
49446
49903
|
let last = Number.NaN;
|
|
49447
49904
|
try {
|
|
49448
|
-
if (
|
|
49905
|
+
if (lstatSync13(file).isFile()) last = Date.parse(JSON.parse(readFileSync54(file, "utf8")).watch ?? "");
|
|
49449
49906
|
} catch {
|
|
49450
49907
|
}
|
|
49451
49908
|
if (Number.isFinite(last) && now - last >= 0 && now - last < WATCH_REMINDER_EVERY_MS) return void 0;
|
|
@@ -49456,11 +49913,14 @@ function watchReminder(dir, opts = {}) {
|
|
|
49456
49913
|
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`;
|
|
49457
49914
|
}
|
|
49458
49915
|
const content = opts.content ?? displayPath(dir);
|
|
49459
|
-
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(
|
|
49916
|
+
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`;
|
|
49460
49917
|
} catch {
|
|
49461
49918
|
return void 0;
|
|
49462
49919
|
}
|
|
49463
49920
|
}
|
|
49921
|
+
function watchOptOutIgnoredNote(given) {
|
|
49922
|
+
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`;
|
|
49923
|
+
}
|
|
49464
49924
|
function watchUrlFor(port, app) {
|
|
49465
49925
|
const origin = `http://127.0.0.1:${port}`;
|
|
49466
49926
|
return { url: `${origin}/preview/a/${encodeURIComponent(app)}`, board: `${origin}/__himi/studio/` };
|
|
@@ -49487,13 +49947,13 @@ function absoluteLoaderArgs(args, resolveSpec = (spec) => import.meta.resolve(sp
|
|
|
49487
49947
|
async function startWatchAtInit(dir, opts) {
|
|
49488
49948
|
if (opts.source !== "bundled") return { skipped: REMOTE_WATCH_SKIP };
|
|
49489
49949
|
const env = opts.env ?? process.env;
|
|
49490
|
-
if (envOn(env[NO_WATCH_ENV])) return { skipped: `${NO_WATCH_ENV} is set, so nothing is left running after init` };
|
|
49491
49950
|
const ci = ciSet(env);
|
|
49492
49951
|
const underTest = !!env.NODE_TEST_CONTEXT;
|
|
49493
49952
|
if (opts.noWatch && (ci || underTest)) return { skipped: "--no-watch" };
|
|
49494
49953
|
if (ci) return { skipped: "the CI environment variable is set, so nothing is left running after init" };
|
|
49495
49954
|
if (underTest) return { skipped: "running under `node --test`, so nothing is left running after init" };
|
|
49496
|
-
const
|
|
49955
|
+
const given = [...opts.noWatch ? ["--no-watch"] : [], ...envOn(env[LEGACY_NO_WATCH_ENV]) ? [LEGACY_NO_WATCH_ENV] : []];
|
|
49956
|
+
const note = given.length ? { note: watchOptOutIgnoredNote(given) } : {};
|
|
49497
49957
|
const deps = { ...defaultStartWatchDeps, ...opts.deps };
|
|
49498
49958
|
const now = opts.now ?? /* @__PURE__ */ new Date();
|
|
49499
49959
|
const live2 = liveWatch(dir, deps.isAlive, now);
|
|
@@ -49512,14 +49972,14 @@ async function startWatchAtInit(dir, opts) {
|
|
|
49512
49972
|
{ cwd: dir, detached: true, stdio: ["ignore", fd, fd], env: { ...env, HIMI_NO_UPDATE_CHECK: "1" } }
|
|
49513
49973
|
);
|
|
49514
49974
|
} finally {
|
|
49515
|
-
|
|
49975
|
+
closeSync4(fd);
|
|
49516
49976
|
}
|
|
49517
49977
|
child.once("error", () => {
|
|
49518
49978
|
});
|
|
49519
49979
|
child.unref();
|
|
49520
49980
|
if (!child.pid) return { skipped: "the live preview did not start (no process) \u2014 start it yourself: `himi run --watch --content <dir> &`", ...note };
|
|
49521
49981
|
const record2 = { v: 1, pid: child.pid, root: runRootId(dir), url, board, log: WATCH_LOG, startedAt: now.toISOString() };
|
|
49522
|
-
if (realDirIn(dir, ".himi")) writeFileAtomic(
|
|
49982
|
+
if (realDirIn(dir, ".himi")) writeFileAtomic(join48(dir, INIT_WATCH_FILE), `${JSON.stringify(record2, null, 2)}
|
|
49523
49983
|
`);
|
|
49524
49984
|
return { started: true, pid: child.pid, url, board, log: WATCH_LOG, ...note };
|
|
49525
49985
|
} catch (e) {
|
|
@@ -49534,7 +49994,7 @@ function watchRunning(w) {
|
|
|
49534
49994
|
function doNowBlock(opts) {
|
|
49535
49995
|
const content = displayPath(opts.dir, opts.cwd);
|
|
49536
49996
|
const home = opts.home ?? "<homeScreenId>";
|
|
49537
|
-
const board =
|
|
49997
|
+
const board = join48(content, ".himi", "studio", "index.html");
|
|
49538
49998
|
const running = watchRunning(opts.watch);
|
|
49539
49999
|
const who = running?.by === "init" ? "init started it" : "already running";
|
|
49540
50000
|
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)`;
|
|
@@ -49553,7 +50013,7 @@ function writeHandoverReel(dir) {
|
|
|
49553
50013
|
try {
|
|
49554
50014
|
if (!realDirIn(dir, ".himi")) return { skipped: ".himi is not a real directory inside the package (a symlink?), so no making-of was written" };
|
|
49555
50015
|
const { html } = buildReel(dir);
|
|
49556
|
-
const file =
|
|
50016
|
+
const file = join48(dir, ".himi", "making-of.html");
|
|
49557
50017
|
writeFileAtomic(file, html);
|
|
49558
50018
|
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` };
|
|
49559
50019
|
} catch (e) {
|
|
@@ -49561,11 +50021,11 @@ function writeHandoverReel(dir) {
|
|
|
49561
50021
|
}
|
|
49562
50022
|
}
|
|
49563
50023
|
function afterHandoverPostcard(dir, opts = {}) {
|
|
49564
|
-
const file =
|
|
50024
|
+
const file = join48(dir, ".himi", "making-of.html");
|
|
49565
50025
|
let fresh = false;
|
|
49566
50026
|
try {
|
|
49567
|
-
const st =
|
|
49568
|
-
fresh = st.isFile() && !st.isSymbolicLink() && st.mtimeMs >=
|
|
50027
|
+
const st = lstatSync13(file);
|
|
50028
|
+
fresh = st.isFile() && !st.isSymbolicLink() && st.mtimeMs >= statSync12(join48(dir, ".himi", "progress.jsonl")).mtimeMs;
|
|
49569
50029
|
} catch {
|
|
49570
50030
|
fresh = false;
|
|
49571
50031
|
}
|
|
@@ -49573,13 +50033,14 @@ function afterHandoverPostcard(dir, opts = {}) {
|
|
|
49573
50033
|
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})`];
|
|
49574
50034
|
return { makingOf, next };
|
|
49575
50035
|
}
|
|
49576
|
-
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,
|
|
50036
|
+
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;
|
|
49577
50037
|
var init_milestones = __esm({
|
|
49578
50038
|
"src/milestones.ts"() {
|
|
49579
50039
|
init_app_source();
|
|
49580
50040
|
init_preview();
|
|
49581
50041
|
init_run_identity();
|
|
49582
50042
|
init_progress();
|
|
50043
|
+
init_run_watch();
|
|
49583
50044
|
init_screen_state();
|
|
49584
50045
|
init_shell_quote();
|
|
49585
50046
|
init_studio_reel();
|
|
@@ -49595,12 +50056,13 @@ var init_milestones = __esm({
|
|
|
49595
50056
|
STILL_BUILDING_MESSAGE = "Still building: here's the home screen now.";
|
|
49596
50057
|
yourFirstLookMessage = (dir) => `First look at your app: ${appName(dir)}'s home screen, with your changes.`;
|
|
49597
50058
|
homeCaveat = (dir, image) => homeStateCaveat(shotStateFor(dir, image));
|
|
49598
|
-
|
|
50059
|
+
LAST_DRAFT_FILE = join48(".himi", "last-draft.json");
|
|
50060
|
+
INIT_SOURCE_FILE = join48(".himi", "init-source.json");
|
|
49599
50061
|
STARTING_POINT_REF = "starting-point";
|
|
49600
50062
|
TEMPLATE_NAME = /^[A-Za-z0-9._-]{1,64}$/;
|
|
49601
|
-
sha256Of2 = (file) => createHash27("sha256").update(
|
|
50063
|
+
sha256Of2 = (file) => createHash27("sha256").update(readFileSync54(file)).digest("hex");
|
|
49602
50064
|
WATCH_REMINDER_EVERY_MS = 10 * 60 * 1e3;
|
|
49603
|
-
REMINDERS_FILE =
|
|
50065
|
+
REMINDERS_FILE = join48(".himi", "reminders.json");
|
|
49604
50066
|
pidAlive = (pid) => {
|
|
49605
50067
|
try {
|
|
49606
50068
|
process.kill(pid, 0);
|
|
@@ -49609,12 +50071,12 @@ var init_milestones = __esm({
|
|
|
49609
50071
|
return e.code === "EPERM";
|
|
49610
50072
|
}
|
|
49611
50073
|
};
|
|
49612
|
-
INIT_WATCH_FILE =
|
|
50074
|
+
INIT_WATCH_FILE = join48(".himi", "watch.json");
|
|
49613
50075
|
INIT_WATCH_STARTING_MS = 5 * 60 * 1e3;
|
|
49614
50076
|
INIT_WATCH_IDLE_MINUTES = 30;
|
|
49615
50077
|
WATCH_LOG = ".himi/watch.log";
|
|
49616
|
-
NO_WATCH_IGNORED_NOTE = "--no-watch
|
|
49617
|
-
|
|
50078
|
+
NO_WATCH_IGNORED_NOTE = watchOptOutIgnoredNote(["--no-watch"]);
|
|
50079
|
+
LEGACY_NO_WATCH_ENV = "HIMI_NO_WATCH";
|
|
49618
50080
|
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";
|
|
49619
50081
|
listenOn = (port) => new Promise((done) => {
|
|
49620
50082
|
const srv = createServer4();
|
|
@@ -50076,8 +50538,8 @@ __export(template_trim_exports, {
|
|
|
50076
50538
|
routeMatcher: () => routeMatcher,
|
|
50077
50539
|
runTemplateTrim: () => runTemplateTrim
|
|
50078
50540
|
});
|
|
50079
|
-
import { cpSync, existsSync as
|
|
50080
|
-
import { dirname as dirname28, join as
|
|
50541
|
+
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";
|
|
50542
|
+
import { dirname as dirname28, join as join49 } from "node:path";
|
|
50081
50543
|
function routeMatcher(pattern) {
|
|
50082
50544
|
const detail = /^\^([A-Za-z0-9_]+)\(\?:\[_\/\]\(\.\+\)\)\?\$$/.exec(pattern);
|
|
50083
50545
|
if (detail) {
|
|
@@ -50771,20 +51233,20 @@ function homeModulesPass(app, kept, edited) {
|
|
|
50771
51233
|
return { text: patched.src, dropped: patched.droppedModules };
|
|
50772
51234
|
}
|
|
50773
51235
|
function listWorkers(dir) {
|
|
50774
|
-
const root =
|
|
50775
|
-
if (!
|
|
50776
|
-
const dirs = readdirSync26(root).filter((n) =>
|
|
51236
|
+
const root = join49(dir, "tier5-src");
|
|
51237
|
+
if (!existsSync49(root)) return { dirs: [], files: {} };
|
|
51238
|
+
const dirs = readdirSync26(root).filter((n) => statSync13(join49(root, n)).isDirectory()).sort();
|
|
50777
51239
|
const files = {};
|
|
50778
51240
|
const walk2 = (abs, rel) => {
|
|
50779
51241
|
for (const name of readdirSync26(abs)) {
|
|
50780
51242
|
if (name === "node_modules") continue;
|
|
50781
|
-
const p =
|
|
51243
|
+
const p = join49(abs, name);
|
|
50782
51244
|
const r = `${rel}/${name}`;
|
|
50783
|
-
if (
|
|
50784
|
-
else if (/\.(?:[cm]?[jt]sx?|json)$/.test(name)) files[r] =
|
|
51245
|
+
if (statSync13(p).isDirectory()) walk2(p, r);
|
|
51246
|
+
else if (/\.(?:[cm]?[jt]sx?|json)$/.test(name)) files[r] = readFileSync55(p, "utf8");
|
|
50785
51247
|
}
|
|
50786
51248
|
};
|
|
50787
|
-
for (const d of dirs) walk2(
|
|
51249
|
+
for (const d of dirs) walk2(join49(root, d), `tier5-src/${d}`);
|
|
50788
51250
|
return { dirs, files };
|
|
50789
51251
|
}
|
|
50790
51252
|
function csv(v) {
|
|
@@ -50807,16 +51269,16 @@ async function runTemplateTrim(req, deps) {
|
|
|
50807
51269
|
links = req.links;
|
|
50808
51270
|
}
|
|
50809
51271
|
const dir = req.dir;
|
|
50810
|
-
const devPath =
|
|
50811
|
-
const configPath =
|
|
50812
|
-
if (!
|
|
51272
|
+
const devPath = join49(dir, "dev/index.ts");
|
|
51273
|
+
const configPath = join49(dir, "config.ts");
|
|
51274
|
+
if (!existsSync49(devPath) || !existsSync49(configPath)) {
|
|
50813
51275
|
return refuse3(new TrimRefusal(`${dir} has no dev/index.ts + config.ts \u2014 point --content at an app scaffolded with \`himi init --template\``));
|
|
50814
51276
|
}
|
|
50815
51277
|
if (isTemplateSourceTree(dir)) {
|
|
50816
51278
|
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`));
|
|
50817
51279
|
}
|
|
50818
|
-
const dev =
|
|
50819
|
-
const config =
|
|
51280
|
+
const dev = readFileSync55(devPath, "utf8");
|
|
51281
|
+
const config = readFileSync55(configPath, "utf8");
|
|
50820
51282
|
const workers = listWorkers(dir);
|
|
50821
51283
|
let plan;
|
|
50822
51284
|
try {
|
|
@@ -50826,34 +51288,34 @@ async function runTemplateTrim(req, deps) {
|
|
|
50826
51288
|
throw e;
|
|
50827
51289
|
}
|
|
50828
51290
|
if (req.dryRun) return { code: 0, result: { ok: true, dryRun: true, ...plan.report, validate: null } };
|
|
50829
|
-
const hadHimi =
|
|
50830
|
-
const backupRel =
|
|
50831
|
-
const backup =
|
|
51291
|
+
const hadHimi = existsSync49(join49(dir, ".himi"));
|
|
51292
|
+
const backupRel = join49(".himi", "trim-backup", (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-"));
|
|
51293
|
+
const backup = join49(dir, backupRel);
|
|
50832
51294
|
const originals = [[devPath, "dev/index.ts"], [configPath, "config.ts"]];
|
|
50833
51295
|
const moved = [];
|
|
50834
51296
|
const restore = () => {
|
|
50835
51297
|
for (const [path2, rel] of originals) {
|
|
50836
|
-
const original =
|
|
50837
|
-
if (!
|
|
51298
|
+
const original = readFileSync55(join49(backup, rel));
|
|
51299
|
+
if (!existsSync49(path2) || !readFileSync55(path2).equals(original)) writeFileSync19(path2, original);
|
|
50838
51300
|
}
|
|
50839
|
-
for (const name of [...moved].reverse())
|
|
51301
|
+
for (const name of [...moved].reverse()) renameSync6(join49(backup, "tier5-src", name), join49(dir, "tier5-src", name));
|
|
50840
51302
|
moved.length = 0;
|
|
50841
51303
|
};
|
|
50842
51304
|
const apply = () => {
|
|
50843
|
-
const moveDir = deps.moveDir ??
|
|
51305
|
+
const moveDir = deps.moveDir ?? renameSync6;
|
|
50844
51306
|
for (const name of plan.removedWorkers) {
|
|
50845
|
-
moveDir(
|
|
51307
|
+
moveDir(join49(dir, "tier5-src", name), join49(backup, "tier5-src", name));
|
|
50846
51308
|
moved.push(name);
|
|
50847
51309
|
}
|
|
50848
|
-
if (plan.dev !== dev)
|
|
50849
|
-
if (plan.config !== config)
|
|
51310
|
+
if (plan.dev !== dev) writeFileSync19(devPath, plan.dev);
|
|
51311
|
+
if (plan.config !== config) writeFileSync19(configPath, plan.config);
|
|
50850
51312
|
};
|
|
50851
51313
|
const dropBackup = () => {
|
|
50852
|
-
|
|
50853
|
-
for (const d of [dirname28(backup),
|
|
50854
|
-
if (d ===
|
|
51314
|
+
rmSync12(backup, { recursive: true, force: true });
|
|
51315
|
+
for (const d of [dirname28(backup), join49(dir, ".himi")]) {
|
|
51316
|
+
if (d === join49(dir, ".himi") && hadHimi) break;
|
|
50855
51317
|
try {
|
|
50856
|
-
if (!readdirSync26(d).length)
|
|
51318
|
+
if (!readdirSync26(d).length) rmSync12(d, { recursive: true });
|
|
50857
51319
|
} catch {
|
|
50858
51320
|
}
|
|
50859
51321
|
}
|
|
@@ -50877,9 +51339,9 @@ async function runTemplateTrim(req, deps) {
|
|
|
50877
51339
|
return { code: 1, result: { ok: false, error: `${error} (${message}); every file was restored`, rolledBack: true, ...base(validate4) } };
|
|
50878
51340
|
};
|
|
50879
51341
|
try {
|
|
50880
|
-
|
|
50881
|
-
for (const [path2, rel] of originals) cpSync(path2,
|
|
50882
|
-
if (plan.removedWorkers.length)
|
|
51342
|
+
mkdirSync24(join49(backup, "dev"), { recursive: true });
|
|
51343
|
+
for (const [path2, rel] of originals) cpSync(path2, join49(backup, rel));
|
|
51344
|
+
if (plan.removedWorkers.length) mkdirSync24(join49(backup, "tier5-src"), { recursive: true });
|
|
50883
51345
|
} catch (e) {
|
|
50884
51346
|
dropBackup();
|
|
50885
51347
|
return { code: 1, result: { ok: false, error: `could not back up the app before trimming it (${e.message}); nothing was changed`, rolledBack: true, ...base() } };
|
|
@@ -50892,7 +51354,7 @@ async function runTemplateTrim(req, deps) {
|
|
|
50892
51354
|
let failure;
|
|
50893
51355
|
let validate3;
|
|
50894
51356
|
try {
|
|
50895
|
-
const written = parseApp(
|
|
51357
|
+
const written = parseApp(readFileSync55(devPath, "utf8"), readFileSync55(configPath, "utf8"));
|
|
50896
51358
|
const leftovers = leftoverNavRefs(written.nav.value, plan.removedIds);
|
|
50897
51359
|
if (leftovers.length) failure = { error: "navigation still names screens this trim removed", leftovers };
|
|
50898
51360
|
else {
|
|
@@ -51215,13 +51677,13 @@ __export(eject_exports, {
|
|
|
51215
51677
|
EJECTABLE: () => EJECTABLE,
|
|
51216
51678
|
ejectModule: () => ejectModule
|
|
51217
51679
|
});
|
|
51218
|
-
import { existsSync as
|
|
51219
|
-
import { dirname as dirname29, join as
|
|
51680
|
+
import { existsSync as existsSync50, mkdirSync as mkdirSync25, readFileSync as readFileSync56, writeFileSync as writeFileSync20 } from "node:fs";
|
|
51681
|
+
import { dirname as dirname29, join as join50, relative as relative10, resolve as resolve43, sep as sep14 } from "node:path";
|
|
51220
51682
|
function locate(repoRootOrCwd, mod) {
|
|
51221
|
-
const inRepo =
|
|
51222
|
-
if (
|
|
51223
|
-
const dist =
|
|
51224
|
-
if (
|
|
51683
|
+
const inRepo = resolve43(repoRootOrCwd, `stdlib/flows/${mod}/src/index.ts`);
|
|
51684
|
+
if (existsSync50(inRepo)) return { path: inRepo, form: "source" };
|
|
51685
|
+
const dist = resolve43(repoRootOrCwd, `node_modules/@wix/himalaya/dist/${mod}.mjs`);
|
|
51686
|
+
if (existsSync50(dist)) return { path: dist, form: "bundle" };
|
|
51225
51687
|
return null;
|
|
51226
51688
|
}
|
|
51227
51689
|
function specifiersFor(mod) {
|
|
@@ -51245,8 +51707,8 @@ function ejectModule(opts) {
|
|
|
51245
51707
|
};
|
|
51246
51708
|
}
|
|
51247
51709
|
const ext = found.form === "source" ? "ts" : "mjs";
|
|
51248
|
-
const target =
|
|
51249
|
-
|
|
51710
|
+
const target = join50(contentDir2, "tier5-src", "_ejected", `${mod}.${ext}`);
|
|
51711
|
+
mkdirSync25(dirname29(target), { recursive: true });
|
|
51250
51712
|
const banner = `// EJECTED from @wix/himalaya/${mod}. This app owns this file now.
|
|
51251
51713
|
//
|
|
51252
51714
|
// Central fixes to @wix/himalaya/${mod} NO LONGER REACH THIS APP \u2014 including any
|
|
@@ -51259,17 +51721,17 @@ function ejectModule(opts) {
|
|
|
51259
51721
|
// gate still applies. Ejecting buys layout and flow, never the floor.
|
|
51260
51722
|
|
|
51261
51723
|
`;
|
|
51262
|
-
const normalized =
|
|
51724
|
+
const normalized = readFileSync56(found.path, "utf8").replace(
|
|
51263
51725
|
/(["'])@himalaya\/([a-z0-9-]+(?:\/[a-z0-9-]+)?)\1/g,
|
|
51264
51726
|
(_m, q, sub) => `${q}@wix/himalaya/${sub}${q}`
|
|
51265
51727
|
);
|
|
51266
|
-
|
|
51728
|
+
writeFileSync20(target, banner + normalized);
|
|
51267
51729
|
const rewired = [];
|
|
51268
51730
|
for (const f of files) {
|
|
51269
|
-
if (!
|
|
51270
|
-
const before =
|
|
51731
|
+
if (!existsSync50(f)) continue;
|
|
51732
|
+
const before = readFileSync56(f, "utf8");
|
|
51271
51733
|
let after = before;
|
|
51272
|
-
let rel = relative10(dirname29(f), target).split(
|
|
51734
|
+
let rel = relative10(dirname29(f), target).split(sep14).join("/").replace(/\.tsx?$/, ".js");
|
|
51273
51735
|
if (!rel.startsWith(".")) rel = `./${rel}`;
|
|
51274
51736
|
for (const spec of specifiersFor(mod)) {
|
|
51275
51737
|
const q = spec.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
@@ -51279,7 +51741,7 @@ function ejectModule(opts) {
|
|
|
51279
51741
|
);
|
|
51280
51742
|
}
|
|
51281
51743
|
if (after !== before) {
|
|
51282
|
-
|
|
51744
|
+
writeFileSync20(f, after);
|
|
51283
51745
|
rewired.push(f);
|
|
51284
51746
|
}
|
|
51285
51747
|
}
|
|
@@ -51387,8 +51849,8 @@ var init_app_map = __esm({
|
|
|
51387
51849
|
});
|
|
51388
51850
|
|
|
51389
51851
|
// src/wix-writes.ts
|
|
51390
|
-
import { existsSync as
|
|
51391
|
-
import { join as
|
|
51852
|
+
import { existsSync as existsSync51, readFileSync as readFileSync57 } from "node:fs";
|
|
51853
|
+
import { join as join51 } from "node:path";
|
|
51392
51854
|
function isProvenRead(method, path2) {
|
|
51393
51855
|
if (/[#\x00-\x1F\x7F]/.test(path2)) return false;
|
|
51394
51856
|
const verb = method.toUpperCase();
|
|
@@ -51506,11 +51968,11 @@ function parseDataAccess(text8, from) {
|
|
|
51506
51968
|
}
|
|
51507
51969
|
function readDataAccess(dir) {
|
|
51508
51970
|
for (const name of ["SPEC.md", "MOBILE-UX.md"]) {
|
|
51509
|
-
const p =
|
|
51510
|
-
if (!
|
|
51971
|
+
const p = join51(dir, name);
|
|
51972
|
+
if (!existsSync51(p)) continue;
|
|
51511
51973
|
let text8;
|
|
51512
51974
|
try {
|
|
51513
|
-
text8 =
|
|
51975
|
+
text8 = readFileSync57(p, "utf8");
|
|
51514
51976
|
} catch {
|
|
51515
51977
|
continue;
|
|
51516
51978
|
}
|
|
@@ -51623,14 +52085,14 @@ __export(test_exports, {
|
|
|
51623
52085
|
scopeDescriptors: () => scopeDescriptors,
|
|
51624
52086
|
writeRecordedMocks: () => writeRecordedMocks
|
|
51625
52087
|
});
|
|
51626
|
-
import { existsSync as
|
|
51627
|
-
import { join as
|
|
52088
|
+
import { existsSync as existsSync52, mkdirSync as mkdirSync26, readFileSync as readFileSync58, writeFileSync as writeFileSync21 } from "node:fs";
|
|
52089
|
+
import { join as join52, resolve as resolve44 } from "node:path";
|
|
51628
52090
|
function readTestConfig(dir) {
|
|
51629
|
-
const p =
|
|
51630
|
-
if (!
|
|
52091
|
+
const p = resolve44(dir, TEST_CONFIG_FILE);
|
|
52092
|
+
if (!existsSync52(p)) return {};
|
|
51631
52093
|
let parsed;
|
|
51632
52094
|
try {
|
|
51633
|
-
parsed = JSON.parse(
|
|
52095
|
+
parsed = JSON.parse(readFileSync58(p, "utf8"));
|
|
51634
52096
|
} catch (e) {
|
|
51635
52097
|
throw new Error(`${TEST_CONFIG_FILE} is not valid JSON \u2014 ${e.message}`);
|
|
51636
52098
|
}
|
|
@@ -51677,7 +52139,7 @@ function readTestConfig(dir) {
|
|
|
51677
52139
|
function readNetRules(path2) {
|
|
51678
52140
|
let parsed;
|
|
51679
52141
|
try {
|
|
51680
|
-
parsed = JSON.parse(
|
|
52142
|
+
parsed = JSON.parse(readFileSync58(path2, "utf8"));
|
|
51681
52143
|
} catch (e) {
|
|
51682
52144
|
throw new Error(`--net ${path2} could not be read as JSON \u2014 ${e.message}`);
|
|
51683
52145
|
}
|
|
@@ -51697,8 +52159,8 @@ function readNetRules(path2) {
|
|
|
51697
52159
|
}
|
|
51698
52160
|
function defaultNetRules(dir) {
|
|
51699
52161
|
for (const rel of ["dev/net-mocks.json", "tests/net-mocks.json"]) {
|
|
51700
|
-
const p =
|
|
51701
|
-
if (
|
|
52162
|
+
const p = resolve44(dir, rel);
|
|
52163
|
+
if (existsSync52(p)) {
|
|
51702
52164
|
const rules = readNetRules(p);
|
|
51703
52165
|
if (rules.length) return { rules, from: rel };
|
|
51704
52166
|
}
|
|
@@ -51714,7 +52176,7 @@ function readCannedResponses() {
|
|
|
51714
52176
|
];
|
|
51715
52177
|
for (const url of candidates) {
|
|
51716
52178
|
try {
|
|
51717
|
-
const parsed = JSON.parse(
|
|
52179
|
+
const parsed = JSON.parse(readFileSync58(url, "utf8"));
|
|
51718
52180
|
const byKey = {};
|
|
51719
52181
|
for (const [k, v] of Object.entries(parsed)) {
|
|
51720
52182
|
if (!k.startsWith("//")) byKey[k] = v;
|
|
@@ -51801,11 +52263,11 @@ function writeRecordedMocks(opts) {
|
|
|
51801
52263
|
};
|
|
51802
52264
|
}
|
|
51803
52265
|
const fs = opts.io ?? {
|
|
51804
|
-
mkdir: (p) =>
|
|
51805
|
-
write: (p, s) =>
|
|
52266
|
+
mkdir: (p) => mkdirSync26(p, { recursive: true }),
|
|
52267
|
+
write: (p, s) => writeFileSync21(p, s)
|
|
51806
52268
|
};
|
|
51807
|
-
fs.mkdir(
|
|
51808
|
-
fs.write(
|
|
52269
|
+
fs.mkdir(join52(opts.dir, "dev"));
|
|
52270
|
+
fs.write(join52(opts.dir, "dev", "net-mocks.json"), JSON.stringify(opts.mocks, null, 2) + "\n");
|
|
51809
52271
|
return { ok: true, rules, to: "dev/net-mocks.json" };
|
|
51810
52272
|
}
|
|
51811
52273
|
function scopeDescriptors(descriptors, requestedIds, invalidScreens) {
|
|
@@ -51816,6 +52278,8 @@ async function runShotsPass(opts) {
|
|
|
51816
52278
|
const scheme = opts.scheme ?? "light";
|
|
51817
52279
|
const shots = [];
|
|
51818
52280
|
for (const baked of opts.descriptors) {
|
|
52281
|
+
let unserved = 0;
|
|
52282
|
+
const isUnserved = opts.deps.isUnserved;
|
|
51819
52283
|
const state = baked.bundleJs ? await opts.deps.bootAndSnapshotState({
|
|
51820
52284
|
screenId: baked.screenId,
|
|
51821
52285
|
descriptor: baked.descriptor,
|
|
@@ -51824,10 +52288,20 @@ async function runShotsPass(opts) {
|
|
|
51824
52288
|
rules: opts.layers.rules ?? null,
|
|
51825
52289
|
...opts.layers.cmsSeed ? { cmsSeed: opts.layers.cmsSeed } : {},
|
|
51826
52290
|
...opts.layers.canned ? { canned: opts.layers.canned } : {},
|
|
51827
|
-
...opts.budgetMs ? { budgetMs: opts.budgetMs } : {}
|
|
52291
|
+
...opts.budgetMs ? { budgetMs: opts.budgetMs } : {},
|
|
52292
|
+
...isUnserved ? {
|
|
52293
|
+
transportWrap: (call) => async (method, path2, body) => {
|
|
52294
|
+
try {
|
|
52295
|
+
return await call(method, path2, body);
|
|
52296
|
+
} catch (e) {
|
|
52297
|
+
if (isUnserved(e)) unserved++;
|
|
52298
|
+
throw e;
|
|
52299
|
+
}
|
|
52300
|
+
}
|
|
52301
|
+
} : {}
|
|
51828
52302
|
}) : null;
|
|
51829
52303
|
const rr = await opts.deps.renderDescriptorWeb(baked.descriptor, opts.tokens, scheme, false, state, opts.width);
|
|
51830
|
-
if (rr.renderable && rr.html) shots.push({ screenId: baked.screenId, html: rr.html, state: screenStateOf(state, rr.html) });
|
|
52304
|
+
if (rr.renderable && rr.html) shots.push({ screenId: baked.screenId, html: rr.html, state: screenStateOf(state, rr.html, { offline: unserved > 0 }) });
|
|
51831
52305
|
}
|
|
51832
52306
|
const pngs = await opts.deps.renderPngBatch(shots.map((x) => ({ html: x.html, scheme, tokens: opts.tokens, ...opts.width ? { width: opts.width } : {} })));
|
|
51833
52307
|
if (pngs === null) return { dir: opts.outDir, written: [], unavailable: true };
|
|
@@ -51881,7 +52355,7 @@ async function runCrawl(opts) {
|
|
|
51881
52355
|
for (const d of list) if (d.screenId !== rep.screenId) coveredBy.set(d.screenId, rep.screenId);
|
|
51882
52356
|
}
|
|
51883
52357
|
}
|
|
51884
|
-
const net = opts.offline ? null : opts.netPath ? { rules: readNetRules(
|
|
52358
|
+
const net = opts.offline ? null : opts.netPath ? { rules: readNetRules(resolve44(opts.netPath)), from: opts.netPath } : defaultNetRules(opts.dir);
|
|
51885
52359
|
const suppressions = opts.config?.skip ?? [];
|
|
51886
52360
|
const suppressed = [];
|
|
51887
52361
|
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));
|
|
@@ -52311,14 +52785,14 @@ var mobile_ux_lint_exports = {};
|
|
|
52311
52785
|
__export(mobile_ux_lint_exports, {
|
|
52312
52786
|
mobileUxIssues: () => mobileUxIssues
|
|
52313
52787
|
});
|
|
52314
|
-
import { existsSync as
|
|
52315
|
-
import { join as
|
|
52788
|
+
import { existsSync as existsSync53, readFileSync as readFileSync59 } from "node:fs";
|
|
52789
|
+
import { join as join53 } from "node:path";
|
|
52316
52790
|
function mobileUxIssues(dir, strict) {
|
|
52317
|
-
const file =
|
|
52318
|
-
if (!
|
|
52791
|
+
const file = join53(dir, "MOBILE-UX.md");
|
|
52792
|
+
if (!existsSync53(file)) return [];
|
|
52319
52793
|
let text8;
|
|
52320
52794
|
try {
|
|
52321
|
-
text8 =
|
|
52795
|
+
text8 = readFileSync59(file, "utf8");
|
|
52322
52796
|
} catch {
|
|
52323
52797
|
return [];
|
|
52324
52798
|
}
|
|
@@ -53999,8 +54473,8 @@ __export(functions_exports, {
|
|
|
53999
54473
|
resolveFunctionsTarget: () => resolveFunctionsTarget,
|
|
54000
54474
|
runRemoteFunctionOperation: () => runRemoteFunctionOperation
|
|
54001
54475
|
});
|
|
54002
|
-
import { readFileSync as
|
|
54003
|
-
import { join as
|
|
54476
|
+
import { readFileSync as readFileSync60, existsSync as existsSync54 } from "node:fs";
|
|
54477
|
+
import { join as join54, resolve as resolve45 } from "node:path";
|
|
54004
54478
|
function resolveFunctionsTarget(input) {
|
|
54005
54479
|
const explicitUrl = input.functionsUrl?.trim();
|
|
54006
54480
|
if (explicitUrl) return { mode: "remote", baseUrl: explicitUrl };
|
|
@@ -54012,16 +54486,16 @@ function resolveFunctionsTarget(input) {
|
|
|
54012
54486
|
return { mode: "local" };
|
|
54013
54487
|
}
|
|
54014
54488
|
async function loadFunctionsApp(dir) {
|
|
54015
|
-
const loaded = await loadContent(
|
|
54489
|
+
const loaded = await loadContent(resolve45(dir));
|
|
54016
54490
|
const config = loaded.module.config;
|
|
54017
54491
|
if (config.kind !== "functions" || !Array.isArray(config.functions?.functions)) {
|
|
54018
54492
|
throw new Error(`content package "${dir}" is not a functions-only app`);
|
|
54019
54493
|
}
|
|
54020
54494
|
const sources = {};
|
|
54021
54495
|
for (const definition of config.functions.functions) {
|
|
54022
|
-
const source = [".ts", ".js", ".mts", ".mjs"].map((extension) =>
|
|
54496
|
+
const source = [".ts", ".js", ".mts", ".mjs"].map((extension) => join54(loaded.dir, "functions", `${definition.name}${extension}`)).find(existsSync54);
|
|
54023
54497
|
if (!source) throw new Error(`missing source for function ${definition.name}; expected functions/${definition.name}.ts`);
|
|
54024
|
-
sources[definition.name] =
|
|
54498
|
+
sources[definition.name] = readFileSync60(source, "utf8");
|
|
54025
54499
|
}
|
|
54026
54500
|
const data = config.cms || config.data ? mergeDataResources(config) : void 0;
|
|
54027
54501
|
const dataErrors = validateDataResources(data, loaded.name).filter((issue2) => issue2.severity === "error");
|
|
@@ -54209,30 +54683,30 @@ var init_cli_bridge = __esm({
|
|
|
54209
54683
|
});
|
|
54210
54684
|
|
|
54211
54685
|
// src/mcp/file-tools.ts
|
|
54212
|
-
import { closeSync as
|
|
54686
|
+
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";
|
|
54213
54687
|
import { randomBytes as randomBytes4 } from "node:crypto";
|
|
54214
|
-
import { dirname as dirname30, isAbsolute as isAbsolute5, join as
|
|
54688
|
+
import { dirname as dirname30, isAbsolute as isAbsolute5, join as join55, relative as relative11, resolve as resolve46, sep as sep15 } from "node:path";
|
|
54215
54689
|
import * as z from "zod";
|
|
54216
54690
|
function within(root, p) {
|
|
54217
|
-
return p === root || p.startsWith(root.endsWith(
|
|
54691
|
+
return p === root || p.startsWith(root.endsWith(sep15) ? root : root + sep15);
|
|
54218
54692
|
}
|
|
54219
54693
|
function realish(p) {
|
|
54220
54694
|
let cur = p;
|
|
54221
54695
|
const tail = [];
|
|
54222
|
-
while (!
|
|
54696
|
+
while (!existsSync55(cur)) {
|
|
54223
54697
|
const up = dirname30(cur);
|
|
54224
54698
|
if (up === cur) break;
|
|
54225
|
-
tail.unshift(cur.slice(up.length + (up.endsWith(
|
|
54699
|
+
tail.unshift(cur.slice(up.length + (up.endsWith(sep15) ? 0 : 1)));
|
|
54226
54700
|
cur = up;
|
|
54227
54701
|
}
|
|
54228
|
-
return
|
|
54702
|
+
return join55(realpathSync8(cur), ...tail);
|
|
54229
54703
|
}
|
|
54230
54704
|
function symlinkComponent(base, abs) {
|
|
54231
54705
|
let cur = base;
|
|
54232
|
-
for (const part of relative11(base, abs).split(
|
|
54233
|
-
cur =
|
|
54706
|
+
for (const part of relative11(base, abs).split(sep15).filter(Boolean)) {
|
|
54707
|
+
cur = join55(cur, part);
|
|
54234
54708
|
try {
|
|
54235
|
-
if (
|
|
54709
|
+
if (lstatSync14(cur).isSymbolicLink()) return relative11(base, cur);
|
|
54236
54710
|
} catch {
|
|
54237
54711
|
return void 0;
|
|
54238
54712
|
}
|
|
@@ -54240,24 +54714,24 @@ function symlinkComponent(base, abs) {
|
|
|
54240
54714
|
return void 0;
|
|
54241
54715
|
}
|
|
54242
54716
|
function workspaceRefusal(root, target) {
|
|
54243
|
-
const r =
|
|
54244
|
-
const t =
|
|
54245
|
-
if (!
|
|
54246
|
-
if (!within(r, t) || !within(
|
|
54717
|
+
const r = resolve46(root);
|
|
54718
|
+
const t = resolve46(r, target);
|
|
54719
|
+
if (!existsSync55(r)) return `the workspace folder ${r} does not exist`;
|
|
54720
|
+
if (!within(r, t) || !within(realpathSync8(r), realish(t))) return `"${target}" is outside the workspace folder ${r}`;
|
|
54247
54721
|
const link = symlinkComponent(r, t);
|
|
54248
54722
|
if (link) return `"${link}" is a symlink \u2014 paths must stay in real folders inside the workspace`;
|
|
54249
54723
|
return null;
|
|
54250
54724
|
}
|
|
54251
54725
|
async function confinedAppDir(args, deps) {
|
|
54252
|
-
const root =
|
|
54253
|
-
const dir = typeof args.dir === "string" && args.dir ?
|
|
54254
|
-
if (!
|
|
54255
|
-
if (!within(root, dir) || !within(
|
|
54726
|
+
const root = resolve46(await deps.defaultDir());
|
|
54727
|
+
const dir = typeof args.dir === "string" && args.dir ? resolve46(root, args.dir) : root;
|
|
54728
|
+
if (!existsSync55(root)) return refuse4("no_workspace", `the workspace folder ${root} does not exist`);
|
|
54729
|
+
if (!within(root, dir) || !within(realpathSync8(root), realish(dir))) return refuse4("outside_workspace", `"${String(args.dir)}" is outside the workspace folder`);
|
|
54256
54730
|
const link = symlinkComponent(root, dir);
|
|
54257
54731
|
if (link) return refuse4("outside_workspace", `"${link}" is a symlink \u2014 the app folder must be a real folder in the workspace`);
|
|
54258
54732
|
const rel = relative11(root, dir);
|
|
54259
54733
|
if (rel && (isDeniedSourcePath(`${rel}/x`) || isDeniedSourcePath(`${rel.toLowerCase()}/x`))) return refuse4("denied", `"${rel}" is a folder this tool does not touch`);
|
|
54260
|
-
if (!
|
|
54734
|
+
if (!existsSync55(join55(dir, CONTENT_MARKER))) {
|
|
54261
54735
|
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`);
|
|
54262
54736
|
}
|
|
54263
54737
|
return dir;
|
|
@@ -54265,19 +54739,19 @@ async function confinedAppDir(args, deps) {
|
|
|
54265
54739
|
function onDiskSpelling(abs) {
|
|
54266
54740
|
let cur = abs;
|
|
54267
54741
|
const tail = [];
|
|
54268
|
-
while (!
|
|
54742
|
+
while (!existsSync55(cur)) {
|
|
54269
54743
|
const up = dirname30(cur);
|
|
54270
54744
|
if (up === cur) break;
|
|
54271
|
-
tail.unshift(cur.slice(up.length + (up.endsWith(
|
|
54745
|
+
tail.unshift(cur.slice(up.length + (up.endsWith(sep15) ? 0 : 1)));
|
|
54272
54746
|
cur = up;
|
|
54273
54747
|
}
|
|
54274
|
-
return
|
|
54748
|
+
return join55(realpathSync8.native(cur), ...tail);
|
|
54275
54749
|
}
|
|
54276
54750
|
function denied(appDir3, abs) {
|
|
54277
54751
|
const rel = relative11(appDir3, abs);
|
|
54278
54752
|
if (isDeniedSourcePath(rel) || isDeniedSourcePath(rel.toLowerCase())) return true;
|
|
54279
54753
|
try {
|
|
54280
|
-
return isDeniedSourcePath(relative11(
|
|
54754
|
+
return isDeniedSourcePath(relative11(realpathSync8.native(appDir3), onDiskSpelling(abs)));
|
|
54281
54755
|
} catch {
|
|
54282
54756
|
return true;
|
|
54283
54757
|
}
|
|
@@ -54286,10 +54760,10 @@ function confinedPath(appDir3, rel) {
|
|
|
54286
54760
|
if (typeof rel !== "string" || !rel.trim()) return { error: "a path is required" };
|
|
54287
54761
|
if (rel.includes("\0")) return { error: "a path may not contain NUL" };
|
|
54288
54762
|
if (isAbsolute5(rel)) return { error: `"${rel}" is absolute \u2014 give a path inside the app folder` };
|
|
54289
|
-
const abs =
|
|
54763
|
+
const abs = resolve46(appDir3, rel);
|
|
54290
54764
|
if (!within(appDir3, abs) || abs === appDir3) return { error: `"${rel}" is outside the app folder` };
|
|
54291
54765
|
const real = realish(abs);
|
|
54292
|
-
const base =
|
|
54766
|
+
const base = existsSync55(appDir3) ? realpathSync8(appDir3) : realish(appDir3);
|
|
54293
54767
|
if (!within(base, real) || real === base) return { error: `"${rel}" is outside the app folder` };
|
|
54294
54768
|
const link = symlinkComponent(appDir3, abs);
|
|
54295
54769
|
if (link) return { error: `"${link}" is a symlink, which this tool does not follow` };
|
|
@@ -54297,22 +54771,22 @@ function confinedPath(appDir3, rel) {
|
|
|
54297
54771
|
return abs;
|
|
54298
54772
|
}
|
|
54299
54773
|
function writeNoFollow(appDir3, abs, content) {
|
|
54300
|
-
|
|
54774
|
+
mkdirSync27(dirname30(abs), { recursive: true });
|
|
54301
54775
|
const before = symlinkComponent(appDir3, abs);
|
|
54302
54776
|
if (before) throw new Error(`"${before}" became a symlink`);
|
|
54303
|
-
const tmp =
|
|
54304
|
-
const fd =
|
|
54777
|
+
const tmp = join55(dirname30(abs), `.himi-write-${process.pid}-${randomBytes4(6).toString("hex")}.tmp`);
|
|
54778
|
+
const fd = openSync4(tmp, constants2.O_WRONLY | constants2.O_CREAT | constants2.O_EXCL | (constants2.O_NOFOLLOW ?? 0), 420);
|
|
54305
54779
|
try {
|
|
54306
54780
|
writeSync(fd, content);
|
|
54307
54781
|
} finally {
|
|
54308
|
-
|
|
54782
|
+
closeSync5(fd);
|
|
54309
54783
|
}
|
|
54310
54784
|
const link = symlinkComponent(appDir3, abs);
|
|
54311
|
-
if (link || !within(
|
|
54312
|
-
|
|
54785
|
+
if (link || !within(realpathSync8(appDir3), realpathSync8(dirname30(abs)))) {
|
|
54786
|
+
rmSync13(tmp, { force: true });
|
|
54313
54787
|
throw new Error(`"${link ?? relative11(appDir3, dirname30(abs))}" changed into a link while writing`);
|
|
54314
54788
|
}
|
|
54315
|
-
|
|
54789
|
+
renameSync7(tmp, abs);
|
|
54316
54790
|
}
|
|
54317
54791
|
function listFiles(appDir3) {
|
|
54318
54792
|
const files = [];
|
|
@@ -54320,7 +54794,7 @@ function listFiles(appDir3) {
|
|
|
54320
54794
|
const parentRel = relative11(appDir3, d);
|
|
54321
54795
|
for (const e of readdirSync27(d, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
54322
54796
|
if (e.isSymbolicLink()) continue;
|
|
54323
|
-
const p =
|
|
54797
|
+
const p = join55(d, e.name);
|
|
54324
54798
|
if (e.isDirectory()) {
|
|
54325
54799
|
if (isDeniedSourceDir(e.name, parentRel) || isDeniedSourceDir(e.name.toLowerCase(), parentRel)) continue;
|
|
54326
54800
|
if (!walk2(p)) return false;
|
|
@@ -54328,7 +54802,7 @@ function listFiles(appDir3) {
|
|
|
54328
54802
|
const rel = relative11(appDir3, p);
|
|
54329
54803
|
if (isDeniedSourcePath(rel) || isDeniedSourcePath(rel.toLowerCase())) continue;
|
|
54330
54804
|
if (files.length >= MAX_LISTED) return false;
|
|
54331
|
-
files.push({ path: rel.split(
|
|
54805
|
+
files.push({ path: rel.split(sep15).join("/"), bytes: statSync14(p).size });
|
|
54332
54806
|
}
|
|
54333
54807
|
}
|
|
54334
54808
|
return true;
|
|
@@ -54345,12 +54819,12 @@ function fileTools() {
|
|
|
54345
54819
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
54346
54820
|
inputSchema: z.object({}),
|
|
54347
54821
|
handler: async (_a, d) => {
|
|
54348
|
-
const root =
|
|
54349
|
-
if (!
|
|
54822
|
+
const root = resolve46(await d.defaultDir());
|
|
54823
|
+
if (!existsSync55(root)) return ok({ ok: true, workspace: root, apps: [] });
|
|
54350
54824
|
const apps = [];
|
|
54351
|
-
if (
|
|
54825
|
+
if (existsSync55(join55(root, CONTENT_MARKER))) apps.push(".");
|
|
54352
54826
|
for (const e of readdirSync27(root, { withFileTypes: true })) {
|
|
54353
|
-
if (e.isDirectory() && !e.name.startsWith(".") &&
|
|
54827
|
+
if (e.isDirectory() && !e.name.startsWith(".") && existsSync55(join55(root, e.name, CONTENT_MARKER))) apps.push(e.name);
|
|
54354
54828
|
}
|
|
54355
54829
|
return ok({ ok: true, workspace: root, apps: apps.sort() });
|
|
54356
54830
|
}
|
|
@@ -54364,7 +54838,7 @@ function fileTools() {
|
|
|
54364
54838
|
handler: async (a, d) => {
|
|
54365
54839
|
const app = await confinedAppDir(a, d);
|
|
54366
54840
|
if (typeof app !== "string") return app;
|
|
54367
|
-
if (!
|
|
54841
|
+
if (!existsSync55(app) || !statSync14(app).isDirectory()) return refuse4("no_app", `no folder at ${app}`);
|
|
54368
54842
|
return ok({ ok: true, dir: app, ...listFiles(app) });
|
|
54369
54843
|
}
|
|
54370
54844
|
},
|
|
@@ -54379,10 +54853,10 @@ function fileTools() {
|
|
|
54379
54853
|
if (typeof app !== "string") return app;
|
|
54380
54854
|
const p = confinedPath(app, a.path);
|
|
54381
54855
|
if (typeof p !== "string") return refuse4("bad_path", p.error);
|
|
54382
|
-
if (!
|
|
54383
|
-
const size =
|
|
54856
|
+
if (!existsSync55(p) || !lstatSync14(p).isFile()) return refuse4("not_found", `no file "${String(a.path)}"`);
|
|
54857
|
+
const size = statSync14(p).size;
|
|
54384
54858
|
if (size > MAX_FILE_BYTES) return refuse4("too_large", `"${String(a.path)}" is ${size} bytes \u2014 over the ${MAX_FILE_BYTES}-byte read limit`);
|
|
54385
|
-
return ok({ ok: true, path: a.path, bytes: size, content:
|
|
54859
|
+
return ok({ ok: true, path: a.path, bytes: size, content: readFileSync61(p, "utf8") });
|
|
54386
54860
|
}
|
|
54387
54861
|
},
|
|
54388
54862
|
{
|
|
@@ -54406,7 +54880,7 @@ function fileTools() {
|
|
|
54406
54880
|
if (typeof p !== "string") refused2.push({ path: f.path, error: p.error });
|
|
54407
54881
|
else if (typeof f.content !== "string") refused2.push({ path: f.path, error: "content must be text" });
|
|
54408
54882
|
else if (Buffer.byteLength(f.content) > MAX_FILE_BYTES) refused2.push({ path: f.path, error: `over the ${MAX_FILE_BYTES}-byte limit` });
|
|
54409
|
-
else if (
|
|
54883
|
+
else if (existsSync55(p) && !lstatSync14(p).isFile()) refused2.push({ path: f.path, error: "not a regular file" });
|
|
54410
54884
|
else if (planned.some((q) => q.abs === p)) refused2.push({ path: f.path, error: "the same file twice in one call" });
|
|
54411
54885
|
else planned.push({ abs: p, rel: String(f.path), content: f.content });
|
|
54412
54886
|
}
|
|
@@ -54439,15 +54913,15 @@ function fileTools() {
|
|
|
54439
54913
|
for (const rel of paths) {
|
|
54440
54914
|
const p = confinedPath(app, rel);
|
|
54441
54915
|
if (typeof p !== "string") refused2.push({ path: rel, error: p.error });
|
|
54442
|
-
else if (!
|
|
54443
|
-
else if (!
|
|
54916
|
+
else if (!existsSync55(p)) refused2.push({ path: rel, error: "no such file" });
|
|
54917
|
+
else if (!lstatSync14(p).isFile()) refused2.push({ path: rel, error: "not a regular file" });
|
|
54444
54918
|
else if (!planned.includes(p)) {
|
|
54445
54919
|
planned.push(p);
|
|
54446
54920
|
deleted.push(rel);
|
|
54447
54921
|
}
|
|
54448
54922
|
}
|
|
54449
54923
|
if (refused2.length) return { ...ok({ ok: false, error: "refused", deleted: [], refused: refused2 }), isError: true };
|
|
54450
|
-
for (const p of planned)
|
|
54924
|
+
for (const p of planned) rmSync13(p);
|
|
54451
54925
|
return ok({ ok: true, dir: app, deleted });
|
|
54452
54926
|
}
|
|
54453
54927
|
}
|
|
@@ -54470,7 +54944,7 @@ var init_file_tools = __esm({
|
|
|
54470
54944
|
|
|
54471
54945
|
// src/mcp/tools.ts
|
|
54472
54946
|
import * as z2 from "zod";
|
|
54473
|
-
import { resolve as
|
|
54947
|
+
import { resolve as resolve47 } from "node:path";
|
|
54474
54948
|
function fromCli(r, extra = []) {
|
|
54475
54949
|
const body = r.json !== void 0 ? JSON.stringify(r.json, null, 2) : r.text.trim();
|
|
54476
54950
|
const content = [];
|
|
@@ -54482,7 +54956,7 @@ ${r.stderr.trim()}`) });
|
|
|
54482
54956
|
}
|
|
54483
54957
|
async function appDir2(args, deps) {
|
|
54484
54958
|
const base = await deps.defaultDir();
|
|
54485
|
-
return typeof args.dir === "string" && args.dir ?
|
|
54959
|
+
return typeof args.dir === "string" && args.dir ? resolve47(base, args.dir) : base;
|
|
54486
54960
|
}
|
|
54487
54961
|
function loginResult(s) {
|
|
54488
54962
|
switch (s.state) {
|
|
@@ -54523,7 +54997,7 @@ function guarded(h) {
|
|
|
54523
54997
|
for (const k of PATH_ARGS) {
|
|
54524
54998
|
const v = args[k];
|
|
54525
54999
|
if (typeof v !== "string" || !v) continue;
|
|
54526
|
-
const why = workspaceRefusal(deps.workspaceBoundary,
|
|
55000
|
+
const why = workspaceRefusal(deps.workspaceBoundary, resolve47(base, v));
|
|
54527
55001
|
if (why) return text6(`refused: ${k} ${why}`, true);
|
|
54528
55002
|
}
|
|
54529
55003
|
}
|
|
@@ -54579,7 +55053,7 @@ function rawTools() {
|
|
|
54579
55053
|
}),
|
|
54580
55054
|
handler: async (a, d) => {
|
|
54581
55055
|
const base = await d.defaultDir();
|
|
54582
|
-
const parent = typeof a.parentDir === "string" && a.parentDir ?
|
|
55056
|
+
const parent = typeof a.parentDir === "string" && a.parentDir ? resolve47(base, a.parentDir) : base;
|
|
54583
55057
|
return fromCli(await d.runCli(["init", String(a.name), "--dir", parent, ...opt("--template", a.template)]));
|
|
54584
55058
|
}
|
|
54585
55059
|
},
|
|
@@ -54759,7 +55233,7 @@ ${log}` : ""}`, true);
|
|
|
54759
55233
|
handler: async (a, d) => {
|
|
54760
55234
|
const gate = await requireLogin(d);
|
|
54761
55235
|
if (gate) return gate;
|
|
54762
|
-
return fromCli(await d.runCli(["share", "--file",
|
|
55236
|
+
return fromCli(await d.runCli(["share", "--file", resolve47(await d.defaultDir(), String(a.file)), ...opt("--expires-at", a.expiresAt), ...opt("--target", d.login.target())]));
|
|
54763
55237
|
}
|
|
54764
55238
|
},
|
|
54765
55239
|
{
|
|
@@ -54884,7 +55358,7 @@ __export(server_exports, {
|
|
|
54884
55358
|
protectStdout: () => protectStdout,
|
|
54885
55359
|
startMcpServer: () => startMcpServer
|
|
54886
55360
|
});
|
|
54887
|
-
import { mkdirSync as
|
|
55361
|
+
import { mkdirSync as mkdirSync28, readFileSync as readFileSync62 } from "node:fs";
|
|
54888
55362
|
import { fileURLToPath as fileURLToPath21 } from "node:url";
|
|
54889
55363
|
import { McpServer } from "@modelcontextprotocol/server";
|
|
54890
55364
|
import { serveStdio, StdioServerTransport } from "@modelcontextprotocol/server/stdio";
|
|
@@ -54923,7 +55397,7 @@ async function startMcpServer(opts) {
|
|
|
54923
55397
|
const runCli = createCliBridge(opts.run);
|
|
54924
55398
|
if (opts.workspace) {
|
|
54925
55399
|
try {
|
|
54926
|
-
|
|
55400
|
+
mkdirSync28(opts.workspace, { recursive: true });
|
|
54927
55401
|
} catch (e) {
|
|
54928
55402
|
console.error(`[himi mcp] could not create the workspace folder ${opts.workspace}: ${e.message}`);
|
|
54929
55403
|
}
|
|
@@ -54944,7 +55418,7 @@ async function startMcpServer(opts) {
|
|
|
54944
55418
|
status: (dir) => previewStatus(dir, previewDeps),
|
|
54945
55419
|
stop: (dir) => stopPreview(dir, previewDeps)
|
|
54946
55420
|
},
|
|
54947
|
-
readFile: (p) =>
|
|
55421
|
+
readFile: (p) => readFileSync62(p),
|
|
54948
55422
|
login: createLoginSession({ ...defaultLoginDeps, selfCommand: previewDeps.selfCommand, ...opts.login }),
|
|
54949
55423
|
...opts.workspace ? { workspaceBoundary: opts.workspace } : {}
|
|
54950
55424
|
};
|
|
@@ -54988,9 +55462,9 @@ __export(native_decode_exports, {
|
|
|
54988
55462
|
runNativeDecode: () => runNativeDecode
|
|
54989
55463
|
});
|
|
54990
55464
|
import { execFileSync as execFileSync5, spawnSync as spawnSync2 } from "node:child_process";
|
|
54991
|
-
import { existsSync as
|
|
55465
|
+
import { existsSync as existsSync56, mkdtempSync as mkdtempSync2, readFileSync as readFileSync63, rmSync as rmSync14, writeFileSync as writeFileSync22 } from "node:fs";
|
|
54992
55466
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
54993
|
-
import { join as
|
|
55467
|
+
import { join as join56 } from "node:path";
|
|
54994
55468
|
import { fileURLToPath as fileURLToPath22 } from "node:url";
|
|
54995
55469
|
function nativeDecodePackageDir() {
|
|
54996
55470
|
return fileURLToPath22(new URL("../../../core/runtime/ios/HimalayaCore/", import.meta.url));
|
|
@@ -55000,8 +55474,8 @@ function nativeDecodeAndroidHarnessDir() {
|
|
|
55000
55474
|
}
|
|
55001
55475
|
function nativeOracleShipped() {
|
|
55002
55476
|
return {
|
|
55003
|
-
ios:
|
|
55004
|
-
android:
|
|
55477
|
+
ios: existsSync56(join56(nativeDecodePackageDir(), "Package.swift")),
|
|
55478
|
+
android: existsSync56(join56(nativeDecodeAndroidHarnessDir(), "gradlew"))
|
|
55005
55479
|
};
|
|
55006
55480
|
}
|
|
55007
55481
|
function detectNativeDecode(options = {}) {
|
|
@@ -55012,7 +55486,7 @@ function detectNativeDecode(options = {}) {
|
|
|
55012
55486
|
return { platform: "ios", available: false, reason: `the iOS decoder oracle requires macOS (darwin); this host is ${hostPlatform}` };
|
|
55013
55487
|
}
|
|
55014
55488
|
if (!swiftAvailable) return { platform: "ios", available: false, reason: "swift is not on PATH" };
|
|
55015
|
-
const packageAvailable = options.packageAvailable ??
|
|
55489
|
+
const packageAvailable = options.packageAvailable ?? existsSync56(join56(packageDir, "Package.swift"));
|
|
55016
55490
|
if (!packageAvailable) {
|
|
55017
55491
|
return {
|
|
55018
55492
|
platform: "ios",
|
|
@@ -55024,7 +55498,7 @@ function detectNativeDecode(options = {}) {
|
|
|
55024
55498
|
}
|
|
55025
55499
|
function localPropertiesSdk(harnessDir) {
|
|
55026
55500
|
try {
|
|
55027
|
-
const match =
|
|
55501
|
+
const match = readFileSync63(join56(harnessDir, "local.properties"), "utf8").match(/^sdk\.dir\s*=\s*(.+)$/m);
|
|
55028
55502
|
return match?.[1]?.trim().replace(/\\([ :\\])/g, "$1") ?? null;
|
|
55029
55503
|
} catch {
|
|
55030
55504
|
return null;
|
|
@@ -55032,8 +55506,8 @@ function localPropertiesSdk(harnessDir) {
|
|
|
55032
55506
|
}
|
|
55033
55507
|
function detectAndroidNativeDecode(options = {}) {
|
|
55034
55508
|
const harnessDir = options.harnessDir ?? nativeDecodeAndroidHarnessDir();
|
|
55035
|
-
const gradlew =
|
|
55036
|
-
const gradlewAvailable = options.gradlewAvailable ??
|
|
55509
|
+
const gradlew = join56(harnessDir, "gradlew");
|
|
55510
|
+
const gradlewAvailable = options.gradlewAvailable ?? existsSync56(gradlew);
|
|
55037
55511
|
if (!gradlewAvailable) {
|
|
55038
55512
|
return {
|
|
55039
55513
|
platform: "android",
|
|
@@ -55043,7 +55517,7 @@ function detectAndroidNativeDecode(options = {}) {
|
|
|
55043
55517
|
}
|
|
55044
55518
|
const androidHome = options.androidHome ?? process.env.ANDROID_HOME ?? "";
|
|
55045
55519
|
const sdk = androidHome.trim() || (options.localPropertiesSdk !== void 0 ? options.localPropertiesSdk : localPropertiesSdk(harnessDir));
|
|
55046
|
-
const sdkAvailable = options.sdkAvailable ?? (typeof sdk === "string" && sdk.length > 0 &&
|
|
55520
|
+
const sdkAvailable = options.sdkAvailable ?? (typeof sdk === "string" && sdk.length > 0 && existsSync56(sdk));
|
|
55047
55521
|
if (!sdkAvailable) {
|
|
55048
55522
|
return {
|
|
55049
55523
|
platform: "android",
|
|
@@ -55115,14 +55589,14 @@ function parseNativeDecodeOutput(stdout, expectedScreenIds, platform = "ios") {
|
|
|
55115
55589
|
return verdicts;
|
|
55116
55590
|
}
|
|
55117
55591
|
function runNativeDecode(options) {
|
|
55118
|
-
const descriptorDir = mkdtempSync2(
|
|
55592
|
+
const descriptorDir = mkdtempSync2(join56(tmpdir3(), "himi-native-decode-"));
|
|
55119
55593
|
const startedAt = Date.now();
|
|
55120
55594
|
try {
|
|
55121
55595
|
for (const descriptor of options.descriptors) {
|
|
55122
55596
|
if (typeof descriptor.descriptorJson !== "string") {
|
|
55123
55597
|
throw new Error(`native decode cannot judge ${descriptor.screenId}: the release builder did not return its exact baked JSON bytes`);
|
|
55124
55598
|
}
|
|
55125
|
-
|
|
55599
|
+
writeFileSync22(join56(descriptorDir, `${encodeURIComponent(descriptor.screenId)}.json`), descriptor.descriptorJson);
|
|
55126
55600
|
}
|
|
55127
55601
|
options.progress?.("building cached Swift oracle");
|
|
55128
55602
|
execFileSync5("swift", ["build", "--package-path", options.packageDir, "--product", "himi-decode-oracle"], {
|
|
@@ -55134,7 +55608,7 @@ function runNativeDecode(options) {
|
|
|
55134
55608
|
encoding: "utf8"
|
|
55135
55609
|
}).trim();
|
|
55136
55610
|
options.progress?.(`running real Swift decoder over ${options.descriptors.length} screen${options.descriptors.length === 1 ? "" : "s"}`);
|
|
55137
|
-
const stdout = execFileSync5(
|
|
55611
|
+
const stdout = execFileSync5(join56(binPath, "himi-decode-oracle"), ["--dir", descriptorDir], {
|
|
55138
55612
|
stdio: ["ignore", "pipe", "pipe"],
|
|
55139
55613
|
encoding: "utf8"
|
|
55140
55614
|
});
|
|
@@ -55148,19 +55622,19 @@ function runNativeDecode(options) {
|
|
|
55148
55622
|
const stderr = failure.stderr ? ` \u2014 ${String(failure.stderr).trim().slice(0, 2e3)}` : "";
|
|
55149
55623
|
throw new Error(`iOS decoder oracle failed: ${failure.message}${stderr}`);
|
|
55150
55624
|
} finally {
|
|
55151
|
-
|
|
55625
|
+
rmSync14(descriptorDir, { recursive: true, force: true });
|
|
55152
55626
|
}
|
|
55153
55627
|
}
|
|
55154
55628
|
function runAndroidNativeDecode(options) {
|
|
55155
|
-
const descriptorDir = mkdtempSync2(
|
|
55156
|
-
const outputPath =
|
|
55629
|
+
const descriptorDir = mkdtempSync2(join56(tmpdir3(), "himi-native-decode-android-"));
|
|
55630
|
+
const outputPath = join56(descriptorDir, "verdicts.ndjson");
|
|
55157
55631
|
const startedAt = Date.now();
|
|
55158
55632
|
try {
|
|
55159
55633
|
for (const descriptor of options.descriptors) {
|
|
55160
55634
|
if (typeof descriptor.descriptorJson !== "string") {
|
|
55161
55635
|
throw new Error(`native decode cannot judge ${descriptor.screenId}: the release builder did not return its exact baked JSON bytes`);
|
|
55162
55636
|
}
|
|
55163
|
-
|
|
55637
|
+
writeFileSync22(join56(descriptorDir, `${encodeURIComponent(descriptor.screenId)}.json`), descriptor.descriptorJson);
|
|
55164
55638
|
}
|
|
55165
55639
|
options.progress?.(`running real Kotlin decoder over ${options.descriptors.length} screen${options.descriptors.length === 1 ? "" : "s"}`);
|
|
55166
55640
|
execFileSync5(options.gradlew, [
|
|
@@ -55175,7 +55649,7 @@ function runAndroidNativeDecode(options) {
|
|
|
55175
55649
|
stdio: ["ignore", "ignore", "pipe"],
|
|
55176
55650
|
encoding: "utf8"
|
|
55177
55651
|
});
|
|
55178
|
-
const stdout =
|
|
55652
|
+
const stdout = readFileSync63(outputPath, "utf8");
|
|
55179
55653
|
return {
|
|
55180
55654
|
platform: "android",
|
|
55181
55655
|
verdicts: parseNativeDecodeOutput(stdout, options.descriptors.map((descriptor) => descriptor.screenId), "android"),
|
|
@@ -55186,7 +55660,7 @@ function runAndroidNativeDecode(options) {
|
|
|
55186
55660
|
const stderr = failure.stderr ? ` \u2014 ${String(failure.stderr).trim().slice(0, 2e3)}` : "";
|
|
55187
55661
|
throw new Error(`Android decoder oracle failed: ${failure.message}${stderr}`);
|
|
55188
55662
|
} finally {
|
|
55189
|
-
|
|
55663
|
+
rmSync14(descriptorDir, { recursive: true, force: true });
|
|
55190
55664
|
}
|
|
55191
55665
|
}
|
|
55192
55666
|
function fieldAt(path2) {
|
|
@@ -55245,30 +55719,30 @@ __export(browser_authoring_exports, {
|
|
|
55245
55719
|
});
|
|
55246
55720
|
import { createServer as createServer5 } from "node:http";
|
|
55247
55721
|
import { createHash as createHash30, randomBytes as randomBytes5 } from "node:crypto";
|
|
55248
|
-
import { existsSync as
|
|
55249
|
-
import { join as
|
|
55722
|
+
import { existsSync as existsSync57, readdirSync as readdirSync28, readFileSync as readFileSync64, realpathSync as realpathSync9, statSync as statSync15 } from "node:fs";
|
|
55723
|
+
import { join as join57, posix as posix2, relative as relative12, sep as sep16 } from "node:path";
|
|
55250
55724
|
function humanMs(ms) {
|
|
55251
55725
|
return ms < 1e3 ? `${ms}ms` : `${Math.round(ms / 1e3)}s`;
|
|
55252
55726
|
}
|
|
55253
55727
|
function collectWorkerSources(tier5SrcDir, bundleName) {
|
|
55254
|
-
const root =
|
|
55728
|
+
const root = join57(tier5SrcDir, bundleName);
|
|
55255
55729
|
const files = {};
|
|
55256
55730
|
const visitedDirs = /* @__PURE__ */ new Set();
|
|
55257
55731
|
const walk2 = (abs, rel) => {
|
|
55258
|
-
const real =
|
|
55732
|
+
const real = realpathSync9(abs);
|
|
55259
55733
|
if (visitedDirs.has(real)) return;
|
|
55260
55734
|
visitedDirs.add(real);
|
|
55261
55735
|
for (const name of readdirSync28(abs).sort()) {
|
|
55262
|
-
const childAbs =
|
|
55736
|
+
const childAbs = join57(abs, name);
|
|
55263
55737
|
const childRel = posix2.join(rel, name);
|
|
55264
|
-
if (
|
|
55738
|
+
if (statSync15(childAbs).isDirectory()) {
|
|
55265
55739
|
walk2(childAbs, childRel);
|
|
55266
55740
|
} else if (/\.(ts|tsx|js|mjs|json)$/.test(name)) {
|
|
55267
|
-
files[childRel] =
|
|
55741
|
+
files[childRel] = readFileSync64(childAbs, "utf8");
|
|
55268
55742
|
}
|
|
55269
55743
|
}
|
|
55270
55744
|
};
|
|
55271
|
-
if (!
|
|
55745
|
+
if (!existsSync57(root)) return null;
|
|
55272
55746
|
walk2(root, `/tier5-src/${bundleName}`);
|
|
55273
55747
|
const entry = [`/tier5-src/${bundleName}/index.ts`, `/tier5-src/${bundleName}/index.js`].find((p) => p in files);
|
|
55274
55748
|
if (!entry) return null;
|
|
@@ -55285,20 +55759,20 @@ function collectRelativeSiblings(files, tier5SrcDir) {
|
|
|
55285
55759
|
const spec = m[1];
|
|
55286
55760
|
const virtual = posix2.normalize(posix2.join(dir, spec));
|
|
55287
55761
|
if (!virtual.startsWith("/tier5-src/")) continue;
|
|
55288
|
-
const onDisk =
|
|
55762
|
+
const onDisk = join57(tier5SrcDir, virtual.slice("/tier5-src/".length));
|
|
55289
55763
|
const candidates = [
|
|
55290
55764
|
onDisk,
|
|
55291
55765
|
`${onDisk}.ts`,
|
|
55292
55766
|
`${onDisk}.js`,
|
|
55293
55767
|
onDisk.replace(/\.js$/, ".ts"),
|
|
55294
|
-
|
|
55295
|
-
|
|
55768
|
+
join57(onDisk, "index.ts"),
|
|
55769
|
+
join57(onDisk, "index.js")
|
|
55296
55770
|
];
|
|
55297
55771
|
for (const abs of candidates) {
|
|
55298
|
-
if (!
|
|
55299
|
-
const key2 = `/tier5-src/${relative12(tier5SrcDir, abs).split(
|
|
55772
|
+
if (!existsSync57(abs) || statSync15(abs).isDirectory()) continue;
|
|
55773
|
+
const key2 = `/tier5-src/${relative12(tier5SrcDir, abs).split(sep16).join(posix2.sep)}`;
|
|
55300
55774
|
if (key2 in files) break;
|
|
55301
|
-
files[key2] =
|
|
55775
|
+
files[key2] = readFileSync64(abs, "utf8");
|
|
55302
55776
|
queue.push(key2);
|
|
55303
55777
|
break;
|
|
55304
55778
|
}
|
|
@@ -55333,7 +55807,7 @@ function collectSdkModules(files, resolveSubpath) {
|
|
|
55333
55807
|
function sdkSubpathReader(resolve54) {
|
|
55334
55808
|
return (sub) => {
|
|
55335
55809
|
try {
|
|
55336
|
-
return
|
|
55810
|
+
return readFileSync64(resolve54(`@wix/himalaya/${sub}`), "utf8");
|
|
55337
55811
|
} catch {
|
|
55338
55812
|
return null;
|
|
55339
55813
|
}
|
|
@@ -55505,8 +55979,8 @@ __export(coverage_exports, {
|
|
|
55505
55979
|
suppressedNativeKeys: () => suppressedNativeKeys,
|
|
55506
55980
|
trendAndStore: () => trendAndStore
|
|
55507
55981
|
});
|
|
55508
|
-
import { existsSync as
|
|
55509
|
-
import { dirname as dirname31, join as
|
|
55982
|
+
import { existsSync as existsSync58, mkdirSync as mkdirSync29, readFileSync as readFileSync65, writeFileSync as writeFileSync23 } from "node:fs";
|
|
55983
|
+
import { dirname as dirname31, join as join58 } from "node:path";
|
|
55510
55984
|
function suppressedNativeKeys(oracle) {
|
|
55511
55985
|
return [...oracle.ios ? [] : ["native-decode|ios"], ...oracle.android ? [] : ["native-decode|android"]];
|
|
55512
55986
|
}
|
|
@@ -55686,11 +56160,11 @@ function assembleLedger(r, options = {}) {
|
|
|
55686
56160
|
return out2;
|
|
55687
56161
|
}
|
|
55688
56162
|
function trendAndStore(dir, entries, suppressed) {
|
|
55689
|
-
const p =
|
|
56163
|
+
const p = join58(dir, LEDGER_FILE);
|
|
55690
56164
|
let previous = null;
|
|
55691
56165
|
try {
|
|
55692
|
-
if (
|
|
55693
|
-
const parsed = JSON.parse(
|
|
56166
|
+
if (existsSync58(p)) {
|
|
56167
|
+
const parsed = JSON.parse(readFileSync65(p, "utf8"));
|
|
55694
56168
|
if (Array.isArray(parsed.keys)) previous = parsed.keys.filter((k) => typeof k === "string");
|
|
55695
56169
|
}
|
|
55696
56170
|
} catch {
|
|
@@ -55698,8 +56172,8 @@ function trendAndStore(dir, entries, suppressed) {
|
|
|
55698
56172
|
const current = entries.map((e) => e.key);
|
|
55699
56173
|
const stored = [.../* @__PURE__ */ new Set([...current, ...(previous ?? []).filter((key2) => (suppressed ?? []).includes(key2))])];
|
|
55700
56174
|
try {
|
|
55701
|
-
|
|
55702
|
-
|
|
56175
|
+
mkdirSync29(dirname31(p), { recursive: true });
|
|
56176
|
+
writeFileSync23(p, JSON.stringify({ keys: stored, ts: (/* @__PURE__ */ new Date()).toISOString() }, null, 2));
|
|
55703
56177
|
} catch {
|
|
55704
56178
|
return null;
|
|
55705
56179
|
}
|
|
@@ -55729,11 +56203,11 @@ function formatLedger(entries, trend) {
|
|
|
55729
56203
|
return out2.join("\n");
|
|
55730
56204
|
}
|
|
55731
56205
|
function diffAndStoreObservations(dir, current) {
|
|
55732
|
-
const p =
|
|
56206
|
+
const p = join58(dir, OBS_FILE);
|
|
55733
56207
|
let previous = null;
|
|
55734
56208
|
try {
|
|
55735
|
-
if (
|
|
55736
|
-
const parsed = JSON.parse(
|
|
56209
|
+
if (existsSync58(p)) {
|
|
56210
|
+
const parsed = JSON.parse(readFileSync65(p, "utf8"));
|
|
55737
56211
|
if (parsed.screens && typeof parsed.screens === "object" && !Array.isArray(parsed.screens) && Object.values(parsed.screens).every(
|
|
55738
56212
|
(v) => Array.isArray(v) && v.every((l) => typeof l === "string")
|
|
55739
56213
|
)) {
|
|
@@ -55743,8 +56217,8 @@ function diffAndStoreObservations(dir, current) {
|
|
|
55743
56217
|
} catch {
|
|
55744
56218
|
}
|
|
55745
56219
|
try {
|
|
55746
|
-
|
|
55747
|
-
|
|
56220
|
+
mkdirSync29(dirname31(p), { recursive: true });
|
|
56221
|
+
writeFileSync23(p, JSON.stringify({ screens: current, ts: (/* @__PURE__ */ new Date()).toISOString() }, null, 2));
|
|
55748
56222
|
} catch {
|
|
55749
56223
|
return null;
|
|
55750
56224
|
}
|
|
@@ -55782,8 +56256,8 @@ var LEDGER_FILE, OBS_FILE;
|
|
|
55782
56256
|
var init_coverage = __esm({
|
|
55783
56257
|
"src/coverage.ts"() {
|
|
55784
56258
|
init_native_decode();
|
|
55785
|
-
LEDGER_FILE =
|
|
55786
|
-
OBS_FILE =
|
|
56259
|
+
LEDGER_FILE = join58(".himi", "test-ledger.json");
|
|
56260
|
+
OBS_FILE = join58(".himi", "test-observations.json");
|
|
55787
56261
|
}
|
|
55788
56262
|
});
|
|
55789
56263
|
|
|
@@ -55794,11 +56268,11 @@ __export(design_kits_exports, {
|
|
|
55794
56268
|
kitsDir: () => kitsDir,
|
|
55795
56269
|
listKits: () => listKits2
|
|
55796
56270
|
});
|
|
55797
|
-
import { existsSync as
|
|
55798
|
-
import { join as
|
|
56271
|
+
import { existsSync as existsSync59, readFileSync as readFileSync66, readdirSync as readdirSync29 } from "node:fs";
|
|
56272
|
+
import { join as join59, basename as basename15 } from "node:path";
|
|
55799
56273
|
import { fileURLToPath as fileURLToPath23 } from "node:url";
|
|
55800
56274
|
function kitsDir() {
|
|
55801
|
-
return KIT_DIR_CANDIDATES.find((p) =>
|
|
56275
|
+
return KIT_DIR_CANDIDATES.find((p) => existsSync59(p));
|
|
55802
56276
|
}
|
|
55803
56277
|
function listKits2() {
|
|
55804
56278
|
const dir = kitsDir();
|
|
@@ -55822,7 +56296,7 @@ In a published @wix/himalaya-cli this is a packaging bug \u2014 the kits should
|
|
|
55822
56296
|
if (!kits.includes(name)) {
|
|
55823
56297
|
return { ok: false, kit: name, kits, problems: [`no design kit named "${name}". Available: ${kits.join(", ")}`] };
|
|
55824
56298
|
}
|
|
55825
|
-
const raw =
|
|
56299
|
+
const raw = readFileSync66(join59(dir, `${name}.json`), "utf8");
|
|
55826
56300
|
let parsed;
|
|
55827
56301
|
try {
|
|
55828
56302
|
parsed = JSON.parse(raw);
|
|
@@ -55846,9 +56320,9 @@ var init_design_kits = __esm({
|
|
|
55846
56320
|
"src/design-kits.ts"() {
|
|
55847
56321
|
KIT_DIR_CANDIDATES = [
|
|
55848
56322
|
// in-repo: tools/himi-cli/src/design-kits.ts → <repo>/stdlib/design-kits/
|
|
55849
|
-
|
|
56323
|
+
join59(fileURLToPath23(new URL("../../..", import.meta.url)), "stdlib", "design-kits"),
|
|
55850
56324
|
// published CLI: <pkg>/dist/cli.mjs → <pkg>/stdlib/design-kits/
|
|
55851
|
-
|
|
56325
|
+
join59(fileURLToPath23(new URL("..", import.meta.url)), "stdlib", "design-kits")
|
|
55852
56326
|
];
|
|
55853
56327
|
}
|
|
55854
56328
|
});
|
|
@@ -55862,11 +56336,11 @@ __export(assets_exports, {
|
|
|
55862
56336
|
removeAssetPayload: () => removeAssetPayload,
|
|
55863
56337
|
verifyReport: () => verifyReport
|
|
55864
56338
|
});
|
|
55865
|
-
import { readFileSync as
|
|
56339
|
+
import { readFileSync as readFileSync67 } from "node:fs";
|
|
55866
56340
|
import { basename as basename16 } from "node:path";
|
|
55867
56341
|
function declaredAssets(dir) {
|
|
55868
56342
|
return collectAssets(dir).map((a) => {
|
|
55869
|
-
const bytes2 =
|
|
56343
|
+
const bytes2 = readFileSync67(a.path);
|
|
55870
56344
|
return {
|
|
55871
56345
|
name: a.name,
|
|
55872
56346
|
sha256: assetSha256(bytes2),
|
|
@@ -55943,7 +56417,7 @@ function verifyReport(dir, app, manifest) {
|
|
|
55943
56417
|
};
|
|
55944
56418
|
}
|
|
55945
56419
|
function addAssetPayload(manifest, filePath, opts) {
|
|
55946
|
-
const bytes2 =
|
|
56420
|
+
const bytes2 = readFileSync67(filePath);
|
|
55947
56421
|
const name = opts.name ?? basename16(filePath);
|
|
55948
56422
|
const sha2565 = assetSha256(bytes2);
|
|
55949
56423
|
const record2 = {
|
|
@@ -57667,9 +58141,9 @@ __export(run_exports, {
|
|
|
57667
58141
|
runPackage: () => runPackage,
|
|
57668
58142
|
withoutProdServe: () => withoutProdServe
|
|
57669
58143
|
});
|
|
57670
|
-
import { cpSync as cpSync2, existsSync as
|
|
58144
|
+
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";
|
|
57671
58145
|
import { networkInterfaces as osNetworkInterfaces, tmpdir as tmpdir4 } from "node:os";
|
|
57672
|
-
import { dirname as dirname32, join as
|
|
58146
|
+
import { dirname as dirname32, join as join60 } from "node:path";
|
|
57673
58147
|
import { fileURLToPath as fileURLToPath24 } from "node:url";
|
|
57674
58148
|
function makePackageRunHandler(opts) {
|
|
57675
58149
|
const store = new FileReleaseStore(opts.releaseRoot);
|
|
@@ -57697,9 +58171,9 @@ async function fetchBytes(url) {
|
|
|
57697
58171
|
}
|
|
57698
58172
|
async function resolvePackage(source, workDir) {
|
|
57699
58173
|
const isUrl2 = /^https?:\/\//i.test(source);
|
|
57700
|
-
if (!isUrl2 &&
|
|
57701
|
-
const appDirManifest =
|
|
57702
|
-
if (
|
|
58174
|
+
if (!isUrl2 && existsSync60(source) && statSync16(source).isDirectory()) {
|
|
58175
|
+
const appDirManifest = join60(source, "release-manifest.json");
|
|
58176
|
+
if (existsSync60(appDirManifest)) {
|
|
57703
58177
|
const opened2 = openPackageDir(source);
|
|
57704
58178
|
if (!opened2.validation.ok) {
|
|
57705
58179
|
throw new Error(`package is not valid:
|
|
@@ -57708,11 +58182,11 @@ async function resolvePackage(source, workDir) {
|
|
|
57708
58182
|
const app2 = opened2.header?.app ?? opened2.manifest?.app;
|
|
57709
58183
|
if (!app2) throw new Error(`${source} has no app id (no himi-package.json and no manifest.app)`);
|
|
57710
58184
|
if (!isSafeAppId(app2)) throw new Error(`${source} declares an unsafe app id ${JSON.stringify(app2)} \u2014 refusing to open it`);
|
|
57711
|
-
const root2 = workDir ?? mkdtempSync3(
|
|
57712
|
-
const target =
|
|
57713
|
-
if (
|
|
57714
|
-
|
|
57715
|
-
|
|
58185
|
+
const root2 = workDir ?? mkdtempSync3(join60(tmpdir4(), "himi-run-"));
|
|
58186
|
+
const target = join60(root2, app2);
|
|
58187
|
+
if (join60(source) !== target) {
|
|
58188
|
+
mkdirSync30(root2, { recursive: true });
|
|
58189
|
+
rmSync15(target, { recursive: true, force: true });
|
|
57716
58190
|
cpSync2(source, target, { recursive: true });
|
|
57717
58191
|
}
|
|
57718
58192
|
return {
|
|
@@ -57727,12 +58201,12 @@ async function resolvePackage(source, workDir) {
|
|
|
57727
58201
|
}
|
|
57728
58202
|
throw new Error(`${source} is a directory but has no release-manifest.json \u2014 is it a package?`);
|
|
57729
58203
|
}
|
|
57730
|
-
const bytes2 = isUrl2 ? await fetchBytes(source) :
|
|
57731
|
-
const root = workDir ?? mkdtempSync3(
|
|
58204
|
+
const bytes2 = isUrl2 ? await fetchBytes(source) : readFileSync68(source);
|
|
58205
|
+
const root = workDir ?? mkdtempSync3(join60(tmpdir4(), "himi-run-"));
|
|
57732
58206
|
const { app, appDir: appDir3 } = unpackTo(bytes2, root);
|
|
57733
58207
|
const opened = openPackageDir(appDir3);
|
|
57734
58208
|
if (!opened.validation.ok) {
|
|
57735
|
-
|
|
58209
|
+
rmSync15(workDir ? appDir3 : root, { recursive: true, force: true });
|
|
57736
58210
|
throw new Error(`package is not valid:
|
|
57737
58211
|
${opened.validation.errors.join("\n ")}`);
|
|
57738
58212
|
}
|
|
@@ -57751,14 +58225,14 @@ async function loadPreviewAssets() {
|
|
|
57751
58225
|
const mod = await Promise.resolve().then(() => (init_preview_assets_generated(), preview_assets_generated_exports));
|
|
57752
58226
|
const here = dirname32(fileURLToPath24(import.meta.url));
|
|
57753
58227
|
const candidates = [
|
|
57754
|
-
|
|
58228
|
+
join60(here, "preview-assets.generated.bin"),
|
|
57755
58229
|
// published: copied next to the bundled CLI
|
|
57756
|
-
|
|
58230
|
+
join60(here, "../../../packages/serve/src/preview-assets.generated.bin")
|
|
57757
58231
|
// in-repo checkout
|
|
57758
58232
|
];
|
|
57759
58233
|
for (const path2 of candidates) {
|
|
57760
|
-
if (!
|
|
57761
|
-
const blob =
|
|
58234
|
+
if (!existsSync60(path2)) continue;
|
|
58235
|
+
const blob = readFileSync68(path2);
|
|
57762
58236
|
return mod.assemblePreviewAssetsFromBlob(new Uint8Array(blob.buffer, blob.byteOffset, blob.byteLength));
|
|
57763
58237
|
}
|
|
57764
58238
|
return /* @__PURE__ */ new Map();
|
|
@@ -57806,7 +58280,7 @@ async function runPackage(source, opts = {}) {
|
|
|
57806
58280
|
const resolved2 = await resolvePackage(source, opts.workDir);
|
|
57807
58281
|
const assets = opts.assets ?? (opts.spa ? /* @__PURE__ */ new Map() : await loadPreviewAssets());
|
|
57808
58282
|
if (!opts.spa && assets.size === 0) {
|
|
57809
|
-
if (resolved2.temp)
|
|
58283
|
+
if (resolved2.temp) rmSync15(resolved2.temp, { recursive: true, force: true });
|
|
57810
58284
|
throw new Error(NO_SPA_MESSAGE);
|
|
57811
58285
|
}
|
|
57812
58286
|
const plane = makePackageRunHandler({
|
|
@@ -57844,7 +58318,7 @@ async function runPackage(source, opts = {}) {
|
|
|
57844
58318
|
lanBound = (await listen(lanServer, port, lanHost)).address;
|
|
57845
58319
|
} catch (e) {
|
|
57846
58320
|
await closeServer(server);
|
|
57847
|
-
if (resolved2.temp)
|
|
58321
|
+
if (resolved2.temp) rmSync15(resolved2.temp, { recursive: true, force: true });
|
|
57848
58322
|
throw new Error(`himi run --lan could not listen on ${lanHost}:${port} (${e.message}); drop --lan, or pass --port for a free one`);
|
|
57849
58323
|
}
|
|
57850
58324
|
bound.push(lanBound);
|
|
@@ -57866,7 +58340,7 @@ async function runPackage(source, opts = {}) {
|
|
|
57866
58340
|
warnings: resolved2.warnings,
|
|
57867
58341
|
close: async () => {
|
|
57868
58342
|
await Promise.all(servers.map(closeServer));
|
|
57869
|
-
if (resolved2.temp)
|
|
58343
|
+
if (resolved2.temp) rmSync15(resolved2.temp, { recursive: true, force: true });
|
|
57870
58344
|
}
|
|
57871
58345
|
};
|
|
57872
58346
|
}
|
|
@@ -57887,285 +58361,6 @@ var init_run = __esm({
|
|
|
57887
58361
|
}
|
|
57888
58362
|
});
|
|
57889
58363
|
|
|
57890
|
-
// src/run-watch.ts
|
|
57891
|
-
var run_watch_exports = {};
|
|
57892
|
-
__export(run_watch_exports, {
|
|
57893
|
-
ReloadBus: () => ReloadBus,
|
|
57894
|
-
boardFile: () => boardFile,
|
|
57895
|
-
diffReleases: () => diffReleases,
|
|
57896
|
-
himiDir: () => himiDir,
|
|
57897
|
-
ignoredChange: () => ignoredChange,
|
|
57898
|
-
isEmptyDiff: () => isEmptyDiff,
|
|
57899
|
-
rebuildBlocker: () => rebuildBlocker,
|
|
57900
|
-
reloadEvents: () => reloadEvents,
|
|
57901
|
-
removeInitWatchRecord: () => removeInitWatchRecord,
|
|
57902
|
-
removeRunRecord: () => removeRunRecord,
|
|
57903
|
-
serialize: () => serialize,
|
|
57904
|
-
watchContent: () => watchContent,
|
|
57905
|
-
watchFlagConflict: () => watchFlagConflict,
|
|
57906
|
-
watchedContentDir: () => watchedContentDir,
|
|
57907
|
-
writeRunRecord: () => writeRunRecord
|
|
57908
|
-
});
|
|
57909
|
-
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";
|
|
57910
|
-
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
57911
|
-
import { extname as extname4, join as join60, resolve as resolve47, sep as sep16 } from "node:path";
|
|
57912
|
-
function diffReleases(prev, next) {
|
|
57913
|
-
const bundles = Object.keys(next.bundles).filter((b) => prev.bundles[b]?.sha256 !== next.bundles[b].sha256).sort();
|
|
57914
|
-
const changedBundles = new Set(bundles);
|
|
57915
|
-
const screens = Object.entries(next.screens).filter(([id, s]) => {
|
|
57916
|
-
const before = prev.screens[id];
|
|
57917
|
-
return !before || before.descriptorHash !== s.descriptorHash || before.bundle !== s.bundle || s.bundle !== null && changedBundles.has(s.bundle);
|
|
57918
|
-
}).map(([id]) => id).sort();
|
|
57919
|
-
const prevIds = Object.keys(prev.screens).sort().join("\n");
|
|
57920
|
-
const nextIds = Object.keys(next.screens).sort().join("\n");
|
|
57921
|
-
return {
|
|
57922
|
-
screens,
|
|
57923
|
-
bundles,
|
|
57924
|
-
tokens: prev.tokensHash !== next.tokensHash,
|
|
57925
|
-
config: prev.appConfigHash !== next.appConfigHash || prevIds !== nextIds
|
|
57926
|
-
};
|
|
57927
|
-
}
|
|
57928
|
-
function reloadEvents(d) {
|
|
57929
|
-
const out2 = [];
|
|
57930
|
-
if (d.bundles.length) out2.push(["bundle.invalidated", { bundles: d.bundles }]);
|
|
57931
|
-
if (d.screens.length) out2.push(["screen.invalidated", { screenIds: d.screens }]);
|
|
57932
|
-
if (d.tokens) out2.push(["tokens.changed", {}]);
|
|
57933
|
-
if (d.config) out2.push(["config.invalidated", {}]);
|
|
57934
|
-
return out2;
|
|
57935
|
-
}
|
|
57936
|
-
function ignoredChange(relPath, isDirectory = () => false) {
|
|
57937
|
-
const parts = relPath.split(/[\\/]/).filter(Boolean);
|
|
57938
|
-
if (!parts.length) return true;
|
|
57939
|
-
const [top] = parts;
|
|
57940
|
-
if ([".himi", "dist", "node_modules", ".git", ".build", ...AGENT_TOOLING_DIRS].includes(top)) return true;
|
|
57941
|
-
if (parts.length === 1 && AGENT_TOOLING_ROOT_FILES.includes(top)) return true;
|
|
57942
|
-
if ((top === "shots" || top === "evidence") && (parts.length > 1 || isDirectory(top))) return true;
|
|
57943
|
-
const name = parts[parts.length - 1];
|
|
57944
|
-
return /(^\.#|~$|\.sw[a-p]$|^\.DS_Store$|\.tmp$)/.test(name);
|
|
57945
|
-
}
|
|
57946
|
-
function watchContent(dir, onChange, opts = {}) {
|
|
57947
|
-
const debounceMs = opts.debounceMs ?? 200;
|
|
57948
|
-
const watch2 = opts.watch ?? ((p, o, l) => fsWatch(p, o, (event, file) => l(event, file === null ? null : String(file))));
|
|
57949
|
-
let timer = null;
|
|
57950
|
-
const pending = /* @__PURE__ */ new Set();
|
|
57951
|
-
const watcher2 = watch2(dir, { recursive: true }, (_event, file) => {
|
|
57952
|
-
if (!file || ignoredChange(file, (rel) => isDir(join60(dir, rel)))) return;
|
|
57953
|
-
pending.add(file);
|
|
57954
|
-
if (timer) clearTimeout(timer);
|
|
57955
|
-
timer = setTimeout(() => {
|
|
57956
|
-
timer = null;
|
|
57957
|
-
const paths = [...pending];
|
|
57958
|
-
pending.clear();
|
|
57959
|
-
onChange(paths);
|
|
57960
|
-
}, debounceMs);
|
|
57961
|
-
});
|
|
57962
|
-
return {
|
|
57963
|
-
close() {
|
|
57964
|
-
if (timer) clearTimeout(timer);
|
|
57965
|
-
watcher2.close();
|
|
57966
|
-
}
|
|
57967
|
-
};
|
|
57968
|
-
}
|
|
57969
|
-
function watchedContentDir(source) {
|
|
57970
|
-
if (!source || /^https?:\/\//i.test(source)) return null;
|
|
57971
|
-
const dir = resolve47(source);
|
|
57972
|
-
try {
|
|
57973
|
-
return statSync16(dir).isDirectory() && existsSync60(join60(dir, "himalaya.content.json")) ? dir : null;
|
|
57974
|
-
} catch {
|
|
57975
|
-
return null;
|
|
57976
|
-
}
|
|
57977
|
-
}
|
|
57978
|
-
function watchFlagConflict(opts) {
|
|
57979
|
-
if (opts.source) {
|
|
57980
|
-
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." };
|
|
57981
|
-
}
|
|
57982
|
-
if (opts.browser) {
|
|
57983
|
-
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." };
|
|
57984
|
-
}
|
|
57985
|
-
if (opts.spa) {
|
|
57986
|
-
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." };
|
|
57987
|
-
}
|
|
57988
|
-
return null;
|
|
57989
|
-
}
|
|
57990
|
-
function rebuildBlocker(outcome, servedMissing) {
|
|
57991
|
-
const failed2 = outcome.failedToBuild[0];
|
|
57992
|
-
if (failed2) return `${failed2.screenId}: ${failed2.error.split("\n")[0]}`;
|
|
57993
|
-
const had = new Set(servedMissing.map((m) => `${m.screen}\0${m.bundle}`));
|
|
57994
|
-
const lost = outcome.missingBundles.find((m) => !had.has(`${m.screen}\0${m.bundle}`));
|
|
57995
|
-
if (lost) return `${lost.screen}: its worker bundle "${lost.bundle}" is referenced but was not built`;
|
|
57996
|
-
return null;
|
|
57997
|
-
}
|
|
57998
|
-
function serialize(task) {
|
|
57999
|
-
let running = null;
|
|
58000
|
-
let again = false;
|
|
58001
|
-
let stopped = false;
|
|
58002
|
-
const go = () => {
|
|
58003
|
-
if (stopped) return;
|
|
58004
|
-
if (running) {
|
|
58005
|
-
again = true;
|
|
58006
|
-
return;
|
|
58007
|
-
}
|
|
58008
|
-
running = (async () => {
|
|
58009
|
-
try {
|
|
58010
|
-
await task();
|
|
58011
|
-
} finally {
|
|
58012
|
-
running = null;
|
|
58013
|
-
if (again && !stopped) {
|
|
58014
|
-
again = false;
|
|
58015
|
-
go();
|
|
58016
|
-
}
|
|
58017
|
-
}
|
|
58018
|
-
})();
|
|
58019
|
-
};
|
|
58020
|
-
return Object.assign(() => go(), {
|
|
58021
|
-
async stop() {
|
|
58022
|
-
stopped = true;
|
|
58023
|
-
again = false;
|
|
58024
|
-
await running?.catch(() => {
|
|
58025
|
-
});
|
|
58026
|
-
}
|
|
58027
|
-
});
|
|
58028
|
-
}
|
|
58029
|
-
function himiDir(dir) {
|
|
58030
|
-
const root = join60(dir, ".himi");
|
|
58031
|
-
try {
|
|
58032
|
-
return lstatSync14(root).isDirectory() ? root : null;
|
|
58033
|
-
} catch (e) {
|
|
58034
|
-
if (e.code !== "ENOENT") return null;
|
|
58035
|
-
mkdirSync30(root, { recursive: true });
|
|
58036
|
-
return lstatSync14(root).isDirectory() ? root : null;
|
|
58037
|
-
}
|
|
58038
|
-
}
|
|
58039
|
-
function writeRunRecord(dir, record2) {
|
|
58040
|
-
const root = himiDir(dir);
|
|
58041
|
-
if (!root) return false;
|
|
58042
|
-
const tmp = join60(root, `.run.json.${process.pid}.${randomUUID4()}.tmp`);
|
|
58043
|
-
try {
|
|
58044
|
-
writeFileSync23(tmp, `${JSON.stringify(record2, null, 2)}
|
|
58045
|
-
`, { flag: "wx" });
|
|
58046
|
-
renameSync7(tmp, join60(root, "run.json"));
|
|
58047
|
-
} catch (e) {
|
|
58048
|
-
rmSync15(tmp, { force: true });
|
|
58049
|
-
throw e;
|
|
58050
|
-
}
|
|
58051
|
-
return true;
|
|
58052
|
-
}
|
|
58053
|
-
function removeRunRecord(dir, owner) {
|
|
58054
|
-
const root = join60(dir, ".himi");
|
|
58055
|
-
try {
|
|
58056
|
-
if (!lstatSync14(root).isDirectory()) return;
|
|
58057
|
-
} catch {
|
|
58058
|
-
return;
|
|
58059
|
-
}
|
|
58060
|
-
if (owner !== void 0) {
|
|
58061
|
-
const bytes2 = readConfined(root, "run.json");
|
|
58062
|
-
let pid;
|
|
58063
|
-
try {
|
|
58064
|
-
pid = bytes2 ? JSON.parse(bytes2.toString("utf8")).pid : void 0;
|
|
58065
|
-
} catch {
|
|
58066
|
-
}
|
|
58067
|
-
if (typeof pid === "number" && pid !== owner) return;
|
|
58068
|
-
}
|
|
58069
|
-
rmSync15(join60(root, "run.json"), { force: true });
|
|
58070
|
-
}
|
|
58071
|
-
function removeInitWatchRecord(dir, owner) {
|
|
58072
|
-
const root = join60(dir, ".himi");
|
|
58073
|
-
try {
|
|
58074
|
-
if (!lstatSync14(root).isDirectory()) return;
|
|
58075
|
-
} catch {
|
|
58076
|
-
return;
|
|
58077
|
-
}
|
|
58078
|
-
const bytes2 = readConfined(root, "watch.json");
|
|
58079
|
-
if (!bytes2) return;
|
|
58080
|
-
let pid;
|
|
58081
|
-
try {
|
|
58082
|
-
pid = JSON.parse(bytes2.toString("utf8")).pid;
|
|
58083
|
-
} catch {
|
|
58084
|
-
return;
|
|
58085
|
-
}
|
|
58086
|
-
if (pid !== owner) return;
|
|
58087
|
-
rmSync15(join60(root, "watch.json"), { force: true });
|
|
58088
|
-
}
|
|
58089
|
-
function boardFile(dir, pathname) {
|
|
58090
|
-
if (pathname === RUN_IDENTITY_PATH) {
|
|
58091
|
-
return new Response(JSON.stringify(runIdentity(dir)), { status: 200, headers: { "content-type": "application/json", "cache-control": "no-store" } });
|
|
58092
|
-
}
|
|
58093
|
-
let rel = null;
|
|
58094
|
-
if (pathname === "/__himi/studio/" || pathname === "/__himi/studio/index.html") rel = join60("studio", "index.html");
|
|
58095
|
-
else if (pathname === "/__himi/studio/state.js") rel = join60("studio", "state.js");
|
|
58096
|
-
else if (pathname === "/__himi/studio/timeline.js") rel = join60("studio", "timeline.js");
|
|
58097
|
-
else {
|
|
58098
|
-
const m = /^\/__himi\/history\/([0-9a-f]{64}\.(png|jpg|gif|webp))$/.exec(pathname);
|
|
58099
|
-
if (m) rel = join60("history", m[1]);
|
|
58100
|
-
}
|
|
58101
|
-
if (!rel) return null;
|
|
58102
|
-
const bytes2 = readConfined(resolve47(dir, ".himi"), rel);
|
|
58103
|
-
if (!bytes2) return new Response("not found", { status: 404 });
|
|
58104
|
-
return new Response(bytes2, {
|
|
58105
|
-
status: 200,
|
|
58106
|
-
headers: { "content-type": BOARD_TYPES[extname4(rel)] ?? "application/octet-stream", "cache-control": rel.startsWith("history") ? "public, max-age=31536000, immutable" : "no-store" }
|
|
58107
|
-
});
|
|
58108
|
-
}
|
|
58109
|
-
function readConfined(root, rel) {
|
|
58110
|
-
try {
|
|
58111
|
-
if (!lstatSync14(root).isDirectory()) return null;
|
|
58112
|
-
const parts = rel.split(sep16);
|
|
58113
|
-
let at2 = root;
|
|
58114
|
-
for (let i = 0; i < parts.length; i++) {
|
|
58115
|
-
at2 = join60(at2, parts[i]);
|
|
58116
|
-
const st = lstatSync14(at2);
|
|
58117
|
-
if (st.isSymbolicLink() || !(i === parts.length - 1 ? st.isFile() : st.isDirectory())) return null;
|
|
58118
|
-
}
|
|
58119
|
-
const canonicalRoot = realpathSync9(root);
|
|
58120
|
-
if (!realpathSync9(at2).startsWith(canonicalRoot + sep16)) return null;
|
|
58121
|
-
const fd = openSync4(at2, constants2.O_RDONLY | (constants2.O_NOFOLLOW ?? 0));
|
|
58122
|
-
try {
|
|
58123
|
-
if (!fstatSync(fd).isFile()) return null;
|
|
58124
|
-
return readFileSync68(fd);
|
|
58125
|
-
} finally {
|
|
58126
|
-
closeSync5(fd);
|
|
58127
|
-
}
|
|
58128
|
-
} catch {
|
|
58129
|
-
return null;
|
|
58130
|
-
}
|
|
58131
|
-
}
|
|
58132
|
-
var isEmptyDiff, ReloadBus, isDir, BOARD_TYPES;
|
|
58133
|
-
var init_run_watch = __esm({
|
|
58134
|
-
"src/run-watch.ts"() {
|
|
58135
|
-
init_source_deny();
|
|
58136
|
-
init_run_identity();
|
|
58137
|
-
isEmptyDiff = (d) => !d.screens.length && !d.bundles.length && !d.tokens && !d.config;
|
|
58138
|
-
ReloadBus = class {
|
|
58139
|
-
subscribers = /* @__PURE__ */ new Set();
|
|
58140
|
-
subscribe(send2) {
|
|
58141
|
-
this.subscribers.add(send2);
|
|
58142
|
-
return () => this.subscribers.delete(send2);
|
|
58143
|
-
}
|
|
58144
|
-
publish(event, data) {
|
|
58145
|
-
for (const send2 of [...this.subscribers]) send2(event, data);
|
|
58146
|
-
}
|
|
58147
|
-
get listeners() {
|
|
58148
|
-
return this.subscribers.size;
|
|
58149
|
-
}
|
|
58150
|
-
};
|
|
58151
|
-
isDir = (path2) => {
|
|
58152
|
-
try {
|
|
58153
|
-
return statSync16(path2).isDirectory();
|
|
58154
|
-
} catch {
|
|
58155
|
-
return false;
|
|
58156
|
-
}
|
|
58157
|
-
};
|
|
58158
|
-
BOARD_TYPES = {
|
|
58159
|
-
".html": "text/html; charset=utf-8",
|
|
58160
|
-
".js": "text/javascript; charset=utf-8",
|
|
58161
|
-
".png": "image/png",
|
|
58162
|
-
".jpg": "image/jpeg",
|
|
58163
|
-
".gif": "image/gif",
|
|
58164
|
-
".webp": "image/webp"
|
|
58165
|
-
};
|
|
58166
|
-
}
|
|
58167
|
-
});
|
|
58168
|
-
|
|
58169
58364
|
// src/run-browser.ts
|
|
58170
58365
|
var run_browser_exports = {};
|
|
58171
58366
|
__export(run_browser_exports, {
|
|
@@ -72212,7 +72407,8 @@ var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
|
|
|
72212
72407
|
"no-agent",
|
|
72213
72408
|
"no-first-look",
|
|
72214
72409
|
"no-image",
|
|
72215
|
-
"no-watch"
|
|
72410
|
+
"no-watch",
|
|
72411
|
+
"mocks"
|
|
72216
72412
|
]);
|
|
72217
72413
|
var SHELL_ENUM = SHELL_NAMES2.join("|");
|
|
72218
72414
|
var SWITCH_FLAGS = new Set([...BOOLEAN_FLAGS].filter((name) => name !== "deep"));
|
|
@@ -72327,7 +72523,8 @@ var USAGE = `himi \u2014 the Himalaya authoring CLI (remote dev loop)
|
|
|
72327
72523
|
preview itself (\`himi run --watch\`, detached, --no-open, log
|
|
72328
72524
|
.himi/watch.log, stops after 30 idle minutes) and reports
|
|
72329
72525
|
watch: {started, pid, url, board, log} \u2014 or {skipped} for a
|
|
72330
|
-
--remote template
|
|
72526
|
+
--remote template, a CI environment or a test runner. Nothing
|
|
72527
|
+
else skips it: outside CI it starts, and watch.note says why
|
|
72331
72528
|
himi template list [--remote] list starter templates + metadata (title/description/tags) + each one's start prompt
|
|
72332
72529
|
himi template prompt <name> print the prompt that starts an app from that template
|
|
72333
72530
|
himi template publish [--dir <app>] [--source-dir <dir>] [--name <n>] [--icon <png>] [--visibility public|unlisted]
|
|
@@ -72373,7 +72570,9 @@ var USAGE = `himi \u2014 the Himalaya authoring CLI (remote dev loop)
|
|
|
72373
72570
|
journal a postcard for the owner (.himi/progress.jsonl): what
|
|
72374
72571
|
changed, which journey step, and the image that shows it (copied
|
|
72375
72572
|
into .himi/history/ so later renders cannot replace it). With no
|
|
72376
|
-
--image the current home shot is attached (never a stale one
|
|
72573
|
+
--image the current home shot is attached (never a stale one,
|
|
72574
|
+
never placeholders or an error screen \u2014 then the newest home
|
|
72575
|
+
picture with content, or none, imageNote says which;
|
|
72377
72576
|
--no-image opts out; imageAttached says which; on the
|
|
72378
72577
|
--step review handover card it attaches even as a repeat,
|
|
72379
72578
|
and the output names the making-of to hand over). Bare
|
|
@@ -72417,7 +72616,7 @@ var USAGE = `himi \u2014 the Himalaya authoring CLI (remote dev loop)
|
|
|
72417
72616
|
(--ai writes rows for you to review first)
|
|
72418
72617
|
himi secrets set|list|rotate --app <app> --name <name> [--environment preview|production]
|
|
72419
72618
|
encrypted secret metadata; set/rotate read the value from stdin
|
|
72420
|
-
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>]
|
|
72619
|
+
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>]
|
|
72421
72620
|
[--remote --app <app> --release <id>]
|
|
72422
72621
|
headless web render + diagnostics (no sim). Default: local content dir.
|
|
72423
72622
|
A bare --png writes .himi/shots/<screenId>.png (what the build board shows).
|
|
@@ -72427,6 +72626,10 @@ var USAGE = `himi \u2014 the Himalaya authoring CLI (remote dev loop)
|
|
|
72427
72626
|
render LOCALLY with --live (+ --png), not --remote.
|
|
72428
72627
|
--live runs the worker's fetches against REAL Wix (minted visitor token) and awaits
|
|
72429
72628
|
load(), so the render shows LIVE data (needs network + a Wix clientId in config.ts);
|
|
72629
|
+
--mocks runs load() against the app's OWN recorded data instead \u2014 dev/net-mocks.json
|
|
72630
|
+
(what \`himi test --record\` writes) and the config's cms seed rows, as \`himi test\`'s
|
|
72631
|
+
shots do; no request leaves this machine. With none, it renders the initial state.
|
|
72632
|
+
The automatic pictures (init's first look, validate's refresh) use it;
|
|
72430
72633
|
--tree adds stable raw structure plus a resolvedRenderTree when hydrated;
|
|
72431
72634
|
--dump-state explicitly includes the full worker snapshot;
|
|
72432
72635
|
--state '{"<svc>":{\u2026}}' injects worker state (render loaded/error states);
|
|
@@ -72950,17 +73153,17 @@ WHAT A PATCH DOES NOT CARRY \u2014 all reported in the output, never silent
|
|
|
72950
73153
|
himi docs releasing-with-himi-serve`;
|
|
72951
73154
|
var COMMANDS = [
|
|
72952
73155
|
{ 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>]" },
|
|
72953
|
-
{ 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" },
|
|
73156
|
+
{ 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" },
|
|
72954
73157
|
{ 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]" },
|
|
72955
73158
|
{ 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]" },
|
|
72956
73159
|
{ 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>]" },
|
|
72957
73160
|
{ 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>]" },
|
|
72958
|
-
{ 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]" },
|
|
73161
|
+
{ 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]" },
|
|
72959
73162
|
{ 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]" },
|
|
72960
73163
|
{ 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>]" },
|
|
72961
|
-
{ 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' },
|
|
73164
|
+
{ 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' },
|
|
72962
73165
|
{ 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]" },
|
|
72963
|
-
{ 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]" },
|
|
73166
|
+
{ 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]" },
|
|
72964
73167
|
{ 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>]" },
|
|
72965
73168
|
{ 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]" },
|
|
72966
73169
|
{ name: "icons", summary: "list the valid Icon.name vocabulary (an unknown name renders a fallback glyph; validate warns on unknown names) (offline)", required: "[--json]" },
|
|
@@ -72971,7 +73174,7 @@ var COMMANDS = [
|
|
|
72971
73174
|
{ 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]" },
|
|
72972
73175
|
{ 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>]" },
|
|
72973
73176
|
{ 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]" },
|
|
72974
|
-
{ 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)" },
|
|
73177
|
+
{ 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)" },
|
|
72975
73178
|
{ 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>" },
|
|
72976
73179
|
{ 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]" },
|
|
72977
73180
|
{ 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>]" },
|
|
@@ -74161,7 +74364,7 @@ function defaultTargetForCommand(command, authoringDir) {
|
|
|
74161
74364
|
return isHimalayaMonorepoCheckout(authoringDir) ? void 0 : "wix";
|
|
74162
74365
|
}
|
|
74163
74366
|
function renderDataSource(input) {
|
|
74164
|
-
if (!input.live) return input.hydrate ? "worker-initial-state" : "static";
|
|
74367
|
+
if (!input.live) return input.mocked ? "app-mocks" : input.hydrate ? "worker-initial-state" : "static";
|
|
74165
74368
|
if (!input.liveCalls) return "static";
|
|
74166
74369
|
if (input.liveCalls.length === 0) return "worker-load-no-network";
|
|
74167
74370
|
return (input.liveReturned ?? 0) > 0 ? "wix-network" : "wix-network-failed";
|
|
@@ -74643,6 +74846,7 @@ async function loadAuthoring() {
|
|
|
74643
74846
|
webWixCall: wml.webWixCall,
|
|
74644
74847
|
freezeTrafficToMocks: nf.freezeTrafficToMocks,
|
|
74645
74848
|
bootAndSnapshotState: crawl.bootAndSnapshotState,
|
|
74849
|
+
isUnservedRequest: crawl.isUnservedRequest,
|
|
74646
74850
|
renderDescriptorWeb: rd.renderDescriptorWeb,
|
|
74647
74851
|
renderPngBatch: rd.renderPngBatch,
|
|
74648
74852
|
pngAvailable: rd.pngAvailable,
|
|
@@ -75866,8 +76070,10 @@ ${handover.hint[0].toUpperCase()}${handover.hint.slice(1)}.`);
|
|
|
75866
76070
|
const handover = step === "review";
|
|
75867
76071
|
if (!images.length && flags["no-image"] !== true && isContentPackage) {
|
|
75868
76072
|
const auto = (await Promise.resolve().then(() => (init_milestones(), milestones_exports))).homeShotForPostcard(dir, { repeatOk: handover });
|
|
75869
|
-
if ("image" in auto)
|
|
75870
|
-
|
|
76073
|
+
if ("image" in auto) {
|
|
76074
|
+
images.push(auto.image);
|
|
76075
|
+
if (auto.note) imageNote = auto.note;
|
|
76076
|
+
} else imageNote = `no picture attached: ${auto.skipped}`;
|
|
75871
76077
|
}
|
|
75872
76078
|
try {
|
|
75873
76079
|
const entry = appendProgress2(dir, { message, ...step ? { step } : {}, images });
|
|
@@ -75875,7 +76081,7 @@ ${handover.hint[0].toUpperCase()}${handover.hint.slice(1)}.`);
|
|
|
75875
76081
|
const { count: count2 } = summarizeProgress2(readProgress2(dir).entries);
|
|
75876
76082
|
const imageAttached = (entry.images?.length ?? 0) > 0;
|
|
75877
76083
|
const handed = handover && isContentPackage ? (await Promise.resolve().then(() => (init_milestones(), milestones_exports))).afterHandoverPostcard(dir, { content: str9(flags.content) }) : null;
|
|
75878
|
-
print({ ok: true, entry, journal: journal2, count: count2, imageAttached,
|
|
76084
|
+
print({ ok: true, entry, journal: journal2, count: count2, imageAttached, ...imageNote ? { imageNote } : {}, ...notified === void 0 ? {} : { notified }, ...handed ? { makingOf: handed.makingOf, next: handed.next } : {}, ...hint });
|
|
75879
76085
|
return 0;
|
|
75880
76086
|
} catch (e) {
|
|
75881
76087
|
if (e instanceof ProgressError2) {
|
|
@@ -75952,7 +76158,7 @@ ${handover.hint[0].toUpperCase()}${handover.hint.slice(1)}.`);
|
|
|
75952
76158
|
return results.every((result2) => result2.ok) ? 0 : 1;
|
|
75953
76159
|
}
|
|
75954
76160
|
const app = await appId(dir);
|
|
75955
|
-
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();
|
|
76161
|
+
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();
|
|
75956
76162
|
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));
|
|
75957
76163
|
let config;
|
|
75958
76164
|
try {
|
|
@@ -76257,6 +76463,7 @@ ${handover.hint[0].toUpperCase()}${handover.hint.slice(1)}.`);
|
|
|
76257
76463
|
bootAndSnapshotState: bootAndSnapshotState2,
|
|
76258
76464
|
renderDescriptorWeb: renderDescriptorWeb2,
|
|
76259
76465
|
renderPngBatch: renderPngBatch2,
|
|
76466
|
+
isUnserved: isUnservedRequest2,
|
|
76260
76467
|
writePng: (path2, base64) => writePngFile(path2, base64),
|
|
76261
76468
|
clearDir: (d) => rmSync16(d, { recursive: true, force: true })
|
|
76262
76469
|
}
|
|
@@ -76392,6 +76599,14 @@ ${[...afterRun.next, ...testReminder ? [testReminder] : []].join("\n")}`);
|
|
|
76392
76599
|
io.error(JSON.stringify({ error: "--static and --live are mutually exclusive: --static renders raw bindings, --live renders real Wix data" }));
|
|
76393
76600
|
return 2;
|
|
76394
76601
|
}
|
|
76602
|
+
if (flags.mocks !== void 0 && flags.mocks !== true) {
|
|
76603
|
+
io.error(JSON.stringify({ error: `--mocks takes no value (got "${String(flags.mocks)}") \u2014 pass it bare` }));
|
|
76604
|
+
return 2;
|
|
76605
|
+
}
|
|
76606
|
+
if (flags.mocks === true && (live2 || flags.static === true)) {
|
|
76607
|
+
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"}` }));
|
|
76608
|
+
return 2;
|
|
76609
|
+
}
|
|
76395
76610
|
let injectedState;
|
|
76396
76611
|
if (flags.state !== void 0) {
|
|
76397
76612
|
const stateArg = str9(flags.state);
|
|
@@ -76462,6 +76677,22 @@ ${[...afterRun.next, ...testReminder ? [testReminder] : []].join("\n")}`);
|
|
|
76462
76677
|
return 2;
|
|
76463
76678
|
}
|
|
76464
76679
|
}
|
|
76680
|
+
let mocks;
|
|
76681
|
+
let mocksNote;
|
|
76682
|
+
if (flags.mocks === true) {
|
|
76683
|
+
let rules = null;
|
|
76684
|
+
let bad2;
|
|
76685
|
+
try {
|
|
76686
|
+
rules = (await Promise.resolve().then(() => (init_test(), test_exports))).defaultNetRules(dir);
|
|
76687
|
+
} catch (e) {
|
|
76688
|
+
bad2 = e.message.split("\n")[0].slice(0, 200);
|
|
76689
|
+
}
|
|
76690
|
+
const cmsSeed = await cmsSeedRows(dir, app).catch(() => null);
|
|
76691
|
+
const seeded = cmsSeed && Object.keys(cmsSeed).length ? cmsSeed : null;
|
|
76692
|
+
const picked = renderMocksFrom(rules, bad2, seeded);
|
|
76693
|
+
mocks = picked.mocks;
|
|
76694
|
+
mocksNote = picked.note;
|
|
76695
|
+
}
|
|
76465
76696
|
const { buildContentBundles: buildContentBundles2, buildReleaseFromContent: buildReleaseFromContent2, bundleOutDir: bundleOutDir2 } = await loadAuthoring();
|
|
76466
76697
|
const bres = await buildContentBundles2(dir, { resolution: resolutionFlag(flags) });
|
|
76467
76698
|
const out2 = commandScratchDir("himi-render-");
|
|
@@ -76470,7 +76701,7 @@ ${[...afterRun.next, ...testReminder ? [testReminder] : []].join("\n")}`);
|
|
|
76470
76701
|
...hasWorkers ? { tier5Dir: bundleOutDir2(dir) } : {},
|
|
76471
76702
|
// The settled state is always kept (printed only under --dump-state): it is what says whether
|
|
76472
76703
|
// the picture landed on the screen's content or on its error, loading or empty state.
|
|
76473
|
-
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 } : {} } }
|
|
76704
|
+
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 } } : {} } }
|
|
76474
76705
|
});
|
|
76475
76706
|
const r = (outcome.renders ?? [])[0];
|
|
76476
76707
|
if (!r) {
|
|
@@ -76500,7 +76731,9 @@ ${[...afterRun.next, ...testReminder ? [testReminder] : []].join("\n")}`);
|
|
|
76500
76731
|
scheme,
|
|
76501
76732
|
renderable: r.renderable,
|
|
76502
76733
|
hydrated: live2 ? "live" : hydrate,
|
|
76503
|
-
dataSource: renderDataSource({ live: live2, hydrate, liveCalls: r.liveCalls, liveReturned: r.liveReturned }),
|
|
76734
|
+
dataSource: renderDataSource({ live: live2, hydrate, liveCalls: r.liveCalls, liveReturned: r.liveReturned, mocked: !!r.mockCalls }),
|
|
76735
|
+
// What --mocks loaded from, how many requests the load made, and the ones no layer held.
|
|
76736
|
+
...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 } : {} } } : {},
|
|
76504
76737
|
...live2 ? {
|
|
76505
76738
|
liveCalls: r.liveCalls?.length ?? 0,
|
|
76506
76739
|
// Beside the attempt count, because the two coming apart IS the signal (#1883).
|
|
@@ -76552,10 +76785,11 @@ ${[...afterRun.next, ...testReminder ? [testReminder] : []].join("\n")}`);
|
|
|
76552
76785
|
let landed = null;
|
|
76553
76786
|
if (pngFile && r.renderable) {
|
|
76554
76787
|
const { recordShotState: recordShotState2, screenStateOf: screenStateOf2 } = await Promise.resolve().then(() => (init_screen_state(), screen_state_exports));
|
|
76555
|
-
|
|
76788
|
+
const mocked = !!r.mockCalls;
|
|
76789
|
+
landed = screenStateOf2(r.serviceState, r.html, { initial: !live2 && !mocked, offline: mocked && (r.mockUnserved?.length ?? 0) > 0 });
|
|
76556
76790
|
recordShotState2(dir, screenId, pngFile, landed);
|
|
76557
76791
|
}
|
|
76558
|
-
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
|
|
76792
|
+
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;
|
|
76559
76793
|
const afterPng = pngFile && !autoSkip ? milestoneKit.afterRenderPng(dir, { screenId, homeScreenId: outcome.homeScreenId ?? null, pngFile, renderable: r.renderable, content: str9(flags.content) }) : null;
|
|
76560
76794
|
const reminder = process.env.HIMI_AUTO_RENDER ? void 0 : milestoneKit.watchReminder(dir, { content: str9(flags.content) });
|
|
76561
76795
|
const renderNext = [...reminder ? [reminder] : [], ...afterPng?.next ?? []];
|
|
@@ -77318,7 +77552,9 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
|
|
|
77318
77552
|
releaseId: release,
|
|
77319
77553
|
content: str9(flags.content),
|
|
77320
77554
|
home: outcome.homeScreenId ?? null,
|
|
77321
|
-
...draftShot ? { homeShot: draftShot, sourceChangedAtMs } : {}
|
|
77555
|
+
...draftShot ? { homeShot: draftShot, sourceChangedAtMs } : {},
|
|
77556
|
+
// Its screen hashes say what changed since the last draft, on the postcard.
|
|
77557
|
+
manifest
|
|
77322
77558
|
}) : null;
|
|
77323
77559
|
print({
|
|
77324
77560
|
ok: true,
|