poveste 0.10.0 → 0.12.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 +2 -2
- package/dist/node/build.js +1 -2
- package/dist/node/builtin-plugins/pinceau-tokens-style.d.ts +1 -0
- package/dist/node/builtin-plugins/pinceau-tokens-style.js +85 -0
- package/dist/node/builtin-plugins/pinceau-tokens.js +1 -83
- package/dist/node/collect/worker.js +12 -1
- package/dist/node/config-validation.d.ts +17 -0
- package/dist/node/config-validation.js +95 -0
- package/dist/node/config.js +32 -0
- package/dist/node/markdown.d.ts +2 -0
- package/dist/node/markdown.js +72 -9
- package/dist/node/server.js +6 -1
- package/dist/node/vite.js +2 -4
- package/package.json +9 -8
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
> Fast and beautiful interactive component playgrounds
|
|
4
4
|
|
|
5
5
|
Poveste builds a browsable *book* of stories from your components, powered by
|
|
6
|
-
[Vite](https://vite.dev). It is a community-maintained
|
|
6
|
+
[Vite](https://vite.dev). It is a community-maintained fork of
|
|
7
7
|
[histoire](https://github.com/histoire-dev/histoire).
|
|
8
8
|
|
|
9
9
|
## Install
|
|
@@ -41,7 +41,7 @@ Run `npx poveste --help` for the full list of options.
|
|
|
41
41
|
|
|
42
42
|
## Requirements
|
|
43
43
|
|
|
44
|
-
Node
|
|
44
|
+
Node `^22.22.2 || ^24.15.0 || >=26.0.0` and Vite `^8.0.0`. See
|
|
45
45
|
[supported versions](https://poveste.dev/guide/getting-started.html#supported-versions) for
|
|
46
46
|
what proves each range.
|
|
47
47
|
|
package/dist/node/build.js
CHANGED
|
@@ -299,12 +299,11 @@ export async function build(ctx) {
|
|
|
299
299
|
}
|
|
300
300
|
function generateBaseHtml(head, body, ctx) {
|
|
301
301
|
return `<!DOCTYPE html>
|
|
302
|
-
<html>
|
|
302
|
+
<html lang="${ctx.config.theme.lang ?? 'en'}">
|
|
303
303
|
<head>
|
|
304
304
|
<title>${ctx.config.theme.title}</title>
|
|
305
305
|
<meta charset="utf-8">
|
|
306
306
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
307
|
-
<meta name="description" content="">
|
|
308
307
|
<style>html,body{margin:0;padding:0}</style>
|
|
309
308
|
${head}
|
|
310
309
|
</head>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const css = ".__pvt-shade {\n height: 80px;\n border-radius: 4px;\n}\n\n.__pvt-text {\n font-size: 4rem;\n display: flex;\n align-items: flex-end;\n}\n\n.__pvt-border {\n border-style: solid;\n border-width: 2px;\n}\n\n.__pvt-spaces {\n background-color: rgb(113 113 122 / 0.1);\n width: min-content;\n}\n\n.__pvt-margin {\n border: dashed 1px rgb(113 113 122 / 0.5);\n width: min-content;\n}\n\n.__pvt-spaces-box,\n.__pvt-margin-box {\n width: 5rem;\n height: 5rem;\n background-color: rgb(113 113 122 / 0.5);\n}\n\n.__pvt-spaces,\n.__pvt-spaces-box,\n.__pvt-margin,\n.__pvt-margin-box,\n.__pvt-drop-shadow {\n border-radius: 4px;\n}\n\n.__pvt-truncate {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.__pvt-drop-shadow {\n width: 8rem;\n height: 8rem;\n background: white;\n margin-bottom: 0.5rem;\n}\n\n.__pvt-drop-shadow {\n background: #4e4e57;\n}\n\n.__pvt-border-radius {\n width: 8rem;\n height: 8rem;\n background-color: rgb(113 113 122 / 0.5);\n}\n\n.__pvt-border-width {\n width: 8rem;\n height: 8rem;\n border-color: rgb(113 113 122 / 0.5);\n background-color: rgb(113 113 122 / 0.1);\n}\n\n.__pvt-width {\n background-color: rgb(113 113 122 / 0.1);\n}\n\n.__pvt-width-box,\n.__pvt-height {\n background-color: rgb(113 113 122 / 0.5);\n}\n\n.__pvt-width-box {\n height: 5rem;\n}";
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// Injected as `style.css` next to the generated story file, so it styles
|
|
2
|
+
// the Pinceau token stories without reaching the book's own stylesheet.
|
|
3
|
+
export const css = `.__pvt-shade {
|
|
4
|
+
height: 80px;
|
|
5
|
+
border-radius: 4px;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.__pvt-text {
|
|
9
|
+
font-size: 4rem;
|
|
10
|
+
display: flex;
|
|
11
|
+
align-items: flex-end;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.__pvt-border {
|
|
15
|
+
border-style: solid;
|
|
16
|
+
border-width: 2px;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.__pvt-spaces {
|
|
20
|
+
background-color: rgb(113 113 122 / 0.1);
|
|
21
|
+
width: min-content;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.__pvt-margin {
|
|
25
|
+
border: dashed 1px rgb(113 113 122 / 0.5);
|
|
26
|
+
width: min-content;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.__pvt-spaces-box,
|
|
30
|
+
.__pvt-margin-box {
|
|
31
|
+
width: 5rem;
|
|
32
|
+
height: 5rem;
|
|
33
|
+
background-color: rgb(113 113 122 / 0.5);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.__pvt-spaces,
|
|
37
|
+
.__pvt-spaces-box,
|
|
38
|
+
.__pvt-margin,
|
|
39
|
+
.__pvt-margin-box,
|
|
40
|
+
.__pvt-drop-shadow {
|
|
41
|
+
border-radius: 4px;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.__pvt-truncate {
|
|
45
|
+
overflow: hidden;
|
|
46
|
+
text-overflow: ellipsis;
|
|
47
|
+
white-space: nowrap;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.__pvt-drop-shadow {
|
|
51
|
+
width: 8rem;
|
|
52
|
+
height: 8rem;
|
|
53
|
+
background: white;
|
|
54
|
+
margin-bottom: 0.5rem;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.__pvt-drop-shadow {
|
|
58
|
+
background: #4e4e57;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.__pvt-border-radius {
|
|
62
|
+
width: 8rem;
|
|
63
|
+
height: 8rem;
|
|
64
|
+
background-color: rgb(113 113 122 / 0.5);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.__pvt-border-width {
|
|
68
|
+
width: 8rem;
|
|
69
|
+
height: 8rem;
|
|
70
|
+
border-color: rgb(113 113 122 / 0.5);
|
|
71
|
+
background-color: rgb(113 113 122 / 0.1);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.__pvt-width {
|
|
75
|
+
background-color: rgb(113 113 122 / 0.1);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.__pvt-width-box,
|
|
79
|
+
.__pvt-height {
|
|
80
|
+
background-color: rgb(113 113 122 / 0.5);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.__pvt-width-box {
|
|
84
|
+
height: 5rem;
|
|
85
|
+
}`;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { join } from 'pathe';
|
|
2
2
|
import { getInjectedImport } from '../util/vendors.js';
|
|
3
|
+
import { css } from './pinceau-tokens-style.js';
|
|
3
4
|
export function pinceauTokens(_options = {}) {
|
|
4
5
|
const themePath = join(process.cwd(), 'node_modules/.vite/pinceau/index.js');
|
|
5
6
|
async function generate(api) {
|
|
@@ -421,86 +422,3 @@ export default {
|
|
|
421
422
|
],
|
|
422
423
|
}`;
|
|
423
424
|
}
|
|
424
|
-
const css = `.__pvt-shade {
|
|
425
|
-
height: 80px;
|
|
426
|
-
border-radius: 4px;
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
.__pvt-text {
|
|
430
|
-
font-size: 4rem;
|
|
431
|
-
display: flex;
|
|
432
|
-
align-items: flex-end;
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
.__pvt-border {
|
|
436
|
-
border-style: solid;
|
|
437
|
-
border-width: 2px;
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
.__pvt-spaces {
|
|
441
|
-
background-color: rgb(113 113 122 / 0.1);
|
|
442
|
-
width: min-content;
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
.__pvt-margin {
|
|
446
|
-
border: dashed 1px rgb(113 113 122 / 0.5);
|
|
447
|
-
width: min-content;
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
.__pvt-spaces-box,
|
|
451
|
-
.__pvt-margin-box {
|
|
452
|
-
width: 5rem;
|
|
453
|
-
height: 5rem;
|
|
454
|
-
background-color: rgb(113 113 122 / 0.5);
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
.__pvt-spaces,
|
|
458
|
-
.__pvt-spaces-box,
|
|
459
|
-
.__pvt-margin,
|
|
460
|
-
.__pvt-margin-box,
|
|
461
|
-
.__pvt-drop-shadow {
|
|
462
|
-
border-radius: 4px;
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
.__pvt-truncate {
|
|
466
|
-
overflow: hidden;
|
|
467
|
-
text-overflow: ellipsis;
|
|
468
|
-
white-space: nowrap;
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
.__pvt-drop-shadow {
|
|
472
|
-
width: 8rem;
|
|
473
|
-
height: 8rem;
|
|
474
|
-
background: white;
|
|
475
|
-
margin-bottom: 0.5rem;
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
.__pvt-drop-shadow {
|
|
479
|
-
background: #4e4e57;
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
.__pvt-border-radius {
|
|
483
|
-
width: 8rem;
|
|
484
|
-
height: 8rem;
|
|
485
|
-
background-color: rgb(113 113 122 / 0.5);
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
.__pvt-border-width {
|
|
489
|
-
width: 8rem;
|
|
490
|
-
height: 8rem;
|
|
491
|
-
border-color: rgb(113 113 122 / 0.5);
|
|
492
|
-
background-color: rgb(113 113 122 / 0.1);
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
.__pvt-width {
|
|
496
|
-
background-color: rgb(113 113 122 / 0.1);
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
.__pvt-width-box,
|
|
500
|
-
.__pvt-height {
|
|
501
|
-
background-color: rgb(113 113 122 / 0.5);
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
.__pvt-width-box {
|
|
505
|
-
height: 5rem;
|
|
506
|
-
}`;
|
|
@@ -13,15 +13,26 @@ let _runner;
|
|
|
13
13
|
// per story broke re-collection.
|
|
14
14
|
let _domEnv;
|
|
15
15
|
let _rpc;
|
|
16
|
+
// A virtual module is cached under the id the plugin resolved it to, not the one
|
|
17
|
+
// it was asked for, so both forms have to go.
|
|
18
|
+
function invalidate(file) {
|
|
19
|
+
_moduleCache.delete(file);
|
|
20
|
+
_moduleCache.delete(`\0${file}`);
|
|
21
|
+
}
|
|
16
22
|
// Cleanup module cache
|
|
17
23
|
parentPort.on('message', (message) => {
|
|
18
24
|
if (message?.kind === 'hst:invalidate') {
|
|
19
|
-
|
|
25
|
+
invalidate(message.file);
|
|
20
26
|
}
|
|
21
27
|
});
|
|
22
28
|
export default async (payload) => {
|
|
23
29
|
const startTime = performance.now();
|
|
24
30
|
process.env.HST_COLLECT = 'true';
|
|
31
|
+
// Here rather than broadcast from the main thread: the pool dispatches tasks
|
|
32
|
+
// on a different port from `broadcastMessage`, and nothing orders the two, so
|
|
33
|
+
// a busy worker could start the task first and read its own stale cache. A
|
|
34
|
+
// story being re-executed is being re-read by definition (#557).
|
|
35
|
+
invalidate(payload.storyFile.moduleId);
|
|
25
36
|
// Before any module runs: an externalised dep reads these at import time.
|
|
26
37
|
for (const [key, value] of Object.entries(payload.defineGlobals ?? {})) {
|
|
27
38
|
;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type checks over the config options that are load-bearing.
|
|
3
|
+
*
|
|
4
|
+
* Nothing validated the config, so a wrong type surfaced wherever the value was
|
|
5
|
+
* first used — `outDir: 42` became `TypeError: input.replace is not a function`
|
|
6
|
+
* inside `pathe`, naming neither Poveste, nor the option, nor the config file
|
|
7
|
+
* (#324).
|
|
8
|
+
*
|
|
9
|
+
* Hand-written rather than a schema library: `poveste` already ships 31 runtime
|
|
10
|
+
* dependencies (#306), and this needs to name a key and a received type, not
|
|
11
|
+
* model the whole surface. Only options whose wrong type fails *inside someone
|
|
12
|
+
* else's code* are listed — the rest fail legibly on their own.
|
|
13
|
+
*/
|
|
14
|
+
export type ConfigKind = 'string' | 'boolean' | 'number' | 'string[]' | 'array' | 'object' | 'function';
|
|
15
|
+
/** What the value actually is, in the words the message uses. */
|
|
16
|
+
export declare function describeKind(value: unknown): string;
|
|
17
|
+
export declare function configProblems(config: unknown, configFile: string): string[];
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
const RULES = [
|
|
2
|
+
{ path: 'outDir', kind: 'string' },
|
|
3
|
+
{ path: 'storyMatch', kind: 'string[]' },
|
|
4
|
+
{ path: 'storyIgnored', kind: 'string[]' },
|
|
5
|
+
{ path: 'plugins', kind: 'array' },
|
|
6
|
+
{ path: 'setupFile', kind: ['string', 'object'] },
|
|
7
|
+
{ path: 'setupCode', kind: 'string[]' },
|
|
8
|
+
{ path: 'globalStyles', kind: 'string[]' },
|
|
9
|
+
{ path: 'isolateStyles', kind: 'boolean' },
|
|
10
|
+
{ path: 'autoApplyContrastColor', kind: 'boolean' },
|
|
11
|
+
{ path: 'defaultBackgroundColor', kind: 'string' },
|
|
12
|
+
{ path: 'sandboxDarkClass', kind: 'string' },
|
|
13
|
+
{ path: 'responsivePresets', kind: 'array' },
|
|
14
|
+
{ path: 'backgroundPresets', kind: 'array' },
|
|
15
|
+
{ path: 'theme.title', kind: 'string' },
|
|
16
|
+
{ path: 'theme.lang', kind: 'string' },
|
|
17
|
+
{ path: 'theme.darkClass', kind: 'string' },
|
|
18
|
+
{ path: 'theme.favicon', kind: 'string' },
|
|
19
|
+
{ path: 'theme.logo.square', kind: 'string' },
|
|
20
|
+
{ path: 'theme.logo.light', kind: 'string' },
|
|
21
|
+
{ path: 'theme.logo.dark', kind: 'string' },
|
|
22
|
+
{ path: 'theme.logoHref', kind: 'string' },
|
|
23
|
+
{ path: 'tree.file', kind: ['string', 'function'] },
|
|
24
|
+
{ path: 'tree.order', kind: ['string', 'function'] },
|
|
25
|
+
{ path: 'vite', kind: ['object', 'function'] },
|
|
26
|
+
];
|
|
27
|
+
/** What the value actually is, in the words the message uses. */
|
|
28
|
+
export function describeKind(value) {
|
|
29
|
+
if (Array.isArray(value)) {
|
|
30
|
+
return value.every(item => typeof item === 'string') ? 'string[]' : 'array';
|
|
31
|
+
}
|
|
32
|
+
return typeof value;
|
|
33
|
+
}
|
|
34
|
+
function matches(value, kind) {
|
|
35
|
+
switch (kind) {
|
|
36
|
+
case 'string[]':
|
|
37
|
+
return Array.isArray(value) && value.every(item => typeof item === 'string');
|
|
38
|
+
case 'array':
|
|
39
|
+
return Array.isArray(value);
|
|
40
|
+
case 'object':
|
|
41
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
42
|
+
case 'string':
|
|
43
|
+
return typeof value === 'string';
|
|
44
|
+
case 'boolean':
|
|
45
|
+
return typeof value === 'boolean';
|
|
46
|
+
case 'number':
|
|
47
|
+
return typeof value === 'number';
|
|
48
|
+
case 'function':
|
|
49
|
+
return typeof value === 'function';
|
|
50
|
+
default:
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function read(config, path) {
|
|
55
|
+
let current = config;
|
|
56
|
+
for (const key of path.split('.')) {
|
|
57
|
+
if (current == null || typeof current !== 'object' || !(key in current)) {
|
|
58
|
+
return { present: false, value: undefined };
|
|
59
|
+
}
|
|
60
|
+
current = current[key];
|
|
61
|
+
}
|
|
62
|
+
return { present: true, value: current };
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* The value echoed back, short enough to be worth reading.
|
|
66
|
+
*
|
|
67
|
+
* JSON rather than `String(value)`: an object stringifies to `[object Object]`,
|
|
68
|
+
* which tells the reader nothing about what they wrote.
|
|
69
|
+
*/
|
|
70
|
+
function preview(value) {
|
|
71
|
+
let text;
|
|
72
|
+
try {
|
|
73
|
+
text = JSON.stringify(value) ?? String(value);
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
text = String(value);
|
|
77
|
+
}
|
|
78
|
+
return text.length > 40 ? `${text.slice(0, 40)}…` : text;
|
|
79
|
+
}
|
|
80
|
+
export function configProblems(config, configFile) {
|
|
81
|
+
const problems = [];
|
|
82
|
+
for (const { path, kind } of RULES) {
|
|
83
|
+
const { present, value } = read(config, path);
|
|
84
|
+
// An absent or explicitly-undefined option falls back to the default.
|
|
85
|
+
if (!present || value === undefined) {
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
const kinds = Array.isArray(kind) ? kind : [kind];
|
|
89
|
+
if (kinds.some(one => matches(value, one))) {
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
problems.push(`${configFile}: \`${path}\` must be ${kinds.join(' or ')}, received ${describeKind(value)} (${preview(value)})`);
|
|
93
|
+
}
|
|
94
|
+
return problems;
|
|
95
|
+
}
|
package/dist/node/config.js
CHANGED
|
@@ -7,6 +7,7 @@ import pc from 'picocolors';
|
|
|
7
7
|
import { mergeConfig as mergeViteConfig, resolveConfig as resolveViteConfig, } from 'vite';
|
|
8
8
|
import { vanillaSupport } from './builtin-plugins/vanilla-support/plugin.js';
|
|
9
9
|
import { defaultColors } from './colors.js';
|
|
10
|
+
import { configProblems } from './config-validation.js';
|
|
10
11
|
import { findUp } from './util/find-up.js';
|
|
11
12
|
const __filename = fileURLToPath(import.meta.url);
|
|
12
13
|
export function getDefaultConfig() {
|
|
@@ -38,6 +39,7 @@ export function getDefaultConfig() {
|
|
|
38
39
|
defaultColorScheme: 'auto',
|
|
39
40
|
storeColorScheme: true,
|
|
40
41
|
darkClass: 'dark',
|
|
42
|
+
lang: 'en',
|
|
41
43
|
},
|
|
42
44
|
responsivePresets: [
|
|
43
45
|
{
|
|
@@ -164,6 +166,16 @@ export async function loadConfigFile(configFile) {
|
|
|
164
166
|
}
|
|
165
167
|
catch (e) {
|
|
166
168
|
console.error(pc.red(`Error while loading ${configFile}`));
|
|
169
|
+
const missing = e?.code === 'ERR_MODULE_NOT_FOUND' || e?.code === 'MODULE_NOT_FOUND'
|
|
170
|
+
? (String(e.message).match(/Cannot find module '([^']+)'/) ?? [])[1]
|
|
171
|
+
: undefined;
|
|
172
|
+
if (missing && !process.env.DEBUG) {
|
|
173
|
+
// The stack is all module-loader and jiti frames, which diagnose neither
|
|
174
|
+
// an uninstalled package nor a typo (#324). `DEBUG=1` keeps it.
|
|
175
|
+
console.error(pc.red(`Cannot find module '${missing}'`));
|
|
176
|
+
console.error(pc.dim('Is it installed, and is the name spelt correctly? Run with DEBUG=1 for the full stack.'));
|
|
177
|
+
throw new Error(`Cannot find module '${missing}' imported from ${configFile}`);
|
|
178
|
+
}
|
|
167
179
|
throw e;
|
|
168
180
|
}
|
|
169
181
|
}
|
|
@@ -237,17 +249,37 @@ export const mergeConfig = createDefu((obj, key, value) => {
|
|
|
237
249
|
return true;
|
|
238
250
|
}
|
|
239
251
|
});
|
|
252
|
+
/** Print every problem, then stop before anything consumes the value. */
|
|
253
|
+
function reportConfigProblems(config, configFile) {
|
|
254
|
+
const problems = configProblems(config, configFile);
|
|
255
|
+
if (problems.length === 0) {
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
for (const problem of problems) {
|
|
259
|
+
console.error(pc.red(problem));
|
|
260
|
+
}
|
|
261
|
+
throw new Error(`Invalid Poveste config: ${problems.length} problem${problems.length === 1 ? '' : 's'} above.`);
|
|
262
|
+
}
|
|
240
263
|
export async function resolveConfig(cwd = process.cwd(), mode, configFile) {
|
|
241
264
|
let result;
|
|
242
265
|
const resolvedConfigFile = resolveConfigFile(cwd, configFile);
|
|
243
266
|
if (resolvedConfigFile) {
|
|
244
267
|
result = await loadConfigFile(resolvedConfigFile);
|
|
268
|
+
// Before the merge, so the message quotes what the user actually wrote
|
|
269
|
+
// rather than a value defaults supplied, and before anything consumes it —
|
|
270
|
+
// the whole point is not reaching `pathe` with a number (#324).
|
|
271
|
+
reportConfigProblems(result, path.relative(cwd, resolvedConfigFile) || resolvedConfigFile);
|
|
245
272
|
}
|
|
246
273
|
const viteConfig = await resolveViteConfig({}, 'serve');
|
|
247
274
|
// The `histoire` key is our own deprecation, and reading it is the whole
|
|
248
275
|
// point — it is what makes poveste drop-in for an existing histoire config.
|
|
249
276
|
// eslint-disable-next-line ts/no-deprecated
|
|
250
277
|
const vitePovesteConfig = (viteConfig.poveste ?? viteConfig.histoire ?? {});
|
|
278
|
+
// The other place a config is written, and validating only `poveste.config.ts`
|
|
279
|
+
// left it out: `examples/svelte5` and `examples/sveltekit` have no such file
|
|
280
|
+
// and configure poveste entirely under this key, so half the reference books
|
|
281
|
+
// took the unvalidated path (#324).
|
|
282
|
+
reportConfigProblems(vitePovesteConfig, viteConfig.configFile ? path.relative(cwd, viteConfig.configFile) : 'the vite config');
|
|
251
283
|
const preUserConfig = mergeConfig(result, vitePovesteConfig);
|
|
252
284
|
const processedDefaultConfig = await processDefaultConfig(getDefaultConfig(), preUserConfig, mode, cwd);
|
|
253
285
|
return resolveConfigPlugins(mergeConfig(preUserConfig, processedDefaultConfig), mode);
|
package/dist/node/markdown.d.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import type { ServerMarkdownFile } from '@poveste/shared';
|
|
1
2
|
import type { Plugin as VitePlugin } from 'vite';
|
|
2
3
|
import type { Context } from './context.js';
|
|
3
4
|
import MarkdownIt from 'markdown-it';
|
|
4
5
|
export declare function onMarkdownListChange(handler: () => unknown): void;
|
|
6
|
+
export declare function onMarkdownFileChange(handler: (file: ServerMarkdownFile) => unknown): void;
|
|
5
7
|
export declare function createMarkdownRenderer(ctx: Context): Promise<MarkdownIt>;
|
|
6
8
|
export declare function createMarkdownPlugins(ctx: Context): Promise<VitePlugin<any>[]>;
|
|
7
9
|
export declare function createMarkdownFilesWatcher(ctx: Context): Promise<{
|
package/dist/node/markdown.js
CHANGED
|
@@ -21,6 +21,24 @@ function notifyMarkdownListChange() {
|
|
|
21
21
|
handler();
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* One markdown file's *content* changed, as distinct from the list of files
|
|
26
|
+
* changing.
|
|
27
|
+
*
|
|
28
|
+
* The rendered html reaches the client as its own virtual module,
|
|
29
|
+
* `virtual:md:<id>`, so invalidating the list module is not enough to repaint an
|
|
30
|
+
* open docs pane — the list is the same, and only that file's module is stale
|
|
31
|
+
* (#370). The module already carries the `import.meta.hot.accept` half.
|
|
32
|
+
*/
|
|
33
|
+
const onMarkdownFileChangeHandlers = [];
|
|
34
|
+
export function onMarkdownFileChange(handler) {
|
|
35
|
+
onMarkdownFileChangeHandlers.push(handler);
|
|
36
|
+
}
|
|
37
|
+
function notifyMarkdownFileChange(file) {
|
|
38
|
+
for (const handler of onMarkdownFileChangeHandlers) {
|
|
39
|
+
handler(file);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
24
42
|
/**
|
|
25
43
|
* Shared between the markdown Vite plugin and the markdown file watcher, which
|
|
26
44
|
* both build a renderer — a `poveste build` reaches this three times. The
|
|
@@ -132,6 +150,21 @@ export async function createMarkdownPlugins(ctx) {
|
|
|
132
150
|
});
|
|
133
151
|
return plugins;
|
|
134
152
|
}
|
|
153
|
+
/**
|
|
154
|
+
* The story a standalone `.story.md` gets: no sibling story file, so its story
|
|
155
|
+
* is derived from frontmatter rather than collected from source.
|
|
156
|
+
*/
|
|
157
|
+
function derivedStoryCode(frontmatter) {
|
|
158
|
+
return `export default ${JSON.stringify({
|
|
159
|
+
id: frontmatter.id,
|
|
160
|
+
title: frontmatter.title,
|
|
161
|
+
icon: frontmatter.icon ?? 'carbon:document-blank',
|
|
162
|
+
iconColor: frontmatter.iconColor,
|
|
163
|
+
group: frontmatter.group,
|
|
164
|
+
docsOnly: true,
|
|
165
|
+
variants: [],
|
|
166
|
+
})}`;
|
|
167
|
+
}
|
|
135
168
|
export async function createMarkdownFilesWatcher(ctx) {
|
|
136
169
|
const md = await createMarkdownRendererWithPlugins(ctx);
|
|
137
170
|
const watcher = chokidar.watch('.', {
|
|
@@ -169,15 +202,7 @@ export async function createMarkdownFilesWatcher(ctx) {
|
|
|
169
202
|
ctx.markdownFiles.push(file);
|
|
170
203
|
if (!isRelatedToStory) {
|
|
171
204
|
const storyRelativePath = relativePath.replace(/\.md$/, '.js');
|
|
172
|
-
const storyFile = addStory(storyRelativePath,
|
|
173
|
-
id: frontmatter.id,
|
|
174
|
-
title: frontmatter.title,
|
|
175
|
-
icon: frontmatter.icon ?? 'carbon:document-blank',
|
|
176
|
-
iconColor: frontmatter.iconColor,
|
|
177
|
-
group: frontmatter.group,
|
|
178
|
-
docsOnly: true,
|
|
179
|
-
variants: [],
|
|
180
|
-
})}`);
|
|
205
|
+
const storyFile = addStory(storyRelativePath, derivedStoryCode(frontmatter));
|
|
181
206
|
file.storyFile = storyFile;
|
|
182
207
|
storyFile.markdownFile = file;
|
|
183
208
|
notifyStoryChange(storyFile);
|
|
@@ -206,12 +231,50 @@ export async function createMarkdownFilesWatcher(ctx) {
|
|
|
206
231
|
notifyMarkdownListChange();
|
|
207
232
|
}
|
|
208
233
|
}
|
|
234
|
+
/**
|
|
235
|
+
* Chokidar emits `change` for an edit to an existing file, and nothing
|
|
236
|
+
* handled it: `addFile` is the only path that reads and renders markdown, and
|
|
237
|
+
* it ran on `add` alone, so an edit was watched, delivered and dropped (#370).
|
|
238
|
+
*
|
|
239
|
+
* Story files get away with the same two-handler shape because they travel
|
|
240
|
+
* through Vite's module graph and Vite updates them. Markdown does not —
|
|
241
|
+
* `addFile` reads and renders in Node, outside the graph.
|
|
242
|
+
*/
|
|
243
|
+
function updateFile(relativePath) {
|
|
244
|
+
const file = ctx.markdownFiles.find(file => file.relativePath === relativePath);
|
|
245
|
+
if (!file) {
|
|
246
|
+
// Not tracked yet — a file that did not match when it was added, say.
|
|
247
|
+
addFile(relativePath);
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
const { data: frontmatter, content } = matter(fs.readFileSync(file.absolutePath, 'utf8'));
|
|
251
|
+
file.frontmatter = frontmatter;
|
|
252
|
+
file.content = content;
|
|
253
|
+
// Rewritten in place rather than by removing and re-adding the story:
|
|
254
|
+
// `moduleId` is derived from the path, so a re-added story carries the id
|
|
255
|
+
// the client already imported and nothing would tell it to look again
|
|
256
|
+
// (#539).
|
|
257
|
+
if (!file.isRelatedToStory && file.storyFile) {
|
|
258
|
+
file.storyFile.moduleCode = derivedStoryCode(frontmatter);
|
|
259
|
+
}
|
|
260
|
+
file.html = md.render(content, {
|
|
261
|
+
file: file.absolutePath,
|
|
262
|
+
});
|
|
263
|
+
notifyMarkdownFileChange(file);
|
|
264
|
+
if (file.storyFile) {
|
|
265
|
+
notifyStoryChange(file.storyFile);
|
|
266
|
+
}
|
|
267
|
+
notifyMarkdownListChange();
|
|
268
|
+
}
|
|
209
269
|
async function stop() {
|
|
210
270
|
await watcher.close();
|
|
211
271
|
}
|
|
212
272
|
watcher
|
|
213
273
|
.on('add', (relativePath) => {
|
|
214
274
|
addFile(relativePath);
|
|
275
|
+
})
|
|
276
|
+
.on('change', (relativePath) => {
|
|
277
|
+
updateFile(relativePath);
|
|
215
278
|
})
|
|
216
279
|
.on('unlink', (relativePath) => {
|
|
217
280
|
removeFile(relativePath);
|
package/dist/node/server.js
CHANGED
|
@@ -4,7 +4,7 @@ import { createServer as createViteServer, mergeConfig as mergeViteConfig } from
|
|
|
4
4
|
import { useCollectStories } from './collect/index.js';
|
|
5
5
|
import { hmrPortFor } from './commands/port.js';
|
|
6
6
|
import { useModuleLoader } from './load.js';
|
|
7
|
-
import { createMarkdownFilesWatcher, onMarkdownListChange } from './markdown.js';
|
|
7
|
+
import { createMarkdownFilesWatcher, onMarkdownFileChange, onMarkdownListChange } from './markdown.js';
|
|
8
8
|
import { DevEventPluginApi, DevPluginApi } from './plugin.js';
|
|
9
9
|
import { onStoryChange, onStoryListChange, watchStories } from './stories.js';
|
|
10
10
|
import { wrapLogError } from './util/log.js';
|
|
@@ -192,6 +192,11 @@ export async function createServer(ctx, options = {}) {
|
|
|
192
192
|
onMarkdownListChange(() => {
|
|
193
193
|
invalidateModule(VirtualFiles.RESOLVED_MARKDOWN_FILES);
|
|
194
194
|
});
|
|
195
|
+
// The list module is unchanged when a file is merely edited — the stale one is
|
|
196
|
+
// that file's own module, which is what carries the rendered html (#370).
|
|
197
|
+
onMarkdownFileChange((file) => {
|
|
198
|
+
invalidateModule(`/__resolved__virtual:md:${file.id}`);
|
|
199
|
+
});
|
|
195
200
|
async function close() {
|
|
196
201
|
for (const cb of pluginOnCleanups) {
|
|
197
202
|
await wrapLogError('plugin.onDev.onCleanup', () => cb());
|
package/dist/node/vite.js
CHANGED
|
@@ -207,12 +207,11 @@ export async function getViteConfigWithPlugins(isServer, ctx) {
|
|
|
207
207
|
res.statusCode = 200;
|
|
208
208
|
let html = `
|
|
209
209
|
<!DOCTYPE html>
|
|
210
|
-
<html>
|
|
210
|
+
<html lang="${ctx.config.theme?.lang ?? 'en'}">
|
|
211
211
|
<head>
|
|
212
212
|
<title></title>
|
|
213
213
|
<meta charset="utf-8">
|
|
214
214
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
215
|
-
<meta name="description" content="">
|
|
216
215
|
</head>
|
|
217
216
|
<body>
|
|
218
217
|
<div id="app"></div>
|
|
@@ -245,13 +244,12 @@ export async function getViteConfigWithPlugins(isServer, ctx) {
|
|
|
245
244
|
res.statusCode = 200;
|
|
246
245
|
let html = `
|
|
247
246
|
<!DOCTYPE html>
|
|
248
|
-
<html>
|
|
247
|
+
<html lang="${ctx.config.theme?.lang ?? 'en'}">
|
|
249
248
|
<head>
|
|
250
249
|
<title></title>
|
|
251
250
|
<link rel="icon" href=""/>
|
|
252
251
|
<meta charset="utf-8">
|
|
253
252
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
254
|
-
<meta name="description" content="">
|
|
255
253
|
${ctx.config.theme?.favicon ? `<link rel="icon" type="${lookupMime(ctx.config.theme.favicon)}" href="${server.config.base}${ctx.config.theme.favicon}"/>` : ''}
|
|
256
254
|
<style>html,body{margin:0;padding:0}</style>
|
|
257
255
|
</head>
|
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "poveste",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
5
|
-
"description": "Interactive component playgrounds for Vue, Svelte and
|
|
4
|
+
"version": "0.12.0",
|
|
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
|
-
"name": "Sorin Gitlan"
|
|
7
|
+
"name": "Sorin Gitlan",
|
|
8
|
+
"url": "https://x.com/50rayn"
|
|
8
9
|
},
|
|
9
10
|
"license": "MIT",
|
|
10
11
|
"homepage": "https://poveste.dev",
|
|
@@ -72,6 +73,10 @@
|
|
|
72
73
|
},
|
|
73
74
|
"dependencies": {
|
|
74
75
|
"@akryum/tinypool": "^0.3.1",
|
|
76
|
+
"@poveste/app": "^0.12.0",
|
|
77
|
+
"@poveste/controls": "^0.12.0",
|
|
78
|
+
"@poveste/shared": "^0.12.0",
|
|
79
|
+
"@poveste/vendors": "^0.12.0",
|
|
75
80
|
"@types/markdown-it": "^14.1.2",
|
|
76
81
|
"birpc": "^0.2.19",
|
|
77
82
|
"change-case": "^5.4.4",
|
|
@@ -97,11 +102,7 @@
|
|
|
97
102
|
"shiki": "^3.0.0",
|
|
98
103
|
"sirv": "^3.0.0",
|
|
99
104
|
"unhead": "^3.1.0",
|
|
100
|
-
"vite-node": "^6.0.0"
|
|
101
|
-
"@poveste/app": "^0.10.0",
|
|
102
|
-
"@poveste/controls": "^0.10.0",
|
|
103
|
-
"@poveste/shared": "^0.10.0",
|
|
104
|
-
"@poveste/vendors": "^0.10.0"
|
|
105
|
+
"vite-node": "^6.0.0"
|
|
105
106
|
},
|
|
106
107
|
"devDependencies": {
|
|
107
108
|
"@types/fs-extra": "^11.0.4",
|