poveste 0.8.2 → 0.10.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/dist/node/bin.js +41 -10
- package/dist/node/build.js +11 -1
- package/dist/node/cleanup.d.ts +8 -0
- package/dist/node/cleanup.js +28 -0
- package/dist/node/collect/index.js +11 -3
- package/dist/node/commands/build.js +13 -5
- package/dist/node/commands/dev.js +47 -39
- package/dist/node/commands/preview.js +21 -13
- package/dist/node/index.d.ts +1 -0
- package/dist/node/index.js +3 -2
- package/package.json +5 -5
package/dist/node/bin.js
CHANGED
|
@@ -13,21 +13,52 @@ process.env.POVESTE = 'true';
|
|
|
13
13
|
// and take it away, which Vike does — and a failed build then ran forever with
|
|
14
14
|
// its error already printed (#405).
|
|
15
15
|
//
|
|
16
|
-
// So failure is reported
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
// the error
|
|
21
|
-
//
|
|
16
|
+
// So failure is reported here, the exit code set, and the process forced out if
|
|
17
|
+
// it does not leave on its own. Closing the collection ports (#426) was not
|
|
18
|
+
// enough on its own: a failed build still hangs in the nuxt4 and sveltekit books,
|
|
19
|
+
// holding `FSEventWrap` and `FSReqCallback` handles — watchers nothing closes on
|
|
20
|
+
// the error path. And `dev` and `preview` hold a listening server by design, so
|
|
21
|
+
// for them no amount of leak-fixing would ever let the loop drain.
|
|
22
22
|
//
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
//
|
|
23
|
+
// The timer is unref'd, so it never fires for a process that ends by itself and
|
|
24
|
+
// costs nothing when there is nothing to force. It is 500ms rather than
|
|
25
|
+
// immediate because exiting on the same tick as `console.error` truncates it:
|
|
26
|
+
// writes to a piped stderr are asynchronous, and a framed compiler error is long.
|
|
27
|
+
//
|
|
28
|
+
// It does mean a leak reads as a clean failure from the outside, so it hides the
|
|
29
|
+
// class of bug #426 was. `POVESTE_NO_FORCE_EXIT` below is how that stays visible
|
|
30
|
+
// without removing it — CI sets it for one step, where a leak is a hang again.
|
|
31
|
+
//
|
|
32
|
+
// Asserting on the reported handle list instead was tried and does not work: a
|
|
33
|
+
// clean failed build leaves transient ones (`Immediate`, `FSReqCallback`), and the
|
|
34
|
+
// allowance needed to go green has to include `FSReqCallback` — most of what the
|
|
35
|
+
// sveltekit leak shows as. Whether the loop drains is the question (#433).
|
|
36
|
+
/**
|
|
37
|
+
* A build asked to prove it drains: the forced exit below is skipped and what is
|
|
38
|
+
* still holding the loop open is printed.
|
|
39
|
+
*
|
|
40
|
+
* The exit code cannot carry that on its own. Forcing the exit makes a process
|
|
41
|
+
* that leaks look exactly like one that does not, which is what left the CI
|
|
42
|
+
* guard's hang branch unreachable and the #426 class of defect undetectable
|
|
43
|
+
* (#433). Under this flag a leak is a hang again, which is measurable, and the
|
|
44
|
+
* handle list says what to go and close.
|
|
45
|
+
*
|
|
46
|
+
* For CI, not for users — a leak should not be the difference between a build
|
|
47
|
+
* that ends and one that does not.
|
|
48
|
+
*/
|
|
49
|
+
function proveItDrains() {
|
|
50
|
+
return !!process.env.POVESTE_NO_FORCE_EXIT;
|
|
51
|
+
}
|
|
27
52
|
function run(command) {
|
|
28
53
|
return command().then(() => undefined, (error) => {
|
|
29
54
|
console.error(error);
|
|
30
55
|
process.exitCode = 1;
|
|
56
|
+
if (proveItDrains()) {
|
|
57
|
+
setImmediate(() => {
|
|
58
|
+
console.error(`POVESTE_ACTIVE_HANDLES: ${JSON.stringify(process.getActiveResourcesInfo())}`);
|
|
59
|
+
});
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
31
62
|
setTimeout(() => process.exit(1), 500).unref();
|
|
32
63
|
});
|
|
33
64
|
}
|
package/dist/node/build.js
CHANGED
|
@@ -54,6 +54,13 @@ export async function build(ctx) {
|
|
|
54
54
|
optimizeDeps: { include: [], noDiscovery: true },
|
|
55
55
|
}));
|
|
56
56
|
await server.pluginContainer.buildStart({});
|
|
57
|
+
// Cleanup a plugin registers runs however this ends. `onBuildEnd` cannot be that
|
|
58
|
+
// channel: "the build ended" and "the build ended well" are different events, and
|
|
59
|
+
// plugins publish results from the latter (#434).
|
|
60
|
+
const cleanupCallbacks = [];
|
|
61
|
+
const onCleanup = (cb) => {
|
|
62
|
+
cleanupCallbacks.push(cb);
|
|
63
|
+
};
|
|
57
64
|
// Everything from here is wrapped so the server is closed on the way out
|
|
58
65
|
// whichever way that is — it is the other handle that kept a failed build
|
|
59
66
|
// alive (#405). `serverClosed` keeps the existing order rather than moving the
|
|
@@ -70,7 +77,7 @@ export async function build(ctx) {
|
|
|
70
77
|
for (const plugin of ctx.config.plugins) {
|
|
71
78
|
if (plugin.onBuild) {
|
|
72
79
|
const api = new BuildPluginApi(ctx, plugin, moduleLoader);
|
|
73
|
-
await plugin.onBuild(api);
|
|
80
|
+
await plugin.onBuild(api, onCleanup);
|
|
74
81
|
changeViteConfigCallbacks.push(...api.changeViteConfigCallbacks);
|
|
75
82
|
buildEndCallbacks.push(...api.buildEndCallbacks);
|
|
76
83
|
previewStoryCallbacks.push(...api.previewStoryCallbacks);
|
|
@@ -285,6 +292,9 @@ export async function build(ctx) {
|
|
|
285
292
|
if (!serverClosed) {
|
|
286
293
|
await wrapLogError('server.close', () => server.close());
|
|
287
294
|
}
|
|
295
|
+
for (const cb of cleanupCallbacks) {
|
|
296
|
+
await wrapLogError('plugin.onBuild.onCleanup', () => cb());
|
|
297
|
+
}
|
|
288
298
|
}
|
|
289
299
|
}
|
|
290
300
|
function generateBaseHtml(head, body, ctx) {
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Awaitable } from '@poveste/shared';
|
|
2
|
+
export declare function onPovesteCleanup(cb: () => Awaitable<void>): void;
|
|
3
|
+
/**
|
|
4
|
+
* Runs every registered callback and forgets them, so a `poveste dev` restart does
|
|
5
|
+
* not close the same thing twice. Logged rather than thrown: a teardown that fails
|
|
6
|
+
* must not replace whatever failure brought us here.
|
|
7
|
+
*/
|
|
8
|
+
export declare function runPovesteCleanups(): Promise<void>;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { wrapLogError } from './util/log.js';
|
|
2
|
+
/**
|
|
3
|
+
* Teardown for things a plugin opens while the config is being resolved, before
|
|
4
|
+
* any command hook has run.
|
|
5
|
+
*
|
|
6
|
+
* `onBuild`'s own `onCleanup` covers what a plugin opens during a build. It cannot
|
|
7
|
+
* cover what already existed: `@poveste/plugin-nuxt` loads Nuxt in `defaultConfig`,
|
|
8
|
+
* so between that and the `onBuild` loop there is a window — finding the stories,
|
|
9
|
+
* resolving the Vite config, starting the collection server — where a failure left
|
|
10
|
+
* Nuxt running with nothing registered to close it (#434).
|
|
11
|
+
*
|
|
12
|
+
* Registered here, its lifetime starts where the resource does.
|
|
13
|
+
*/
|
|
14
|
+
const callbacks = [];
|
|
15
|
+
export function onPovesteCleanup(cb) {
|
|
16
|
+
callbacks.push(cb);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Runs every registered callback and forgets them, so a `poveste dev` restart does
|
|
20
|
+
* not close the same thing twice. Logged rather than thrown: a teardown that fails
|
|
21
|
+
* must not replace whatever failure brought us here.
|
|
22
|
+
*/
|
|
23
|
+
export async function runPovesteCleanups() {
|
|
24
|
+
const pending = callbacks.splice(0, callbacks.length);
|
|
25
|
+
for (const cb of pending) {
|
|
26
|
+
await wrapLogError('poveste.cleanup', () => cb());
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -79,18 +79,23 @@ export function useCollectStories(options, ctx) {
|
|
|
79
79
|
});
|
|
80
80
|
}
|
|
81
81
|
async function executeStoryFile(storyFile) {
|
|
82
|
+
// The channel belongs to this execution, and nothing used to close it. On a
|
|
83
|
+
// build that fails, the executions still in flight are abandoned mid-run and
|
|
84
|
+
// their main-thread ports stay open and listening — enough live handles to
|
|
85
|
+
// keep the process alive with nothing left to do (#426).
|
|
86
|
+
let channel;
|
|
82
87
|
try {
|
|
83
|
-
|
|
88
|
+
channel = createChannel();
|
|
84
89
|
const payload = {
|
|
85
90
|
root: server.config.root,
|
|
86
91
|
base: server.config.base,
|
|
87
92
|
storyFile,
|
|
88
|
-
port: workerPort,
|
|
93
|
+
port: channel.workerPort,
|
|
89
94
|
defineGlobals,
|
|
90
95
|
};
|
|
91
96
|
const { storyData } = await threadPool.run(payload, {
|
|
92
97
|
transferList: [
|
|
93
|
-
workerPort,
|
|
98
|
+
channel.workerPort,
|
|
94
99
|
],
|
|
95
100
|
});
|
|
96
101
|
if (storyData.length === 0) {
|
|
@@ -125,6 +130,9 @@ export function useCollectStories(options, ctx) {
|
|
|
125
130
|
throw e;
|
|
126
131
|
}
|
|
127
132
|
}
|
|
133
|
+
finally {
|
|
134
|
+
channel?.port.close();
|
|
135
|
+
}
|
|
128
136
|
}
|
|
129
137
|
async function destroy() {
|
|
130
138
|
await threadPool.destroy();
|
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
import { build } from '../build.js';
|
|
2
|
+
import { runPovesteCleanups } from '../cleanup.js';
|
|
2
3
|
import { createContext } from '../context.js';
|
|
3
4
|
export async function buildCommand(options) {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
// Plugins open things while the config is resolved — before any hook here has
|
|
6
|
+
// run — so the teardown has to span the command, not one phase of it (#434).
|
|
7
|
+
try {
|
|
8
|
+
const ctx = await createContext({
|
|
9
|
+
configFile: options.config,
|
|
10
|
+
mode: 'build',
|
|
11
|
+
});
|
|
12
|
+
await build(ctx);
|
|
13
|
+
}
|
|
14
|
+
finally {
|
|
15
|
+
await runPovesteCleanups();
|
|
16
|
+
}
|
|
9
17
|
}
|
|
@@ -1,54 +1,62 @@
|
|
|
1
1
|
import chokidar from 'chokidar';
|
|
2
2
|
import pc from 'picocolors';
|
|
3
|
+
import { runPovesteCleanups } from '../cleanup.js';
|
|
3
4
|
import { resolveConfigFile } from '../config.js';
|
|
4
5
|
import { createContext } from '../context.js';
|
|
5
6
|
import { createServer } from '../server.js';
|
|
6
7
|
import { resolvePort } from './port.js';
|
|
7
8
|
export async function devCommand(options) {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
9
|
+
// Plugins open things while the config is resolved — before any hook here has
|
|
10
|
+
// run — so the teardown has to span the command, not one phase of it (#434).
|
|
11
|
+
try {
|
|
12
|
+
const port = resolvePort(options.port, 'dev');
|
|
13
|
+
let stop;
|
|
14
|
+
async function start() {
|
|
15
|
+
const ctx = await createContext({
|
|
16
|
+
configFile: options.config,
|
|
17
|
+
mode: 'dev',
|
|
18
|
+
});
|
|
19
|
+
const { server, viteConfigFile, close } = await createServer(ctx, {
|
|
20
|
+
port,
|
|
21
|
+
open: options.open,
|
|
22
|
+
host: options.host,
|
|
23
|
+
});
|
|
24
|
+
server.printUrls();
|
|
25
|
+
// Poveste config watcher
|
|
26
|
+
let watcher;
|
|
27
|
+
if (viteConfigFile) {
|
|
28
|
+
watcher = chokidar.watch(viteConfigFile, {
|
|
29
|
+
ignoreInitial: true,
|
|
30
|
+
});
|
|
31
|
+
watcher.on('change', () => {
|
|
32
|
+
restart('Vite');
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
return async () => {
|
|
36
|
+
await watcher?.close();
|
|
37
|
+
await close();
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
async function restart(source) {
|
|
41
|
+
if (stop) {
|
|
42
|
+
console.log(pc.blue(`${source} config changed, restarting...`));
|
|
43
|
+
await stop();
|
|
44
|
+
stop = null; // Don't call stop again until new start() is done
|
|
45
|
+
stop = await start();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
stop = await start();
|
|
49
|
+
const configFile = await resolveConfigFile(undefined, options.config);
|
|
50
|
+
if (configFile) {
|
|
51
|
+
const watcher = chokidar.watch(configFile, {
|
|
25
52
|
ignoreInitial: true,
|
|
26
53
|
});
|
|
27
54
|
watcher.on('change', () => {
|
|
28
|
-
restart('
|
|
55
|
+
restart('Poveste');
|
|
29
56
|
});
|
|
30
57
|
}
|
|
31
|
-
return async () => {
|
|
32
|
-
await watcher?.close();
|
|
33
|
-
await close();
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
async function restart(source) {
|
|
37
|
-
if (stop) {
|
|
38
|
-
console.log(pc.blue(`${source} config changed, restarting...`));
|
|
39
|
-
await stop();
|
|
40
|
-
stop = null; // Don't call stop again until new start() is done
|
|
41
|
-
stop = await start();
|
|
42
|
-
}
|
|
43
58
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
if (configFile) {
|
|
47
|
-
const watcher = chokidar.watch(configFile, {
|
|
48
|
-
ignoreInitial: true,
|
|
49
|
-
});
|
|
50
|
-
watcher.on('change', () => {
|
|
51
|
-
restart('Poveste');
|
|
52
|
-
});
|
|
59
|
+
finally {
|
|
60
|
+
await runPovesteCleanups();
|
|
53
61
|
}
|
|
54
62
|
}
|
|
@@ -1,20 +1,28 @@
|
|
|
1
|
+
import { runPovesteCleanups } from '../cleanup.js';
|
|
1
2
|
import { createContext } from '../context.js';
|
|
2
3
|
import { startPreview } from '../preview.js';
|
|
3
4
|
import { resolvePort } from './port.js';
|
|
4
5
|
export async function previewCommand(options) {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
6
|
+
// Plugins open things while the config is resolved — before any hook here has
|
|
7
|
+
// run — so the teardown has to span the command, not one phase of it (#434).
|
|
8
|
+
try {
|
|
9
|
+
const port = resolvePort(options.port, 'preview');
|
|
10
|
+
const ctx = await createContext({
|
|
11
|
+
mode: 'build',
|
|
12
|
+
});
|
|
13
|
+
for (const plugin of ctx.config.plugins) {
|
|
14
|
+
if (plugin.onPreview) {
|
|
15
|
+
await plugin.onPreview();
|
|
16
|
+
}
|
|
12
17
|
}
|
|
18
|
+
const { printUrls } = await startPreview({
|
|
19
|
+
port,
|
|
20
|
+
host: options.host,
|
|
21
|
+
open: options.open,
|
|
22
|
+
}, ctx);
|
|
23
|
+
printUrls();
|
|
24
|
+
}
|
|
25
|
+
finally {
|
|
26
|
+
await runPovesteCleanups();
|
|
13
27
|
}
|
|
14
|
-
const { printUrls } = await startPreview({
|
|
15
|
-
port,
|
|
16
|
-
host: options.host,
|
|
17
|
-
open: options.open,
|
|
18
|
-
}, ctx);
|
|
19
|
-
printUrls();
|
|
20
28
|
}
|
package/dist/node/index.d.ts
CHANGED
package/dist/node/index.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
// A plugin that generates story source needs to reference poveste's own
|
|
2
|
+
// vendored copies of Vue and the controls from inside that generated code.
|
|
3
|
+
export { onPovesteCleanup } from './cleanup.js';
|
|
1
4
|
export { defaultColors } from './colors.js';
|
|
2
5
|
export * from './config.js';
|
|
3
6
|
export * from './plugin.js';
|
|
4
|
-
// A plugin that generates story source needs to reference poveste's own
|
|
5
|
-
// vendored copies of Vue and the controls from inside that generated code.
|
|
6
7
|
export { getInjectedImport } from './util/vendors.js';
|
|
7
8
|
export * from '@poveste/shared';
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "poveste",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.10.0",
|
|
5
5
|
"description": "Interactive component playgrounds for Vue, Svelte and Nuxt — a maintained, drop-in successor to histoire",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Sorin Gitlan"
|
|
@@ -98,10 +98,10 @@
|
|
|
98
98
|
"sirv": "^3.0.0",
|
|
99
99
|
"unhead": "^3.1.0",
|
|
100
100
|
"vite-node": "^6.0.0",
|
|
101
|
-
"@poveste/app": "^0.
|
|
102
|
-
"@poveste/controls": "^0.
|
|
103
|
-
"@poveste/shared": "^0.
|
|
104
|
-
"@poveste/vendors": "^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
105
|
},
|
|
106
106
|
"devDependencies": {
|
|
107
107
|
"@types/fs-extra": "^11.0.4",
|