poveste 0.18.3 → 0.19.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/README.md CHANGED
@@ -5,7 +5,9 @@
5
5
  Poveste builds a browsable *book* of stories from your components, powered by
6
6
  [Vite](https://vite.dev). It is a drop-in fork of
7
7
  [histoire](https://github.com/histoire-dev/histoire), covering Vue, Nuxt, Svelte,
8
- SvelteKit and Quasar — the last of which histoire does not support.
8
+ SvelteKit and Quasar — the last of which histoire does not support. Solid and React
9
+ are supported as renderers only: their stories render, and they have no controls
10
+ panel.
9
11
 
10
12
  <img src="https://raw.githubusercontent.com/poveste-dev/poveste/main/screenshot.png" alt="A poveste book: the story list, a variant grid, and the controls panel" width="900">
11
13
 
@@ -18,6 +20,8 @@ pnpm i -D poveste @poveste/plugin-vue # Vue 3
18
20
  pnpm i -D poveste @poveste/plugin-svelte # Svelte 5 and SvelteKit
19
21
  pnpm i -D poveste @poveste/plugin-nuxt # Nuxt 4
20
22
  pnpm i -D poveste @poveste/plugin-quasar # Quasar
23
+ pnpm i -D poveste @poveste/plugin-solid # Solid, renderer only
24
+ pnpm i -D poveste @poveste/plugin-react # React 19, renderer only
21
25
  ```
22
26
 
23
27
  > **Installed an older version than you expected?** For about a day after a release, pnpm installs the previous one with no error and no hint, and for a plugin that's new in that release, its empty `0.0.1` placeholder. That's pnpm's release-age cooldown, not a broken publish. `package.json` keeps the old `^x.y.z`, so waiting doesn't fix it: install again with `npm`, with `--config.minimum-release-age=0` (kebab-case: pnpm 12.4 ignores the camelCase spelling), or with exact versions.
package/dist/node/bin.js CHANGED
@@ -84,6 +84,7 @@ program.command('build')
84
84
  program.command('preview')
85
85
  .describe('preview the built directory')
86
86
  .option('-p, --port <port>', 'Listening port of the server')
87
+ .option('-c, --config <file>', `[string] use specified config file`)
87
88
  .option('--open', 'Open in your default browser')
88
89
  .option('--host [host]', '[string] specify hostname (omit value or pass `0.0.0.0` to expose to all network interfaces)')
89
90
  .action(async (options) => {
@@ -6,6 +6,7 @@ import { createPath } from '../tree.js';
6
6
  import { escapeRegExp } from '../util/escape-regexp.js';
7
7
  import { slash } from '../util/fs.js';
8
8
  import { globalsFromDefine } from './define-globals.js';
9
+ import { strayLayoutKeys } from './layout-keys.js';
9
10
  import { createModuleServer } from './module-server.js';
10
11
  import { createPool } from './pool.js';
11
12
  import { INVALIDATE, INVALIDATE_ALL } from './task.js';
@@ -82,6 +83,9 @@ export function useCollectStories(options, ctx) {
82
83
  }
83
84
  }
84
85
  }
86
+ for (const stray of strayLayoutKeys(finalData.layout)) {
87
+ console.warn(pc.yellow(`⚠️ ${storyFile.path}: ${stray}`));
88
+ }
85
89
  if (!finalData.layout) {
86
90
  finalData.layout = { type: 'single', iframe: true };
87
91
  }
@@ -0,0 +1 @@
1
+ export declare function strayLayoutKeys(layout: unknown): string[];
@@ -0,0 +1,33 @@
1
+ /**
2
+ * The keys of a story's `layout` that its `type` does not read.
3
+ *
4
+ * `StoryLayout` is a union, and its two members spell one idea two ways:
5
+ * `iframe` on a single layout, `iframeGrid` on a grid. A key from the other
6
+ * member is carried through untouched and read by nothing, so
7
+ * `{ type: 'grid', iframe: false }` got iframes and nobody was told (#333).
8
+ */
9
+ const READ_BY = {
10
+ single: ['iframe', 'isolate'],
11
+ grid: ['width', 'iframeGrid', 'isolate'],
12
+ };
13
+ /** What the author of a stray key meant, where the other layout has a key for it. */
14
+ const MEANT = {
15
+ single: { iframeGrid: 'iframe' },
16
+ grid: { iframe: 'iframeGrid' },
17
+ };
18
+ export function strayLayoutKeys(layout) {
19
+ if (!layout || typeof layout !== 'object') {
20
+ return [];
21
+ }
22
+ const { type, ...rest } = layout;
23
+ const read = READ_BY[String(type)];
24
+ if (!read) {
25
+ return [`layout.type is ${JSON.stringify(type)}, and a layout is 'single' or 'grid'`];
26
+ }
27
+ return Object.keys(rest)
28
+ .filter(key => !read.includes(key))
29
+ .map((key) => {
30
+ const meant = MEANT[String(type)]?.[key];
31
+ return `layout.${key} is not read by a ${type} layout, so it does nothing${meant ? `: use ${meant}` : ''}`;
32
+ });
33
+ }
@@ -0,0 +1,12 @@
1
+ import type { Logger } from 'vite';
2
+ /**
3
+ * The collection server's logger: `base`, less one message.
4
+ *
5
+ * Collection runs its modules in Node, through an environment Vite treats as a
6
+ * browser's. A package's Node build importing `node:http` is then reported as
7
+ * "externalized for browser compatibility", which is false of where it runs: 25
8
+ * lines from `ofetch` in a Nuxt book, reading as errors about a build that worked
9
+ * (#1203). The same line from the book's own server or build is about the
10
+ * browser, and is left alone.
11
+ */
12
+ export declare function collectionLogger(base: Logger): Logger;
@@ -0,0 +1,19 @@
1
+ /**
2
+ * The collection server's logger: `base`, less one message.
3
+ *
4
+ * Collection runs its modules in Node, through an environment Vite treats as a
5
+ * browser's. A package's Node build importing `node:http` is then reported as
6
+ * "externalized for browser compatibility", which is false of where it runs: 25
7
+ * lines from `ofetch` in a Nuxt book, reading as errors about a build that worked
8
+ * (#1203). The same line from the book's own server or build is about the
9
+ * browser, and is left alone.
10
+ */
11
+ export function collectionLogger(base) {
12
+ const quiet = (log) => (message, options) => {
13
+ if (!message.includes('has been externalized for browser compatibility')) {
14
+ log(message, options);
15
+ }
16
+ };
17
+ // Assigned onto `base`, not spread from it: `hasWarned` is state its own methods set.
18
+ return Object.assign(base, { warn: quiet(base.warn.bind(base)), warnOnce: quiet(base.warnOnce.bind(base)) });
19
+ }
@@ -122,7 +122,11 @@ class InteropEvaluator {
122
122
  assigned = true;
123
123
  },
124
124
  };
125
- const run = new AsyncFunction(ssrModuleExportsKey, ssrImportMetaKey, ssrImportKey, ssrDynamicImportKey, ssrExportAllKey, ssrExportNameKey, 'require', 'exports', 'module', '__filename', '__dirname', `"use strict";\n${code}`);
125
+ // In a block of its own, so a module's top-level `const require = createRequire(…)`
126
+ // or `const __dirname = …` shadows the parameter of that name. In the function's
127
+ // own scope it redeclared it, a syntax error that failed every story of the book
128
+ // (#1301). The closing brace goes on a new line: the code may end in a line comment.
129
+ const run = new AsyncFunction(ssrModuleExportsKey, ssrImportMetaKey, ssrImportKey, ssrDynamicImportKey, ssrExportAllKey, ssrExportNameKey, 'require', 'exports', 'module', '__filename', '__dirname', `"use strict";{\n${code}\n}`);
126
130
  await run(context[ssrModuleExportsKey], context[ssrImportMetaKey], context[ssrImportKey], context[ssrDynamicImportKey], context[ssrExportAllKey], context[ssrExportNameKey], createRequire(filename), cjsExports, moduleProxy, filename, dirname(filename));
127
131
  }
128
132
  async runExternalModule(file) {
@@ -1,4 +1,5 @@
1
1
  export interface PreviewOptions {
2
+ config?: string;
2
3
  port?: number | string;
3
4
  host?: string | boolean;
4
5
  open?: boolean;
@@ -7,7 +7,11 @@ export async function previewCommand(options) {
7
7
  // run — so the teardown has to span the command, not one phase of it (#434).
8
8
  try {
9
9
  const port = resolvePort(options.port, 'preview');
10
+ // The same file the book was built with: `outDir` is read from it, and
11
+ // without it a book built with `--config` was looked for in the default
12
+ // directory and every path answered 404 (#1302).
10
13
  const ctx = await createContext({
14
+ configFile: options.config,
11
15
  mode: 'build',
12
16
  });
13
17
  for (const plugin of ctx.config.plugins) {
@@ -11,7 +11,7 @@
11
11
  * model the whole surface. Only options whose wrong type fails *inside someone
12
12
  * else's code* are listed — the rest fail legibly on their own.
13
13
  */
14
- export type ConfigKind = 'string' | 'boolean' | 'number' | 'string[]' | 'array' | 'object' | 'function';
14
+ export type ConfigKind = 'string' | 'boolean' | 'number' | 'string[]' | 'array' | 'object' | 'function' | 'null';
15
15
  /** What the value actually is, in the words the message uses. */
16
16
  export declare function describeKind(value: unknown): string;
17
17
  export declare function configProblems(config: unknown, configFile: string): string[];
@@ -11,6 +11,13 @@ const RULES = [
11
11
  { path: 'defaultBackgroundColor', kind: 'string' },
12
12
  { path: 'sandboxDarkClass', kind: 'string' },
13
13
  { path: 'responsivePresets', kind: 'array' },
14
+ { path: 'preview', kind: 'object' },
15
+ { path: 'preview.backgroundColor', kind: 'string' },
16
+ { path: 'preview.responsiveWidth', kind: ['number', 'null'] },
17
+ { path: 'preview.responsiveHeight', kind: ['number', 'null'] },
18
+ { path: 'preview.rotate', kind: 'boolean' },
19
+ { path: 'preview.checkerboard', kind: 'boolean' },
20
+ { path: 'preview.textDirection', kind: 'string' },
14
21
  { path: 'backgroundPresets', kind: 'array' },
15
22
  { path: 'theme.title', kind: 'string' },
16
23
  { path: 'theme.lang', kind: 'string' },
@@ -26,6 +33,9 @@ const RULES = [
26
33
  ];
27
34
  /** What the value actually is, in the words the message uses. */
28
35
  export function describeKind(value) {
36
+ if (value === null) {
37
+ return 'null';
38
+ }
29
39
  if (Array.isArray(value)) {
30
40
  return value.every(item => typeof item === 'string') ? 'string[]' : 'array';
31
41
  }
@@ -47,6 +57,8 @@ function matches(value, kind) {
47
57
  return typeof value === 'number';
48
58
  case 'function':
49
59
  return typeof value === 'function';
60
+ case 'null':
61
+ return value === null;
50
62
  default:
51
63
  return false;
52
64
  }
@@ -7,6 +7,7 @@ export declare function loadConfigFile(configFile: string): Promise<Partial<Pove
7
7
  export declare const mergeBuildConfig: import("defu").DefuFn;
8
8
  export declare const mergeConfig: import("defu").DefuFn;
9
9
  export declare function resolveConfig(cwd: string | undefined, mode: ConfigMode, configFile?: string): Promise<PovesteConfig>;
10
+ export declare function processDefaultConfig(defaultConfig: PovesteConfig, preUserConfig: Partial<PovesteConfig>, mode: ConfigMode, _cwd: string): Promise<PovesteConfig>;
10
11
  export declare function processConfig(ctx: Context): Promise<void>;
11
12
  export declare function defineConfig(config: Partial<PovesteConfig>): Partial<PovesteConfig>;
12
13
  declare module 'vite' {
@@ -325,13 +325,18 @@ async function resolveConfigPlugins(config, mode) {
325
325
  }
326
326
  return config;
327
327
  }
328
- async function processDefaultConfig(defaultConfig, preUserConfig, mode, _cwd) {
328
+ export async function processDefaultConfig(defaultConfig, preUserConfig, mode, _cwd) {
329
329
  // Apply plugins
330
330
  for (const plugin of [...defaultConfig.plugins, ...preUserConfig.plugins ?? []]) {
331
331
  if (plugin.defaultConfig) {
332
332
  const result = await plugin.defaultConfig(defaultConfig, mode);
333
333
  if (result) {
334
- defaultConfig = mergeOnto(result, defaultConfig);
334
+ // A plugin's story globs add to the defaults: a Solid plugin declaring
335
+ // `.story.tsx` must not drop `.story.vue` from a book that has both. Only
336
+ // here. A user's `storyMatch` still replaces, since narrowing is a
337
+ // legitimate use (#1124).
338
+ const storyMatch = result.storyMatch && [...new Set([...defaultConfig.storyMatch, ...result.storyMatch])];
339
+ defaultConfig = mergeOnto(storyMatch ? { ...result, storyMatch } : result, defaultConfig);
335
340
  }
336
341
  }
337
342
  }
@@ -0,0 +1,23 @@
1
+ import type { Plugin } from '@poveste/shared';
2
+ /**
3
+ * Asks each plugin with an `onDevEvent` in turn, and returns the first answer
4
+ * to an event that wants one. An `on…` event goes to every plugin and answers
5
+ * nothing.
6
+ *
7
+ * A plugin that throws is logged under its name and skipped. Left to the
8
+ * listener, the rejection went unhandled and exited `poveste dev` (#1170).
9
+ */
10
+ export declare function answerDevEvent(plugins: Plugin[], event: string, ask: (plugin: Plugin) => unknown): Promise<unknown>;
11
+ export interface DevCommandResult {
12
+ id: string;
13
+ error?: string;
14
+ }
15
+ /**
16
+ * Runs a command's server half and always reports back. Left to the listener, a
17
+ * `serverAction` that threw (a story file that already exists, a path with no
18
+ * directory) went unhandled and exited `poveste dev` (#1176).
19
+ */
20
+ export declare function runDevCommand(commands: {
21
+ id: string;
22
+ serverAction?: (params: any) => unknown;
23
+ }[], id: string, params: unknown): Promise<DevCommandResult>;
@@ -0,0 +1,42 @@
1
+ import pc from 'picocolors';
2
+ /**
3
+ * Asks each plugin with an `onDevEvent` in turn, and returns the first answer
4
+ * to an event that wants one. An `on…` event goes to every plugin and answers
5
+ * nothing.
6
+ *
7
+ * A plugin that throws is logged under its name and skipped. Left to the
8
+ * listener, the rejection went unhandled and exited `poveste dev` (#1170).
9
+ */
10
+ export async function answerDevEvent(plugins, event, ask) {
11
+ for (const plugin of plugins) {
12
+ if (!plugin.onDevEvent) {
13
+ continue;
14
+ }
15
+ try {
16
+ const result = await ask(plugin);
17
+ if (!event.startsWith('on') && result !== undefined) {
18
+ return result;
19
+ }
20
+ }
21
+ catch (error) {
22
+ console.error(pc.red(`[Plugin:${plugin.name}]`), `onDevEvent threw on "${event}":`, error);
23
+ }
24
+ }
25
+ return undefined;
26
+ }
27
+ /**
28
+ * Runs a command's server half and always reports back. Left to the listener, a
29
+ * `serverAction` that threw (a story file that already exists, a path with no
30
+ * directory) went unhandled and exited `poveste dev` (#1176).
31
+ */
32
+ export async function runDevCommand(commands, id, params) {
33
+ const command = commands.find(c => c.id === id);
34
+ try {
35
+ await command?.serverAction?.(params);
36
+ return { id };
37
+ }
38
+ catch (error) {
39
+ console.error(pc.red(`[poveste] command "${id}" failed:`), error);
40
+ return { id, error: error instanceof Error ? error.message : String(error) };
41
+ }
42
+ }
@@ -1,6 +1,6 @@
1
1
  export { onPovesteCleanup } from './cleanup.js';
2
2
  export { defaultColors } from './colors.js';
3
- export * from './config.js';
3
+ export { configFileNames, defineConfig, getDefaultConfig, loadConfigFile, mergeBuildConfig, mergeConfig, processConfig, resolveConfig, resolveConfigFile, } from './config.js';
4
4
  export * from './plugin.js';
5
5
  export { getInjectedImport } from './util/vendors.js';
6
6
  export * from '@poveste/shared';
@@ -2,7 +2,9 @@
2
2
  // vendored copies of Vue and the controls from inside that generated code.
3
3
  export { onPovesteCleanup } from './cleanup.js';
4
4
  export { defaultColors } from './colors.js';
5
- export * from './config.js';
5
+ // Named, not `export *`: an `export` added to config.ts so a spec can reach a
6
+ // helper would otherwise publish it (#1291).
7
+ export { configFileNames, defineConfig, getDefaultConfig, loadConfigFile, mergeBuildConfig, mergeConfig, processConfig, resolveConfig, resolveConfigFile, } from './config.js';
6
8
  export * from './plugin.js';
7
9
  export { getInjectedImport } from './util/vendors.js';
8
10
  export * from '@poveste/shared';
@@ -3,6 +3,7 @@ import pc from 'picocolors';
3
3
  import { createServer as createViteServer, mergeConfig as mergeViteConfig } from 'vite';
4
4
  import { useCollectStories } from './collect/index.js';
5
5
  import { socketOnBookPort } from './commands/port.js';
6
+ import { answerDevEvent } from './dev-event.js';
6
7
  import { useModuleLoader } from './load.js';
7
8
  import { createMarkdownFilesWatcher, onMarkdownFileChange, onMarkdownListChange } from './markdown.js';
8
9
  import { DevEventPluginApi, DevPluginApi } from './plugin.js';
@@ -102,15 +103,10 @@ async function startServer(ctx, options, onOpen, close) {
102
103
  }
103
104
  // Custom dev events
104
105
  server.ws.on(`poveste:dev-event`, async ({ event, payload }) => {
105
- for (const plugin of ctx.config.plugins) {
106
- if (plugin.onDevEvent) {
107
- const api = new DevEventPluginApi(ctx, plugin, moduleLoader, event, payload, watches);
108
- const result = await plugin.onDevEvent(api);
109
- if (!event.startsWith('on') && result !== undefined) {
110
- server.ws.send(`poveste:dev-event-result`, { event, result });
111
- break;
112
- }
113
- }
106
+ const result = await answerDevEvent(ctx.config.plugins, event, plugin => plugin.onDevEvent?.(new DevEventPluginApi(ctx, plugin, moduleLoader, event, payload, watches)));
107
+ // Sent even when nothing answered, so the client's promise settles.
108
+ if (!event.startsWith('on')) {
109
+ server.ws.send(`poveste:dev-event-result`, { event, result });
114
110
  }
115
111
  });
116
112
  // Wait for pre-bundling (in `listen()`)
@@ -37,8 +37,14 @@ export declare function vendoredAliases(dependencies: Record<string, string> | u
37
37
  * is the point: `poveste` has no `semver` dependency, a patch release is the wrong
38
38
  * place to add one, and a hand-rolled `satisfies` that silently mis-handles a form it
39
39
  * was never given is the failure this repo keeps producing. So `^X.Y.Z` with a major
40
- * of at least 1 is answered, and `^0.x`, a prerelease, a comparator set or anything
41
- * else returns false and leaves the chrome on the copy it can certainly run.
40
+ * of at least 1 is answered, and `^0.x`, a comparator set or anything else returns
41
+ * false and leaves the chrome on the copy it can certainly run.
42
+ *
43
+ * A prerelease is answered too, though npm's own caret leaves one out. Refusing it
44
+ * left the chrome on the vendored Vue while pinia's bare `vue` took the project's
45
+ * prerelease, which is #1060's split, and the book rendered no story (#1163). So a
46
+ * prerelease counts when its `X.Y.Z` is above the floor in the same major. One of the
47
+ * floor itself sorts below the floor, and is refused.
42
48
  */
43
49
  export declare function satisfiesCaret(version: string | undefined, range: string | undefined): boolean;
44
50
  /**
@@ -69,17 +69,24 @@ export function vendoredAliases(dependencies) {
69
69
  * is the point: `poveste` has no `semver` dependency, a patch release is the wrong
70
70
  * place to add one, and a hand-rolled `satisfies` that silently mis-handles a form it
71
71
  * was never given is the failure this repo keeps producing. So `^X.Y.Z` with a major
72
- * of at least 1 is answered, and `^0.x`, a prerelease, a comparator set or anything
73
- * else returns false and leaves the chrome on the copy it can certainly run.
72
+ * of at least 1 is answered, and `^0.x`, a comparator set or anything else returns
73
+ * false and leaves the chrome on the copy it can certainly run.
74
+ *
75
+ * A prerelease is answered too, though npm's own caret leaves one out. Refusing it
76
+ * left the chrome on the vendored Vue while pinia's bare `vue` took the project's
77
+ * prerelease, which is #1060's split, and the book rendered no story (#1163). So a
78
+ * prerelease counts when its `X.Y.Z` is above the floor in the same major. One of the
79
+ * floor itself sorts below the floor, and is refused.
74
80
  */
75
81
  export function satisfiesCaret(version, range) {
76
82
  const wanted = /^\^(\d+)\.(\d+)\.(\d+)$/.exec(range ?? '');
77
- const have = /^(\d+)\.(\d+)\.(\d+)$/.exec(version ?? '');
83
+ const have = /^(\d+)\.(\d+)\.(\d+)(-[0-9A-Z.-]+)?$/i.exec(version ?? '');
78
84
  if (!wanted || !have) {
79
85
  return false;
80
86
  }
81
87
  const [wantedMajor, wantedMinor, wantedPatch] = wanted.slice(1).map(Number);
82
- const [major, minor, patch] = have.slice(1).map(Number);
88
+ const [major, minor, patch] = have.slice(1, 4).map(Number);
89
+ const prerelease = have[4] !== undefined;
83
90
  // `^0.x` narrows to the minor, and no vendored range uses it. Refuse rather than
84
91
  // implement a rule nothing here exercises.
85
92
  if (wantedMajor === 0 || wantedMajor === undefined || major === undefined) {
@@ -91,7 +98,7 @@ export function satisfiesCaret(version, range) {
91
98
  if (minor !== wantedMinor) {
92
99
  return minor > wantedMinor;
93
100
  }
94
- return patch >= wantedPatch;
101
+ return prerelease ? patch > wantedPatch : patch >= wantedPatch;
95
102
  }
96
103
  /**
97
104
  * Points the chrome's Vue at the project's, when they are two copies.
@@ -33,19 +33,37 @@ ${resolvedStories.map((file, index) => {
33
33
  export let files = [${files.map(file => `{${JSON.stringify(file).slice(1, -1)}, component: Comp${file.index}, source: () => import(${jsString(`virtual:story-source:${file.story.id}`)})}`).join(',\n')}]
34
34
  export let tree = ${JSON.stringify(makeTree(ctx.config, resolvedStories))}
35
35
  export let generation = ${storiesGeneration.get(ctx) ?? 0}
36
- const handlers = []
36
+ // Every copy of this module on a page shares these. A page can hold two: each
37
+ // importer's URL is stamped when the server serves it, so a list that changes
38
+ // between two of them gives each its own copy, and the one evaluated last takes
39
+ // over the hot update. Kept per copy, the handlers stayed with the other one, and
40
+ // the page applied every update to a list nobody was watching (#1237).
41
+ const shared = import.meta.hot
42
+ ? (import.meta.hot.data.shared ??= { handlers: [], generation: -Infinity, files, tree })
43
+ : { handlers: [], generation, files, tree }
44
+ if (generation > shared.generation) {
45
+ const announce = shared.generation !== -Infinity
46
+ Object.assign(shared, { generation, files, tree })
47
+ if (announce) {
48
+ shared.handlers.forEach(h => h(files, tree))
49
+ }
50
+ }
37
51
  export function onUpdate (cb) {
38
- handlers.push(cb)
52
+ shared.handlers.push(cb)
53
+ // Registered through a copy the page has already moved past.
54
+ if (shared.generation > generation) {
55
+ cb(shared.files, shared.tree)
56
+ }
39
57
  }
40
58
  if (import.meta.hot) {
41
59
  import.meta.hot.accept(newModule => {
60
+ // Undefined when the new copy failed to load. The next update carries the list.
61
+ if (!newModule) {
62
+ return
63
+ }
42
64
  files = newModule.files
43
65
  tree = newModule.tree
44
66
  generation = newModule.generation
45
- handlers.forEach(h => {
46
- h(newModule.files, newModule.tree)
47
- newModule.onUpdate(h)
48
- })
49
67
  })
50
68
  }`;
51
69
  }
package/dist/node/vite.js CHANGED
@@ -2,8 +2,10 @@ import { existsSync } from 'node:fs';
2
2
  import { createRequire } from 'node:module';
3
3
  import { lookup as lookupMime } from 'mrmime';
4
4
  import { dirname, join, relative } from 'pathe';
5
- import { loadConfigFromFile as loadViteConfigFromFile, mergeConfig as mergeViteConfig, searchForWorkspaceRoot, } from 'vite';
5
+ import { createLogger, loadConfigFromFile as loadViteConfigFromFile, mergeConfig as mergeViteConfig, normalizePath, searchForWorkspaceRoot, } from 'vite';
6
6
  import { APP_PATH, TEMP_PATH } from './alias.js';
7
+ import { collectionLogger } from './collect/logger.js';
8
+ import { runDevCommand } from './dev-event.js';
7
9
  import { createMarkdownPlugins } from './markdown.js';
8
10
  import { optimizeEntries } from './optimize-entries.js';
9
11
  import { notifyStoryChange } from './stories.js';
@@ -146,6 +148,13 @@ export async function getViteConfigWithPlugins(isServer, ctx) {
146
148
  }
147
149
  plugins.push({
148
150
  name: 'poveste-vite-plugin',
151
+ // Only the core knows which files a book's `storyMatch` makes stories, and a
152
+ // framework plugin may compile those differently from the components they
153
+ // import (#1164). Not on `this.meta`: a Rolldown build does not carry what
154
+ // `options()` sets there into `transform`.
155
+ api: {
156
+ isStoryFile: (id) => ctx.storyFiles.some(file => normalizePath(file.path) === id),
157
+ },
149
158
  config(_, { command }) {
150
159
  const collapsedVue = isServer || process.env['POVESTE_DEV'] ? {} : collapseVendoredVue({ root: ctx.root });
151
160
  return {
@@ -375,11 +384,8 @@ export async function getViteConfigWithPlugins(isServer, ctx) {
375
384
  plugins.push({
376
385
  name: 'poveste:dev-commands',
377
386
  configureServer(server) {
378
- server.ws.on('poveste:dev-command', ({ id, params }) => {
379
- const command = ctx.registeredCommands.find(c => c.id === id);
380
- if (command?.serverAction) {
381
- command.serverAction(params);
382
- }
387
+ server.ws.on('poveste:dev-command', async ({ id, params }) => {
388
+ server.ws.send('poveste:dev-command-result', await runDevCommand(ctx.registeredCommands, id, params));
383
389
  });
384
390
  },
385
391
  });
@@ -447,6 +453,11 @@ export async function getViteConfigWithPlugins(isServer, ctx) {
447
453
  configFile: false,
448
454
  plugins,
449
455
  });
456
+ if (isServer) {
457
+ viteConfig.customLogger = collectionLogger(viteConfig.customLogger ?? createLogger(viteConfig.logLevel, {
458
+ ...viteConfig.clearScreen === undefined ? {} : { allowClearScreen: viteConfig.clearScreen },
459
+ }));
460
+ }
450
461
  return {
451
462
  viteConfig,
452
463
  viteConfigFile: userViteConfigFile?.path ?? null,
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "poveste",
3
3
  "type": "module",
4
- "version": "0.18.3",
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.",
4
+ "version": "0.19.0",
5
+ "description": "Interactive component playgrounds for Vue, Nuxt, Svelte, SvelteKit and Quasar, plus Solid and React (renderer only) — a drop-in fork of histoire, with Quasar support histoire does not ship.",
6
6
  "author": {
7
7
  "name": "Sorin Gitlan",
8
8
  "url": "https://x.com/50rayn"
@@ -31,6 +31,7 @@
31
31
  "sveltekit",
32
32
  "solid",
33
33
  "solidjs",
34
+ "react",
34
35
  "cli"
35
36
  ],
36
37
  "publishConfig": {
@@ -77,10 +78,10 @@
77
78
  },
78
79
  "dependencies": {
79
80
  "@11ty/gray-matter": "^3.0.0",
80
- "@poveste/app": "^0.18.3",
81
- "@poveste/controls": "^0.18.3",
82
- "@poveste/shared": "^0.18.3",
83
- "@poveste/vendors": "^0.18.3",
81
+ "@poveste/app": "^0.19.0",
82
+ "@poveste/controls": "^0.19.0",
83
+ "@poveste/shared": "^0.19.0",
84
+ "@poveste/vendors": "^0.19.0",
84
85
  "change-case": "^5.4.4",
85
86
  "chokidar": "^5.0.0",
86
87
  "defu": "^6.1.4",