describe-me 0.5.3 → 0.6.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 +58 -44
- package/dist-cli/build-live-preview.d.ts +6 -0
- package/dist-cli/build-live-preview.js +62 -0
- package/dist-cli/code-of.d.ts +2 -0
- package/dist-cli/code-of.js +4 -0
- package/dist-cli/import-build-live.d.ts +27 -0
- package/dist-cli/import-build-live.js +20 -0
- package/dist-cli/import-create-live-server.d.ts +20 -0
- package/dist-cli/import-create-live-server.js +20 -0
- package/dist-cli/live-consequences.d.ts +8 -0
- package/dist-cli/live-consequences.js +8 -0
- package/dist-cli/live-predates.d.ts +2 -0
- package/dist-cli/live-predates.js +4 -0
- package/dist-cli/live-preview-endpoint.d.ts +8 -0
- package/dist-cli/live-preview-endpoint.js +26 -0
- package/dist-cli/live-preview-info.d.ts +12 -0
- package/dist-cli/live-preview-info.js +1 -0
- package/dist-cli/live-project.d.ts +14 -0
- package/dist-cli/live-project.js +31 -0
- package/dist-cli/main.js +8 -4
- package/dist-cli/parse-args.d.ts +13 -2
- package/dist-cli/parse-args.js +9 -3
- package/dist-cli/reason-of.d.ts +5 -0
- package/dist-cli/reason-of.js +13 -0
- package/dist-cli/resolve-live-entry.d.ts +5 -0
- package/dist-cli/resolve-live-entry.js +42 -0
- package/dist-cli/run-build.d.ts +7 -1
- package/dist-cli/run-build.js +8 -1
- package/dist-cli/run-dev.d.ts +13 -2
- package/dist-cli/run-dev.js +26 -5
- package/dist-cli/start-live-preview.d.ts +7 -0
- package/dist-cli/start-live-preview.js +34 -0
- package/package.json +7 -4
- package/src/apply-viewport.ts +21 -3
- package/src/inspector.ts +35 -22
- package/src/keyboard.ts +42 -9
- package/src/listen-to-live.ts +128 -0
- package/src/live-iframe.ts +6 -0
- package/src/live-section.ts +84 -0
- package/src/live-session.ts +35 -0
- package/src/live-shown.ts +10 -0
- package/src/live-src.ts +32 -0
- package/src/live-status-words.ts +15 -0
- package/src/load-live-preview.ts +80 -0
- package/src/main-panel.ts +5 -2
- package/src/main.ts +6 -0
- package/src/paint-live.ts +63 -0
- package/src/render-frame.ts +8 -5
- package/src/reset-live-session.ts +12 -0
- package/src/stage.ts +24 -4
- package/src/state-hash.ts +5 -1
- package/src/state.ts +8 -0
- package/src/states-gallery.ts +6 -2
- package/src/style.css +107 -0
- package/src/toggle-live.ts +16 -0
- package/src/viewport-controls.ts +37 -4
package/README.md
CHANGED
|
@@ -1,59 +1,73 @@
|
|
|
1
1
|
# describe-me
|
|
2
2
|
|
|
3
|
-
The viewer and
|
|
4
|
-
living component documentation generated from the Vitest tests
|
|
5
|
-
|
|
6
|
-
you can scrub through.
|
|
3
|
+
**Your tests are your stories.** The viewer and the `describe-me` CLI of
|
|
4
|
+
describe-me: living component documentation generated from the Vitest tests
|
|
5
|
+
you already have.
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
**[Read the docs](https://grzehub.github.io/describe-me/)** · open the viewer
|
|
8
|
+
of the [browser-mode example](https://grzehub.github.io/describe-me/examples/react-browser/)
|
|
9
|
+
or the [jsdom example](https://grzehub.github.io/describe-me/examples/react-jsdom/)
|
|
10
|
+
· [Limitations](https://grzehub.github.io/describe-me/limitations.html)
|
|
10
11
|
|
|
11
|
-
|
|
12
|
+
describe-me captures the DOM after each step of a test and shows the result as
|
|
13
|
+
a browsable catalog. `describe` blocks become the sidebar, each `it` is a
|
|
14
|
+
story, and each step is a frame you can scrub through. Stories cannot rot,
|
|
15
|
+
because they are tests. If one drifts from the component, CI goes red.
|
|
16
|
+
|
|
17
|
+
Status: prototype. React 18 and 19, Vitest 4 and 5 in browser mode or jsdom,
|
|
18
|
+
recorded frames and Live in the viewer.
|
|
19
|
+
|
|
20
|
+
## Which package do I need
|
|
21
|
+
|
|
22
|
+
| Package | What it is | Install it |
|
|
23
|
+
| --------------------- | ------------------------------------------- | ----------------------- |
|
|
24
|
+
| `describe-me` | the viewer and the `describe-me` CLI (this) | yes |
|
|
25
|
+
| `@describe-me/vitest` | the Vitest plugin, setup files and reporter | yes |
|
|
26
|
+
| `@describe-me/react` | `render` functions that record frames | yes |
|
|
27
|
+
| `@describe-me/core` | the recorder and the manifest types | no, the others bring it |
|
|
28
|
+
|
|
29
|
+
## Quick start
|
|
12
30
|
|
|
13
31
|
```sh
|
|
14
32
|
pnpm add -D describe-me @describe-me/vitest @describe-me/react
|
|
15
33
|
```
|
|
16
34
|
|
|
17
|
-
|
|
18
|
-
|
|
35
|
+
```ts
|
|
36
|
+
// vitest.config.ts
|
|
37
|
+
import { describeMe } from '@describe-me/vitest/plugin'
|
|
38
|
+
|
|
39
|
+
export default defineConfig({
|
|
40
|
+
plugins: [react(), describeMe()],
|
|
41
|
+
test: { environment: 'jsdom' }, // or your browser-mode settings
|
|
42
|
+
})
|
|
43
|
+
```
|
|
19
44
|
|
|
20
|
-
|
|
21
|
-
|
|
45
|
+
Run your tests, then `describe-me dev` opens the viewer.
|
|
46
|
+
[Getting started](https://grzehub.github.io/describe-me/getting-started.html)
|
|
47
|
+
walks through both environments. Keep all describe-me packages on one version
|
|
48
|
+
([why](https://grzehub.github.io/describe-me/installation.html#lockstep)).
|
|
22
49
|
|
|
23
|
-
##
|
|
50
|
+
## This package
|
|
24
51
|
|
|
25
52
|
```sh
|
|
26
|
-
describe-me dev # viewer
|
|
27
|
-
describe-me build --out docs #
|
|
53
|
+
describe-me dev # the viewer, repainted while vitest --watch runs
|
|
54
|
+
describe-me build --out docs # a static site: the viewer and its data
|
|
28
55
|
```
|
|
29
56
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
`
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
This needs the network. By default `build` connects to Google Fonts, Bunny
|
|
48
|
-
Fonts and jsDelivr when the data loads fonts from them and the cache has no
|
|
49
|
-
fresh copy. Downloads are cached in `node_modules/.cache/describe-me/fonts`,
|
|
50
|
-
so after one online build later builds run offline. An unreachable host costs
|
|
51
|
-
up to about 10 s once, then it is skipped. A font that does not download keeps
|
|
52
|
-
loading from the network, and the build never fails because of it.
|
|
53
|
-
`--no-vendor-fonts` keeps the build off the network, for CI without network
|
|
54
|
-
access or with privacy rules.
|
|
55
|
-
|
|
56
|
-
The static site uses relative URLs, so it works from a sub-path such as
|
|
57
|
-
GitHub Pages.
|
|
58
|
-
|
|
59
|
-
See the [docs](https://grzehub.github.io/describe-me/) for the full picture.
|
|
57
|
+
`describe-me build --live` adds Live to the site. Read
|
|
58
|
+
[what such a site contains](https://grzehub.github.io/describe-me/viewer.html#flag-live)
|
|
59
|
+
before you publish it.
|
|
60
|
+
|
|
61
|
+
`build` writes a site with relative URLs, so it works under a sub-path such as
|
|
62
|
+
GitHub Pages. It needs a web server and does not open from disk. It also
|
|
63
|
+
downloads web fonts from Google Fonts, Bunny Fonts and Fontsource into the
|
|
64
|
+
site, so it shows them offline.
|
|
65
|
+
[Viewer and CLI](https://grzehub.github.io/describe-me/viewer.html) lists every
|
|
66
|
+
flag and what the viewer can do.
|
|
67
|
+
|
|
68
|
+
`vite` is a peer dependency, which pnpm and npm install on their own. With
|
|
69
|
+
yarn, add it to your `devDependencies`.
|
|
70
|
+
|
|
71
|
+
## License
|
|
72
|
+
|
|
73
|
+
MIT
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builds the live preview of the manifest's tests into `<out>/__live/` and
|
|
3
|
+
* writes `__live.json` next to the viewer, so the static site shows Live.
|
|
4
|
+
* On failure it leaves neither behind and throws the reason.
|
|
5
|
+
*/
|
|
6
|
+
export declare function buildLivePreview(data: string, out: string, root: string | undefined): Promise<void>;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { existsSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { join, resolve } from 'node:path';
|
|
3
|
+
import { LIVE_PROTOCOL } from '@describe-me/core/types';
|
|
4
|
+
import { codeOf } from './code-of.js';
|
|
5
|
+
import { importBuildLive } from './import-build-live.js';
|
|
6
|
+
import { LIVE_CONSEQUENCES } from './live-consequences.js';
|
|
7
|
+
import { liveProject } from './live-project.js';
|
|
8
|
+
import { reasonOf } from './reason-of.js';
|
|
9
|
+
const STOPPED = LIVE_CONSEQUENCES.build;
|
|
10
|
+
// The code `buildLive()` rejects with when the project's config sets `live: false`.
|
|
11
|
+
const LIVE_OFF = 'DESCRIBE_ME_LIVE_OFF';
|
|
12
|
+
const READY = { status: 'ready', base: '__live/' };
|
|
13
|
+
/** The module ids of the manifest. Even with `--root` the build needs them, as its list of tests. */
|
|
14
|
+
function manifestTestFiles(data) {
|
|
15
|
+
const file = join(resolve(data), 'manifest.json');
|
|
16
|
+
if (!existsSync(file)) {
|
|
17
|
+
throw new Error(`${file} is missing, ${STOPPED}. Run the tests first, because the manifest lists the tests to build.`);
|
|
18
|
+
}
|
|
19
|
+
try {
|
|
20
|
+
const manifest = JSON.parse(readFileSync(file, 'utf8'));
|
|
21
|
+
return manifest.modules.map((module) => module.id);
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
throw new Error(`cannot read ${file} (${reasonOf(error)}), ${STOPPED}.`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/** The reason for an error of `buildLive()`, which knows nothing of the CLI. */
|
|
28
|
+
function buildFailure(error) {
|
|
29
|
+
if (codeOf(error) === LIVE_OFF) {
|
|
30
|
+
return new Error(`${reasonOf(error)}, but --live asks for it, ${STOPPED}. Remove one of the two.`);
|
|
31
|
+
}
|
|
32
|
+
return new Error(`the live build failed: ${reasonOf(error)}`);
|
|
33
|
+
}
|
|
34
|
+
async function buildProject(project, outDir, testFiles) {
|
|
35
|
+
const buildLive = await importBuildLive(project.root);
|
|
36
|
+
const built = await buildLive({ ...project, outDir, testFiles }).catch((error) => {
|
|
37
|
+
throw buildFailure(error);
|
|
38
|
+
});
|
|
39
|
+
if (built.protocol !== LIVE_PROTOCOL) {
|
|
40
|
+
throw new Error(`all describe-me packages must be on the same version. The live build in ${project.root} speaks protocol ${built.protocol}, this viewer ${LIVE_PROTOCOL}, ${STOPPED}.`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Builds the live preview of the manifest's tests into `<out>/__live/` and
|
|
45
|
+
* writes `__live.json` next to the viewer, so the static site shows Live.
|
|
46
|
+
* On failure it leaves neither behind and throws the reason.
|
|
47
|
+
*/
|
|
48
|
+
export async function buildLivePreview(data, out, root) {
|
|
49
|
+
const outDir = join(resolve(out), '__live');
|
|
50
|
+
let testFiles;
|
|
51
|
+
try {
|
|
52
|
+
testFiles = manifestTestFiles(data);
|
|
53
|
+
await buildProject(liveProject(data, root, STOPPED), outDir, testFiles);
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
rmSync(outDir, { recursive: true, force: true });
|
|
57
|
+
throw new Error(reasonOf(error), { cause: error });
|
|
58
|
+
}
|
|
59
|
+
const files = testFiles.length === 1 ? '1 test file' : `${testFiles.length} test files`;
|
|
60
|
+
writeFileSync(join(resolve(out), '__live.json'), JSON.stringify(READY));
|
|
61
|
+
process.stdout.write(` ➜ Live preview: ${join(out, '__live')}/ (${files})\n`);
|
|
62
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the CLI passes to `buildLive()` from `@describe-me/vitest/live`.
|
|
3
|
+
* Declared here, because `describe-me` does not depend on `@describe-me/vitest`.
|
|
4
|
+
*/
|
|
5
|
+
export interface LivePreviewBuildOptions {
|
|
6
|
+
/** The project root. */
|
|
7
|
+
root: string;
|
|
8
|
+
/** The Vitest config file, relative to `root`. Absent: the one Vitest picks there. */
|
|
9
|
+
configFile?: string;
|
|
10
|
+
/** Where the live page goes. */
|
|
11
|
+
outDir: string;
|
|
12
|
+
/** The module ids of the manifest. */
|
|
13
|
+
testFiles: string[];
|
|
14
|
+
}
|
|
15
|
+
/** The part of a finished live build that the CLI uses. */
|
|
16
|
+
export interface LivePreviewBuild {
|
|
17
|
+
/** The live protocol of the project's describeMe() plugin. */
|
|
18
|
+
protocol: number;
|
|
19
|
+
}
|
|
20
|
+
/** `buildLive()` as the CLI calls it. */
|
|
21
|
+
export type BuildLive = (options: LivePreviewBuildOptions) => Promise<LivePreviewBuild>;
|
|
22
|
+
/**
|
|
23
|
+
* `buildLive()` from the project's own `@describe-me/vitest/live`, so the
|
|
24
|
+
* build runs the plugin the tests ran with. Throws the reason when the
|
|
25
|
+
* project has none.
|
|
26
|
+
*/
|
|
27
|
+
export declare function importBuildLive(root: string): Promise<BuildLive>;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { pathToFileURL } from 'node:url';
|
|
2
|
+
import { LIVE_CONSEQUENCES } from './live-consequences.js';
|
|
3
|
+
import { livePredates } from './live-predates.js';
|
|
4
|
+
import { resolveLiveEntry } from './resolve-live-entry.js';
|
|
5
|
+
/**
|
|
6
|
+
* `buildLive()` from the project's own `@describe-me/vitest/live`, so the
|
|
7
|
+
* build runs the plugin the tests ran with. Throws the reason when the
|
|
8
|
+
* project has none.
|
|
9
|
+
*/
|
|
10
|
+
export async function importBuildLive(root) {
|
|
11
|
+
const entry = resolveLiveEntry(root, LIVE_CONSEQUENCES.build);
|
|
12
|
+
const exported = await import(pathToFileURL(entry).href);
|
|
13
|
+
const build = typeof exported === 'object' && exported !== null && 'buildLive' in exported
|
|
14
|
+
? exported.buildLive
|
|
15
|
+
: undefined;
|
|
16
|
+
if (typeof build !== 'function') {
|
|
17
|
+
throw livePredates(root, 'buildLive()', LIVE_CONSEQUENCES.build);
|
|
18
|
+
}
|
|
19
|
+
return build;
|
|
20
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { LiveProject } from './live-project.js';
|
|
2
|
+
/**
|
|
3
|
+
* The part of the server from `@describe-me/vitest/live` that the CLI uses.
|
|
4
|
+
* Declared here, because `describe-me` does not depend on `@describe-me/vitest`.
|
|
5
|
+
*/
|
|
6
|
+
export interface LivePreviewServer {
|
|
7
|
+
/** The URL the live page is under. */
|
|
8
|
+
base: string;
|
|
9
|
+
/** The live protocol of the project's describeMe() plugin. */
|
|
10
|
+
protocol: number;
|
|
11
|
+
close(): Promise<void>;
|
|
12
|
+
}
|
|
13
|
+
/** `createLiveServer()` as the CLI calls it. */
|
|
14
|
+
export type CreateLiveServer = (project: LiveProject) => Promise<LivePreviewServer>;
|
|
15
|
+
/**
|
|
16
|
+
* `createLiveServer()` from the project's own `@describe-me/vitest/live`, so
|
|
17
|
+
* the preview runs the plugin the tests ran with. Throws the reason when the
|
|
18
|
+
* project has none.
|
|
19
|
+
*/
|
|
20
|
+
export declare function importCreateLiveServer(root: string): Promise<CreateLiveServer>;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { pathToFileURL } from 'node:url';
|
|
2
|
+
import { LIVE_CONSEQUENCES } from './live-consequences.js';
|
|
3
|
+
import { livePredates } from './live-predates.js';
|
|
4
|
+
import { resolveLiveEntry } from './resolve-live-entry.js';
|
|
5
|
+
/**
|
|
6
|
+
* `createLiveServer()` from the project's own `@describe-me/vitest/live`, so
|
|
7
|
+
* the preview runs the plugin the tests ran with. Throws the reason when the
|
|
8
|
+
* project has none.
|
|
9
|
+
*/
|
|
10
|
+
export async function importCreateLiveServer(root) {
|
|
11
|
+
const entry = resolveLiveEntry(root, LIVE_CONSEQUENCES.dev);
|
|
12
|
+
const exported = await import(pathToFileURL(entry).href);
|
|
13
|
+
const create = typeof exported === 'object' && exported !== null && 'createLiveServer' in exported
|
|
14
|
+
? exported.createLiveServer
|
|
15
|
+
: undefined;
|
|
16
|
+
if (typeof create !== 'function') {
|
|
17
|
+
throw livePredates(root, 'the live preview', LIVE_CONSEQUENCES.dev);
|
|
18
|
+
}
|
|
19
|
+
return create;
|
|
20
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How a reason goes on when the live preview cannot start or build: what
|
|
3
|
+
* that means for the command, so the same reason fits `dev` and `build --live`.
|
|
4
|
+
*/
|
|
5
|
+
export declare const LIVE_CONSEQUENCES: {
|
|
6
|
+
readonly dev: "so the viewer runs without Live";
|
|
7
|
+
readonly build: "so describe-me build --live stopped";
|
|
8
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How a reason goes on when the live preview cannot start or build: what
|
|
3
|
+
* that means for the command, so the same reason fits `dev` and `build --live`.
|
|
4
|
+
*/
|
|
5
|
+
export const LIVE_CONSEQUENCES = {
|
|
6
|
+
dev: 'so the viewer runs without Live',
|
|
7
|
+
build: 'so describe-me build --live stopped',
|
|
8
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Plugin } from 'vite';
|
|
2
|
+
import type { LivePreviewInfo } from './live-preview-info.js';
|
|
3
|
+
/**
|
|
4
|
+
* Answers `GET` and `HEAD /__live.json` on the viewer's dev server with what
|
|
5
|
+
* `info()` says at that moment. It is an inline plugin of the CLI, because
|
|
6
|
+
* the viewer's `vite.config.ts` cannot see the CLI's state.
|
|
7
|
+
*/
|
|
8
|
+
export declare function livePreviewEndpoint(info: () => LivePreviewInfo): Plugin;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
const PATH = '/__live.json';
|
|
2
|
+
/**
|
|
3
|
+
* Answers `GET` and `HEAD /__live.json` on the viewer's dev server with what
|
|
4
|
+
* `info()` says at that moment. It is an inline plugin of the CLI, because
|
|
5
|
+
* the viewer's `vite.config.ts` cannot see the CLI's state.
|
|
6
|
+
*/
|
|
7
|
+
export function livePreviewEndpoint(info) {
|
|
8
|
+
return {
|
|
9
|
+
name: 'describe-me:live-preview',
|
|
10
|
+
configureServer(server) {
|
|
11
|
+
server.middlewares.use((request, response, next) => {
|
|
12
|
+
const path = (request.url ?? '').split('?')[0];
|
|
13
|
+
const readable = request.method === 'GET' || request.method === 'HEAD';
|
|
14
|
+
if (path !== PATH || !readable) {
|
|
15
|
+
next();
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
const body = JSON.stringify(info());
|
|
19
|
+
response.setHeader('Content-Type', 'application/json');
|
|
20
|
+
response.setHeader('Cache-Control', 'no-store');
|
|
21
|
+
response.setHeader('Content-Length', Buffer.byteLength(body));
|
|
22
|
+
response.end(request.method === 'HEAD' ? undefined : body);
|
|
23
|
+
});
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** What `__live.json` next to the viewer says about the live preview. */
|
|
2
|
+
export interface LivePreviewInfo {
|
|
3
|
+
status: 'starting' | 'ready' | 'off' | 'error';
|
|
4
|
+
/**
|
|
5
|
+
* The URL `live.html` is under, relative to `__live.json` or absolute:
|
|
6
|
+
* `__live/` on a static site, `http://localhost:5173/__describe-me/` under
|
|
7
|
+
* `describe-me dev`. Only with `ready`.
|
|
8
|
+
*/
|
|
9
|
+
base?: string;
|
|
10
|
+
/** Why the live preview did not start. Only with `error`. */
|
|
11
|
+
error?: string;
|
|
12
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** The project the live preview runs, as `createLiveServer()` and `buildLive()` take it. */
|
|
2
|
+
export interface LiveProject {
|
|
3
|
+
/** The project root. */
|
|
4
|
+
root: string;
|
|
5
|
+
/** The Vitest config file, relative to `root`. Absent: the one Vitest picks there. */
|
|
6
|
+
configFile?: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* The project of the live preview. `--root` replaces both the root and the
|
|
10
|
+
* config file of the manifest, so the preview uses the config Vitest picks in
|
|
11
|
+
* that directory. Throws the reason, followed by `consequence`, when there is
|
|
12
|
+
* neither.
|
|
13
|
+
*/
|
|
14
|
+
export declare function liveProject(data: string, root: string | undefined, consequence: string): LiveProject;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { join, resolve } from 'node:path';
|
|
3
|
+
/**
|
|
4
|
+
* The project of the live preview. `--root` replaces both the root and the
|
|
5
|
+
* config file of the manifest, so the preview uses the config Vitest picks in
|
|
6
|
+
* that directory. Throws the reason, followed by `consequence`, when there is
|
|
7
|
+
* neither.
|
|
8
|
+
*/
|
|
9
|
+
export function liveProject(data, root, consequence) {
|
|
10
|
+
if (root !== undefined) {
|
|
11
|
+
return { root: resolve(root) };
|
|
12
|
+
}
|
|
13
|
+
const dir = resolve(data);
|
|
14
|
+
const file = join(dir, 'manifest.json');
|
|
15
|
+
if (!existsSync(file)) {
|
|
16
|
+
throw new Error(`no manifest in ${dir} to find the project by, ${consequence}. Run the tests and start again, or pass --root.`);
|
|
17
|
+
}
|
|
18
|
+
let manifest;
|
|
19
|
+
try {
|
|
20
|
+
manifest = JSON.parse(readFileSync(file, 'utf8'));
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
24
|
+
throw new Error(`cannot read ${file} (${message}), ${consequence}.`);
|
|
25
|
+
}
|
|
26
|
+
const { root: projectRoot, configFile } = manifest;
|
|
27
|
+
if (configFile !== undefined && !existsSync(resolve(projectRoot, configFile))) {
|
|
28
|
+
throw new Error(`the manifest names the config file ${configFile}, which is not in ${projectRoot}, ${consequence}. Run the tests again, or pass --root.`);
|
|
29
|
+
}
|
|
30
|
+
return { root: projectRoot, configFile };
|
|
31
|
+
}
|
package/dist-cli/main.js
CHANGED
|
@@ -3,8 +3,8 @@ import { runBuild } from './run-build.js';
|
|
|
3
3
|
import { runDev } from './run-dev.js';
|
|
4
4
|
const USAGE = `describe-me — living component docs from your tests
|
|
5
5
|
|
|
6
|
-
describe-me dev [--data .describe-me] [--port 6006] viewer
|
|
7
|
-
describe-me build [--data .describe-me] [--out describe-me-dist] [--no-vendor-fonts] static site
|
|
6
|
+
describe-me dev [--data .describe-me] [--port 6006] [--no-live] [--root <dir>] viewer that follows the test runs, and the live preview
|
|
7
|
+
describe-me build [--data .describe-me] [--out describe-me-dist] [--no-vendor-fonts] [--live] [--root <dir>] static site
|
|
8
8
|
`;
|
|
9
9
|
/** Entry point of the `describe-me` binary. */
|
|
10
10
|
async function main() {
|
|
@@ -14,10 +14,14 @@ async function main() {
|
|
|
14
14
|
return;
|
|
15
15
|
}
|
|
16
16
|
if (options.command === 'build') {
|
|
17
|
-
await runBuild(options.data, options.out, {
|
|
17
|
+
await runBuild(options.data, options.out, {
|
|
18
|
+
vendorFonts: options.vendorFonts,
|
|
19
|
+
live: options.live,
|
|
20
|
+
root: options.root,
|
|
21
|
+
});
|
|
18
22
|
return;
|
|
19
23
|
}
|
|
20
|
-
await runDev(options.data, options.port);
|
|
24
|
+
await runDev(options.data, options.port, { live: options.live, root: options.root });
|
|
21
25
|
}
|
|
22
26
|
main().catch((error) => {
|
|
23
27
|
process.stderr.write(`describe-me: ${error instanceof Error ? error.message : String(error)}\n`);
|
package/dist-cli/parse-args.d.ts
CHANGED
|
@@ -9,9 +9,20 @@ export interface CliOptions {
|
|
|
9
9
|
port: number;
|
|
10
10
|
/** `build`: download web fonts from known font hosts into the site. */
|
|
11
11
|
vendorFonts: boolean;
|
|
12
|
+
/**
|
|
13
|
+
* `dev`: start the live preview next to the viewer. `build`: build it into
|
|
14
|
+
* the site. On for `dev` and off for `build`, unless `--live` or `--no-live` says otherwise.
|
|
15
|
+
*/
|
|
16
|
+
live: boolean;
|
|
17
|
+
/**
|
|
18
|
+
* `dev` and `build --live`: the project root of the live preview, instead of
|
|
19
|
+
* the manifest's root and config file.
|
|
20
|
+
*/
|
|
21
|
+
root: string | undefined;
|
|
12
22
|
}
|
|
13
23
|
/**
|
|
14
|
-
* Turn `describe-me <command> [--data dir] [--out dir] [--port n] [--no-vendor-fonts]
|
|
15
|
-
* into options with defaults.
|
|
24
|
+
* Turn `describe-me <command> [--data dir] [--out dir] [--port n] [--no-vendor-fonts]
|
|
25
|
+
* [--live | --no-live] [--root dir]` into options with defaults. `build` ignores
|
|
26
|
+
* `--port`, and `--root` without `--live`.
|
|
16
27
|
*/
|
|
17
28
|
export declare function parseArgs(argv: string[]): CliOptions;
|
package/dist-cli/parse-args.js
CHANGED
|
@@ -9,8 +9,9 @@ function commandFrom(positional, help) {
|
|
|
9
9
|
return 'dev';
|
|
10
10
|
}
|
|
11
11
|
/**
|
|
12
|
-
* Turn `describe-me <command> [--data dir] [--out dir] [--port n] [--no-vendor-fonts]
|
|
13
|
-
* into options with defaults.
|
|
12
|
+
* Turn `describe-me <command> [--data dir] [--out dir] [--port n] [--no-vendor-fonts]
|
|
13
|
+
* [--live | --no-live] [--root dir]` into options with defaults. `build` ignores
|
|
14
|
+
* `--port`, and `--root` without `--live`.
|
|
14
15
|
*/
|
|
15
16
|
export function parseArgs(argv) {
|
|
16
17
|
const { values, positionals } = parseNodeArgs({
|
|
@@ -23,13 +24,18 @@ export function parseArgs(argv) {
|
|
|
23
24
|
port: { type: 'string', default: '6006' },
|
|
24
25
|
help: { type: 'boolean', short: 'h', default: false },
|
|
25
26
|
'vendor-fonts': { type: 'boolean', default: true },
|
|
27
|
+
live: { type: 'boolean' },
|
|
28
|
+
root: { type: 'string' },
|
|
26
29
|
},
|
|
27
30
|
});
|
|
31
|
+
const command = commandFrom(positionals[0], values.help);
|
|
28
32
|
return {
|
|
29
|
-
command
|
|
33
|
+
command,
|
|
30
34
|
data: values.data,
|
|
31
35
|
out: values.out,
|
|
32
36
|
port: Number(values.port),
|
|
33
37
|
vendorFonts: values['vendor-fonts'],
|
|
38
|
+
live: values.live ?? command === 'dev',
|
|
39
|
+
root: values.root,
|
|
34
40
|
};
|
|
35
41
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
const PREFIX = 'describe-me: ';
|
|
2
|
+
// Vite and its bundler colour their errors for the terminal.
|
|
3
|
+
const COLOURS = new RegExp(String.raw `\u001b\[[\d;]*m`, 'g');
|
|
4
|
+
/**
|
|
5
|
+
* The message of an error as the CLI prints it after its own
|
|
6
|
+
* `describe-me: `: without colours, and without a prefix of its own.
|
|
7
|
+
*/
|
|
8
|
+
export function reasonOf(error) {
|
|
9
|
+
const message = (error instanceof Error ? error.message : String(error))
|
|
10
|
+
.replace(COLOURS, '')
|
|
11
|
+
.trim();
|
|
12
|
+
return message.startsWith(PREFIX) ? message.slice(PREFIX.length) : message;
|
|
13
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
import { codeOf } from './code-of.js';
|
|
5
|
+
import { livePredates } from './live-predates.js';
|
|
6
|
+
function notFound(root, consequence) {
|
|
7
|
+
return new Error(`cannot find @describe-me/vitest from ${root}, ${consequence}. Pass --root with the project directory.`);
|
|
8
|
+
}
|
|
9
|
+
/** Whether `node_modules/@describe-me/vitest` is in `root` or a directory above it. */
|
|
10
|
+
function isInstalledFrom(root) {
|
|
11
|
+
for (let dir = root;; dir = dirname(dir)) {
|
|
12
|
+
if (existsSync(join(dir, 'node_modules', '@describe-me', 'vitest', 'package.json'))) {
|
|
13
|
+
return true;
|
|
14
|
+
}
|
|
15
|
+
if (dirname(dir) === dir) {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* The file `@describe-me/vitest/live` resolves to from the project root.
|
|
22
|
+
* Throws the reason, followed by `consequence`, when the project has none.
|
|
23
|
+
*/
|
|
24
|
+
export function resolveLiveEntry(root, consequence) {
|
|
25
|
+
// The bin shims of package managers put their own store on NODE_PATH, which
|
|
26
|
+
// `resolve()` searches too. Only the project's own node_modules count.
|
|
27
|
+
if (!isInstalledFrom(root)) {
|
|
28
|
+
throw notFound(root, consequence);
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
return createRequire(join(root, 'package.json')).resolve('@describe-me/vitest/live');
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
if (codeOf(error) === 'ERR_PACKAGE_PATH_NOT_EXPORTED') {
|
|
35
|
+
throw livePredates(root, 'the live preview', consequence);
|
|
36
|
+
}
|
|
37
|
+
if (codeOf(error) === 'MODULE_NOT_FOUND') {
|
|
38
|
+
throw notFound(root, consequence);
|
|
39
|
+
}
|
|
40
|
+
throw error;
|
|
41
|
+
}
|
|
42
|
+
}
|
package/dist-cli/run-build.d.ts
CHANGED
|
@@ -2,9 +2,15 @@
|
|
|
2
2
|
export interface RunBuildOptions {
|
|
3
3
|
/** Download web fonts from known font hosts into the site. */
|
|
4
4
|
vendorFonts: boolean;
|
|
5
|
+
/** Build the live preview into the site as `__live/`. */
|
|
6
|
+
live: boolean;
|
|
7
|
+
/** The project root of the live preview. Default: the manifest's `root` and `configFile`. */
|
|
8
|
+
root?: string;
|
|
5
9
|
}
|
|
6
10
|
/**
|
|
7
11
|
* Produce a self-contained static site: the viewer plus the data directory
|
|
8
|
-
* under `__data
|
|
12
|
+
* under `__data/`, and with `live` the live preview under `__live/`. A live
|
|
13
|
+
* build that fails stops the command. Font vendoring works on the copy of the
|
|
14
|
+
* data and never fails the build.
|
|
9
15
|
*/
|
|
10
16
|
export declare function runBuild(data: string, out: string, options: RunBuildOptions): Promise<void>;
|
package/dist-cli/run-build.js
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
2
|
import { join, resolve } from 'node:path';
|
|
3
3
|
import { build } from 'vite';
|
|
4
|
+
import { buildLivePreview } from './build-live-preview.js';
|
|
4
5
|
import { fontCacheDir } from './font-cache-dir.js';
|
|
5
6
|
import { printVendorReport } from './print-vendor-report.js';
|
|
6
7
|
import { vendorFonts } from './vendor-fonts.js';
|
|
7
8
|
import { viewerRoot } from './viewer-root.js';
|
|
8
9
|
/**
|
|
9
10
|
* Produce a self-contained static site: the viewer plus the data directory
|
|
10
|
-
* under `__data
|
|
11
|
+
* under `__data/`, and with `live` the live preview under `__live/`. A live
|
|
12
|
+
* build that fails stops the command. Font vendoring works on the copy of the
|
|
13
|
+
* data and never fails the build.
|
|
11
14
|
*/
|
|
12
15
|
export async function runBuild(data, out, options) {
|
|
13
16
|
process.env.DESCRIBE_ME_DIR = resolve(data);
|
|
@@ -17,6 +20,10 @@ export async function runBuild(data, out, options) {
|
|
|
17
20
|
configFile: resolve(root, 'vite.config.ts'),
|
|
18
21
|
build: { outDir: resolve(out), emptyOutDir: true },
|
|
19
22
|
});
|
|
23
|
+
// After the viewer, whose build empties `out` and so removes an old `__live/` and `__live.json`.
|
|
24
|
+
if (options.live) {
|
|
25
|
+
await buildLivePreview(data, out, options.root);
|
|
26
|
+
}
|
|
20
27
|
if (!options.vendorFonts) {
|
|
21
28
|
return;
|
|
22
29
|
}
|