@takazudo/zfb 0.1.0-next.6 → 0.1.0-next.60

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/CHANGELOG.md CHANGED
@@ -2,6 +2,32 @@
2
2
 
3
3
  > **Newer releases:** see https://takazudomodular.com/pj/zudo-front-builder/docs/changelog/ for v0.1.0-next.5 and later. Entries below are historical (kept for npm readers).
4
4
 
5
+ ## Unreleased
6
+
7
+ ### New features
8
+
9
+ **`VNode`, `VNodeArray`, `VNodeObject` exported from `"@takazudo/zfb"`** (#972):
10
+
11
+ The structural JSX-node types are now part of the public API:
12
+
13
+ ```ts
14
+ import type { VNode, VNodeArray, VNodeObject } from "@takazudo/zfb";
15
+ ```
16
+
17
+ `VNode` now includes a bare `object` member (matching Preact's own `ComponentChild` design), making Preact's `ComponentChildren`, `VNode<Props>`, `JSX.Element`, and `JSX.Element[]` all assignable at `Island` input boundaries (`children` and `ssrFallback`) with zero `as unknown as` casts.
18
+
19
+ **Name-collision caveat for Preact consumers:** if a consumer file already has `import { VNode } from "preact"`, use a qualified import to avoid the clash:
20
+
21
+ ```ts
22
+ import type { VNode as ZfbVNode } from "@takazudo/zfb";
23
+ ```
24
+
25
+ ### Breaking changes (pre-1.0)
26
+
27
+ **`linkValidation.allowExternal` removed** (#925):
28
+
29
+ The `allowExternal` config knob has been removed. It was accepted but never did anything — external URL network validation is out of scope. Migration: delete `allowExternal` from your `linkValidation` config; external URLs continue to be silently skipped (unchanged runtime behaviour).
30
+
5
31
  ## 0.1.0-next.4
6
32
 
7
33
  ### Bug fixes
package/bin/zfb.mjs CHANGED
@@ -1,9 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
  // Followed biome's pattern: pure os/cpu lookup → resolve platform package → spawn binary.
3
3
  // See: https://github.com/biomejs/biome (packages/js/biome/bin/biome.mjs reference)
4
+ // Divergence from biome: async spawn + signal forwarding instead of spawnSync.
5
+ // biome is a short-lived CLI, but `zfb dev` is a long-running server — without
6
+ // forwarding, SIGTERM to the wrapper orphans the native binary (PPID 1) and
7
+ // leaves the dev-server port bound (issue #873).
4
8
  import { existsSync } from "node:fs";
5
- import { spawnSync } from "node:child_process";
9
+ import { spawn } from "node:child_process";
6
10
  import { createRequire } from "node:module";
11
+ import { constants as osConstants } from "node:os";
7
12
  import { join } from "node:path";
8
13
 
9
14
  const require = createRequire(import.meta.url);
@@ -57,26 +62,61 @@ if (!existsSync(binPath)) {
57
62
  process.exit(1);
58
63
  }
59
64
 
60
- const result = spawnSync(binPath, process.argv.slice(2), { stdio: "inherit" });
65
+ const child = spawn(binPath, process.argv.slice(2), { stdio: "inherit" });
61
66
 
62
- // Surface spawn errors that spawnSync stores in result.error rather than
63
- // propagating to stderr. Without this, a 0644 binary (EACCES) is silently
64
- // swallowed and the process exits with code 1 and no message — making it
65
- // impossible for the user to diagnose a corrupted/incomplete npm install.
66
- // (Issue #447 / #441 pnpm publish strips the executable bit.)
67
- if (result.error) {
68
- if (result.error.code === "EACCES") {
67
+ // Forward termination signals to the child. Supervisors (concurrently
68
+ // --kill-others, Playwright webServer teardown, timeout(1), CI runners)
69
+ // signal only the wrapper; without forwarding, the native server survives
70
+ // with PPID 1 and keeps its port bound. Terminal Ctrl+C already signals the
71
+ // whole foreground group, so the child may receive SIGINT twice — the native
72
+ // binary treats repeated signals as the same shutdown request.
73
+ // SIGHUP is POSIX-only: on Windows child.kill("SIGHUP") throws (libuv only
74
+ // emulates SIGINT/SIGTERM/SIGKILL/SIGQUIT), and the OS tears down console
75
+ // processes on window close anyway.
76
+ const forwardedSignals =
77
+ process.platform === "win32" ? ["SIGINT", "SIGTERM"] : ["SIGINT", "SIGTERM", "SIGHUP"];
78
+ const signalForwarders = new Map();
79
+ for (const sig of forwardedSignals) {
80
+ const forward = () => child.kill(sig);
81
+ signalForwarders.set(sig, forward);
82
+ process.on(sig, forward);
83
+ }
84
+
85
+ // Surface spawn failures clearly. Without this, a 0644 binary (EACCES) is
86
+ // silently swallowed and the process exits with code 1 and no message —
87
+ // making it impossible for the user to diagnose a corrupted/incomplete npm
88
+ // install. (Issue #447 / #441 — pnpm publish strips the executable bit.)
89
+ child.on("error", (error) => {
90
+ if (error.code === "EACCES") {
69
91
  process.stderr.write(
70
92
  `[zfb] binary is not executable; was the install corrupt?\n` +
71
93
  ` ${binPath}\n` +
72
94
  ` Try reinstalling: npm install --include=optional\n`,
73
95
  );
74
96
  } else {
75
- process.stderr.write(
76
- `[zfb] failed to spawn binary: ${result.error.message}\n` + ` ${binPath}\n`,
77
- );
97
+ process.stderr.write(`[zfb] failed to spawn binary: ${error.message}\n` + ` ${binPath}\n`);
78
98
  }
79
99
  process.exit(1);
80
- }
100
+ });
81
101
 
82
- process.exit(result.status ?? 1);
102
+ child.on("exit", (code, signal) => {
103
+ if (signal) {
104
+ // Re-raise the child's termination signal on ourselves so the caller
105
+ // sees the real cause of death (e.g. WIFSIGNALED), not a plain exit code.
106
+ // Remove our forwarding listener first or it would swallow the re-raise.
107
+ const forward = signalForwarders.get(signal);
108
+ if (forward) {
109
+ process.off(signal, forward);
110
+ }
111
+ try {
112
+ process.kill(process.pid, signal);
113
+ } catch {
114
+ // Signal cannot be re-raised on this platform (Windows emulation).
115
+ }
116
+ // Reached only if the re-raised signal did not terminate us — fall back
117
+ // to the shell's 128+n convention for death-by-signal.
118
+ const signum = osConstants.signals[signal];
119
+ process.exit(signum ? 128 + signum : 1);
120
+ }
121
+ process.exit(code ?? 1);
122
+ });
@@ -0,0 +1,55 @@
1
+ /**
2
+ * SSR helper for referencing a client-script asset URL in a page or layout.
3
+ *
4
+ * ## Usage
5
+ *
6
+ * ```tsx
7
+ * import { clientScript } from "@takazudo/zfb";
8
+ *
9
+ * export default function MyPage() {
10
+ * return (
11
+ * <html>
12
+ * <head>
13
+ * <script type="module" src={clientScript("search-widget")} />
14
+ * </head>
15
+ * <body>…</body>
16
+ * </html>
17
+ * );
18
+ * }
19
+ * ```
20
+ *
21
+ * ## How it works
22
+ *
23
+ * `clientScript(name)` returns the **stable URL** for the named client-script
24
+ * entry: `${base}/assets/client/<name>.js`. The production build pipeline
25
+ * (`ProductionAssetPipeline`) rewrites every occurrence of this stable URL in
26
+ * rendered HTML to the content-hashed equivalent
27
+ * (`/assets/client/<name>-<hash>.js`), so the hash never needs to be known at
28
+ * SSR time.
29
+ *
30
+ * The `base` prefix is read from `globalThis.__zfb?.base` (emitted by the
31
+ * bundler when at least one `*.client.*` file exists in the project). For
32
+ * root-mounted sites (`base` is absent or `"/"`), the prefix is the empty
33
+ * string and `clientScript("search-widget")` returns
34
+ * `"/assets/client/search-widget.js"`. For a sub-path deploy
35
+ * (`base="/foo/"`), it returns `"/foo/assets/client/search-widget.js"`.
36
+ *
37
+ * ## SSR-only note (v1)
38
+ *
39
+ * This is an SSR-time helper. Calling it in browser-executed code works but
40
+ * the base prefix (`globalThis.__zfb.base`) is currently not shipped to the
41
+ * browser — `clientScript()` will return the unprefixed stable URL
42
+ * (`/assets/client/<name>.js`) in that context. For the common use-case of
43
+ * rendering a `<script src>` tag at SSR time this is not a problem.
44
+ */
45
+ /**
46
+ * Returns the base-prefixed stable public URL for the named client-script entry.
47
+ *
48
+ * `name` is the entry name (file stem minus `.client`, e.g. `"search-widget"`
49
+ * for `search-widget.client.ts`).
50
+ *
51
+ * The returned URL is `${base}/assets/client/<name>.js`. The production
52
+ * pipeline rewrites it to the hashed URL; dev serves the stable URL directly.
53
+ */
54
+ export declare function clientScript(name: string): string;
55
+ //# sourceMappingURL=client-script.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client-script.d.ts","sourceRoot":"","sources":["../src/client-script.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AAKH;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAUjD"}
@@ -0,0 +1,67 @@
1
+ /**
2
+ * SSR helper for referencing a client-script asset URL in a page or layout.
3
+ *
4
+ * ## Usage
5
+ *
6
+ * ```tsx
7
+ * import { clientScript } from "@takazudo/zfb";
8
+ *
9
+ * export default function MyPage() {
10
+ * return (
11
+ * <html>
12
+ * <head>
13
+ * <script type="module" src={clientScript("search-widget")} />
14
+ * </head>
15
+ * <body>…</body>
16
+ * </html>
17
+ * );
18
+ * }
19
+ * ```
20
+ *
21
+ * ## How it works
22
+ *
23
+ * `clientScript(name)` returns the **stable URL** for the named client-script
24
+ * entry: `${base}/assets/client/<name>.js`. The production build pipeline
25
+ * (`ProductionAssetPipeline`) rewrites every occurrence of this stable URL in
26
+ * rendered HTML to the content-hashed equivalent
27
+ * (`/assets/client/<name>-<hash>.js`), so the hash never needs to be known at
28
+ * SSR time.
29
+ *
30
+ * The `base` prefix is read from `globalThis.__zfb?.base` (emitted by the
31
+ * bundler when at least one `*.client.*` file exists in the project). For
32
+ * root-mounted sites (`base` is absent or `"/"`), the prefix is the empty
33
+ * string and `clientScript("search-widget")` returns
34
+ * `"/assets/client/search-widget.js"`. For a sub-path deploy
35
+ * (`base="/foo/"`), it returns `"/foo/assets/client/search-widget.js"`.
36
+ *
37
+ * ## SSR-only note (v1)
38
+ *
39
+ * This is an SSR-time helper. Calling it in browser-executed code works but
40
+ * the base prefix (`globalThis.__zfb.base`) is currently not shipped to the
41
+ * browser — `clientScript()` will return the unprefixed stable URL
42
+ * (`/assets/client/<name>.js`) in that context. For the common use-case of
43
+ * rendering a `<script src>` tag at SSR time this is not a problem.
44
+ */
45
+ /** Stable public-URL prefix for client-script entries, matching the Rust constant. */
46
+ const CLIENT_SCRIPTS_URL_PREFIX = "/assets/client/";
47
+ /**
48
+ * Returns the base-prefixed stable public URL for the named client-script entry.
49
+ *
50
+ * `name` is the entry name (file stem minus `.client`, e.g. `"search-widget"`
51
+ * for `search-widget.client.ts`).
52
+ *
53
+ * The returned URL is `${base}/assets/client/<name>.js`. The production
54
+ * pipeline rewrites it to the hashed URL; dev serves the stable URL directly.
55
+ */
56
+ export function clientScript(name) {
57
+ // `globalThis.__zfb?.base` is set to the resolved base prefix by the
58
+ // bundler when the project has at least one `*.client.*` entry (#978).
59
+ // For root-mounted or no-base builds the value is `""`, yielding
60
+ // `/assets/client/<name>.js` as expected. The `?? ""` fallback handles
61
+ // the edge case where __zfb.base was not emitted (zero-script project,
62
+ // or a browser context where the slot was never populated).
63
+ const base = globalThis.__zfb;
64
+ const prefix = base?.base ?? "";
65
+ return `${prefix}${CLIENT_SCRIPTS_URL_PREFIX}${name}.js`;
66
+ }
67
+ //# sourceMappingURL=client-script.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client-script.js","sourceRoot":"","sources":["../src/client-script.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AAEH,sFAAsF;AACtF,MAAM,yBAAyB,GAAG,iBAAiB,CAAC;AAEpD;;;;;;;;GAQG;AACH,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,qEAAqE;IACrE,uEAAuE;IACvE,iEAAiE;IACjE,uEAAuE;IACvE,uEAAuE;IACvE,4DAA4D;IAC5D,MAAM,IAAI,GAAI,UAAsC,CAAC,KAAsC,CAAC;IAC5F,MAAM,MAAM,GAAG,IAAI,EAAE,IAAI,IAAI,EAAE,CAAC;IAChC,OAAO,GAAG,MAAM,GAAG,yBAAyB,GAAG,IAAI,KAAK,CAAC;AAC3D,CAAC"}