poveste 0.16.2 → 0.17.1

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 CHANGED
@@ -47,7 +47,7 @@ Run `npx poveste --help` for the full list of options.
47
47
 
48
48
  ## Requirements
49
49
 
50
- Node `>=22.22.2` and Vite `^8.0.0`. See
50
+ Node `>=24.15.0` and Vite `^8.0.0`. See
51
51
  [supported versions](https://poveste.dev/guide/getting-started#supported-versions) for
52
52
  what proves each range.
53
53
 
package/bin.mjs CHANGED
@@ -2,6 +2,7 @@
2
2
  'use strict'
3
3
 
4
4
  import process from 'node:process'
5
+ import { supportedFrameworks } from './framework-floor.mjs'
5
6
  import { supportedNode } from './node-floor.mjs'
6
7
 
7
8
  // Before the CLI is imported, not inside it: on a Node below the floor the import
@@ -12,7 +13,11 @@ import { supportedNode } from './node-floor.mjs'
12
13
  // Imported dynamically for the same reason. A static import is parsed with this
13
14
  // module, before a line of it runs, so syntax `dist/` may carry would win the race
14
15
  // against the message explaining it.
15
- if (supportedNode()) {
16
+ // The framework floor goes here for the same reason: on a framework below a
17
+ // plugin's peer, importing the CLI fails first and names neither (#1062). Not for
18
+ // `--help` or `--version`, which load no command and so cannot reach that failure.
19
+ const informational = process.argv.slice(2).some(arg => ['--help', '-h', '--version', '-v'].includes(arg))
20
+ if (supportedNode() && (informational || supportedFrameworks())) {
16
21
  import('./dist/node/bin.js').catch((error) => {
17
22
  // `exitCode`, not `exit`: the error is the last thing written, and forcing the
18
23
  // process out on the same tick truncates it on a pipe.
@@ -1,3 +1,3 @@
1
1
  export { default as MountStory } from './MountStory';
2
2
  export { default as RenderStory } from './RenderStory';
3
- export declare function generateSourceCode(): void;
3
+ export declare function generateSourceCode(): undefined;
@@ -0,0 +1,6 @@
1
+ import type { ClientSupportPlugin } from '@poveste/shared';
2
+ import type { Component } from '@poveste/vendors/vue';
3
+ import type * as client from './client';
4
+ type Satisfies<T extends ClientSupportPlugin<Component>> = T;
5
+ export type ClientContract = Satisfies<typeof client>;
6
+ export {};
@@ -1,2 +1,9 @@
1
- export declare function serializeError(error: unknown, seen?: WeakSet<Error>): unknown;
1
+ /** What this module needs of a thrown value to be able to describe it. */
2
+ interface ErrorLike {
3
+ name: string;
4
+ message: string;
5
+ stack?: string | undefined;
6
+ }
7
+ export declare function serializeError(error: unknown, seen?: WeakSet<ErrorLike>): unknown;
2
8
  export declare function deserializeError(value: unknown): unknown;
9
+ export {};
@@ -7,6 +7,27 @@ const MARK = 'pvt:error';
7
7
  function isSerialized(value) {
8
8
  return typeof value === 'object' && value !== null && value.kind === MARK;
9
9
  }
10
+ /*
11
+ * Not `instanceof Error`, and not `Error.isError` either. A `DOMException` from
12
+ * jsdom fails both — the first because `runScripts: 'dangerously'` makes jsdom a
13
+ * separate realm, the second because jsdom's is a webidl2js object rather than a
14
+ * genuine error — while carrying a name, a message and a stack. It is what a
15
+ * story's own DOM call throws, and a clone of one retains nothing, so the host
16
+ * received `{}` (#1097).
17
+ *
18
+ * A `stack` is required and not merely read: `{ name, message }` is a shape a
19
+ * story may legitimately throw, and it should arrive as the data it is.
20
+ */
21
+ function errorLike(value) {
22
+ if (Error.isError(value)) {
23
+ return true;
24
+ }
25
+ if (typeof value !== 'object' || value === null) {
26
+ return false;
27
+ }
28
+ const { name, message, stack } = value;
29
+ return typeof name === 'string' && typeof message === 'string' && typeof stack === 'string';
30
+ }
10
31
  // A property the clone rejects takes the whole message down with it, which is worse
11
32
  // than dropping the property.
12
33
  function cloneable(value) {
@@ -25,7 +46,7 @@ function cloneable(value) {
25
46
  * the error it was called to describe and replace it with its own.
26
47
  */
27
48
  export function serializeError(error, seen = new WeakSet()) {
28
- if (!(error instanceof Error)) {
49
+ if (!errorLike(error)) {
29
50
  return cloneable(error) ? error : String(error);
30
51
  }
31
52
  if (seen.has(error)) {
@@ -1,5 +1,4 @@
1
1
  import { cpus } from 'node:os';
2
- import { escapeRegExp } from '@poveste/shared';
3
2
  import path, { relative } from 'pathe';
4
3
  import pc from 'picocolors';
5
4
  import { TEMP_PATH } from '../alias.js';
@@ -25,7 +24,7 @@ export function useCollectStories(options, ctx) {
25
24
  // @TODO temporary fix for https://github.com/histoire-dev/histoire/issues/409
26
25
  /vite\w*\/dist\/client\/(client|env).mjs/,
27
26
  ...ctx.config.viteNodeInlineDeps ?? [],
28
- new RegExp(escapeRegExp(path.resolve(TEMP_PATH, 'plugins'))),
27
+ new RegExp(RegExp.escape(path.resolve(TEMP_PATH, 'plugins'))),
29
28
  ],
30
29
  transformMode: ctx.config.viteNodeTransformMode,
31
30
  });
@@ -23,7 +23,10 @@ function createVirtualConsole() {
23
23
  if (type === JSDOM_ERROR_CSS_PARSING)
24
24
  return;
25
25
  if (type === JSDOM_ERROR_UNHANDLED_EXCEPTION) {
26
- globalThis.console.error((err.cause instanceof Error ? err.cause.stack : undefined) ?? err.message);
26
+ // `Error.isError` and not `instanceof`: the cause was constructed inside
27
+ // jsdom's realm, where `instanceof Error` is false and the stack — the
28
+ // only part that says which line threw — is discarded (#1093).
29
+ globalThis.console.error((Error.isError(err.cause) ? err.cause.stack : undefined) ?? err.message);
27
30
  }
28
31
  else {
29
32
  globalThis.console.error(err.message);
package/dist/node/vite.js CHANGED
@@ -1,6 +1,5 @@
1
1
  import { existsSync } from 'node:fs';
2
2
  import { createRequire } from 'node:module';
3
- import { escapeRegExp } from '@poveste/shared';
4
3
  import { lookup as lookupMime } from 'mrmime';
5
4
  import { dirname, join, relative } from 'pathe';
6
5
  import { loadConfigFromFile as loadViteConfigFromFile, mergeConfig as mergeViteConfig, searchForWorkspaceRoot, } from 'vite';
@@ -347,7 +346,7 @@ export async function getViteConfigWithPlugins(isServer, ctx) {
347
346
  if (/\.(?:vue|js)(?:$|\?)/.test(id)) {
348
347
  const original = code;
349
348
  for (const [flag, value] of Object.entries(flags)) {
350
- code = code.replace(new RegExp(escapeRegExp(flag), 'g'), value);
349
+ code = code.replace(new RegExp(RegExp.escape(flag), 'g'), value);
351
350
  }
352
351
  if (original !== code)
353
352
  return code;
@@ -407,11 +406,11 @@ export async function getViteConfigWithPlugins(isServer, ctx) {
407
406
  ['vue', 'vue'],
408
407
  ].reduce((acc, [name, entry]) => {
409
408
  acc.push({
410
- find: new RegExp(`^${escapeRegExp(name)}$`),
409
+ find: new RegExp(`^${RegExp.escape(name)}$`),
411
410
  replacement: `@poveste/vendors/${entry}`,
412
411
  });
413
412
  acc.push({
414
- find: new RegExp(`^${escapeRegExp(name)}/`),
413
+ find: new RegExp(`^${RegExp.escape(name)}/`),
415
414
  replacement: `@poveste/vendors/${entry}/`,
416
415
  });
417
416
  return acc;
@@ -0,0 +1,88 @@
1
+ // The framework check the CLI makes before it loads anything, beside the Node one.
2
+ //
3
+ // A framework plugin declares its framework as a peer, and the package manager
4
+ // enforces that well — until a reader takes `--legacy-peer-deps`, which npm's own
5
+ // ERESOLVE output suggests. On Nuxt 3 what they meet next is `Cannot find package
6
+ // 'vite'`: Nuxt 4 hoists the `vite@^8` Poveste imports and Nuxt 3 does not. That
7
+ // failure comes from importing the CLI's commands, before the config — and so the
8
+ // plugin — is loaded at all, which is why the plugin cannot check its own peer in
9
+ // time and this file does (#1062).
10
+ //
11
+ // Nuxt only, until a second instance is reported. One measured case does not
12
+ // justify holding six plugins' peers here; another is one line in `PEERS`.
13
+ //
14
+ // Plain ES2015 beside `node-floor.mjs`, for the reason that file gives.
15
+
16
+ import { readFileSync, writeSync } from 'node:fs'
17
+ import { dirname, join } from 'node:path'
18
+ import process from 'node:process'
19
+ import { floorOf, isBelow } from './node-floor.mjs'
20
+
21
+ /** Each plugin whose framework is checked, and the peer that names it. */
22
+ export const PEERS = {
23
+ '@poveste/plugin-nuxt': 'nuxt',
24
+ }
25
+
26
+ /**
27
+ * The floor of a `^x.y.z` or `>=x.y.z` range, or `null` for any other shape.
28
+ *
29
+ * Only the floor: a version above a caret's major is the package manager's to
30
+ * refuse, and saying nothing about a shape this cannot read keeps our own
31
+ * manifest drift from becoming a reader's failed command.
32
+ */
33
+ export function rangeFloor(range) {
34
+ return floorOf(String(range ?? '').trim().replace(/^\^/, '>='))
35
+ }
36
+
37
+ /**
38
+ * The manifest of `name` as Node would find it from `dir`, or `null`.
39
+ *
40
+ * Only a missing file sends the walk upward, as it does Node's. A manifest that
41
+ * is there and unreadable ends it: going on would read an ancestor's copy, which
42
+ * in a monorepo can be another package's framework at another version.
43
+ */
44
+ export function installedManifest(name, dir) {
45
+ for (let current = dir; ; current = dirname(current)) {
46
+ try {
47
+ return JSON.parse(readFileSync(join(current, 'node_modules', name, 'package.json'), 'utf8'))
48
+ }
49
+ catch (error) {
50
+ if (error?.code !== 'ENOENT' || dirname(current) === current) {
51
+ return null
52
+ }
53
+ }
54
+ }
55
+ }
56
+
57
+ /** The Node message's shape, so the two read as one family. */
58
+ export function unsupportedFrameworkMessage(framework, version, plugin, range) {
59
+ return `You are using ${framework} ${version}. ${plugin} requires ${framework} ${range}. Please upgrade ${framework}.`
60
+ }
61
+
62
+ /** A sentence per checked plugin whose installed framework is below its peer floor. */
63
+ export function frameworkProblems(dir) {
64
+ const problems = []
65
+
66
+ for (const [plugin, framework] of Object.entries(PEERS)) {
67
+ const range = installedManifest(plugin, dir)?.peerDependencies?.[framework]
68
+ const floor = rangeFloor(range)
69
+ const version = installedManifest(framework, dir)?.version
70
+ if (floor && version && isBelow(version, floor)) {
71
+ problems.push(unsupportedFrameworkMessage(framework, version, plugin, range))
72
+ }
73
+ }
74
+
75
+ return problems
76
+ }
77
+
78
+ /** Whether the project's frameworks may run the CLI, saying so on stderr when not. */
79
+ export function supportedFrameworks(dir = process.cwd()) {
80
+ const problems = frameworkProblems(dir)
81
+ if (problems.length === 0) {
82
+ return true
83
+ }
84
+
85
+ writeSync(2, `${problems.join('\n')}\n`)
86
+ process.exitCode = 1
87
+ return false
88
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "poveste",
3
3
  "type": "module",
4
- "version": "0.16.2",
4
+ "version": "0.17.1",
5
5
  "description": "Interactive component playgrounds for Vue, Nuxt, Svelte, SvelteKit and Quasar — a drop-in fork of histoire, with Quasar support histoire does not ship.",
6
6
  "author": {
7
7
  "name": "Sorin Gitlan",
@@ -61,23 +61,24 @@
61
61
  "client.d.ts",
62
62
  "client.js",
63
63
  "dist",
64
+ "framework-floor.mjs",
64
65
  "index.d.ts",
65
66
  "node-floor.mjs",
66
67
  "plugin.d.ts",
67
68
  "plugin.js"
68
69
  ],
69
70
  "engines": {
70
- "node": ">=22.22.2"
71
+ "node": ">=24.15.0"
71
72
  },
72
73
  "peerDependencies": {
73
74
  "vite": "^8.0.0"
74
75
  },
75
76
  "dependencies": {
76
77
  "@11ty/gray-matter": "^3.0.0",
77
- "@poveste/app": "^0.16.2",
78
- "@poveste/controls": "^0.16.2",
79
- "@poveste/shared": "^0.16.2",
80
- "@poveste/vendors": "^0.16.2",
78
+ "@poveste/app": "^0.17.1",
79
+ "@poveste/controls": "^0.17.1",
80
+ "@poveste/shared": "^0.17.1",
81
+ "@poveste/vendors": "^0.17.1",
81
82
  "change-case": "^5.4.4",
82
83
  "chokidar": "^5.0.0",
83
84
  "defu": "^6.1.4",