kizlo 0.6.1 → 0.7.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/brand/icons.d.ts +10 -12
- package/dist/brand/icons.js +19 -17
- package/dist/cli/commands/dev.js +25 -14
- package/dist/cli/wp/dev.js +16 -1
- package/dist/integrations/nextjs/metadata.js +1 -2
- package/dist/package.js +1 -1
- package/dist/router.d.ts +1 -3
- package/package.json +2 -2
package/dist/brand/icons.d.ts
CHANGED
|
@@ -7,29 +7,27 @@ interface IconDescriptor {
|
|
|
7
7
|
type: string;
|
|
8
8
|
/** `"any"` for scalable/single sources; omitted for `.ico`. */
|
|
9
9
|
sizes?: string;
|
|
10
|
-
/** Media query for scheme-specific variants, e.g. `(prefers-color-scheme: dark)`. */
|
|
11
|
-
media?: string;
|
|
12
10
|
}
|
|
13
11
|
interface ManifestIcon {
|
|
14
12
|
src: string;
|
|
15
13
|
type: string;
|
|
16
14
|
sizes: string;
|
|
17
|
-
/**
|
|
18
|
-
* `"maskable"` marks art with a safe zone the launcher may crop to any
|
|
19
|
-
* adaptive shape; omitted (default `"any"`) sources render uncropped.
|
|
20
|
-
*/
|
|
21
|
-
purpose?: "any" | "maskable";
|
|
22
15
|
}
|
|
23
16
|
interface ResolvedIcons {
|
|
24
|
-
/**
|
|
17
|
+
/**
|
|
18
|
+
* `rel="icon"` candidate: a single scheme-agnostic source, or empty. Browsers
|
|
19
|
+
* disagree on scheme-swapping favicons (Safari ignores the `media` attribute
|
|
20
|
+
* and never re-evaluates an SVG's internal `@media`), so one icon that reads
|
|
21
|
+
* on both light and dark tab bars is the only cross-browser-safe surface.
|
|
22
|
+
*/
|
|
25
23
|
icon: IconDescriptor[];
|
|
26
24
|
/** `rel="apple-touch-icon"` candidate (raster-guarded), or empty. */
|
|
27
25
|
appleTouch: IconDescriptor[];
|
|
28
26
|
/**
|
|
29
|
-
* Web-manifest icon entries (raster or SVG):
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
27
|
+
* Web-manifest icon entries (raster or SVG): a single square source —
|
|
28
|
+
* expanded to one entry per real square rendition — that every non-iOS
|
|
29
|
+
* install surface (Android, Chrome, macOS Safari) renders uncropped. Absent
|
|
30
|
+
* when no manifest-compatible source exists.
|
|
33
31
|
*/
|
|
34
32
|
manifestIcons: ManifestIcon[];
|
|
35
33
|
}
|
package/dist/brand/icons.js
CHANGED
|
@@ -32,12 +32,20 @@ function manifestSource(media) {
|
|
|
32
32
|
function pixelSize(media) {
|
|
33
33
|
return media.width && media.height ? `${media.width}x${media.height}` : "any";
|
|
34
34
|
}
|
|
35
|
-
|
|
35
|
+
/**
|
|
36
|
+
* Append the attachment id as a `?v=` cache-buster. Safari (and every browser)
|
|
37
|
+
* keys its favicon cache by URL, so tying the version to the attachment id means
|
|
38
|
+
* the URL changes when a different icon is chosen — busting the stale cache —
|
|
39
|
+
* while staying stable across renders when the icon is unchanged.
|
|
40
|
+
*/
|
|
41
|
+
function versioned(src, id) {
|
|
42
|
+
return `${src}${src.includes("?") ? "&" : "?"}v=${id}`;
|
|
43
|
+
}
|
|
44
|
+
function toDescriptor(media) {
|
|
36
45
|
return {
|
|
37
|
-
url: media.src,
|
|
46
|
+
url: versioned(media.src, media.id),
|
|
38
47
|
type: media.mime ?? "",
|
|
39
|
-
sizes: media.mime && ICO_MIMES.has(media.mime) ? void 0 : pixelSize(media)
|
|
40
|
-
...extra
|
|
48
|
+
sizes: media.mime && ICO_MIMES.has(media.mime) ? void 0 : pixelSize(media)
|
|
41
49
|
};
|
|
42
50
|
}
|
|
43
51
|
/**
|
|
@@ -47,14 +55,12 @@ function toDescriptor(media, extra) {
|
|
|
47
55
|
* or above the floor so browsers get genuine 192/512-ish icons; if none qualify
|
|
48
56
|
* (e.g. a non-square upload) the original is used as-is.
|
|
49
57
|
*/
|
|
50
|
-
function toManifestIcons(media
|
|
58
|
+
function toManifestIcons(media) {
|
|
51
59
|
const type = media.mime ?? "";
|
|
52
|
-
const withPurpose = purpose ? { purpose } : {};
|
|
53
60
|
if (media.mime === SVG_MIME) return [{
|
|
54
61
|
src: media.src,
|
|
55
62
|
type,
|
|
56
|
-
sizes: "any"
|
|
57
|
-
...withPurpose
|
|
63
|
+
sizes: "any"
|
|
58
64
|
}];
|
|
59
65
|
const square = [{
|
|
60
66
|
src: media.src,
|
|
@@ -75,8 +81,7 @@ function toManifestIcons(media, purpose) {
|
|
|
75
81
|
icons.push({
|
|
76
82
|
src: r.src,
|
|
77
83
|
type,
|
|
78
|
-
sizes
|
|
79
|
-
...withPurpose
|
|
84
|
+
sizes
|
|
80
85
|
});
|
|
81
86
|
}
|
|
82
87
|
return icons;
|
|
@@ -87,16 +92,13 @@ function toManifestIcons(media, purpose) {
|
|
|
87
92
|
*/
|
|
88
93
|
function resolveIcons(brand) {
|
|
89
94
|
const icon = [];
|
|
90
|
-
const
|
|
91
|
-
if (
|
|
92
|
-
|
|
93
|
-
const apple = raster(brand.ios_app_icon) ?? raster(brand.favicon);
|
|
95
|
+
const primary = brand.favicon ?? brand.logo_icon;
|
|
96
|
+
if (primary) icon.push(toDescriptor(primary));
|
|
97
|
+
const apple = raster(brand.app_icon) ?? raster(brand.favicon);
|
|
94
98
|
const appleTouch = apple ? [toDescriptor(apple)] : [];
|
|
95
99
|
const manifestIcons = [];
|
|
96
|
-
const anySource = manifestSource(brand.logo_icon) ?? manifestSource(brand.favicon);
|
|
100
|
+
const anySource = manifestSource(brand.app_icon) ?? manifestSource(brand.logo_icon) ?? manifestSource(brand.favicon);
|
|
97
101
|
if (anySource) manifestIcons.push(...toManifestIcons(anySource));
|
|
98
|
-
const maskable = manifestSource(brand.android_app_icon);
|
|
99
|
-
if (maskable) manifestIcons.push(...toManifestIcons(maskable, "maskable"));
|
|
100
102
|
return {
|
|
101
103
|
icon,
|
|
102
104
|
appleTouch,
|
package/dist/cli/commands/dev.js
CHANGED
|
@@ -11,7 +11,6 @@ import { join } from "node:path";
|
|
|
11
11
|
import { existsSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
12
12
|
import { defineCommand } from "citty";
|
|
13
13
|
import { randomBytes } from "node:crypto";
|
|
14
|
-
import { networkInterfaces } from "node:os";
|
|
15
14
|
|
|
16
15
|
//#region src/cli/commands/dev.ts
|
|
17
16
|
async function resolve$1(cwd) {
|
|
@@ -26,22 +25,17 @@ function note(text) {
|
|
|
26
25
|
const { dim, reset } = palette();
|
|
27
26
|
process.stdout.write(` ${dim}${text}${reset}\n`);
|
|
28
27
|
}
|
|
29
|
-
/** First non-internal IPv4 address, for the LAN-reachable "Network" URL (undefined when offline). */
|
|
30
|
-
function lanAddress() {
|
|
31
|
-
for (const ifaces of Object.values(networkInterfaces())) for (const iface of ifaces ?? []) if (iface.family === "IPv4" && !iface.internal) return iface.address;
|
|
32
|
-
}
|
|
33
28
|
/**
|
|
34
|
-
* Print the connection summary as an aligned, Next.js-style block. Shows the wp-admin
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
* wp-admin password (it's never stored, so this is the only chance to see it).
|
|
29
|
+
* Print the connection summary as an aligned, Next.js-style block. Shows the wp-admin URL on
|
|
30
|
+
* the loopback ("Local") and — when the stack is installed on a LAN address rather than
|
|
31
|
+
* localhost — on the network, so it can be opened from other devices. On a fresh install, also
|
|
32
|
+
* show the one-time wp-admin password (it's never stored, so this is the only chance to see it).
|
|
38
33
|
*/
|
|
39
34
|
function printSummary(info) {
|
|
40
35
|
const { cyan, dim, reset } = palette();
|
|
41
|
-
const port = new URL(info.url)
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
if (lan) rows.push(["WP Network", `http://${lan}:${port}/wp-admin`]);
|
|
36
|
+
const { port, hostname } = new URL(info.url);
|
|
37
|
+
const rows = [["WP Local", `http://localhost:${port}/wp-admin`]];
|
|
38
|
+
if (hostname !== "localhost") rows.push(["WP Network", `${info.url}/wp-admin`]);
|
|
45
39
|
rows.push(["Database Connection", `127.0.0.1:${info.dbPort}`]);
|
|
46
40
|
const width = Math.max(...rows.map(([label]) => label.length)) + 1;
|
|
47
41
|
const lines = ["", ...rows.map(([label, value]) => ` ${dim}- ${`${label}:`.padEnd(width)}${reset} ${cyan}${value}${reset}`)];
|
|
@@ -104,6 +98,23 @@ function updateWpEnv(cfg, info) {
|
|
|
104
98
|
};
|
|
105
99
|
}
|
|
106
100
|
/**
|
|
101
|
+
* Re-point `KIZLO_DEV_WORDPRESS_URL` at the current dev URL on a warm resume, so a LAN address that
|
|
102
|
+
* changed since the last session (a new DHCP lease, a different network) doesn't leave the app talking
|
|
103
|
+
* to a dead IP. Only this one key is rewritten — the credentials and secret from the fresh install are
|
|
104
|
+
* preserved — and it's a no-op when `.env` is absent (nothing provisioned yet) or already current.
|
|
105
|
+
* The full fresh-install write goes through {@link updateWpEnv} instead; this covers the resume path it
|
|
106
|
+
* skips. Returns true when it changed the file. The app still needs a restart to read the new value.
|
|
107
|
+
*/
|
|
108
|
+
function syncDevUrl(cfg, url) {
|
|
109
|
+
const envPath = join(cfg.configDir, ".env");
|
|
110
|
+
if (!existsSync(envPath)) return false;
|
|
111
|
+
const existing = readFileSync(envPath, "utf8");
|
|
112
|
+
if (readEnvValue(existing, "KIZLO_DEV_WORDPRESS_URL") === url) return false;
|
|
113
|
+
const { content } = mergeEnv(existing, { KIZLO_DEV_WORDPRESS_URL: url }, new Set(["KIZLO_DEV_WORDPRESS_URL"]), envGroups("KIZLO_BACKEND_URL"));
|
|
114
|
+
writeFileSync(envPath, content);
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
107
118
|
* Arm the foreground teardown — call this *before* the containers start, so a cancel at
|
|
108
119
|
* any point (even mid-startup, with mysql up but wordpress still booting) stops whatever
|
|
109
120
|
* already came up. The teardown is owned by a detached **watchdog** process, not by this
|
|
@@ -189,7 +200,7 @@ async function startForeground(cfg) {
|
|
|
189
200
|
});
|
|
190
201
|
if (!sync.ok) note(`Could not sync the site settings to WordPress (${sync.error}) — make sure the kizlo plugin is active, then set them from the Kizlo settings.`);
|
|
191
202
|
}
|
|
192
|
-
}
|
|
203
|
+
} else if (syncDevUrl(ready, creds.url)) note("Updated .env WordPress URL to the current network address — restart your app to pick it up");
|
|
193
204
|
printSummary(creds);
|
|
194
205
|
const { green, dim, bold, reset } = palette();
|
|
195
206
|
const ms = Date.now() - start;
|
package/dist/cli/wp/dev.js
CHANGED
|
@@ -7,12 +7,27 @@ import { prepareByo } from "./byo.js";
|
|
|
7
7
|
import { join } from "node:path";
|
|
8
8
|
import { existsSync, rmSync } from "node:fs";
|
|
9
9
|
import { randomBytes } from "node:crypto";
|
|
10
|
+
import { networkInterfaces } from "node:os";
|
|
10
11
|
|
|
11
12
|
//#region src/cli/wp/dev.ts
|
|
12
13
|
/** A strong random admin password (144 bits, URL/JSON/CLI-safe characters). */
|
|
13
14
|
function generatePassword() {
|
|
14
15
|
return randomBytes(18).toString("base64url");
|
|
15
16
|
}
|
|
17
|
+
/** First non-internal IPv4 address — the router-assigned LAN address (undefined when offline). */
|
|
18
|
+
function lanAddress() {
|
|
19
|
+
for (const ifaces of Object.values(networkInterfaces())) for (const iface of ifaces ?? []) if (iface.family === "IPv4" && !iface.internal) return iface.address;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* The URL the dev stack is provisioned at. We prefer the router-assigned LAN address over `localhost`
|
|
23
|
+
* so it's reachable from off the host (the app in a container or on another device). Serving WordPress
|
|
24
|
+
* under the request host is already handled by the container's `WORDPRESS_CONFIG_EXTRA` (it derives
|
|
25
|
+
* `WP_HOME`/`WP_SITEURL` from `HTTP_HOST` per request), so this is only the address we write into `.env`
|
|
26
|
+
* and print. Falls back to `localhost` when the machine is offline, so bare local dev still works.
|
|
27
|
+
*/
|
|
28
|
+
function devUrl(port) {
|
|
29
|
+
return `http://${lanAddress() ?? "localhost"}:${port}`;
|
|
30
|
+
}
|
|
16
31
|
/**
|
|
17
32
|
* Seed `dev.fixtures` into a freshly installed dev stack, reusing the test seeding
|
|
18
33
|
* primitives: seed the default subscriber so `ctx.userId` exists, then run each `seed`
|
|
@@ -58,7 +73,7 @@ async function seedDevFixtures(cfg, url) {
|
|
|
58
73
|
* concern.
|
|
59
74
|
*/
|
|
60
75
|
async function bootstrapDev(cfg) {
|
|
61
|
-
const url =
|
|
76
|
+
const url = devUrl(cfg.port);
|
|
62
77
|
const byo = !existsSync(join(cfg.wordpressDir, "wp-includes", "version.php")) && cfg.byo ? await prepareByo(cfg.byo, cfg.wordpressDir, cfg.configDir) : void 0;
|
|
63
78
|
await composeUp();
|
|
64
79
|
const installed = (await compose([
|
package/dist/package.js
CHANGED
package/dist/router.d.ts
CHANGED
|
@@ -779,9 +779,7 @@ declare const ROUTER_MAP: {
|
|
|
779
779
|
logo_wordmark?: number | null | undefined;
|
|
780
780
|
logo_wordmark_dark?: number | null | undefined;
|
|
781
781
|
favicon?: number | null | undefined;
|
|
782
|
-
|
|
783
|
-
ios_app_icon?: number | null | undefined;
|
|
784
|
-
android_app_icon?: number | null | undefined;
|
|
782
|
+
app_icon?: number | null | undefined;
|
|
785
783
|
theme_color?: number | null | undefined;
|
|
786
784
|
theme_color_dark?: number | null | undefined;
|
|
787
785
|
background_color?: number | null | undefined;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kizlo",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A headless WordPress toolkit for TypeScript.",
|
|
6
6
|
"keywords": [
|
|
@@ -76,7 +76,7 @@
|
|
|
76
76
|
},
|
|
77
77
|
"dependencies": {
|
|
78
78
|
"@clack/prompts": "^1.5.1",
|
|
79
|
-
"@kizlo/shared": "0.
|
|
79
|
+
"@kizlo/shared": "0.4.0",
|
|
80
80
|
"@orpc/client": "^1.14.3",
|
|
81
81
|
"@orpc/contract": "^1.14.3",
|
|
82
82
|
"@orpc/openapi": "^1.14.3",
|