@wix/himalaya-cli 0.1436.0 → 0.1438.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 +135 -30
- package/guides/base44-app-to-himi-app.md +18 -6
- package/guides/himi-json-output.md +6 -1
- package/guides/himi-test.md +2 -1
- package/package.json +2 -2
package/dist/cli.mjs
CHANGED
|
@@ -6939,7 +6939,7 @@ function validator(schema) {
|
|
|
6939
6939
|
}
|
|
6940
6940
|
function compileV2(html, profile, options) {
|
|
6941
6941
|
const diagnostics = [], sourceMap2 = {}, dependencies = [options.file];
|
|
6942
|
-
const
|
|
6942
|
+
const requiredCapabilities2 = /* @__PURE__ */ new Set();
|
|
6943
6943
|
const $2 = cheerio2.load(html, { sourceCodeLocationInfo: true });
|
|
6944
6944
|
const loc = (el2) => ({ file: options.file, line: el2?.sourceCodeLocation?.startLine ?? 1, column: el2?.sourceCodeLocation?.startCol ?? 1 });
|
|
6945
6945
|
const error = (el2, code, message, hint) => diagnostics.push({ ...loc(el2), code, severity: "error", message, ...hint ? { hint } : {} });
|
|
@@ -6973,7 +6973,7 @@ function compileV2(html, profile, options) {
|
|
|
6973
6973
|
} else error(script, "HHP-SCRIPT", "Use one external application/himalaya-controller script. Inline/page JavaScript is unavailable.");
|
|
6974
6974
|
}
|
|
6975
6975
|
let info4;
|
|
6976
|
-
for (const cap of moduleCapabilities(controller?.source ?? directService?.source ?? "", controller?.file ?? options.file, profile))
|
|
6976
|
+
for (const cap of moduleCapabilities(controller?.source ?? directService?.source ?? "", controller?.file ?? options.file, profile)) requiredCapabilities2.add(cap);
|
|
6977
6977
|
if (controller) {
|
|
6978
6978
|
dependencies.push(controller.file);
|
|
6979
6979
|
info4 = analyzeController(controller.source, controller.file, profile, diagnostics);
|
|
@@ -7049,7 +7049,7 @@ function compileV2(html, profile, options) {
|
|
|
7049
7049
|
if (!info4?.handlers.has(action.slice(`tier5.${serviceName}.`.length))) error(el2, "HHP-EVENT", `Unknown structured handler ${action}.`);
|
|
7050
7050
|
} else if (action.startsWith("capability.")) {
|
|
7051
7051
|
const [, cap, method] = action.split(".");
|
|
7052
|
-
|
|
7052
|
+
requiredCapabilities2.add(cap);
|
|
7053
7053
|
if (!grants(profile.declaredCapabilities, cap)) error(el2, "HHP-CAPABILITY-MISSING", `Action requires declared capability ${cap}.`);
|
|
7054
7054
|
if (!own5(profile.capabilities, cap)?.methods.includes(method)) error(el2, "HHP-EVENT", `Unknown capability method ${action}.`);
|
|
7055
7055
|
} else if (!profile.handlers?.includes(action)) error(el2, "HHP-EVENT", `Action ${action} is not declared by this app.`);
|
|
@@ -7288,7 +7288,7 @@ function compileV2(html, profile, options) {
|
|
|
7288
7288
|
if (tag === "a") {
|
|
7289
7289
|
if (attrs.href?.startsWith("#")) props.action = { actionId: "himi.open", params: { screenId: attrs.href.slice(1) } };
|
|
7290
7290
|
else if (/^https:\/\//.test(attrs.href ?? "")) {
|
|
7291
|
-
|
|
7291
|
+
requiredCapabilities2.add("open_url");
|
|
7292
7292
|
if (!grants(profile.declaredCapabilities, "open_url")) error(el2, "HHP-CAPABILITY-MISSING", "External links require open_url.");
|
|
7293
7293
|
props.action = { actionId: "capability.open_url.open", args: { url: attrs.href } };
|
|
7294
7294
|
} else error(el2, "HHP-LINK", "Links require #screenId or an HTTPS URL.");
|
|
@@ -7419,7 +7419,7 @@ ${directService.source}
|
|
|
7419
7419
|
bundleName,
|
|
7420
7420
|
sourceMap: sourceMap2,
|
|
7421
7421
|
dependencies,
|
|
7422
|
-
manifest: { profileVersion: "hhp/2", catalogVersion: profile.catalogVersion, screenId, bundleName, serviceName: info4 ? serviceName : null, requiredCapabilities: [...
|
|
7422
|
+
manifest: { profileVersion: "hhp/2", catalogVersion: profile.catalogVersion, screenId, bundleName, serviceName: info4 ? serviceName : null, requiredCapabilities: [...requiredCapabilities2].sort(), inputSha256: createHash6("sha256").update(html).digest("hex") }
|
|
7423
7423
|
};
|
|
7424
7424
|
}
|
|
7425
7425
|
var camel, semantic, actionAliases, ajv, validators;
|
|
@@ -8022,6 +8022,7 @@ function buttonVariant(style) {
|
|
|
8022
8022
|
var SYSTEM_FONT_FAMILY, TONAL_ELEVATION_ALPHA, BUTTON_MIN_HIT_TARGET, nonNegative, nonEmpty2, own, COLOR_TOKEN_PROPS, TYPOGRAPHY_TOKEN_PROPS, BUTTON_VARIANTS;
|
|
8023
8023
|
var init_tokens = __esm({
|
|
8024
8024
|
"../../core/ts/src/tokens.ts"() {
|
|
8025
|
+
"use strict";
|
|
8025
8026
|
SYSTEM_FONT_FAMILY = "$system";
|
|
8026
8027
|
TONAL_ELEVATION_ALPHA = [0, 0.05, 0.08, 0.11, 0.12, 0.14];
|
|
8027
8028
|
BUTTON_MIN_HIT_TARGET = 44;
|
|
@@ -8077,6 +8078,7 @@ function resolveTranslation(value, catalog3, sourceCatalog) {
|
|
|
8077
8078
|
var BINDING_SIGILS, BINDING_SIGIL_RE, TRANSLATION_PREFIX, TRANSLATION_KEY_RE;
|
|
8078
8079
|
var init_bindings = __esm({
|
|
8079
8080
|
"../../core/ts/src/bindings.ts"() {
|
|
8081
|
+
"use strict";
|
|
8080
8082
|
BINDING_SIGILS = ["service", "flag", "item", "t"];
|
|
8081
8083
|
BINDING_SIGIL_RE = new RegExp(
|
|
8082
8084
|
`^\\$(?:${BINDING_SIGILS.filter((s2) => s2 !== "t").join("|")})\\b|^\\$t:`
|
|
@@ -15049,7 +15051,7 @@ async function compileContentHhp(root, opts = {}) {
|
|
|
15049
15051
|
seen.add(file3);
|
|
15050
15052
|
const source = readFileSync16(file3, "utf8");
|
|
15051
15053
|
inspectModule(source, file3, profile, diagnostics);
|
|
15052
|
-
for (const cap of moduleCapabilities(source, file3, profile))
|
|
15054
|
+
for (const cap of moduleCapabilities(source, file3, profile)) requiredCapabilities2.add(cap);
|
|
15053
15055
|
const sf2 = ts3.createSourceFile(file3, source, ts3.ScriptTarget.Latest, true);
|
|
15054
15056
|
for (const stmt of sf2.statements) if ((ts3.isImportDeclaration(stmt) || ts3.isExportDeclaration(stmt)) && stmt.moduleSpecifier && ts3.isStringLiteral(stmt.moduleSpecifier) && stmt.moduleSpecifier.text.startsWith(".")) {
|
|
15055
15057
|
let imported = localPath(root, stmt.moduleSpecifier.text, file3);
|
|
@@ -15064,9 +15066,9 @@ async function compileContentHhp(root, opts = {}) {
|
|
|
15064
15066
|
diagnostics.push(...result2.diagnostics);
|
|
15065
15067
|
results.push(result2);
|
|
15066
15068
|
const seen = /* @__PURE__ */ new Set();
|
|
15067
|
-
const
|
|
15069
|
+
const requiredCapabilities2 = new Set(result2.manifest.requiredCapabilities);
|
|
15068
15070
|
for (const dependency of result2.dependencies.filter((f2) => /\.tsx?$/.test(f2))) inspect2(dependency);
|
|
15069
|
-
result2.manifest.requiredCapabilities = [...
|
|
15071
|
+
result2.manifest.requiredCapabilities = [...requiredCapabilities2].sort();
|
|
15070
15072
|
outputs.set(`dev/hhp/${id}.descriptor.json`, JSON.stringify(result2.descriptor, null, 2) + "\n");
|
|
15071
15073
|
outputs.set(`dev/hhp/${id}.map.json`, JSON.stringify({ ...result2.manifest, nodes: Object.fromEntries(Object.entries(result2.sourceMap).map(([key3, node]) => [key3, { ...node, file: relative3(root, node.file).split(sep6).join("/") }])) }, null, 2) + "\n");
|
|
15072
15074
|
if (result2.worker && result2.bundleName) {
|
|
@@ -20698,14 +20700,15 @@ async function localDbEntities(dir) {
|
|
|
20698
20700
|
const names2 = (schema?.entities ?? []).map((e2) => e2?.name).filter((n2) => typeof n2 === "string" && n2.length > 0);
|
|
20699
20701
|
return names2.length ? names2.map((name) => ({ name })) : null;
|
|
20700
20702
|
}
|
|
20701
|
-
function renderMocksFrom(rules, bad2, seeded) {
|
|
20702
|
-
if (rules || seeded) {
|
|
20703
|
+
function renderMocksFrom(rules, bad2, seeded, base44) {
|
|
20704
|
+
if (rules || seeded || base44) {
|
|
20703
20705
|
return {
|
|
20704
20706
|
mocks: {
|
|
20705
20707
|
rules: rules?.rules ?? null,
|
|
20706
20708
|
cmsSeed: seeded,
|
|
20707
|
-
|
|
20708
|
-
...
|
|
20709
|
+
...base44 ? { base44Data: base44 } : {},
|
|
20710
|
+
from: [...rules ? [rules.from] : [], ...seeded ? ["config cms seed rows"] : [], ...base44 ? ["Base44 seed / book sample rows"] : []],
|
|
20711
|
+
...bad2 ? { ignored: `the net mocks could not be read (${bad2}), so only ${[...seeded ? ["the cms seed rows"] : [], ...base44 ? ["the Base44 seed / book sample rows"] : []].join(" and ")} were used` } : {}
|
|
20709
20712
|
}
|
|
20710
20713
|
};
|
|
20711
20714
|
}
|
|
@@ -22771,6 +22774,9 @@ var init_plan = __esm({
|
|
|
22771
22774
|
});
|
|
22772
22775
|
|
|
22773
22776
|
// ../../core/sdk/capability-catalog/src/builtins.ts
|
|
22777
|
+
function hasBuiltin(platform, id) {
|
|
22778
|
+
return CAPABILITY_BUILTINS[platform].includes(id);
|
|
22779
|
+
}
|
|
22774
22780
|
var IOS_BUILTINS, WEB_BUILTINS, CAPABILITY_BUILTINS;
|
|
22775
22781
|
var init_builtins = __esm({
|
|
22776
22782
|
"../../core/sdk/capability-catalog/src/builtins.ts"() {
|
|
@@ -23279,6 +23285,10 @@ function binaryCoreSatellitePath(product) {
|
|
|
23279
23285
|
function mavenArtifactForGradleProject(project) {
|
|
23280
23286
|
return project.replace(/^:/, "");
|
|
23281
23287
|
}
|
|
23288
|
+
function binaryModuleLinks(buildSet2) {
|
|
23289
|
+
const { spm, gradle } = modulesFor(buildSet2);
|
|
23290
|
+
return { spm, maven: gradle.map((g2) => `${BINARY_CORE_MAVEN_GROUP}:${mavenArtifactForGradleProject(g2)}`) };
|
|
23291
|
+
}
|
|
23282
23292
|
function coreVersionHasModules(version4) {
|
|
23283
23293
|
const parse4 = (v2) => /^(\d+)\.(\d+)\.(\d+)/.exec(v2.trim())?.slice(1).map(Number);
|
|
23284
23294
|
const a2 = parse4(version4);
|
|
@@ -23337,6 +23347,58 @@ var init_binary_core = __esm({
|
|
|
23337
23347
|
});
|
|
23338
23348
|
|
|
23339
23349
|
// ../../core/sdk/capability-catalog/src/index.ts
|
|
23350
|
+
var src_exports = {};
|
|
23351
|
+
__export(src_exports, {
|
|
23352
|
+
BINARY_CORE_ANDROID_PUBLISH_BUILDS: () => BINARY_CORE_ANDROID_PUBLISH_BUILDS,
|
|
23353
|
+
BINARY_CORE_IOS_PATH_ONLY_SATELLITES: () => BINARY_CORE_IOS_PATH_ONLY_SATELLITES,
|
|
23354
|
+
BINARY_CORE_IOS_SATELLITES: () => BINARY_CORE_IOS_SATELLITES,
|
|
23355
|
+
BINARY_CORE_IOS_TRAIT_SATELLITES: () => BINARY_CORE_IOS_TRAIT_SATELLITES,
|
|
23356
|
+
BINARY_CORE_IOS_TRAIT_SELECTOR: () => BINARY_CORE_IOS_TRAIT_SELECTOR,
|
|
23357
|
+
BINARY_CORE_MAVEN_GROUP: () => BINARY_CORE_MAVEN_GROUP,
|
|
23358
|
+
BINARY_CORE_MODULES_SINCE: () => BINARY_CORE_MODULES_SINCE,
|
|
23359
|
+
BINARY_CORE_PRE_SPLIT_USAGE_STRINGS: () => BINARY_CORE_PRE_SPLIT_USAGE_STRINGS,
|
|
23360
|
+
BINARY_CORE_SPM_PACKAGE: () => BINARY_CORE_SPM_PACKAGE,
|
|
23361
|
+
BINARY_CORE_USAGE_STRINGS: () => BINARY_CORE_USAGE_STRINGS,
|
|
23362
|
+
BRIDGE_CAPABILITY_IDS: () => BRIDGE_CAPABILITY_IDS,
|
|
23363
|
+
CAPABILITY_BUILTINS: () => CAPABILITY_BUILTINS,
|
|
23364
|
+
CAPABILITY_IMPLIES: () => CAPABILITY_IMPLIES,
|
|
23365
|
+
CAPABILITY_NATIVE: () => CAPABILITY_NATIVE,
|
|
23366
|
+
CATALOG_CAPABILITY_IDS: () => CATALOG_CAPABILITY_IDS,
|
|
23367
|
+
COMPONENT_MODULES: () => COMPONENT_MODULES,
|
|
23368
|
+
KNOWN_CAPABILITY_IDS: () => KNOWN_CAPABILITY_IDS,
|
|
23369
|
+
NATIVE_MODULES: () => NATIVE_MODULES,
|
|
23370
|
+
NOT_DIST_ADDABLE: () => NOT_DIST_ADDABLE,
|
|
23371
|
+
androidPlanFor: () => androidPlanFor,
|
|
23372
|
+
binaryCoreSatellitePath: () => binaryCoreSatellitePath,
|
|
23373
|
+
binaryCoreSatelliteTrait: () => binaryCoreSatelliteTrait,
|
|
23374
|
+
binaryCoreUsageStringsFor: () => binaryCoreUsageStringsFor,
|
|
23375
|
+
binaryCoreVersion: () => binaryCoreVersion,
|
|
23376
|
+
binaryModuleLinks: () => binaryModuleLinks,
|
|
23377
|
+
capabilitiesOfModule: () => capabilitiesOfModule,
|
|
23378
|
+
capabilitiesUsedBy: () => capabilitiesUsedBy,
|
|
23379
|
+
capabilityDispatchesIn: () => capabilityDispatchesIn,
|
|
23380
|
+
catalogKey: () => catalogKey,
|
|
23381
|
+
componentModuleRequirements: () => componentModuleRequirements,
|
|
23382
|
+
coreVersionHasModules: () => coreVersionHasModules,
|
|
23383
|
+
distAddRefusal: () => distAddRefusal,
|
|
23384
|
+
distributionOptIns: () => distributionOptIns,
|
|
23385
|
+
expandCapabilities: () => expandCapabilities,
|
|
23386
|
+
grants: () => grants,
|
|
23387
|
+
hasBuiltin: () => hasBuiltin,
|
|
23388
|
+
iosPlanFor: () => iosPlanFor,
|
|
23389
|
+
isBinaryCore: () => isBinaryCore,
|
|
23390
|
+
isKnownCapability: () => isKnownCapability,
|
|
23391
|
+
linksModule: () => linksModule,
|
|
23392
|
+
macosPlanFor: () => macosPlanFor,
|
|
23393
|
+
mavenArtifactForGradleProject: () => mavenArtifactForGradleProject,
|
|
23394
|
+
moduleForGradleProject: () => moduleForGradleProject,
|
|
23395
|
+
moduleForSpmProduct: () => moduleForSpmProduct,
|
|
23396
|
+
modulesFor: () => modulesFor,
|
|
23397
|
+
nativeRequirementsFor: () => nativeRequirementsFor,
|
|
23398
|
+
needsNativeBuild: () => needsNativeBuild,
|
|
23399
|
+
unlinkedComponentModules: () => unlinkedComponentModules,
|
|
23400
|
+
usageStringCopyFor: () => usageStringCopyFor
|
|
23401
|
+
});
|
|
23340
23402
|
var init_src2 = __esm({
|
|
23341
23403
|
"../../core/sdk/capability-catalog/src/index.ts"() {
|
|
23342
23404
|
init_types();
|
|
@@ -131109,8 +131171,8 @@ var init_AppRegistry = __esm({
|
|
|
131109
131171
|
});
|
|
131110
131172
|
|
|
131111
131173
|
// ../../core/web-renderer/src/index.ts
|
|
131112
|
-
var
|
|
131113
|
-
__export(
|
|
131174
|
+
var src_exports2 = {};
|
|
131175
|
+
__export(src_exports2, {
|
|
131114
131176
|
Bound: () => Bound,
|
|
131115
131177
|
BoundList: () => BoundList,
|
|
131116
131178
|
FakeServiceProvider: () => FakeServiceProvider,
|
|
@@ -131471,7 +131533,7 @@ async function renderDescriptorWeb(descriptor, tokens2, scheme, wantPng, service
|
|
|
131471
131533
|
) : void 0;
|
|
131472
131534
|
let html;
|
|
131473
131535
|
try {
|
|
131474
|
-
const { renderDescriptorToHtml: renderDescriptorToHtml2 } = await Promise.resolve().then(() => (init_src5(),
|
|
131536
|
+
const { renderDescriptorToHtml: renderDescriptorToHtml2 } = await Promise.resolve().then(() => (init_src5(), src_exports2));
|
|
131475
131537
|
html = renderDescriptorToHtml2(
|
|
131476
131538
|
payload,
|
|
131477
131539
|
tokens2 ?? {},
|
|
@@ -131741,7 +131803,7 @@ async function pngFontFaceCss(tokens2, opts = {}) {
|
|
|
131741
131803
|
return rules.join("");
|
|
131742
131804
|
}
|
|
131743
131805
|
async function idiomFontFaceCss(idiomContext) {
|
|
131744
|
-
const { controlIdiomFor: controlIdiomFor2, idiomControls: idiomControls2 } = await Promise.resolve().then(() => (init_src5(),
|
|
131806
|
+
const { controlIdiomFor: controlIdiomFor2, idiomControls: idiomControls2 } = await Promise.resolve().then(() => (init_src5(), src_exports2));
|
|
131745
131807
|
const bundled = idiomControls2(controlIdiomFor2(idiomContext)).font.bundled;
|
|
131746
131808
|
if (!bundled) return "";
|
|
131747
131809
|
try {
|
|
@@ -140221,6 +140283,7 @@ async function buildFromLoaded(preloaded, dest, opts = {}) {
|
|
|
140221
140283
|
knownScreenIds: /* @__PURE__ */ new Set(),
|
|
140222
140284
|
rules: collect2.render.mocks.rules ?? null,
|
|
140223
140285
|
...collect2.render.mocks.cmsSeed ? { cmsSeed: collect2.render.mocks.cmsSeed } : {},
|
|
140286
|
+
...collect2.render.mocks.base44Data ? { base44Data: collect2.render.mocks.base44Data } : {},
|
|
140224
140287
|
localDbSchema: appLocalDbSchema,
|
|
140225
140288
|
...collect2.render.loadBudgetMs ? { budgetMs: collect2.render.loadBudgetMs } : {},
|
|
140226
140289
|
transportWrap: (call) => async (method, path2, body) => {
|
|
@@ -140278,7 +140341,7 @@ async function buildFromLoaded(preloaded, dest, opts = {}) {
|
|
|
140278
140341
|
};
|
|
140279
140342
|
let handlers;
|
|
140280
140343
|
let minBinaryVersion = null;
|
|
140281
|
-
const
|
|
140344
|
+
const requiredCapabilities2 = new Set(config.capabilities ?? []);
|
|
140282
140345
|
const capabilityUsage = {
|
|
140283
140346
|
app: [...config.capabilities ?? []],
|
|
140284
140347
|
...Array.isArray(config.capabilityEnvelope) ? { envelope: [...config.capabilityEnvelope] } : {},
|
|
@@ -140289,7 +140352,7 @@ async function buildFromLoaded(preloaded, dest, opts = {}) {
|
|
|
140289
140352
|
const ids = Array.isArray(declared) ? declared.filter((c2) => typeof c2 === "string") : [];
|
|
140290
140353
|
const components2 = [...collectComponentTypes(descriptor)].sort();
|
|
140291
140354
|
capabilityUsage.screens.push({ screenId, bundle, declared: ids, components: components2 });
|
|
140292
|
-
for (const c2 of componentModuleRequirements(components2, [...
|
|
140355
|
+
for (const c2 of componentModuleRequirements(components2, [...requiredCapabilities2, ...ids])) requiredCapabilities2.add(c2);
|
|
140293
140356
|
};
|
|
140294
140357
|
const requiredNativeComponents = /* @__PURE__ */ new Set();
|
|
140295
140358
|
const tierScreens = [];
|
|
@@ -140533,7 +140596,7 @@ async function buildFromLoaded(preloaded, dest, opts = {}) {
|
|
|
140533
140596
|
minBinaryVersion = maximumBinaryVersion(minBinaryVersion, lb.minBinaryVersion);
|
|
140534
140597
|
const declared = lb.declaredCapabilities ?? lb.declared_capabilities;
|
|
140535
140598
|
if (Array.isArray(declared)) {
|
|
140536
|
-
for (const capability of declared) if (typeof capability === "string")
|
|
140599
|
+
for (const capability of declared) if (typeof capability === "string") requiredCapabilities2.add(capability);
|
|
140537
140600
|
}
|
|
140538
140601
|
noteScreenCapabilities(screenId, bundleName, declared, descriptor);
|
|
140539
140602
|
} else {
|
|
@@ -140683,7 +140746,7 @@ async function buildFromLoaded(preloaded, dest, opts = {}) {
|
|
|
140683
140746
|
minBinaryVersion = maximumBinaryVersion(minBinaryVersion, lb.minBinaryVersion);
|
|
140684
140747
|
const declared = lb.declaredCapabilities ?? lb.declared_capabilities;
|
|
140685
140748
|
if (Array.isArray(declared)) {
|
|
140686
|
-
for (const c2 of declared) if (typeof c2 === "string")
|
|
140749
|
+
for (const c2 of declared) if (typeof c2 === "string") requiredCapabilities2.add(c2);
|
|
140687
140750
|
}
|
|
140688
140751
|
noteScreenCapabilities(screenId, bundleName, declared, descriptor);
|
|
140689
140752
|
} else {
|
|
@@ -140851,7 +140914,7 @@ async function buildFromLoaded(preloaded, dest, opts = {}) {
|
|
|
140851
140914
|
// whose source catalog serves copy (native screens, #4221/#4250) now carries `en` here.
|
|
140852
140915
|
...Object.keys(catalogs).length ? { catalogs } : {},
|
|
140853
140916
|
playerRequirements: {
|
|
140854
|
-
capabilities: [...
|
|
140917
|
+
capabilities: [...requiredCapabilities2].sort(),
|
|
140855
140918
|
nativeComponents: [...requiredNativeComponents].sort(),
|
|
140856
140919
|
// `none` is the scaffold's spelling of "no auth" (`new-app --auth=none`), not a provider a
|
|
140857
140920
|
// host must carry — recording it blocked every such app in the Player picker (#3695).
|
|
@@ -167793,10 +167856,13 @@ __export(plan_exports, {
|
|
|
167793
167856
|
RESERVED_SCREEN_IDS: () => RESERVED_SCREEN_IDS,
|
|
167794
167857
|
displayName: () => displayName2,
|
|
167795
167858
|
gateFor: () => gateFor,
|
|
167859
|
+
hasLinkField: () => hasLinkField,
|
|
167796
167860
|
humanize: () => humanize,
|
|
167861
|
+
missingCapabilities: () => missingCapabilities,
|
|
167797
167862
|
planEntity: () => planEntity,
|
|
167798
167863
|
planEntityScreens: () => planEntityScreens,
|
|
167799
167864
|
pluralize: () => pluralize,
|
|
167865
|
+
requiredCapabilities: () => requiredCapabilities,
|
|
167800
167866
|
rowTitleField: () => rowTitleField,
|
|
167801
167867
|
snake: () => snake
|
|
167802
167868
|
});
|
|
@@ -167872,6 +167938,12 @@ function fieldReadOnly(f2) {
|
|
|
167872
167938
|
const rls = f2.rls;
|
|
167873
167939
|
return !!rls && typeof rls === "object" && "write" in rls && rls.write !== true;
|
|
167874
167940
|
}
|
|
167941
|
+
function requiredCapabilities(sp) {
|
|
167942
|
+
return ["base44", ...sp.plans.some(hasLinkField) ? ["open_url"] : []];
|
|
167943
|
+
}
|
|
167944
|
+
function missingCapabilities(sp, declared) {
|
|
167945
|
+
return requiredCapabilities(sp).filter((c2) => !declared.has(c2)).map((capability) => ({ capability, entities: (capability === "open_url" ? sp.plans.filter(hasLinkField) : sp.plans).map((p2) => p2.name) }));
|
|
167946
|
+
}
|
|
167875
167947
|
function ruleFor(spec, op) {
|
|
167876
167948
|
const rls = spec.rls;
|
|
167877
167949
|
if (!rls || typeof rls !== "object") return void 0;
|
|
@@ -167950,7 +168022,7 @@ function planEntityScreens(specs, app = "") {
|
|
|
167950
168022
|
if (names2.length <= ENTITY_TAB_SLOTS) return { plans, tabs: names2, more: [] };
|
|
167951
168023
|
return { plans, tabs: names2.slice(0, ENTITY_TAB_SLOTS - 1), more: names2.slice(ENTITY_TAB_SLOTS - 1) };
|
|
167952
168024
|
}
|
|
167953
|
-
var PlanError, RESERVED_SCREEN_IDS, MORE_SCREEN_ID, ENTITY_TAB_SLOTS, OWNER_RULES, pascal, PREFERRED_TITLES, LONG_TEXT, IMAGE_KEY, SEGMENTED_MAX, isPlainString, isEnum, isDate, isImage, isLongText, nounOf;
|
|
168025
|
+
var PlanError, RESERVED_SCREEN_IDS, MORE_SCREEN_ID, ENTITY_TAB_SLOTS, OWNER_RULES, pascal, PREFERRED_TITLES, LONG_TEXT, IMAGE_KEY, SEGMENTED_MAX, isPlainString, isEnum, isDate, isImage, isLongText, hasLinkField, nounOf;
|
|
167954
168026
|
var init_plan4 = __esm({
|
|
167955
168027
|
"src/base44-screens/plan.ts"() {
|
|
167956
168028
|
init_base44_schema();
|
|
@@ -167976,6 +168048,7 @@ var init_plan4 = __esm({
|
|
|
167976
168048
|
isDate = (f2) => f2.type === "string" && (f2.format === "date" || f2.format === "date-time");
|
|
167977
168049
|
isImage = (f2) => f2.type === "string" && !isEnum(f2) && (f2.format === "file" || IMAGE_KEY.test(f2.key));
|
|
167978
168050
|
isLongText = (f2) => f2.type === "string" && !isEnum(f2) && (f2.format === "richtext" || LONG_TEXT.test(f2.key));
|
|
168051
|
+
hasLinkField = (p2) => p2.detail.some((d2) => d2.show === "link");
|
|
167979
168052
|
nounOf = (display) => display.split(" ").map((w2) => /^[A-Z]{2,}s?$/.test(w2) ? w2 : w2.toLowerCase()).join(" ");
|
|
167980
168053
|
}
|
|
167981
168054
|
});
|
|
@@ -168027,13 +168100,25 @@ export default defineEntityList(PLAN);
|
|
|
168027
168100
|
`;
|
|
168028
168101
|
}
|
|
168029
168102
|
function renderDetailEntry(p2) {
|
|
168030
|
-
|
|
168103
|
+
if (!hasLinkField(p2)) {
|
|
168104
|
+
return `// One ${p2.name} (${p2.ids.detail}), pushed from ${p2.ids.list} with its id \u2014 generated by \`himi base44 screens\`.
|
|
168031
168105
|
// The behaviour lives in the shared engine (../_shared/entity-detail.ts); this file only names the
|
|
168032
168106
|
// entity's plan.
|
|
168033
168107
|
import { defineEntityDetail } from "../_shared/entity-detail.js";
|
|
168034
168108
|
import { PLAN } from "../_shared/${planFile(p2)}.js";
|
|
168035
168109
|
|
|
168036
168110
|
export default defineEntityDetail(PLAN);
|
|
168111
|
+
`;
|
|
168112
|
+
}
|
|
168113
|
+
return `// One ${p2.name} (${p2.ids.detail}), pushed from ${p2.ids.list} with its id \u2014 generated by \`himi base44 screens\`.
|
|
168114
|
+
// The behaviour lives in the shared engine (../_shared/entity-detail.ts); this file names the
|
|
168115
|
+
// entity's plan and hands it \`open\`, which an email / URL fact opens through (the open_url
|
|
168116
|
+
// capability \u2014 only an entity with such a field imports it).
|
|
168117
|
+
import { open } from "@himalaya/open-url";
|
|
168118
|
+
import { defineEntityDetail } from "../_shared/entity-detail.js";
|
|
168119
|
+
import { PLAN } from "../_shared/${planFile(p2)}.js";
|
|
168120
|
+
|
|
168121
|
+
export default defineEntityDetail(PLAN, { open });
|
|
168037
168122
|
`;
|
|
168038
168123
|
}
|
|
168039
168124
|
function renderDevRegistrations(sp) {
|
|
@@ -184220,7 +184305,8 @@ var USAGE = `himi \u2014 the Himalaya authoring CLI (remote dev loop)
|
|
|
184220
184305
|
--live runs the worker's fetches against REAL Wix (minted visitor token) and awaits
|
|
184221
184306
|
load(), so the render shows LIVE data (needs network + a Wix clientId in config.ts);
|
|
184222
184307
|
--mocks runs load() against the app's OWN recorded data instead \u2014 dev/net-mocks.json
|
|
184223
|
-
(what \`himi test --record\` writes)
|
|
184308
|
+
(what \`himi test --record\` writes), the config's cms seed rows, and for a Base44 app
|
|
184309
|
+
its base44.entities seed rows then the analyze book's sample rows, as \`himi test\`'s
|
|
184224
184310
|
shots do; no request leaves this machine. With none, it renders the initial state.
|
|
184225
184311
|
It is the DEFAULT whenever the app has recorded data (mocks.default in the output);
|
|
184226
184312
|
--no-mocks renders the offline initial state instead;
|
|
@@ -184773,7 +184859,9 @@ var USAGE = `himi \u2014 the Himalaya authoring CLI (remote dev loop)
|
|
|
184773
184859
|
base44.entities entity (base44-starter packages), write
|
|
184774
184860
|
controls gated by each entity's rls. Re-running is a
|
|
184775
184861
|
no-op; a hand-edited generated file is skipped-edited
|
|
184776
|
-
unless --force.
|
|
184862
|
+
unless --force. An email / URL field opens through
|
|
184863
|
+
open_url, so config.ts capabilities must list it
|
|
184864
|
+
(else missing_capability). Never calls Base44.
|
|
184777
184865
|
himi base44 entities create [--app <a> | --content <dir>] [--yes] [--allow-destructive]
|
|
184778
184866
|
[--apply-permissions] [--empty-app-ok]
|
|
184779
184867
|
create/update the app's OWNED Himi<App>* entities. Base44's
|
|
@@ -184926,7 +185014,7 @@ var COMMANDS2 = [
|
|
|
184926
185014
|
{ 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>]" },
|
|
184927
185015
|
{ 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' },
|
|
184928
185016
|
{ 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]" },
|
|
184929
|
-
{ 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 (the DEFAULT whenever the app has recorded data; --no-mocks for the offline initial state) 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 | --no-mocks] [--state <json|file>] [--input <json|file>] [--width <px> | --device <name>] [--rtl] [--remote --app --release]" },
|
|
185017
|
+
{ 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 (the DEFAULT whenever the app has recorded data; --no-mocks for the offline initial state) runs load() against the app's own recorded data (dev/net-mocks.json from `himi test --record`, then the config's cms seed rows, and the Base44 layer `himi test` uses \u2014 base44.entities seed rows and the analyze book's sample 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 | --no-mocks] [--state <json|file>] [--input <json|file>] [--width <px> | --device <name>] [--rtl] [--remote --app --release]" },
|
|
184930
185018
|
{ 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>]" },
|
|
184931
185019
|
{ 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]" },
|
|
184932
185020
|
{ name: "icons", summary: "list the valid Icon.name vocabulary (an unknown name renders a fallback glyph; validate warns on unknown names) (offline)", required: "[--json]" },
|
|
@@ -188818,7 +188906,8 @@ ${[...afterRun.next, ...testReminder ? [testReminder] : []].join("\n")}`);
|
|
|
188818
188906
|
}
|
|
188819
188907
|
const cmsSeed = await cmsSeedRows(dir, app).catch(() => null);
|
|
188820
188908
|
const seeded = cmsSeed && Object.keys(cmsSeed).length ? cmsSeed : null;
|
|
188821
|
-
const
|
|
188909
|
+
const base44Data = await base44CrawlData(dir, app).catch(() => null);
|
|
188910
|
+
const picked = renderMocksFrom(rules, bad2, seeded, base44Data);
|
|
188822
188911
|
mocks = picked.mocks;
|
|
188823
188912
|
if (!mocksByDefault || bad2) mocksNote = picked.note;
|
|
188824
188913
|
}
|
|
@@ -188830,7 +188919,7 @@ ${[...afterRun.next, ...testReminder ? [testReminder] : []].join("\n")}`);
|
|
|
188830
188919
|
...hasWorkers ? { tier5Dir: bundleOutDir2(dir) } : {},
|
|
188831
188920
|
// The settled state is always kept (printed only under --dump-state): it is what says whether
|
|
188832
188921
|
// the picture landed on the screen's content or on its error, loading or empty state.
|
|
188833
|
-
collect: { lint: true, strict, render: { screenId, scheme, png: wantPng, hydrate, dumpState: true, ...live3 ? { 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 } } : {} } }
|
|
188922
|
+
collect: { lint: true, strict, render: { screenId, scheme, png: wantPng, hydrate, dumpState: true, ...live3 ? { 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, ...mocks.base44Data ? { base44Data: mocks.base44Data } : {} } } : {} } }
|
|
188834
188923
|
});
|
|
188835
188924
|
const r2 = (outcome.renders ?? [])[0];
|
|
188836
188925
|
if (!r2) {
|
|
@@ -192860,9 +192949,12 @@ ${report3.ok ? "OK \u2014 data + serve plane reachable." : typeof report3.hint =
|
|
|
192860
192949
|
}
|
|
192861
192950
|
const app = await appId(dir);
|
|
192862
192951
|
let base44;
|
|
192952
|
+
let appCapabilities;
|
|
192863
192953
|
try {
|
|
192864
192954
|
const { loadAppConfigModule: loadAppConfigModule2 } = await Promise.resolve().then(() => (init_app_config_loader(), app_config_loader_exports));
|
|
192865
|
-
|
|
192955
|
+
const loaded = await loadAppConfigModule2(dir);
|
|
192956
|
+
base44 = loaded?.base44;
|
|
192957
|
+
appCapabilities = loaded?.capabilities;
|
|
192866
192958
|
} catch (e2) {
|
|
192867
192959
|
const finding = (await Promise.resolve().then(() => (init_base44_lint(), base44_lint_exports))).base44DefineAppFinding(e2);
|
|
192868
192960
|
if (finding) {
|
|
@@ -192881,7 +192973,7 @@ ${report3.ok ? "OK \u2014 data + serve plane reachable." : typeof report3.hint =
|
|
|
192881
192973
|
io2.error(JSON.stringify({ ok: false, code: "invalid_config", app, issues: errors, hint: '`himi validate` reports these with pass: "base44"' }));
|
|
192882
192974
|
return 1;
|
|
192883
192975
|
}
|
|
192884
|
-
const { planEntityScreens: planEntityScreens2, PlanError: PlanError2 } = await Promise.resolve().then(() => (init_plan4(), plan_exports));
|
|
192976
|
+
const { planEntityScreens: planEntityScreens2, PlanError: PlanError2, missingCapabilities: missingCapabilities2, requiredCapabilities: requiredCapabilities2 } = await Promise.resolve().then(() => (init_plan4(), plan_exports));
|
|
192885
192977
|
let sp;
|
|
192886
192978
|
try {
|
|
192887
192979
|
sp = planEntityScreens2(specs, app);
|
|
@@ -192889,6 +192981,18 @@ ${report3.ok ? "OK \u2014 data + serve plane reachable." : typeof report3.hint =
|
|
|
192889
192981
|
if (e2 instanceof PlanError2) return fail6(e2.code, e2.message, { app, ...e2.extra });
|
|
192890
192982
|
throw e2;
|
|
192891
192983
|
}
|
|
192984
|
+
const { catalogKey: catalogKey2, expandCapabilities: expandCapabilities2 } = await Promise.resolve().then(() => (init_src2(), src_exports));
|
|
192985
|
+
const declaredCaps = expandCapabilities2((Array.isArray(appCapabilities) ? appCapabilities : []).filter((c2) => typeof c2 === "string").map(catalogKey2));
|
|
192986
|
+
const missingCaps = missingCapabilities2(sp, declaredCaps);
|
|
192987
|
+
if (missingCaps.length) {
|
|
192988
|
+
const [first] = missingCaps;
|
|
192989
|
+
return fail6("missing_capability", `config.ts capabilities does not declare "${first.capability}", which the generated screens need (${first.entities.join(", ")})`, {
|
|
192990
|
+
app,
|
|
192991
|
+
capability: first.capability,
|
|
192992
|
+
missing: missingCaps,
|
|
192993
|
+
hint: `add it \u2014 capabilities: [..., ${missingCaps.map((m2) => `"${m2.capability}"`).join(", ")}] \u2014 then re-run; the native config follows from the capability catalog (himi docs capabilities). Nothing was written.`
|
|
192994
|
+
});
|
|
192995
|
+
}
|
|
192892
192996
|
const rendered = gen.renderAll(sp);
|
|
192893
192997
|
const dryRun = flags["dry-run"] === true;
|
|
192894
192998
|
const { files } = gen.writeGenerated(dir, rendered, { force: flags.force === true, dryRun });
|
|
@@ -192899,6 +193003,7 @@ ${report3.ok ? "OK \u2014 data + serve plane reachable." : typeof report3.hint =
|
|
|
192899
193003
|
...dryRun ? { dryRun: true } : {},
|
|
192900
193004
|
files,
|
|
192901
193005
|
plans: sp.plans.map((p2) => ({ entity: p2.name, physical: p2.entity, list: p2.ids.list, detail: p2.ids.detail, gates: p2.gates })),
|
|
193006
|
+
capabilities: requiredCapabilities2(sp),
|
|
192902
193007
|
tabs: sp.tabs,
|
|
192903
193008
|
more: sp.more,
|
|
192904
193009
|
...orphaned.length ? { orphaned, note: "generated files for entities no longer declared \u2014 never deleted; remove them by hand if the screens should go" } : {},
|
|
@@ -139,12 +139,12 @@ Write `.himi/screen-plan.md` before editing code. For each entity in `book.entit
|
|
|
139
139
|
| `string` + long text (`description`, `notes`, `body`, …) | `Text` with `maxLines` | `TextField` with `multilineMaxLines` |
|
|
140
140
|
| `string` + `enum` | `Badge` / `Text` | `SegmentedControl` (≤ 4 values) or `Picker` |
|
|
141
141
|
| `string` + `format: date` / `date-time` | formatted `Text` | `DatePicker` |
|
|
142
|
-
| `string` + `format: email` / `uri` |
|
|
142
|
+
| `string` + `format: email` / `uri` | a tappable row: an email opens mail, a URL the in-app browser, through `@himalaya/open-url` (needs `open_url`, below) | `TextField` with `keyboard: "email"` / `"url"` |
|
|
143
143
|
| `string` holding an image URL (`image`, `photo`, `avatar`, `*_url`) | `Image` | read-only, see [file upload](#what-the-sdk-cannot-do) |
|
|
144
144
|
| `number` / `integer` | `Text` (with unit) | `Stepper` (small ranges) or numeric `TextField` |
|
|
145
145
|
| `boolean` | `Badge` or `Text` | `Toggle` |
|
|
146
|
-
| `array` of strings |
|
|
147
|
-
| `object` / nested `array` of objects |
|
|
146
|
+
| `array` of strings | read-only `FilterChips` (no selection, no `onChange`) | `ChipsInput` |
|
|
147
|
+
| `object` / nested `array` of objects | a labelled sub-section of key/value rows, one level deep: one row per key (an array: one row per item, `Item 1`, …), a deeper value inline as `Key: value · …` | none. Edit it on the web app |
|
|
148
148
|
| built-ins `id`, `created_date`, `updated_date`, `created_by` | "Created …" metadata row | never editable |
|
|
149
149
|
|
|
150
150
|
4. **Respect the entity's rules.** `rls` (and field-level `rls`) decide what the signed-in user
|
|
@@ -161,7 +161,17 @@ Write `.himi/screen-plan.md` before editing code. For each entity in `book.entit
|
|
|
161
161
|
A field whose field-level `rls.write` is not `true` is read-only in the form. The gate is
|
|
162
162
|
advisory: a denied call still fails with Base44's message, and every screen paints that error
|
|
163
163
|
rather than swallowing it.
|
|
164
|
-
5. **
|
|
164
|
+
5. **Declare `open_url` when an entity has an email or URL field.** The detail opens those facts
|
|
165
|
+
through `@himalaya/open-url`, so the app must list the capability:
|
|
166
|
+
`capabilities: ["base44", "open_url"]` in `config.ts`. Without it `himi base44 screens` writes
|
|
167
|
+
nothing and fails with `code: "missing_capability"` (`capability: "open_url"`, and `missing[]`
|
|
168
|
+
names the entities that need it). The binary's native config follows from that list (the
|
|
169
|
+
capability catalog, `himi docs capabilities`), so a shell built without it needs a new
|
|
170
|
+
`himi dist`. Only the detail of an entity with such a field imports `@himalaya/open-url` and
|
|
171
|
+
declares `open_url`. A value that is not an address or an http(s) URL (`javascript:`, an app
|
|
172
|
+
scheme) is shown as plain text and never opened. A link, chips or section fact with no value
|
|
173
|
+
shows a `—` row instead.
|
|
174
|
+
6. **Use the sample rows** (`sampleRows`, already redacted) to size rows and choose the subtitle,
|
|
165
175
|
never as data the app ships.
|
|
166
176
|
|
|
167
177
|
Compose each screen with [`designing-himi-screens`](designing-himi-screens.md)
|
|
@@ -202,8 +212,10 @@ Re-running is safe. Each generated file starts with
|
|
|
202
212
|
`skipped-edited` and left alone, and the command still exits 0. Pass `--force` to overwrite it;
|
|
203
213
|
- files for an entity you removed from `config.ts` are reported in `orphaned[]` and never deleted.
|
|
204
214
|
|
|
205
|
-
`--dry-run` reports the same outcomes without writing.
|
|
206
|
-
(`
|
|
215
|
+
`--dry-run` reports the same outcomes without writing. On success `capabilities[]` lists what the
|
|
216
|
+
generated screens need (`base44`, plus `open_url` with a link field). Failures carry a top-level
|
|
217
|
+
`code` (`no_entities_block`, `entity_id_collision`, `missing_capability`,
|
|
218
|
+
`content_dir_not_base44_starter`), per
|
|
207
219
|
[`himi-json-output`](himi-json-output.md). The generator never calls Base44.
|
|
208
220
|
|
|
209
221
|
To change a screen beyond what the plan expresses (a filtered list, a child section, a custom
|
|
@@ -324,9 +324,14 @@ on stderr (exit 1):
|
|
|
324
324
|
- `no_entities_block`: `config.ts` declares no `base44.entities`, so there is nothing to generate.
|
|
325
325
|
- `entity_id_collision`: two entities would generate the same screen id (or one would shadow
|
|
326
326
|
`b44_functions` / `b44_account` / `b44_more`); `entity`, `screenId` and `with` name the clash.
|
|
327
|
+
- `missing_capability`: an entity has an email / URL field, which the detail opens through
|
|
328
|
+
`@himalaya/open-url`, and `config.ts` `capabilities` does not list `open_url`. `capability` names
|
|
329
|
+
it and `missing[]` gives `{ capability, entities }` per missing id. Nothing is written. Add it to
|
|
330
|
+
`capabilities` and re-run.
|
|
327
331
|
- `config_unloadable` / `invalid_config`: as for `himi base44 entities` above.
|
|
328
332
|
|
|
329
|
-
On success (`ok: true`, stdout, exit 0) `
|
|
333
|
+
On success (`ok: true`, stdout, exit 0) `capabilities[]` lists the ids the generated screens need
|
|
334
|
+
(`base44`, plus `open_url` with a link field), and `files[]` gives every generated path with its `outcome`:
|
|
330
335
|
`written`, `unchanged`, or `skipped-edited` (the file was edited by hand and was left alone; pass
|
|
331
336
|
`--force` to overwrite it). A skipped file is not a failure. `orphaned[]`, when present, lists
|
|
332
337
|
generated files for entities no longer declared. They are never deleted.
|
package/guides/himi-test.md
CHANGED
|
@@ -219,7 +219,8 @@ every run; record against a **QA site**, never production. See
|
|
|
219
219
|
|
|
220
220
|
**The pictures use it too.** `himi render <id> --png` runs the worker's load against the same
|
|
221
221
|
recorded data whenever the app has some — `dev/net-mocks.json`, then the config's cms seed rows,
|
|
222
|
-
|
|
222
|
+
and for a Base44 app the same Base44 layer `himi test` uses (`base44.entities` seed rows, then the
|
|
223
|
+
analyze book's sample rows) — never the canned fixture and never a request — so a data-driven screen paints its content instead
|
|
223
224
|
of its offline placeholders. It is the default (`mocks.default` in the output); `--mocks` asks for
|
|
224
225
|
it explicitly, `--no-mocks` renders the offline initial state, and `--live`, `--static` and
|
|
225
226
|
`--state` each render the data they name instead. The automatic pictures (`himi init`'s first look,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wix/himalaya-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1438.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "himi — the Himalaya authoring CLI. Build a Tier-5 content package on your own machine, push it to the production serve as a draft, preview it on web + Himi Player, and read worker logs back — the remote dev loop. Wraps @himalaya/serve-cli for transport.",
|
|
6
6
|
"keywords": [
|
|
@@ -71,5 +71,5 @@
|
|
|
71
71
|
"artifactId": "himalaya-cli"
|
|
72
72
|
}
|
|
73
73
|
},
|
|
74
|
-
"falconPackageHash": "
|
|
74
|
+
"falconPackageHash": "ecadfa529dda6d1f49570340c04988f711cb239a5209ce8c69e31e86"
|
|
75
75
|
}
|