poveste 0.18.1 → 0.18.3

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.
@@ -7,11 +7,23 @@
7
7
  * neither the flag nor the mistake. A non-numeric value fails the same way.
8
8
  */
9
9
  export declare function resolvePort(value: unknown, command: string): number | undefined;
10
+ type SocketOptions = Record<string, unknown>;
10
11
  /**
11
- * The HMR socket port for a book served on `devPort`. Derived from the dev port
12
- * so each concurrent dev server's socket is as distinct as its book port (#175),
13
- * offset clear of the usual dev-port range and wrapped so it stays a valid port
14
- * for an unusually high `--port`. `@nuxt/vite-builder` otherwise pins every
15
- * server to the framework default (24678), a constant that collides (#221).
12
+ * The book server's HMR socket settings: whatever the merged config asked for,
13
+ * minus a port or server of its own, so Vite serves the socket on the book's port
14
+ * and the browser dials the page's own origin. Vite takes these from `server.ws`
15
+ * and from the deprecated `server.hmr` alike, so both are cleared.
16
+ *
17
+ * A socket on a second port was never routed on StackBlitz, which previews each
18
+ * port on its own hostname, so a page opened before collection ended waited for
19
+ * the list on a socket that never connected, and stayed blank (#1245). It is
20
+ * also how `@nuxt/vite-builder`'s 24678 default made two books collide (#221).
16
21
  */
17
- export declare function hmrPortFor(devPort: number | undefined): number;
22
+ export declare function socketOnBookPort(server: {
23
+ hmr?: unknown;
24
+ ws?: unknown;
25
+ }): {
26
+ hmr: SocketOptions;
27
+ ws: SocketOptions;
28
+ };
29
+ export {};
@@ -22,14 +22,24 @@ export function resolvePort(value, command) {
22
22
  }
23
23
  return port;
24
24
  }
25
+ function withoutOwnPort(options) {
26
+ if (options === null || typeof options !== 'object') {
27
+ return {};
28
+ }
29
+ const { port: _port, server: _server, ...rest } = options;
30
+ return rest;
31
+ }
25
32
  /**
26
- * The HMR socket port for a book served on `devPort`. Derived from the dev port
27
- * so each concurrent dev server's socket is as distinct as its book port (#175),
28
- * offset clear of the usual dev-port range and wrapped so it stays a valid port
29
- * for an unusually high `--port`. `@nuxt/vite-builder` otherwise pins every
30
- * server to the framework default (24678), a constant that collides (#221).
33
+ * The book server's HMR socket settings: whatever the merged config asked for,
34
+ * minus a port or server of its own, so Vite serves the socket on the book's port
35
+ * and the browser dials the page's own origin. Vite takes these from `server.ws`
36
+ * and from the deprecated `server.hmr` alike, so both are cleared.
37
+ *
38
+ * A socket on a second port was never routed on StackBlitz, which previews each
39
+ * port on its own hostname, so a page opened before collection ended waited for
40
+ * the list on a socket that never connected, and stayed blank (#1245). It is
41
+ * also how `@nuxt/vite-builder`'s 24678 default made two books collide (#221).
31
42
  */
32
- export function hmrPortFor(devPort) {
33
- const base = devPort ?? 6006;
34
- return base < 45536 ? base + 20000 : base - 20000;
43
+ export function socketOnBookPort(server) {
44
+ return { hmr: withoutOwnPort(server.hmr), ws: withoutOwnPort(server.ws) };
35
45
  }
@@ -1,3 +1,4 @@
1
+ import './iterator.js';
1
2
  export declare function createDomEnv(): {
2
3
  window: Window & typeof globalThis;
3
4
  destroy: () => void;
@@ -1,6 +1,11 @@
1
+ /* eslint-disable perfectionist/sort-imports -- order is load order here */
2
+ // First, and kept first: it defines `Iterator` where the runtime lacks it, and
3
+ // jsdom extends `Iterator` while it loads (#1238).
4
+ import './iterator.js';
1
5
  import { JSDOM, VirtualConsole, } from 'jsdom';
2
6
  import { isError } from '../util/is-error.js';
3
7
  import { populateGlobal } from './util.js';
8
+ /* eslint-enable perfectionist/sort-imports */
4
9
  // jsdom error types — not exported by jsdom.
5
10
  const JSDOM_ERROR_CSS_PARSING = 'css-parsing';
6
11
  const JSDOM_ERROR_UNHANDLED_EXCEPTION = 'unhandled-exception';
@@ -0,0 +1,2 @@
1
+ /** Defines `globalThis.Iterator` and its helpers where the runtime has neither. */
2
+ export declare function installIterator(): void;
@@ -0,0 +1,213 @@
1
+ // StackBlitz's WebContainer reports Node 22.22.3 but has no global `Iterator`, and
2
+ // jsdom 30.1 extends it when it loads, so every story failed to collect there
3
+ // (#1238). Real Node has had it since 22.0 and never runs this.
4
+ //
5
+ // Vendored rather than core-js: core-js and core-js-pure both carry a postinstall,
6
+ // and pnpm 11 and 12 fail an install outright on a dependency's unapproved build
7
+ // script. The full ES2025 helper set rather than only the `find` jsdom calls
8
+ // today, so the next helper jsdom reaches for is already here.
9
+ const IteratorPrototype = Object.getPrototypeOf(Object.getPrototypeOf([][Symbol.iterator]()));
10
+ function callable(fn, name) {
11
+ if (typeof fn !== 'function') {
12
+ throw new TypeError(`Iterator.prototype.${name} expects a function`);
13
+ }
14
+ return fn;
15
+ }
16
+ function count(value, name) {
17
+ const n = Number(value);
18
+ if (Number.isNaN(n)) {
19
+ throw new RangeError(`Iterator.prototype.${name} expects a number`);
20
+ }
21
+ const whole = Math.trunc(n);
22
+ if (whole < 0) {
23
+ throw new RangeError(`Iterator.prototype.${name} expects a non-negative number`);
24
+ }
25
+ return whole;
26
+ }
27
+ // Closing after a callback threw keeps the callback's error, as the spec does.
28
+ function closeQuietly(iterator) {
29
+ try {
30
+ iterator.return?.();
31
+ }
32
+ catch { }
33
+ }
34
+ function guarded(iterator, run) {
35
+ try {
36
+ return run();
37
+ }
38
+ catch (error) {
39
+ closeQuietly(iterator);
40
+ throw error;
41
+ }
42
+ }
43
+ /**
44
+ * A lazy helper. `step` turns each value into what to yield, and `stop` ends it
45
+ * before the next read. The underlying iterator is closed whenever this one ends
46
+ * before it does.
47
+ */
48
+ function lazy(iterator, step, stop = () => false) {
49
+ const next = iterator.next;
50
+ return (function* () {
51
+ let exhausted = false;
52
+ try {
53
+ for (let index = 0; !stop(); index++) {
54
+ const result = next.call(iterator);
55
+ if (result.done) {
56
+ exhausted = true;
57
+ return;
58
+ }
59
+ yield* guarded(iterator, () => step(result.value, index));
60
+ }
61
+ }
62
+ finally {
63
+ if (!exhausted) {
64
+ closeQuietly(iterator);
65
+ }
66
+ }
67
+ })();
68
+ }
69
+ /** An eager helper: `visit` returns true to stop, which closes the iterator. */
70
+ function each(iterator, visit) {
71
+ const next = iterator.next;
72
+ for (let index = 0;; index++) {
73
+ const result = next.call(iterator);
74
+ if (result.done) {
75
+ return;
76
+ }
77
+ if (guarded(iterator, () => visit(result.value, index))) {
78
+ closeQuietly(iterator);
79
+ return;
80
+ }
81
+ }
82
+ }
83
+ function flattenable(value, name) {
84
+ if (value === null || (typeof value !== 'object' && typeof value !== 'function')) {
85
+ throw new TypeError(`Iterator.${name} expects an iterator or an iterable`);
86
+ }
87
+ const method = Reflect.get(value, Symbol.iterator);
88
+ const inner = method === undefined ? value : method.call(value);
89
+ return { [Symbol.iterator]: () => inner };
90
+ }
91
+ const helpers = {
92
+ map(fn) {
93
+ const mapper = callable(fn, 'map');
94
+ return lazy(this, (value, index) => [mapper(value, index)]);
95
+ },
96
+ filter(fn) {
97
+ const predicate = callable(fn, 'filter');
98
+ return lazy(this, (value, index) => (predicate(value, index) ? [value] : []));
99
+ },
100
+ take(limit) {
101
+ let remaining = count(limit, 'take');
102
+ return lazy(this, (value) => {
103
+ remaining--;
104
+ return [value];
105
+ }, () => remaining === 0);
106
+ },
107
+ drop(limit) {
108
+ let remaining = count(limit, 'drop');
109
+ return lazy(this, (value) => {
110
+ if (remaining > 0) {
111
+ remaining--;
112
+ return [];
113
+ }
114
+ return [value];
115
+ });
116
+ },
117
+ flatMap(fn) {
118
+ const mapper = callable(fn, 'flatMap');
119
+ return lazy(this, (value, index) => flattenable(mapper(value, index), 'prototype.flatMap'));
120
+ },
121
+ reduce(fn, ...initial) {
122
+ const reducer = callable(fn, 'reduce');
123
+ let accumulator = initial[0];
124
+ let started = initial.length > 0;
125
+ each(this, (value, index) => {
126
+ if (started) {
127
+ accumulator = reducer(accumulator, value, index);
128
+ }
129
+ else {
130
+ accumulator = value;
131
+ started = true;
132
+ }
133
+ return false;
134
+ });
135
+ if (!started) {
136
+ throw new TypeError('Iterator.prototype.reduce of an empty iterator with no initial value');
137
+ }
138
+ return accumulator;
139
+ },
140
+ toArray() {
141
+ const out = [];
142
+ each(this, (value) => {
143
+ out.push(value);
144
+ return false;
145
+ });
146
+ return out;
147
+ },
148
+ forEach(fn) {
149
+ const visit = callable(fn, 'forEach');
150
+ each(this, (value, index) => {
151
+ visit(value, index);
152
+ return false;
153
+ });
154
+ },
155
+ some(fn) {
156
+ const predicate = callable(fn, 'some');
157
+ let found = false;
158
+ each(this, (value, index) => {
159
+ found = Boolean(predicate(value, index));
160
+ return found;
161
+ });
162
+ return found;
163
+ },
164
+ every(fn) {
165
+ const predicate = callable(fn, 'every');
166
+ let all = true;
167
+ each(this, (value, index) => {
168
+ all = Boolean(predicate(value, index));
169
+ return !all;
170
+ });
171
+ return all;
172
+ },
173
+ find(fn) {
174
+ const predicate = callable(fn, 'find');
175
+ let found;
176
+ each(this, (value, index) => {
177
+ if (predicate(value, index)) {
178
+ found = value;
179
+ return true;
180
+ }
181
+ return false;
182
+ });
183
+ return found;
184
+ },
185
+ };
186
+ /** Defines `globalThis.Iterator` and its helpers where the runtime has neither. */
187
+ export function installIterator() {
188
+ if (typeof Reflect.get(globalThis, 'Iterator') !== 'undefined') {
189
+ return;
190
+ }
191
+ function Iterator() {
192
+ if (new.target === undefined || new.target === Iterator) {
193
+ throw new TypeError('Iterator is an abstract class and cannot be constructed directly');
194
+ }
195
+ }
196
+ Iterator.prototype = IteratorPrototype;
197
+ Object.defineProperty(IteratorPrototype, 'constructor', { value: Iterator, writable: true, configurable: true });
198
+ Object.defineProperty(Iterator, 'from', {
199
+ value(value) {
200
+ const iterator = (typeof value === 'string' ? value : flattenable(value, 'from'))[Symbol.iterator]();
201
+ return Object.prototype.isPrototypeOf.call(IteratorPrototype, iterator) ? iterator : lazy(iterator, item => [item]);
202
+ },
203
+ writable: true,
204
+ configurable: true,
205
+ });
206
+ for (const [name, fn] of Object.entries(helpers)) {
207
+ if (!(name in IteratorPrototype)) {
208
+ Object.defineProperty(IteratorPrototype, name, { value: fn, writable: true, configurable: true });
209
+ }
210
+ }
211
+ Object.defineProperty(globalThis, 'Iterator', { value: Iterator, writable: true, configurable: true });
212
+ }
213
+ installIterator();
@@ -2,7 +2,7 @@ import { performance } from 'node:perf_hooks';
2
2
  import pc from 'picocolors';
3
3
  import { createServer as createViteServer, mergeConfig as mergeViteConfig } from 'vite';
4
4
  import { useCollectStories } from './collect/index.js';
5
- import { hmrPortFor } from './commands/port.js';
5
+ import { socketOnBookPort } from './commands/port.js';
6
6
  import { useModuleLoader } from './load.js';
7
7
  import { createMarkdownFilesWatcher, onMarkdownFileChange, onMarkdownListChange } from './markdown.js';
8
8
  import { DevEventPluginApi, DevPluginApi } from './plugin.js';
@@ -42,14 +42,19 @@ async function startServer(ctx, options, onOpen, close) {
42
42
  serverConfig.hmr = false;
43
43
  }
44
44
  else {
45
- // `@nuxt/vite-builder` pins the HMR socket to the constant 24678 on every
46
- // server it configures, so two poveste dev servers on different `--port`s
47
- // collide on it and the second silently loses HMR (#221). It only does
48
- // this when `server.hmr` carries no port of its own (it fills the gap with
49
- // `defu`), so pin the socket to a port derived from this book's `--port`,
50
- // before the config is resolved: each dev server then owns a distinct one
51
- // (#175).
52
- serverConfig.hmr = { port: hmrPortFor(options.port ?? serverConfig.port) };
45
+ // On the book's own port, never a second one: StackBlitz does not route
46
+ // it, and `@nuxt/vite-builder`'s default collides between books (#1245,
47
+ // #221). Post-ordered, because that default is filled in by Nuxt's own
48
+ // `config` hook.
49
+ viteConfig.plugins = [...viteConfig.plugins ?? [], {
50
+ name: 'poveste:socket-on-book-port',
51
+ config: {
52
+ order: 'post',
53
+ handler(config) {
54
+ config.server = { ...config.server, ...socketOnBookPort(config.server ?? {}) };
55
+ },
56
+ },
57
+ }];
53
58
  if (options.open) {
54
59
  serverConfig.open = true;
55
60
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "poveste",
3
3
  "type": "module",
4
- "version": "0.18.1",
4
+ "version": "0.18.3",
5
5
  "description": "Interactive component playgrounds for Vue, Nuxt, Svelte, SvelteKit and Quasar, plus Solid (renderer only) — a drop-in fork of histoire, with Quasar support histoire does not ship.",
6
6
  "author": {
7
7
  "name": "Sorin Gitlan",
@@ -77,10 +77,10 @@
77
77
  },
78
78
  "dependencies": {
79
79
  "@11ty/gray-matter": "^3.0.0",
80
- "@poveste/app": "^0.18.1",
81
- "@poveste/controls": "^0.18.1",
82
- "@poveste/shared": "^0.18.1",
83
- "@poveste/vendors": "^0.18.1",
80
+ "@poveste/app": "^0.18.3",
81
+ "@poveste/controls": "^0.18.3",
82
+ "@poveste/shared": "^0.18.3",
83
+ "@poveste/vendors": "^0.18.3",
84
84
  "change-case": "^5.4.4",
85
85
  "chokidar": "^5.0.0",
86
86
  "defu": "^6.1.4",