webcake-landing-mcp 1.0.21 → 1.0.22
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/README.md +11 -7
- package/README.vi.md +11 -7
- package/dist/changelog.json +7 -7
- package/dist/http.js +1 -0
- package/dist/persistence/config.js +40 -7
- package/dist/persistence/webcake-client.js +31 -6
- package/dist/smoke.js +16 -1
- package/dist/web-guide.js +51 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -221,7 +221,8 @@ lands in logs). Any header that's missing falls back to the matching env var:
|
|
|
221
221
|
| `x-webcake-env` | `WEBCAKE_ENV` | named environment (`local`/`staging`/`prod`) |
|
|
222
222
|
| `x-webcake-org-id` | `WEBCAKE_ORG_ID` | default org |
|
|
223
223
|
| `x-webcake-api-base` | `WEBCAKE_API_BASE` | overrides the env preset's API base |
|
|
224
|
-
| `x-webcake-app-base` | `WEBCAKE_APP_BASE` | overrides the env preset's
|
|
224
|
+
| `x-webcake-app-base` | `WEBCAKE_APP_BASE` | overrides the env preset's SPA base (login connect page) |
|
|
225
|
+
| `x-webcake-builder-base` | `WEBCAKE_BUILDER_BASE` | overrides the builder host used for editor/preview links |
|
|
225
226
|
|
|
226
227
|
> The reference + generation tools (`get_generation_guide`, `list_elements`, `validate_page`, …) need **no
|
|
227
228
|
> token** — only the persistence tools (`create_page`, `update_page`, …) use it. Without a JWT, those return
|
|
@@ -290,7 +291,8 @@ flow can also be done entirely in the SPA, no backend route needed.)
|
|
|
290
291
|
| `WEBCAKE_API_BASE` | No* | Backend base URL, e.g. `http://localhost:5800`. Required to persist (or set `WEBCAKE_ENV`). |
|
|
291
292
|
| `WEBCAKE_JWT` | No* | Account JWT (dashboard auth). Required to persist — expires, refresh when needed. |
|
|
292
293
|
| `WEBCAKE_ORG_ID` | No | Default organization id for `create_page` (overridden by its `organization_id` arg). Omit → personal page. |
|
|
293
|
-
| `WEBCAKE_APP_BASE` | No | Optional base used
|
|
294
|
+
| `WEBCAKE_APP_BASE` | No | Optional SPA base — used for the browser `login` connect page. |
|
|
295
|
+
| `WEBCAKE_BUILDER_BASE` | No | Optional builder host for the editor/preview links in the result. Defaults to the env preset, else derived from the API host (`api.x`→`builder.x`). |
|
|
294
296
|
| `WEBCAKE_CONFIG_DIR` | No | Dir for the saved `auth.json` written by `login` (default `~/.webcake-landing-mcp`). |
|
|
295
297
|
|
|
296
298
|
> \* `WEBCAKE_API_BASE` and `WEBCAKE_JWT` are only needed for the persistence tools. The reference and
|
|
@@ -304,11 +306,13 @@ flow can also be done entirely in the SPA, no backend route needed.)
|
|
|
304
306
|
Instead of setting both base URLs by hand, pick a named environment — one source of
|
|
305
307
|
truth for the API + app bases:
|
|
306
308
|
|
|
307
|
-
| `--env` / `WEBCAKE_ENV` | API base (`WEBCAKE_API_BASE`) | App base (`WEBCAKE_APP_BASE`) |
|
|
308
|
-
|
|
309
|
-
| `local` | `http://localhost:5800` | `http://localhost:5173` |
|
|
310
|
-
| `staging` | `https://api.staging.webcake.io` | `https://staging.webcake.io` |
|
|
311
|
-
| `prod` | `https://api.webcake.io` | `https://webcake.io` |
|
|
309
|
+
| `--env` / `WEBCAKE_ENV` | API base (`WEBCAKE_API_BASE`) | App base (`WEBCAKE_APP_BASE`) | Builder base (`WEBCAKE_BUILDER_BASE`) |
|
|
310
|
+
|-------------------------|-------------------------------|-------------------------------|----------------------------------------|
|
|
311
|
+
| `local` | `http://localhost:5800` | `http://localhost:5173` | `http://builder.localhost:5800` |
|
|
312
|
+
| `staging` | `https://api.staging.webcake.io` | `https://staging.webcake.io` | `https://builder.staging.webcake.io` |
|
|
313
|
+
| `prod` | `https://api.webcake.io` | `https://webcake.io` | `https://builder.webcake.io` |
|
|
314
|
+
|
|
315
|
+
> The **editor/preview link** returned after `create_page`/`update_page` opens on the **builder host** (above), not the API or SPA base.
|
|
312
316
|
|
|
313
317
|
```bash
|
|
314
318
|
npx -y webcake-landing-mcp login --env local # connect against your local SPA + API
|
package/README.vi.md
CHANGED
|
@@ -221,7 +221,8 @@ Header nào thiếu sẽ fallback về biến env tương ứng:
|
|
|
221
221
|
| `x-webcake-env` | `WEBCAKE_ENV` | môi trường có tên (`local`/`staging`/`prod`) |
|
|
222
222
|
| `x-webcake-org-id` | `WEBCAKE_ORG_ID` | org mặc định |
|
|
223
223
|
| `x-webcake-api-base` | `WEBCAKE_API_BASE` | ghi đè API base của preset |
|
|
224
|
-
| `x-webcake-app-base` | `WEBCAKE_APP_BASE` | ghi đè
|
|
224
|
+
| `x-webcake-app-base` | `WEBCAKE_APP_BASE` | ghi đè SPA base của preset (trang connect khi `login`) |
|
|
225
|
+
| `x-webcake-builder-base` | `WEBCAKE_BUILDER_BASE` | ghi đè host builder dùng cho link editor/preview |
|
|
225
226
|
|
|
226
227
|
> Tool tham chiếu + generation (`get_generation_guide`, `list_elements`, `validate_page`, …) **không cần
|
|
227
228
|
> token** — chỉ tool lưu trữ (`create_page`, `update_page`, …) mới dùng. Không có JWT thì các tool đó trả
|
|
@@ -285,7 +286,8 @@ SPA cũng được, khỏi cần route backend.)
|
|
|
285
286
|
| `WEBCAKE_API_BASE` | Không* | Base URL backend, ví dụ `http://localhost:5800`. Cần để lưu trang (hoặc đặt `WEBCAKE_ENV`). |
|
|
286
287
|
| `WEBCAKE_JWT` | Không* | JWT tài khoản (auth dashboard). Cần để lưu trang — sẽ hết hạn, làm mới khi cần. |
|
|
287
288
|
| `WEBCAKE_ORG_ID` | Không | Organization mặc định cho `create_page` (bị ghi đè bởi tham số `organization_id`). Bỏ trống → trang cá nhân. |
|
|
288
|
-
| `WEBCAKE_APP_BASE` | Không |
|
|
289
|
+
| `WEBCAKE_APP_BASE` | Không | SPA base tuỳ chọn — dùng cho trang connect khi `login` qua trình duyệt. |
|
|
290
|
+
| `WEBCAKE_BUILDER_BASE` | Không | Host builder tuỳ chọn cho link editor/preview trong kết quả. Mặc định lấy theo preset môi trường, nếu không thì suy ra từ host API (`api.x`→`builder.x`). |
|
|
289
291
|
| `WEBCAKE_CONFIG_DIR` | Không | Thư mục chứa `auth.json` do `login` ghi (mặc định `~/.webcake-landing-mcp`). |
|
|
290
292
|
|
|
291
293
|
> \* `WEBCAKE_API_BASE` và `WEBCAKE_JWT` chỉ cần cho các tool lưu trữ. Các tool tham chiếu và kiểm tra
|
|
@@ -299,11 +301,13 @@ SPA cũng được, khỏi cần route backend.)
|
|
|
299
301
|
Thay vì đặt thủ công cả hai base URL, hãy chọn một môi trường có tên — một nguồn sự thật duy nhất
|
|
300
302
|
cho API + app base (mặc định là `prod`):
|
|
301
303
|
|
|
302
|
-
| `--env` / `WEBCAKE_ENV` | API base (`WEBCAKE_API_BASE`) | App base (`WEBCAKE_APP_BASE`) |
|
|
303
|
-
|
|
304
|
-
| `local` | `http://localhost:5800` | `http://localhost:5173` |
|
|
305
|
-
| `staging` | `https://api.staging.webcake.io` | `https://staging.webcake.io` |
|
|
306
|
-
| `prod` *(mặc định)* | `https://api.webcake.io` | `https://webcake.io` |
|
|
304
|
+
| `--env` / `WEBCAKE_ENV` | API base (`WEBCAKE_API_BASE`) | App base (`WEBCAKE_APP_BASE`) | Builder base (`WEBCAKE_BUILDER_BASE`) |
|
|
305
|
+
|-------------------------|-------------------------------|-------------------------------|----------------------------------------|
|
|
306
|
+
| `local` | `http://localhost:5800` | `http://localhost:5173` | `http://builder.localhost:5800` |
|
|
307
|
+
| `staging` | `https://api.staging.webcake.io` | `https://staging.webcake.io` | `https://builder.staging.webcake.io` |
|
|
308
|
+
| `prod` *(mặc định)* | `https://api.webcake.io` | `https://webcake.io` | `https://builder.webcake.io` |
|
|
309
|
+
|
|
310
|
+
> **Link editor/preview** trả về sau `create_page`/`update_page` mở trên **host builder** (bảng trên), không phải API hay SPA base.
|
|
307
311
|
|
|
308
312
|
```bash
|
|
309
313
|
npx -y webcake-landing-mcp login --env local # đăng nhập vào SPA + API local
|
package/dist/changelog.json
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"v": "1.0.22",
|
|
4
|
+
"d": "07/06/2026",
|
|
5
|
+
"type": "Added",
|
|
6
|
+
"en": "New WEBCAKE_BUILDER_BASE environment variable, x-webcake-builder-base HTTP header, and ?builder_base= query parameter set the page-builder host used…",
|
|
7
|
+
"vi": "Biến môi trường WEBCAKE_BUILDER_BASE mới, HTTP header x-webcake-builder-base, và tham số truy vấn ?builder_base= cho phép đặt host của page-builder…"
|
|
8
|
+
},
|
|
2
9
|
{
|
|
3
10
|
"v": "1.0.21",
|
|
4
11
|
"d": "07/06/2026",
|
|
@@ -33,12 +40,5 @@
|
|
|
33
40
|
"type": "Fixed",
|
|
34
41
|
"en": "get_element and get_generation_guide no longer suggest https://picsum.photos as an alternative image placeholder; agents are now directed to use…",
|
|
35
42
|
"vi": "get_element và get_generation_guide không còn gợi ý https://picsum.photos làm ảnh placeholder thay thế; agent nay được hướng dẫn chỉ dùng…"
|
|
36
|
-
},
|
|
37
|
-
{
|
|
38
|
-
"v": "1.0.16",
|
|
39
|
-
"d": "06/06/2026",
|
|
40
|
-
"type": "Changed",
|
|
41
|
-
"en": "The server icon (served at /favicon.svg and embedded in serverInfo.icons) has been refined to the official Webcake brand mark: a green-gradient…",
|
|
42
|
-
"vi": "Icon server (phục vụ tại /favicon.svg và nhúng trong serverInfo.icons) đã được tinh chỉnh về đúng logo thương hiệu Webcake: ô bo góc gradient xanh…"
|
|
43
43
|
}
|
|
44
44
|
]
|
package/dist/http.js
CHANGED
|
@@ -18,7 +18,9 @@
|
|
|
18
18
|
* WEBCAKE_API_BASE e.g. http://localhost:5800 (required to call the backend)
|
|
19
19
|
* WEBCAKE_JWT the account JWT (required to call the backend)
|
|
20
20
|
* WEBCAKE_ORG_ID optional default organization id for create_page
|
|
21
|
-
* WEBCAKE_APP_BASE optional base for
|
|
21
|
+
* WEBCAKE_APP_BASE optional SPA base (used for the login connect page)
|
|
22
|
+
* WEBCAKE_BUILDER_BASE optional builder host for editor/preview URLs in the result
|
|
23
|
+
* (defaults to the env preset, else derived from the API host)
|
|
22
24
|
* WEBCAKE_CONFIG_DIR optional dir for the saved auth.json (default ~/.webcake-landing-mcp)
|
|
23
25
|
*/
|
|
24
26
|
import { homedir } from "node:os";
|
|
@@ -29,12 +31,14 @@ import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
|
29
31
|
* base URLs. Selecting one (via the `--env` flag, WEBCAKE_ENV, the `x-webcake-env`
|
|
30
32
|
* header, or `?env=` in the URL) fills in both bases so callers don't repeat them.
|
|
31
33
|
* Explicit WEBCAKE_API_BASE / WEBCAKE_APP_BASE (or per-request overrides) win over
|
|
32
|
-
* the preset. `apiBase` is the backend; `appBase` is the SPA (
|
|
34
|
+
* the preset. `apiBase` is the backend; `appBase` is the SPA (login connect page);
|
|
35
|
+
* `builderBase` is the page builder host that serves the `/editor/v2` URL returned
|
|
36
|
+
* after create/update (a distinct host — NOT the API and NOT the SPA).
|
|
33
37
|
*/
|
|
34
38
|
export const ENVIRONMENTS = {
|
|
35
|
-
local: { apiBase: "http://localhost:5800", appBase: "http://localhost:5173" },
|
|
36
|
-
staging: { apiBase: "https://api.staging.webcake.io", appBase: "https://staging.webcake.io" },
|
|
37
|
-
prod: { apiBase: "https://api.webcake.io", appBase: "https://webcake.io" },
|
|
39
|
+
local: { apiBase: "http://localhost:5800", appBase: "http://localhost:5173", builderBase: "http://builder.localhost:5800" },
|
|
40
|
+
staging: { apiBase: "https://api.staging.webcake.io", appBase: "https://staging.webcake.io", builderBase: "https://builder.staging.webcake.io" },
|
|
41
|
+
prod: { apiBase: "https://api.webcake.io", appBase: "https://webcake.io", builderBase: "https://builder.webcake.io" },
|
|
38
42
|
};
|
|
39
43
|
export const ENV_NAMES = Object.keys(ENVIRONMENTS);
|
|
40
44
|
/** True when `v` names a known environment (local|staging|prod). */
|
|
@@ -45,6 +49,23 @@ export function isEnvName(v) {
|
|
|
45
49
|
export function resolveEnv(name) {
|
|
46
50
|
return isEnvName(name) ? ENVIRONMENTS[name] : undefined;
|
|
47
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* Derive the page-builder host from the API base when no preset / explicit value is
|
|
54
|
+
* given: `api.<domain>` → `builder.<domain>`, otherwise `builder.<host>` (so
|
|
55
|
+
* `http://localhost:5800` → `http://builder.localhost:5800`, matching the presets).
|
|
56
|
+
*/
|
|
57
|
+
export function deriveBuilderBase(apiBase) {
|
|
58
|
+
if (!apiBase)
|
|
59
|
+
return undefined;
|
|
60
|
+
try {
|
|
61
|
+
const u = new URL(apiBase);
|
|
62
|
+
u.hostname = u.hostname.startsWith("api.") ? `builder.${u.hostname.slice(4)}` : `builder.${u.hostname}`;
|
|
63
|
+
return u.origin;
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
48
69
|
export function readConfig(overrides = {}) {
|
|
49
70
|
const saved = readSavedConfig();
|
|
50
71
|
// A named environment supplies default base URLs; explicit values still win.
|
|
@@ -58,12 +79,22 @@ export function readConfig(overrides = {}) {
|
|
|
58
79
|
missing.push("WEBCAKE_JWT");
|
|
59
80
|
if (missing.length)
|
|
60
81
|
return { config: null, missing };
|
|
82
|
+
const cleanBase = base.replace(/\/+$/, "");
|
|
83
|
+
// The editor/preview URL lives on the builder host (e.g. builder.localhost:5800),
|
|
84
|
+
// not the API base (5800) nor the SPA (5173). Resolve it explicitly so the link
|
|
85
|
+
// returned to the user opens in the page builder.
|
|
86
|
+
const builderBase = (overrides.builderBase ??
|
|
87
|
+
process.env.WEBCAKE_BUILDER_BASE ??
|
|
88
|
+
preset?.builderBase ??
|
|
89
|
+
saved.builderBase ??
|
|
90
|
+
deriveBuilderBase(cleanBase))?.replace(/\/+$/, "");
|
|
61
91
|
return {
|
|
62
92
|
config: {
|
|
63
|
-
base:
|
|
93
|
+
base: cleanBase,
|
|
64
94
|
jwt: jwt,
|
|
65
95
|
orgId: overrides.orgId ?? process.env.WEBCAKE_ORG_ID ?? saved.orgId,
|
|
66
96
|
appBase: (overrides.appBase ?? process.env.WEBCAKE_APP_BASE ?? preset?.appBase ?? saved.appBase)?.replace(/\/+$/, ""),
|
|
97
|
+
builderBase,
|
|
67
98
|
},
|
|
68
99
|
missing: [],
|
|
69
100
|
};
|
|
@@ -79,7 +110,8 @@ function header(headers, name) {
|
|
|
79
110
|
* x-webcake-org-id organization id
|
|
80
111
|
* x-webcake-env named environment (local|staging|prod) for the base URLs
|
|
81
112
|
* x-webcake-api-base backend base URL (overrides the env preset)
|
|
82
|
-
* x-webcake-app-base
|
|
113
|
+
* x-webcake-app-base SPA base used for the login connect page (overrides the preset)
|
|
114
|
+
* x-webcake-builder-base builder host for editor/preview URLs (overrides the preset)
|
|
83
115
|
* Any header that is absent falls back to the corresponding env var in readConfig.
|
|
84
116
|
*/
|
|
85
117
|
export function configFromHeaders(headers) {
|
|
@@ -90,6 +122,7 @@ export function configFromHeaders(headers) {
|
|
|
90
122
|
jwt: header(headers, "x-webcake-jwt") ?? bearer,
|
|
91
123
|
orgId: header(headers, "x-webcake-org-id"),
|
|
92
124
|
appBase: header(headers, "x-webcake-app-base"),
|
|
125
|
+
builderBase: header(headers, "x-webcake-builder-base"),
|
|
93
126
|
env: header(headers, "x-webcake-env"),
|
|
94
127
|
};
|
|
95
128
|
}
|
|
@@ -15,6 +15,33 @@ function authHeaders(config, orgId) {
|
|
|
15
15
|
headers["x-org-id"] = `${org}`;
|
|
16
16
|
return headers;
|
|
17
17
|
}
|
|
18
|
+
/**
|
|
19
|
+
* Resolve the editor/preview link the backend returns onto the page-builder host
|
|
20
|
+
* (config.builderBase, e.g. builder.localhost:5800), NOT the API base. The backend
|
|
21
|
+
* may return either a path (`/editor/v2/<id>`) or an absolute URL on its own host
|
|
22
|
+
* (`http://localhost:5800/editor/v2/<id>`) — in both cases we keep only the
|
|
23
|
+
* path+query and re-root it on the builder host.
|
|
24
|
+
*/
|
|
25
|
+
export function toEditorUrl(config, raw) {
|
|
26
|
+
if (!raw)
|
|
27
|
+
return raw;
|
|
28
|
+
const builder = config.builderBase ?? config.appBase;
|
|
29
|
+
if (!builder)
|
|
30
|
+
return raw;
|
|
31
|
+
let pathQuery = raw;
|
|
32
|
+
if (/^https?:\/\//i.test(raw)) {
|
|
33
|
+
try {
|
|
34
|
+
const u = new URL(raw);
|
|
35
|
+
pathQuery = u.pathname + u.search + u.hash;
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
/* not a parseable URL — use as-is */
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (!pathQuery.startsWith("/"))
|
|
42
|
+
pathQuery = `/${pathQuery}`;
|
|
43
|
+
return `${builder.replace(/\/+$/, "")}${pathQuery}`;
|
|
44
|
+
}
|
|
18
45
|
/** Build (but do not send) the create request — used for dry-run previews. */
|
|
19
46
|
export function buildRequest(config, name, source, orgId) {
|
|
20
47
|
return {
|
|
@@ -88,7 +115,6 @@ export async function createPage(config, name, source, orgId) {
|
|
|
88
115
|
const pageId = data?.page_id;
|
|
89
116
|
const editorPath = data?.editor_url;
|
|
90
117
|
const previewPath = data?.preview_url;
|
|
91
|
-
const app = config.appBase;
|
|
92
118
|
if (!res.ok || !pageId) {
|
|
93
119
|
// The backend's failure envelope is { success:false, message } on 422; auth
|
|
94
120
|
// plugs return plain-text 401/403 (json is null). Surface the real reason
|
|
@@ -105,8 +131,8 @@ export async function createPage(config, name, source, orgId) {
|
|
|
105
131
|
ok: true,
|
|
106
132
|
status: res.status,
|
|
107
133
|
page_id: pageId,
|
|
108
|
-
editor_url:
|
|
109
|
-
preview_url:
|
|
134
|
+
editor_url: toEditorUrl(config, editorPath),
|
|
135
|
+
preview_url: toEditorUrl(config, previewPath),
|
|
110
136
|
organization_id: (orgId ?? config.orgId) ?? null,
|
|
111
137
|
raw: data,
|
|
112
138
|
};
|
|
@@ -190,7 +216,6 @@ export async function updatePageSource(config, pageId, source) {
|
|
|
190
216
|
}
|
|
191
217
|
const data = json?.data ?? json;
|
|
192
218
|
const pageIdOut = data?.page_id;
|
|
193
|
-
const app = config.appBase;
|
|
194
219
|
if (!res.ok || !pageIdOut) {
|
|
195
220
|
const backendMsg = json?.message ?? json?.reason ?? (json ? undefined : text.slice(0, 200));
|
|
196
221
|
return {
|
|
@@ -204,8 +229,8 @@ export async function updatePageSource(config, pageId, source) {
|
|
|
204
229
|
ok: true,
|
|
205
230
|
status: res.status,
|
|
206
231
|
page_id: pageIdOut,
|
|
207
|
-
editor_url:
|
|
208
|
-
preview_url:
|
|
232
|
+
editor_url: toEditorUrl(config, data?.editor_url),
|
|
233
|
+
preview_url: toEditorUrl(config, data?.preview_url),
|
|
209
234
|
organization_id: data?.organization_id ?? null,
|
|
210
235
|
raw: data,
|
|
211
236
|
};
|
package/dist/smoke.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import { createElement, CONTAINER_TYPES, FIELD_TYPES, LIBRARY, ELEMENT_TYPES, ELEMENTS, } from "./domains/landing/elements/index.js";
|
|
6
6
|
import { validatePage, pageSchema } from "./domains/landing/validate.js";
|
|
7
7
|
import { readConfig, resolveEnv, ENV_NAMES } from "./persistence/config.js";
|
|
8
|
+
import { toEditorUrl } from "./persistence/webcake-client.js";
|
|
8
9
|
let failures = 0;
|
|
9
10
|
const check = (name, cond, extra) => {
|
|
10
11
|
if (cond) {
|
|
@@ -212,7 +213,7 @@ check("clean form has no binding warnings", rbg.warnings.length === 0, rbg.warni
|
|
|
212
213
|
console.log("== config: named environment presets (local/staging/prod) ==");
|
|
213
214
|
{
|
|
214
215
|
// Deterministic: isolate from any ambient WEBCAKE_* and the saved auth.json on the dev box.
|
|
215
|
-
for (const k of ["WEBCAKE_API_BASE", "WEBCAKE_APP_BASE", "WEBCAKE_ENV", "WEBCAKE_JWT", "WEBCAKE_ORG_ID"])
|
|
216
|
+
for (const k of ["WEBCAKE_API_BASE", "WEBCAKE_APP_BASE", "WEBCAKE_BUILDER_BASE", "WEBCAKE_ENV", "WEBCAKE_JWT", "WEBCAKE_ORG_ID"])
|
|
216
217
|
delete process.env[k];
|
|
217
218
|
process.env.WEBCAKE_CONFIG_DIR = "/nonexistent/webcake-smoke";
|
|
218
219
|
check("env names are local/staging/prod", setEq(new Set(ENV_NAMES), ["local", "staging", "prod"]), ENV_NAMES);
|
|
@@ -224,6 +225,20 @@ console.log("== config: named environment presets (local/staging/prod) ==");
|
|
|
224
225
|
check("readConfig(env=local) fills api+app base", local?.base === "http://localhost:5800" && local?.appBase === "http://localhost:5173", local);
|
|
225
226
|
check("explicit base overrides the preset", readConfig({ env: "prod", base: "http://x:1", jwt: "t" }).config?.base === "http://x:1");
|
|
226
227
|
check("unknown env leaves base missing", readConfig({ env: "bogus", jwt: "t" }).missing.includes("WEBCAKE_API_BASE"));
|
|
228
|
+
// builder host for editor/preview URLs (distinct from the api + app bases)
|
|
229
|
+
check("env presets carry builder bases", resolveEnv("prod")?.builderBase === "https://builder.webcake.io" && resolveEnv("local")?.builderBase === "http://builder.localhost:5800");
|
|
230
|
+
check("readConfig(env=prod) sets builderBase", prod?.builderBase === "https://builder.webcake.io", prod);
|
|
231
|
+
check("readConfig(env=local) sets builderBase", local?.builderBase === "http://builder.localhost:5800", local);
|
|
232
|
+
check("readConfig(env=staging) sets builderBase", readConfig({ env: "staging", jwt: "t" }).config?.builderBase === "https://builder.staging.webcake.io");
|
|
233
|
+
check("builderBase derives from a custom api base (api. → builder.)", readConfig({ base: "https://api.example.com", jwt: "t" }).config?.builderBase === "https://builder.example.com");
|
|
234
|
+
check("builderBase derives from a localhost api base", readConfig({ base: "http://localhost:5800", jwt: "t" }).config?.builderBase === "http://builder.localhost:5800");
|
|
235
|
+
check("explicit builderBase overrides the preset", readConfig({ env: "prod", builderBase: "https://b.test", jwt: "t" }).config?.builderBase === "https://b.test");
|
|
236
|
+
// editor/preview link is re-rooted on the builder host, whether the backend
|
|
237
|
+
// returns a path or an absolute URL on its own host.
|
|
238
|
+
const localCfg = readConfig({ env: "local", jwt: "t" }).config;
|
|
239
|
+
check("editor url from a path → builder host", toEditorUrl(localCfg, "/editor/v2/abc") === "http://builder.localhost:5800/editor/v2/abc");
|
|
240
|
+
check("editor url from an absolute api url → builder host", toEditorUrl(localCfg, "http://localhost:5800/editor/v2/abc?x=1") === "http://builder.localhost:5800/editor/v2/abc?x=1");
|
|
241
|
+
check("editor url passthrough when empty", toEditorUrl(localCfg, undefined) === undefined);
|
|
227
242
|
}
|
|
228
243
|
console.log(`\n${failures === 0 ? "ALL GOOD" : failures + " FAILURE(S)"}`);
|
|
229
244
|
process.exit(failures === 0 ? 0 : 1);
|
package/dist/web-guide.js
CHANGED
|
@@ -58,6 +58,9 @@ const ICONS = {
|
|
|
58
58
|
arrow: '<path d="M5 12h14"/><path d="m12 5 7 7-7 7"/>',
|
|
59
59
|
clock: '<circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/>',
|
|
60
60
|
globe: '<circle cx="12" cy="12" r="10"/><path d="M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20"/><path d="M2 12h20"/>',
|
|
61
|
+
bulb: '<path d="M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5"/><path d="M9 18h6"/><path d="M10 22h4"/>',
|
|
62
|
+
server: '<rect width="20" height="8" x="2" y="2" rx="2"/><rect width="20" height="8" x="2" y="14" rx="2"/><path d="M6 6h.01"/><path d="M6 18h.01"/>',
|
|
63
|
+
window: '<rect x="2" y="4" width="20" height="16" rx="2"/><path d="M2 9h20"/><path d="M6 6.5h.01"/><path d="M9 6.5h.01"/>',
|
|
61
64
|
};
|
|
62
65
|
function icon(name) {
|
|
63
66
|
return `<svg class="i" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${ICONS[name] ?? ""}</svg>`;
|
|
@@ -136,6 +139,14 @@ const T = {
|
|
|
136
139
|
leadPost: " của bạn. Không kéo thả, không cài server — kết nối một lần là xong.",
|
|
137
140
|
ctaStart: "Bắt đầu kết nối",
|
|
138
141
|
ctaStar: "Star trên GitHub",
|
|
142
|
+
flowH2: "Mô hình hoạt động",
|
|
143
|
+
flow: [
|
|
144
|
+
{ icon: "bulb", t: "Bạn", s: "ý tưởng" },
|
|
145
|
+
{ icon: "brain", t: "Trợ lý AI", s: "Claude · Cursor" },
|
|
146
|
+
{ icon: "server", t: "MCP", s: "webcake-landing" },
|
|
147
|
+
{ icon: "window", t: "WebCake", s: "trang thật" },
|
|
148
|
+
],
|
|
149
|
+
flowCap: "Bạn mô tả bằng lời → AI học mô hình thật từ MCP → MCP dựng JSON + kiểm tra → lưu thành trang thật trên WebCake. Bạn nhận link, mở editor, publish.",
|
|
139
150
|
howH2: "Cách hoạt động",
|
|
140
151
|
how: [
|
|
141
152
|
{
|
|
@@ -206,6 +217,14 @@ const T = {
|
|
|
206
217
|
leadPost: ". No drag-and-drop, no server to host — connect once and you're set.",
|
|
207
218
|
ctaStart: "Get connected",
|
|
208
219
|
ctaStar: "Star on GitHub",
|
|
220
|
+
flowH2: "How it flows",
|
|
221
|
+
flow: [
|
|
222
|
+
{ icon: "bulb", t: "You", s: "your idea" },
|
|
223
|
+
{ icon: "brain", t: "AI assistant", s: "Claude · Cursor" },
|
|
224
|
+
{ icon: "server", t: "MCP", s: "webcake-landing" },
|
|
225
|
+
{ icon: "window", t: "WebCake", s: "a real page" },
|
|
226
|
+
],
|
|
227
|
+
flowCap: "You describe it in words → the AI learns the real model from the MCP → the MCP builds the JSON + validates → it's saved as a real WebCake page. You get a link, open the editor, publish.",
|
|
209
228
|
howH2: "How it works",
|
|
210
229
|
how: [
|
|
211
230
|
{
|
|
@@ -440,6 +459,27 @@ export function guideHtml(origin, lang = "vi") {
|
|
|
440
459
|
.feat li{display:flex;gap:13px;align-items:center;font-size:.97rem;padding:13px 16px}
|
|
441
460
|
.feat li b{color:var(--ink)}
|
|
442
461
|
.cta-row{display:flex;gap:12px;flex-wrap:wrap;margin:22px 0 6px}
|
|
462
|
+
/* Flow diagram: nodes connected by wires with a traveling "packet" */
|
|
463
|
+
.flow{display:flex;align-items:flex-start;gap:0;padding:24px 18px 18px;overflow-x:auto}
|
|
464
|
+
.flow .node{flex:0 0 auto;display:flex;flex-direction:column;align-items:center;gap:8px;text-align:center;width:104px}
|
|
465
|
+
.flow .node .ic{width:54px;height:54px;border-radius:16px}
|
|
466
|
+
.flow .node .ic .i{width:27px;height:27px}
|
|
467
|
+
.flow .node b{font-size:.93rem}
|
|
468
|
+
.flow .node span{font-size:.75rem;color:var(--mut)}
|
|
469
|
+
.flow .wire{flex:1 1 auto;min-width:30px;position:relative;height:2px;margin-top:27px;
|
|
470
|
+
background:linear-gradient(90deg,var(--line),rgba(29,185,84,.45),var(--line))}
|
|
471
|
+
.flow .wire .pkt{position:absolute;top:50%;left:0;width:9px;height:9px;margin:-5px 0 0 -4px;border-radius:50%;
|
|
472
|
+
background:var(--g);box-shadow:0 0 9px 1px rgba(29,185,84,.7)}
|
|
473
|
+
.flow .wire::after{content:"";position:absolute;right:-1px;top:50%;width:7px;height:7px;margin-top:-4px;
|
|
474
|
+
border-top:2px solid var(--g7);border-right:2px solid var(--g7);transform:rotate(45deg)}
|
|
475
|
+
.flow-cap{color:var(--mut);font-size:.9rem;margin:2px 2px 0;max-width:68ch}
|
|
476
|
+
@media(prefers-reduced-motion:no-preference){
|
|
477
|
+
.flow .wire .pkt{animation:pkt 2.4s ease-in-out infinite}
|
|
478
|
+
@keyframes pkt{0%{left:0;opacity:0}12%{opacity:1}88%{opacity:1}100%{left:100%;opacity:0}}
|
|
479
|
+
.flow .node .ic{animation:nodepop 2.4s ease-in-out infinite}
|
|
480
|
+
}
|
|
481
|
+
@media(prefers-reduced-motion:reduce){.flow .wire .pkt{display:none}}
|
|
482
|
+
@keyframes nodepop{0%,100%{box-shadow:none}50%{box-shadow:0 0 0 4px rgba(29,185,84,.12)}}
|
|
443
483
|
.btn{display:inline-flex;align-items:center;gap:9px;padding:11px 19px;border-radius:11px;cursor:pointer;
|
|
444
484
|
background:var(--g);color:#fff;text-decoration:none;font-weight:700;font-size:.93rem;
|
|
445
485
|
box-shadow:0 4px 12px -4px rgba(29,185,84,.5);transition:transform .15s ease,background .15s ease}
|
|
@@ -540,6 +580,17 @@ export function guideHtml(origin, lang = "vi") {
|
|
|
540
580
|
<a class="btn ghost" href="${GITHUB_URL}">${icon("star")} ${t.ctaStar}</a>
|
|
541
581
|
</div>
|
|
542
582
|
|
|
583
|
+
<h2 class="reveal">${t.flowH2}</h2>
|
|
584
|
+
<div class="glass flow reveal">
|
|
585
|
+
${t.flow
|
|
586
|
+
.map((n, i) => `<div class="node"><span class="ic" style="animation-delay:${(i * 0.8).toFixed(1)}s">${icon(n.icon)}</span><b>${n.t}</b><span>${n.s}</span></div>` +
|
|
587
|
+
(i < t.flow.length - 1
|
|
588
|
+
? `<div class="wire"><i class="pkt" style="animation-delay:${(i * 0.8).toFixed(1)}s"></i></div>`
|
|
589
|
+
: ""))
|
|
590
|
+
.join("\n ")}
|
|
591
|
+
</div>
|
|
592
|
+
<p class="flow-cap reveal">${t.flowCap}</p>
|
|
593
|
+
|
|
543
594
|
<h2 class="reveal">${t.howH2}</h2>
|
|
544
595
|
<div class="grid">
|
|
545
596
|
${t.how
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "webcake-landing-mcp",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.22",
|
|
4
4
|
"description": "MCP server exposing Webcake landing-page element schemas + AI usage hints, and persisting LLM-generated page sources to a Webcake backend.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|