@wix/himalaya-cli 0.1181.0 → 0.1183.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 +63 -12
- package/guides/base44-app-to-himi-app.md +241 -0
- package/guides/index.json +6 -0
- package/package.json +2 -2
- package/template-icons/base44-starter.png +0 -0
package/dist/cli.mjs
CHANGED
|
@@ -1391,6 +1391,21 @@ function defaultWorkerTsconfig() {
|
|
|
1391
1391
|
function offRepoImports(body) {
|
|
1392
1392
|
return body.replace(/(["'])@himalaya\/([^"']+)\1/g, "$1@wix/himalaya/$2$1");
|
|
1393
1393
|
}
|
|
1394
|
+
function templateTakesBase44AppId(template) {
|
|
1395
|
+
if (!availableTemplates().includes(template)) return void 0;
|
|
1396
|
+
const config = join3(templateSourceDir(template), "config.ts");
|
|
1397
|
+
return existsSync2(config) && readFileSync3(config, "utf8").includes(`"${BASE44_APP_ID_PLACEHOLDER}"`);
|
|
1398
|
+
}
|
|
1399
|
+
function applyBase44AppId(dir, appId2) {
|
|
1400
|
+
if (!BASE44_APP_ID_RE.test(appId2)) throw new Error(`"${appId2}" is not a Base44 app id (8\u201364 letters and digits)`);
|
|
1401
|
+
const config = join3(dir, "config.ts");
|
|
1402
|
+
if (!existsSync2(config)) return false;
|
|
1403
|
+
const body = readFileSync3(config, "utf8");
|
|
1404
|
+
const literal = `"${BASE44_APP_ID_PLACEHOLDER}"`;
|
|
1405
|
+
if (!body.includes(literal)) return false;
|
|
1406
|
+
writeFileSync3(config, body.replaceAll(literal, `"${appId2}"`));
|
|
1407
|
+
return true;
|
|
1408
|
+
}
|
|
1394
1409
|
function templateIconPath(name) {
|
|
1395
1410
|
if (!/^[a-z0-9][a-z0-9-]*$/i.test(name)) return null;
|
|
1396
1411
|
const p = join3(TEMPLATE_ICONS_DIR, `${name}.png`);
|
|
@@ -1785,7 +1800,7 @@ A per-app \`npm i @wix/himalaya\` is optional and only buys your editor local ty
|
|
|
1785
1800
|
);
|
|
1786
1801
|
return finishScaffold({ dir, files, ...kept.length ? { kept: kept.sort() } : {} });
|
|
1787
1802
|
}
|
|
1788
|
-
var PLAN_FILES, PACKAGE_GITIGNORE, MODULE_SOURCE_FILE, TEMPLATES_DIR, SHOWCASE_TEMPLATES_DIR, SHOWCASE_TEMPLATE_NAMES, TEMPLATE_META_FILE, TEMPLATE_ICONS_DIR, HEX_COLOR, PALETTE_KEYS, IDENTITY_SENTINELS, SENTINEL_RE, escapeRe, NAME_FIELDS, IDENTITY_FILES, DOTID_RE, BINARY_EXTENSIONS, extOf;
|
|
1803
|
+
var PLAN_FILES, PACKAGE_GITIGNORE, MODULE_SOURCE_FILE, TEMPLATES_DIR, SHOWCASE_TEMPLATES_DIR, SHOWCASE_TEMPLATE_NAMES, BASE44_APP_ID_PLACEHOLDER, BASE44_APP_ID_RE, TEMPLATE_META_FILE, TEMPLATE_ICONS_DIR, HEX_COLOR, PALETTE_KEYS, IDENTITY_SENTINELS, SENTINEL_RE, escapeRe, NAME_FIELDS, IDENTITY_FILES, DOTID_RE, BINARY_EXTENSIONS, extOf;
|
|
1789
1804
|
var init_scaffold = __esm({
|
|
1790
1805
|
"src/scaffold.ts"() {
|
|
1791
1806
|
init_source_deny();
|
|
@@ -1796,7 +1811,9 @@ var init_scaffold = __esm({
|
|
|
1796
1811
|
MODULE_SOURCE_FILE = /\.(?:[cm]?[jt]sx?)$/i;
|
|
1797
1812
|
TEMPLATES_DIR = resolve2(dirname2(fileURLToPath(import.meta.url)), "../templates");
|
|
1798
1813
|
SHOWCASE_TEMPLATES_DIR = resolve2(dirname2(fileURLToPath(import.meta.url)), "../showcase-apps");
|
|
1799
|
-
SHOWCASE_TEMPLATE_NAMES = ["video-courses"];
|
|
1814
|
+
SHOWCASE_TEMPLATE_NAMES = ["video-courses", "base44-starter"];
|
|
1815
|
+
BASE44_APP_ID_PLACEHOLDER = "ReplaceWithYourBase44AppId";
|
|
1816
|
+
BASE44_APP_ID_RE = /^[A-Za-z0-9]{8,64}$/;
|
|
1800
1817
|
TEMPLATE_META_FILE = "himalaya.template.json";
|
|
1801
1818
|
TEMPLATE_ICONS_DIR = resolve2(dirname2(fileURLToPath(import.meta.url)), "../template-icons");
|
|
1802
1819
|
HEX_COLOR = /^#(?:[0-9a-f]{3}|[0-9a-f]{4}|[0-9a-f]{6}|[0-9a-f]{8})$/i;
|
|
@@ -6790,7 +6807,6 @@ function walk(node, registry, path2, out2) {
|
|
|
6790
6807
|
var SYSTEM_FONT_FAMILY, TONAL_ELEVATION_ALPHA, COLOR_TOKEN_PROPS, TYPOGRAPHY_TOKEN_PROPS;
|
|
6791
6808
|
var init_tokens = __esm({
|
|
6792
6809
|
"../../core/ts/src/tokens.ts"() {
|
|
6793
|
-
"use strict";
|
|
6794
6810
|
SYSTEM_FONT_FAMILY = "$system";
|
|
6795
6811
|
TONAL_ELEVATION_ALPHA = [0, 0.05, 0.08, 0.11, 0.12, 0.14];
|
|
6796
6812
|
COLOR_TOKEN_PROPS = {
|
|
@@ -24526,7 +24542,6 @@ function shouldRevealError(args) {
|
|
|
24526
24542
|
var EMAIL_RE, URL_HOST_RE, INTEGER_RE, NUMBER_RE;
|
|
24527
24543
|
var init_validation = __esm({
|
|
24528
24544
|
"../../core/ts/src/validation.ts"() {
|
|
24529
|
-
"use strict";
|
|
24530
24545
|
EMAIL_RE = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9-]+(\.[A-Za-z0-9-]+)*\.[A-Za-z]{2,}$/;
|
|
24531
24546
|
URL_HOST_RE = /^[A-Za-z0-9-]+(\.[A-Za-z0-9-]+)*(:[0-9]+)?$/;
|
|
24532
24547
|
INTEGER_RE = /^[+-]?[0-9]+$/;
|
|
@@ -25186,7 +25201,6 @@ function validateWorkerSubtree(raw, slotId, policy, actionCatalog) {
|
|
|
25186
25201
|
var WORKER_SUBTREE_CEILINGS, ID_PART, ACTION_ID;
|
|
25187
25202
|
var init_worker_subtree = __esm({
|
|
25188
25203
|
"../../core/ts/src/worker-subtree.ts"() {
|
|
25189
|
-
"use strict";
|
|
25190
25204
|
init_worker_subtree_safe_types_generated();
|
|
25191
25205
|
WORKER_SUBTREE_CEILINGS = Object.freeze({
|
|
25192
25206
|
maxEncodedBytes: 65536,
|
|
@@ -78140,9 +78154,11 @@ var USAGE = `himi \u2014 the Himalaya authoring CLI (remote dev loop)
|
|
|
78140
78154
|
create/store or inspect CAPTCHA keys (not login tokens).
|
|
78141
78155
|
Setup needs final --ios-bundle-id / --android-package-name.
|
|
78142
78156
|
|
|
78143
|
-
himi init <name> [--dir <parent>] [--template <t>] [--remote] [--push-setup | --no-push-setup] [--no-agent] [--no-first-look]
|
|
78157
|
+
himi init <name> [--dir <parent>] [--template <t>] [--remote] [--base44-app-id <id>] [--push-setup | --no-push-setup] [--no-agent] [--no-first-look]
|
|
78144
78158
|
scaffold a starter content package (--template <t> starts
|
|
78145
|
-
from a reference app; --remote fetches it off the serve plane
|
|
78159
|
+
from a reference app; --remote fetches it off the serve plane;
|
|
78160
|
+
--base44-app-id writes a Base44 app id into a Base44 template
|
|
78161
|
+
such as base44-starter \u2014 himi docs base44-app-to-himi-app),
|
|
78146
78162
|
with the agent files inside it (AGENTS.md, CLAUDE.md, the himalaya
|
|
78147
78163
|
skill, a session-start hook); --no-agent skips them. Also makes
|
|
78148
78164
|
the build board (no browser is opened) and the first picture of
|
|
@@ -78873,7 +78889,7 @@ WHAT A PATCH DOES NOT CARRY \u2014 all reported in the output, never silent
|
|
|
78873
78889
|
himi docs releasing-with-himi-serve`;
|
|
78874
78890
|
var COMMANDS2 = [
|
|
78875
78891
|
{ 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>]" },
|
|
78876
|
-
{ 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" },
|
|
78892
|
+
{ 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] [--base44-app-id <id>] [--no-agent] [--no-first-look] | --templates" },
|
|
78877
78893
|
{ 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). Adopting one: `inspect` writes the adoption inventory (.himi/adopt.json: screens, workers, which screens can call Wix/network on appear \u2014 bootsOffline \u2014 placeholder and sample copy, proposed Data access rows, typed-journey stubs); `screens` lists, shows, rewrites or adds ONE dev/index.ts entry; `sweep` replaces sample identity/copy inside string literals; `recommend` ranks the catalog for a business description or a site's verticals; `trim --from-spec` takes the kept screens from SPEC.md's Screen catalogue", 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>|--from-spec [SPEC.md] [--content <dir>] [--links follow|drop] [--dry-run] [--force] | inspect [--content <dir>] [--dry-run] | screens [--show <id>] [--write <id> --from <file>] [--add <id> --from <file>] | sweep --set 'Old=>New' | --map <file> [--dry-run] | recommend [--text <t>] [--verticals a,b] [--book <dir>] [--remote]" },
|
|
78878
78894
|
{ 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]" },
|
|
78879
78895
|
{ 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>]" },
|
|
@@ -80030,7 +80046,9 @@ function keptPlan(kept) {
|
|
|
80030
80046
|
const note = keptPlanNote(kept);
|
|
80031
80047
|
return note ? { kept, note } : {};
|
|
80032
80048
|
}
|
|
80033
|
-
|
|
80049
|
+
var NoBase44SlotError = class extends Error {
|
|
80050
|
+
};
|
|
80051
|
+
async function initFromRemoteTemplate(name, parentDir, template, c, opts = {}) {
|
|
80034
80052
|
const dir = resolve56(parentDir, name);
|
|
80035
80053
|
if (existsSync73(join74(dir, "himalaya.content.json"))) {
|
|
80036
80054
|
throw new Error(`content package already exists at ${dir}`);
|
|
@@ -80041,6 +80059,9 @@ async function initFromRemoteTemplate(name, parentDir, template, c) {
|
|
|
80041
80059
|
if (!archive?.files || typeof archive.files !== "object") {
|
|
80042
80060
|
throw new Error(`template "${template}" returned no source files`);
|
|
80043
80061
|
}
|
|
80062
|
+
if (opts.requireBase44Slot && !String(archive.files["config.ts"] ?? "").includes(`"${BASE44_APP_ID_PLACEHOLDER}"`)) {
|
|
80063
|
+
throw new NoBase44SlotError(`template "${template}" has no Base44 app id to fill`);
|
|
80064
|
+
}
|
|
80044
80065
|
return finishScaffold(materializeSource(archive.files, name, dir), template);
|
|
80045
80066
|
}
|
|
80046
80067
|
function contentDir(flags) {
|
|
@@ -80955,6 +80976,26 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
|
|
|
80955
80976
|
}
|
|
80956
80977
|
const template = str9(flags.template);
|
|
80957
80978
|
const parentDir = resolve56(str9(flags.dir) ?? process.cwd());
|
|
80979
|
+
const base44AppId = flags["base44-app-id"] === void 0 ? void 0 : String(flags["base44-app-id"]);
|
|
80980
|
+
if (base44AppId !== void 0) {
|
|
80981
|
+
if (!BASE44_APP_ID_RE.test(base44AppId)) {
|
|
80982
|
+
io.error(JSON.stringify({ error: `--base44-app-id "${base44AppId}" is not a Base44 app id (8\u201364 letters and digits)`, hint: "it is the id in your app's URL: https://app.base44.com/apps/<id>" }));
|
|
80983
|
+
return 2;
|
|
80984
|
+
}
|
|
80985
|
+
if (!template) {
|
|
80986
|
+
io.error(JSON.stringify({ error: "--base44-app-id needs --template (e.g. --template base44-starter)" }));
|
|
80987
|
+
return 2;
|
|
80988
|
+
}
|
|
80989
|
+
if (flags.remote !== true && templateTakesBase44AppId(template) === false) {
|
|
80990
|
+
io.error(JSON.stringify({ error: `template "${template}" has no Base44 app id to fill`, hint: "use --template base44-starter" }));
|
|
80991
|
+
return 2;
|
|
80992
|
+
}
|
|
80993
|
+
}
|
|
80994
|
+
const base44Applied = (dir) => {
|
|
80995
|
+
if (base44AppId === void 0) return {};
|
|
80996
|
+
if (!applyBase44AppId(dir, base44AppId)) throw new Error(`--base44-app-id: ${dir}/config.ts lost its Base44 app id placeholder`);
|
|
80997
|
+
return { base44: { appId: base44AppId } };
|
|
80998
|
+
};
|
|
80958
80999
|
const withAgentFiles = async (dir) => {
|
|
80959
81000
|
const seeded = await seedAgentFilesFor(dir, flags);
|
|
80960
81001
|
if (!seeded.agent || "error" in seeded.agent) return seeded;
|
|
@@ -80999,8 +81040,12 @@ ${doNow.map((l) => ` ${l}`).join("\n")}`);
|
|
|
80999
81040
|
if (template && flags.remote === true) {
|
|
81000
81041
|
let fetched;
|
|
81001
81042
|
try {
|
|
81002
|
-
fetched = await initFromRemoteTemplate(name, parentDir, template, client2());
|
|
81043
|
+
fetched = await initFromRemoteTemplate(name, parentDir, template, client2(), { requireBase44Slot: base44AppId !== void 0 });
|
|
81003
81044
|
} catch (e) {
|
|
81045
|
+
if (e instanceof NoBase44SlotError) {
|
|
81046
|
+
io.error(JSON.stringify({ error: e.message, hint: "use --template base44-starter" }));
|
|
81047
|
+
return 2;
|
|
81048
|
+
}
|
|
81004
81049
|
if (!availableTemplates().includes(template)) {
|
|
81005
81050
|
io.error(JSON.stringify({ error: `remote template "${template}" unavailable: ${e.message}`, hint: "check the template name with `himi template list --remote`, or drop --remote to use a bundled template" }));
|
|
81006
81051
|
return 1;
|
|
@@ -81008,17 +81053,23 @@ ${doNow.map((l) => ` ${l}`).join("\n")}`);
|
|
|
81008
81053
|
io.error(JSON.stringify({ warning: `remote template fetch failed (${e.message}); falling back to the bundled "${template}"` }));
|
|
81009
81054
|
}
|
|
81010
81055
|
if (fetched) {
|
|
81056
|
+
const b442 = base44Applied(fetched.dir);
|
|
81011
81057
|
const agent2 = await startSession(fetched.dir, await withAgentFiles(fetched.dir), "remote");
|
|
81012
81058
|
const pushSetup2 = await offerPushSetup2({ contentDir: fetched.dir, flags, io, deps: { validate: createTrimValidator(deps) } });
|
|
81013
|
-
print({ ok: true, doNow: agent2.doNow, app: name, dir: fetched.dir, template, source: "remote", files: fetched.files, ...keptPlan(fetched.kept), ...agent2, ...await adoptAtInit(fetched.dir), pushSetup: pushSetup2, doNowAgain: agent2.doNow });
|
|
81059
|
+
print({ ok: true, doNow: agent2.doNow, app: name, dir: fetched.dir, template, source: "remote", files: fetched.files, ...b442, ...keptPlan(fetched.kept), ...agent2, ...await adoptAtInit(fetched.dir), pushSetup: pushSetup2, doNowAgain: agent2.doNow });
|
|
81014
81060
|
sayDoNow(agent2.doNow, true);
|
|
81015
81061
|
return 0;
|
|
81016
81062
|
}
|
|
81017
81063
|
}
|
|
81064
|
+
if (base44AppId !== void 0 && template && templateTakesBase44AppId(template) === false) {
|
|
81065
|
+
io.error(JSON.stringify({ error: `template "${template}" has no Base44 app id to fill`, hint: "use --template base44-starter" }));
|
|
81066
|
+
return 2;
|
|
81067
|
+
}
|
|
81018
81068
|
const res = scaffoldContentPackage(name, parentDir, template);
|
|
81069
|
+
const b44 = base44Applied(res.dir);
|
|
81019
81070
|
const agent = await startSession(res.dir, await withAgentFiles(res.dir), "bundled");
|
|
81020
81071
|
const pushSetup = await offerPushSetup2({ contentDir: res.dir, flags, io, deps: { validate: createTrimValidator(deps) } });
|
|
81021
|
-
print({ ok: true, doNow: agent.doNow, app: name, dir: res.dir, ...template ? { template, source: "embedded" } : {}, files: res.files, ...keptPlan(res.kept), ...agent, ...template ? await adoptAtInit(res.dir) : {}, pushSetup, doNowAgain: agent.doNow });
|
|
81072
|
+
print({ ok: true, doNow: agent.doNow, app: name, dir: res.dir, ...template ? { template, source: "embedded" } : {}, files: res.files, ...b44, ...keptPlan(res.kept), ...agent, ...template ? await adoptAtInit(res.dir) : {}, pushSetup, doNowAgain: agent.doNow });
|
|
81022
81073
|
sayDoNow(agent.doNow, true);
|
|
81023
81074
|
return 0;
|
|
81024
81075
|
}
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
# From a Base44 app → a native Himi app
|
|
2
|
+
|
|
3
|
+
This page takes an agent from a [Base44](https://base44.com) app to a running Himi app that
|
|
4
|
+
uses that app's backend, with the himi CLI alone:
|
|
5
|
+
|
|
6
|
+
```
|
|
7
|
+
himi base44 analyze → himi init --template base44-starter → one list + detail per entity → himi test → himi run / Himi Player
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
The Base44 app keeps its data, its users and its backend functions. The Himi app is a native UI
|
|
11
|
+
over them: every screen is a Tier-5 worker that calls the app through the `base44` capability
|
|
12
|
+
(`@wix/himalaya/base44` off-repo, `@himalaya/base44` in this repo). Nothing is copied out of
|
|
13
|
+
Base44 and nothing is written back to it except through the same API calls the web app makes.
|
|
14
|
+
|
|
15
|
+
> Read [`base44-app-context`](base44-app-context.md) (`himi docs base44-app-context`) for what the
|
|
16
|
+
> book contains and what it never collects. This page is what to *do* with the book.
|
|
17
|
+
|
|
18
|
+
## 0. What you need
|
|
19
|
+
|
|
20
|
+
- The Base44 **app id**: the id in the app's editor URL, `https://app.base44.com/apps/<id>`. It
|
|
21
|
+
is a public id, not a secret.
|
|
22
|
+
- For the full book (entities, functions, auth), a Base44 session as the app's owner or an
|
|
23
|
+
editor: `himi base44 login` (or `BASE44_ACCESS_TOKEN`). Without one, `--url` reads only what
|
|
24
|
+
any visitor sees, which has no entity schemas.
|
|
25
|
+
|
|
26
|
+
## 1. Read the app into a book
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
himi base44 analyze --app-id <id> # owner pass → .himi/base44-book/
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Read `.himi/base44-book/BOOK.md` first, then `book.json` for detail. Check `gaps[]` before you
|
|
33
|
+
plan: a missing `entities` chapter means you have no schemas to build from, so sign in and run it
|
|
34
|
+
again instead of guessing fields.
|
|
35
|
+
|
|
36
|
+
The parts of the book this procedure uses:
|
|
37
|
+
|
|
38
|
+
| Book field | Drives |
|
|
39
|
+
|---|---|
|
|
40
|
+
| `identity.name`, `identity.colors`, `look` | the app name, `tokens.json` accent and surfaces |
|
|
41
|
+
| `entities[]` (`name`, `fields[]`, `required`, `rls`, `sampleRows`) | one list + detail (+ form) per entity |
|
|
42
|
+
| `routes[]` (`path`, `component`, `entities`) | which entities the web app treats as primary, so which get a tab |
|
|
43
|
+
| `auth.visibility`, `auth.providers` | whether the app needs the Account tab, and which sign-in it offers |
|
|
44
|
+
| `functions[]` (`name`, `kind`, `invoke`) | actions on the screens that need them |
|
|
45
|
+
| `agents[]`, `connectors[]`, `secrets.names` | nothing on the device. See [What the SDK cannot do](#what-the-sdk-cannot-do) |
|
|
46
|
+
|
|
47
|
+
## 2. Scaffold from the starter
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
himi init my-app --template base44-starter --base44-app-id <id>
|
|
51
|
+
cd my-app
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
`base44-starter` is a complete, working app over any Base44 app with a `Task` entity. To try it
|
|
55
|
+
before pointing it at yours, pass the public sample app (`--base44-app-id 69725232345f461f64ad1623`),
|
|
56
|
+
whose `Task` entity anyone may read and write:
|
|
57
|
+
|
|
58
|
+
| Tab | Screen | Worker | Pattern it shows |
|
|
59
|
+
|---|---|---|---|
|
|
60
|
+
| Tasks | `b44_tasks` | `tasks` | list, create, bulk-create, update, delete; loading / empty / error / ready; pull to refresh |
|
|
61
|
+
| (pushed) | `b44_task` | `task` | read one row by its id (a nav param), edit, delete then go back |
|
|
62
|
+
| Functions | `b44_functions` | `functions` | `functions.invoke(name, payload)` with the reply and the error painted |
|
|
63
|
+
| Account | `b44_account` | `account` | Base44 email + password and emailed-code sign-in, register, reset, profile, sign out |
|
|
64
|
+
|
|
65
|
+
`config.ts` declares `capabilities: ["base44"]` and `base44: { appId }`. `--base44-app-id` writes
|
|
66
|
+
the id there, and that line is the only place it is read from. Without the flag the id is the
|
|
67
|
+
placeholder `ReplaceWithYourBase44AppId`, which talks to no app, so every screen shows its error
|
|
68
|
+
state until you edit it in. A malformed id (anything but 8–64 letters and digits) is refused before
|
|
69
|
+
anything is written.
|
|
70
|
+
|
|
71
|
+
Run it once before you change anything, so you know the starter works against your app:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
himi run --content . # web preview — the Tasks tab lists your Task rows
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
If your app has no `Task` entity, the Tasks tab shows its error state with Base44's own message.
|
|
78
|
+
That is expected: you are about to replace it.
|
|
79
|
+
|
|
80
|
+
## 3. Plan the screens from the book
|
|
81
|
+
|
|
82
|
+
Write `.himi/screen-plan.md` before editing code. For each entity in `book.entities`:
|
|
83
|
+
|
|
84
|
+
1. **Decide whether it gets a tab.** An entity that a top-level route reads (`routes[].entities`)
|
|
85
|
+
and that a user browses gets a tab. A child entity (one whose rows only make sense under a
|
|
86
|
+
parent, such as `OrderItem` under `Order`) gets a section on the parent's detail screen
|
|
87
|
+
instead. Keep to 3–5 tabs. Account always takes one when the app is not public (step 5).
|
|
88
|
+
2. **Pick the list row.** The title is the first `required` string field, preferring one named
|
|
89
|
+
`title`, `name` or `label`. The subtitle is one or two short fields (a status `enum`, a date, a
|
|
90
|
+
number). Never put a long text field in a row.
|
|
91
|
+
3. **Pick the detail and form fields** from each field's JSON Schema type:
|
|
92
|
+
|
|
93
|
+
| Field (`type` / `format` / `enum`) | Detail shows | Form control |
|
|
94
|
+
|---|---|---|
|
|
95
|
+
| `string` | `Text` | `TextField` |
|
|
96
|
+
| `string` + long text (`description`, `notes`, `body`, …) | `Text` with `maxLines` | `TextField` with `multilineMaxLines` |
|
|
97
|
+
| `string` + `enum` | `Badge` / `Text` | `SegmentedControl` (≤ 4 values) or `Picker` |
|
|
98
|
+
| `string` + `format: date` / `date-time` | formatted `Text` | `DatePicker` |
|
|
99
|
+
| `string` + `format: email` / `uri` | `Text`, tap opens mail / URL | `TextField` with `keyboard: "email"` / `"url"` |
|
|
100
|
+
| `string` holding an image URL (`image`, `photo`, `avatar`, `*_url`) | `Image` | read-only, see [file upload](#what-the-sdk-cannot-do) |
|
|
101
|
+
| `number` / `integer` | `Text` (with unit) | `Stepper` (small ranges) or numeric `TextField` |
|
|
102
|
+
| `boolean` | `Badge` or `Text` | `Toggle` |
|
|
103
|
+
| `array` of strings | `FilterChips` / `ChipsInput` (read-only) | `ChipsInput` |
|
|
104
|
+
| `object` / nested `array` of objects | a sub-section of rows | none at first. Edit it on the web app |
|
|
105
|
+
| built-ins `id`, `created_date`, `updated_date`, `created_by` | "Created …" metadata row | never editable |
|
|
106
|
+
|
|
107
|
+
4. **Respect the entity's rules.** `rls` (and field-level `rls`) decide what the signed-in user
|
|
108
|
+
may do. Offer only the writes `BOOK.md` lists for the entity. An operation whose rule is
|
|
109
|
+
`false` gets no button. A rule that is a condition (`{ "created_by": "{{user.email}}" }`)
|
|
110
|
+
means the write only works for a signed-in user, on their own rows, so it belongs behind
|
|
111
|
+
sign-in (step 5). A denied call still fails with Base44's message, and every screen must paint
|
|
112
|
+
that error rather than swallow it.
|
|
113
|
+
5. **Use the sample rows** (`sampleRows`, already redacted) to size rows and choose the subtitle,
|
|
114
|
+
never as data the app ships.
|
|
115
|
+
|
|
116
|
+
Compose each screen with [`designing-himi-screens`](designing-himi-screens.md)
|
|
117
|
+
(`himi docs designing-himi-screens`): hierarchy, spacing, and the four states.
|
|
118
|
+
|
|
119
|
+
## 4. Build one list + detail per entity
|
|
120
|
+
|
|
121
|
+
Copy the Task pair and rename it. For an entity `Project`:
|
|
122
|
+
|
|
123
|
+
- `tier5-src/projects/index.ts` from `tier5-src/tasks/index.ts`. Set `ENTITY = "Project"`, the
|
|
124
|
+
service name (`b44Projects`), and `toRow()` for the fields you chose. Keep the structure:
|
|
125
|
+
module-eval `load()`, the `latestList` sequence that drops stale replies, the `pendingWrites`
|
|
126
|
+
guard, and `derive()` producing the `hide*` flags.
|
|
127
|
+
- `tier5-src/project/index.ts` from `tier5-src/task/index.ts`. `inputId()` reads the row id from
|
|
128
|
+
the nav param, `get(id)` loads it, and `remove` returns `navigate(…, { presentation: "back" })`
|
|
129
|
+
from the action the host dispatched.
|
|
130
|
+
- In `dev/index.ts`, a `projects()` and a `project()` builder (copy `tasks()` and `task()`), each
|
|
131
|
+
bound with `bundle: "<worker dir>"` and `declaredCapabilities: ["base44"]`. Register both in
|
|
132
|
+
`screens.exact` and the tab root in `seed.screens`.
|
|
133
|
+
- In `config.ts`, a `linkableScreens` entry for each (the detail with `paramKeys: ["id"]` and its
|
|
134
|
+
list as `parentScreenId`) and a `tabBar` item for the list.
|
|
135
|
+
|
|
136
|
+
The calls a worker makes, all on `entities("<Name>")`:
|
|
137
|
+
|
|
138
|
+
| Need | Call |
|
|
139
|
+
|---|---|
|
|
140
|
+
| newest first, one page | `.list({ sort: "-created_date", limit: 20, fields: [...] })` |
|
|
141
|
+
| next page | `.list({ sort, limit, skip })` |
|
|
142
|
+
| filtered list (a tab per status, the signed-in user's rows) | `.filter({ status: "open" }, { sort, limit })` |
|
|
143
|
+
| one row | `.get(id)` |
|
|
144
|
+
| create / update / delete | `.create(data)` / `.update(id, patch)` / `.delete(id)` |
|
|
145
|
+
| several rows at once | `.bulkCreate(rows)` |
|
|
146
|
+
|
|
147
|
+
Rules that keep it correct:
|
|
148
|
+
|
|
149
|
+
- **The detail is an exact screen fed its id as a nav param**, opened with
|
|
150
|
+
`himi.open { screenId: "<detail>", id: "$item.id" }`. It is not a templated route, because
|
|
151
|
+
Base44 row ids exist only at runtime and a release can bake only the ids it knows.
|
|
152
|
+
- **Bindings substitute whole values only.** `"Open $item.title"` renders literally on device.
|
|
153
|
+
Compute the whole string in the worker (`openLabel: \`Open ${title}\``) and bind
|
|
154
|
+
`$item.openLabel`. `himi validate` flags this as an error.
|
|
155
|
+
- **Every write re-lists**, and a failed write keeps the list on screen with the error above it.
|
|
156
|
+
Only a failed *first* list is the error state.
|
|
157
|
+
- Remove `tasks` / `task` once your own entities replace them, and change `homeScreenId`.
|
|
158
|
+
|
|
159
|
+
## 5. Auth: the Account tab
|
|
160
|
+
|
|
161
|
+
| `book.auth.visibility` | Do |
|
|
162
|
+
|---|---|
|
|
163
|
+
| `public` | Keep Account only if writes need a user (an `rls` condition on `user`). Otherwise remove it. |
|
|
164
|
+
| `private` / `workspace` | Keep Account, and make it the first thing a signed-out user sees: gate each list's write controls (and, for `private`, the lists themselves) on the account state. |
|
|
165
|
+
|
|
166
|
+
Offer only the providers in `book.auth.providers` that the SDK supports: `email-password` (with
|
|
167
|
+
the emailed-code form the starter already has). **Do not offer Google, Microsoft, Facebook, Apple
|
|
168
|
+
or SSO sign-in**: the Base44 capability has no social sign-in yet, so those buttons would be dead.
|
|
169
|
+
|
|
170
|
+
The token never reaches the worker. Native code keeps it (Keychain / encrypted prefs), so the
|
|
171
|
+
session survives a relaunch, and `auth.status()` reports it on the next boot.
|
|
172
|
+
|
|
173
|
+
An app that lets users sign up must let them delete their account in the app (App Store
|
|
174
|
+
5.1.1(v)). A regular Base44 user cannot delete their own record, so write a backend function that
|
|
175
|
+
does it with the service role. Name it in `config.ts`
|
|
176
|
+
(`base44DeleteAccountFunction = "deleteMyAccount"`) and the Account tab shows the Delete row.
|
|
177
|
+
|
|
178
|
+
## 6. Functions as actions
|
|
179
|
+
|
|
180
|
+
Each `book.functions[]` entry with `kind: "invoke"` is callable from a worker as
|
|
181
|
+
`functions.invoke("<name>", payload)`. Put it where the user needs it, not on a debug screen:
|
|
182
|
+
|
|
183
|
+
- a function that acts on one row (send an invoice, recalculate a total) → a button on that
|
|
184
|
+
entity's detail screen, passing the row id in the payload, then re-`get` the row;
|
|
185
|
+
- a function that acts on the user (export, invite) → a row on the Account tab;
|
|
186
|
+
- a scheduled or entity-triggered function (`automations`) → nothing on the device. It runs on
|
|
187
|
+
Base44 by itself.
|
|
188
|
+
|
|
189
|
+
A `kind: "http"` function serves its own HTTP requests (`Deno.serve`). Call it with
|
|
190
|
+
`functions.invoke` only if it also handles an invoke payload. Otherwise leave it on the web.
|
|
191
|
+
|
|
192
|
+
Paint the reply and paint the failure: a function that is missing, not deployed or that throws
|
|
193
|
+
must read as an error, never as an empty reply. The Functions tab of the starter is a developer
|
|
194
|
+
tool for trying functions by name. Remove it from the shipped app once each function you need
|
|
195
|
+
has its own button.
|
|
196
|
+
|
|
197
|
+
## 7. Look
|
|
198
|
+
|
|
199
|
+
Take `identity.colors` and the `look` chapter into `tokens.json`: `accentPrimary` from the
|
|
200
|
+
brand colour (check that it reaches 4.5:1 on `surfacePrimary` and `surfaceTertiary`, since
|
|
201
|
+
`himi validate` warns when it does not), the app name from `identity.name` into
|
|
202
|
+
`himalaya.content.json`, and the icon with `himi icon set` or `himi icon generate`. Keep the web
|
|
203
|
+
app's words: screen titles, tab labels and empty states come from its pages, not invented copy.
|
|
204
|
+
|
|
205
|
+
## 8. Test, run, ship
|
|
206
|
+
|
|
207
|
+
```bash
|
|
208
|
+
himi validate --content . --strict # zero errors
|
|
209
|
+
himi test --content . --json # derived crawl: failures and warnings must be 0
|
|
210
|
+
himi run --content . # web preview against the live Base44 app
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
`himi test` crawls every action against the real workers with hermetic transport, so it proves
|
|
214
|
+
the wiring, not your data. Its gap ledger names what the hermetic crawl could not reach (rows that
|
|
215
|
+
only exist on the live app, for instance). Close a gap with the remedy it names, or accept it.
|
|
216
|
+
Then drive the real flow in `himi run`: list, create, edit, delete a row of each entity, and
|
|
217
|
+
sign in when the app is not public.
|
|
218
|
+
|
|
219
|
+
**Where it runs.** The web preview and the web Himi Player serve `base44` themselves. A native
|
|
220
|
+
shell has to link the Base44 SDK. The native Himi Player does not link it yet, so it lists a Base44
|
|
221
|
+
release as not supported on that device. A branded native binary of the `himi-base44` shell is
|
|
222
|
+
#3674.
|
|
223
|
+
|
|
224
|
+
Publish with `himi push` like any other content package.
|
|
225
|
+
|
|
226
|
+
## What the SDK cannot do
|
|
227
|
+
|
|
228
|
+
The `base44` capability is `auth.*`, `entities(name).*` and `functions.invoke`. It has no:
|
|
229
|
+
|
|
230
|
+
- **realtime** subscriptions. Re-list on `onAppear` and on pull-to-refresh instead;
|
|
231
|
+
- **`integrations.*`** (LLM, email, image generation, file storage). Call them from a backend
|
|
232
|
+
function and invoke that;
|
|
233
|
+
- **file upload**. Upload from a backend function (pass the file's URL or base64 in the payload),
|
|
234
|
+
or keep the upload on the web app;
|
|
235
|
+
- **agents**, **connectors** or **secrets**. They stay on Base44, and a function that needs one
|
|
236
|
+
uses it server-side;
|
|
237
|
+
- **service-role** access. A shipped client never holds an admin credential, so anything that
|
|
238
|
+
needs one is a backend function.
|
|
239
|
+
|
|
240
|
+
Route each of these through `functions.invoke`. Never put a token, key or secret in a worker or
|
|
241
|
+
in `config.ts`.
|
package/guides/index.json
CHANGED
|
@@ -220,6 +220,12 @@
|
|
|
220
220
|
"category": "CLI & Loop",
|
|
221
221
|
"file": "base44-app-context.md"
|
|
222
222
|
},
|
|
223
|
+
{
|
|
224
|
+
"id": "base44-app-to-himi-app",
|
|
225
|
+
"title": "From a Base44 app → a native Himi app",
|
|
226
|
+
"category": "CLI & Loop",
|
|
227
|
+
"file": "base44-app-to-himi-app.md"
|
|
228
|
+
},
|
|
223
229
|
{
|
|
224
230
|
"id": "headless-business-to-himi-app",
|
|
225
231
|
"title": "From Wix Headless / Back Office → a native Himi app",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wix/himalaya-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1183.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "himi — the Himalaya authoring CLI. Build a Tier-5 content package on your own machine, push it to the production serve as a draft, preview it on web + Himi Player, and read worker logs back — the remote dev loop. Wraps @himalaya/serve-cli for transport.",
|
|
6
6
|
"keywords": [
|
|
@@ -67,5 +67,5 @@
|
|
|
67
67
|
"artifactId": "himalaya-cli"
|
|
68
68
|
}
|
|
69
69
|
},
|
|
70
|
-
"falconPackageHash": "
|
|
70
|
+
"falconPackageHash": "7ca344d0a8415adec66c315d8b104421a94353b9f7fc240429171467"
|
|
71
71
|
}
|
|
Binary file
|