forgebook 0.2.0 → 0.3.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 +112 -5
- package/dist/bin/forgebook/agentLoopCommands.mjs +3 -0
- package/dist/bin/forgebook/agentText.d.ts +2 -2
- package/dist/bin/forgebook/agentText.js +15 -13
- package/dist/bin/forgebook/agents.md +21 -6
- package/dist/bin/forgebook/mcpTools.js +1 -1
- package/dist/bin/forgebook/newStoriesFile.js +7 -2
- package/dist/bin/forgebook-docs/collectDocs.js +1 -1
- package/dist/src/argsProps.d.ts +32 -0
- package/dist/src/argsProps.js +69 -0
- package/dist/src/catalogue.d.ts +48 -5
- package/dist/src/catalogue.js +47 -8
- package/dist/src/frame/mountFrame.js +26 -7
- package/dist/src/frame/touchCaret.d.ts +43 -0
- package/dist/src/frame/touchCaret.js +195 -0
- package/dist/src/frame/touchEvents.d.ts +3 -2
- package/dist/src/frame/touchEvents.js +17 -18
- package/dist/src/frame/touchLayer.js +48 -7
- package/dist/src/propGroups.d.ts +66 -3
- package/dist/src/propGroups.js +67 -13
- package/dist/src/shell/ApiReference.d.ts +9 -4
- package/dist/src/shell/ApiReference.js +47 -39
- package/dist/src/shell/AudienceSections.js +1 -1
- package/dist/src/shell/ComponentPage.d.ts +5 -2
- package/dist/src/shell/ComponentPage.js +4 -18
- package/dist/src/shell/DocBlocks.js +1 -1
- package/dist/src/shell/PropRows.d.ts +42 -0
- package/dist/src/shell/PropRows.js +60 -0
- package/dist/src/shell/PropsTab.d.ts +14 -10
- package/dist/src/shell/PropsTab.js +18 -58
- package/dist/src/shell/RequiredFirstSwitch.d.ts +16 -0
- package/dist/src/shell/RequiredFirstSwitch.js +8 -0
- package/dist/src/shell/Stage.js +11 -23
- package/dist/src/shell/StoryCoverage.js +4 -1
- package/dist/src/shell/docPage.css +6 -0
- package/dist/src/shell/dockTabs.d.ts +7 -5
- package/dist/src/shell/dockTabs.js +4 -4
- package/dist/src/shell/reportedPanel.d.ts +12 -0
- package/dist/src/shell/reportedPanel.js +19 -0
- package/dist/src/shell/shell.css +56 -1
- package/dist/src/shell/strings.d.ts +2 -0
- package/dist/src/shell/strings.js +2 -0
- package/dist/src/shell/usePanelDocs.d.ts +11 -0
- package/dist/src/shell/usePanelDocs.js +29 -0
- package/dist/src/shell/useRequiredFirst.d.ts +27 -0
- package/dist/src/shell/useRequiredFirst.js +67 -0
- package/dist/src/storyCheck.d.ts +2 -0
- package/dist/src/storyCheck.js +2 -1
- package/dist/src/storyGlobals.js +2 -1
- package/dist/src/storyTree.d.ts +11 -1
- package/dist/src/storyTree.js +3 -1
- package/dist/src/viewState.d.ts +7 -0
- package/dist/src/viewState.js +13 -0
- package/dist/src/vite/agent.types.d.ts +12 -0
- package/dist/src/vite/agentApi.js +6 -1
- package/dist/src/vite/agentRoutes.js +18 -4
- package/dist/src/vite/builtTexts.d.ts +2 -2
- package/dist/src/vite/builtTexts.js +7 -6
- package/dist/src/vite/configFile.d.ts +3 -2
- package/dist/src/vite/configFile.js +63 -14
- package/dist/src/vite/docPagesModule.d.ts +10 -7
- package/dist/src/vite/docPagesModule.js +14 -11
- package/dist/src/vite/docsExtractor.d.ts +6 -0
- package/dist/src/vite/docsExtractor.js +13 -0
- package/dist/src/vite/fileAccess.d.ts +51 -0
- package/dist/src/vite/fileAccess.js +109 -0
- package/dist/src/vite/forgebookPlugin.d.ts +1 -1
- package/dist/src/vite/forgebookPlugin.js +61 -96
- package/dist/src/vite/framesServer.js +21 -5
- package/dist/src/vite/generatedModules.d.ts +23 -0
- package/dist/src/vite/generatedModules.js +58 -0
- package/dist/src/vite/hotSnippets.js +7 -3
- package/dist/src/vite/indexStoryFile.d.ts +2 -2
- package/dist/src/vite/indexStoryFile.js +2 -2
- package/dist/src/vite/indexedParameters.d.ts +1 -1
- package/dist/src/vite/indexedParameters.js +21 -1
- package/dist/src/vite/originGuard.d.ts +14 -4
- package/dist/src/vite/originGuard.js +15 -6
- package/dist/src/vite/pages.d.ts +3 -2
- package/dist/src/vite/pages.js +23 -16
- package/dist/src/vite/pluginIds.d.ts +9 -2
- package/dist/src/vite/pluginIds.js +21 -6
- package/dist/src/vite/pluginModules.d.ts +39 -10
- package/dist/src/vite/pluginModules.js +67 -22
- package/dist/src/vite/prebundled.js +2 -1
- package/dist/src/vite/searchModule.d.ts +6 -4
- package/dist/src/vite/searchModule.js +8 -7
- package/dist/src/vite/storyFilesModule.d.ts +59 -27
- package/dist/src/vite/storyFilesModule.js +88 -58
- package/dist/src/vite/storyPatterns.d.ts +39 -0
- package/dist/src/vite/storyPatterns.js +100 -0
- package/dist/src/vite/textQueries.d.ts +7 -1
- package/dist/src/vite/textQueries.js +7 -1
- package/package.json +2 -2
|
@@ -1,72 +1,95 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The module that answers a `?forgebook-files` import
|
|
3
|
-
*
|
|
4
|
-
* the group's "Used in"
|
|
2
|
+
* The module that answers a group of story files: a `?forgebook-files` import's pattern, or a catalogue's `stories`
|
|
3
|
+
* list (`FILES_SPECIFIER_PREFIX`). Every story file the group lists, as one module with each file's index (read from
|
|
4
|
+
* its source, never run), the loaders of its module, its component's docs and its code, and the group's "Used in"
|
|
5
|
+
* loader.
|
|
5
6
|
*
|
|
6
7
|
* @remarks
|
|
7
|
-
* One module per
|
|
8
|
-
* one, a few hundred requests per frame.
|
|
8
|
+
* One module per group, instead of an import per file: in development the browser fetched every index module one by
|
|
9
|
+
* one, a few hundred requests per frame. A catalogue's `stories` are one group, not one per pattern: its `!` patterns
|
|
10
|
+
* leave files out of what all the others list, a file two patterns list is listed once, and "Used in" joins stories
|
|
11
|
+
* across every pattern. The keys are the files' paths relative to the declaring module
|
|
9
12
|
* (`../src/x.stories.tsx`), the same keys `storyFileKeys` gives the tests that read the files from the disk. A build
|
|
10
13
|
* takes each file's docs, code and page from the group's texts module, written as data, instead of their loaders'
|
|
11
14
|
* imports (`builtTexts.ts`).
|
|
12
15
|
*
|
|
13
16
|
* @module
|
|
14
17
|
*/
|
|
15
|
-
import {
|
|
18
|
+
import { readFileSync } from "node:fs";
|
|
16
19
|
import { codeLiteral } from "../codeLiteral.js";
|
|
17
20
|
import { autoTitle, patternBase } from "./autoTitle.js";
|
|
18
21
|
import { isSettledSince, nowNs, stampPaths } from "./diskCache.js";
|
|
19
22
|
import { indexStoryFile } from "./indexStoryFile.js";
|
|
23
|
+
import { isNegated, listedFiles } from "./storyPatterns.js";
|
|
20
24
|
import { VITE_PATHS } from "./vitePaths.js";
|
|
21
25
|
/** Between a group's parts in a module's id: no path or pattern holds it. */
|
|
22
|
-
const GROUP_SEPARATOR = "::";
|
|
26
|
+
export const GROUP_SEPARATOR = "::";
|
|
27
|
+
/** Before a pattern's count of climbed folders in a module's id, when it is a `!` pattern. */
|
|
28
|
+
const NEGATED_MARK = "!";
|
|
23
29
|
/**
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* before it (`app::../storybook`: the folder and the separator), so the pattern's leading `../` are counted, never
|
|
27
|
-
* written. The files keep their keys relative to the declaring module (`../src/x.stories.tsx`), unique in a source and
|
|
28
|
-
* the testing package's too.
|
|
30
|
+
* One pattern written for a module's id: whether it leaves files out and how many folders it climbs, then the rest of
|
|
31
|
+
* it. The mark goes with the count, never with the rest, which may start with an extglob's `!(…)`.
|
|
29
32
|
*
|
|
30
|
-
* @param folder - the declaring module's folder.
|
|
31
33
|
* @param pattern - the pattern as the import writes it (`../src/**\/*.stories.tsx`).
|
|
32
34
|
*/
|
|
33
|
-
|
|
34
|
-
const
|
|
35
|
+
const patternParts = (pattern) => {
|
|
36
|
+
const isLeftOut = isNegated(pattern);
|
|
37
|
+
const segments = (isLeftOut ? pattern.slice(1) : pattern).split("/");
|
|
35
38
|
const firstOwn = segments.findIndex((segment) => segment !== "." && segment !== "..");
|
|
36
39
|
const climbed = firstOwn === -1 ? segments : segments.slice(0, firstOwn);
|
|
37
40
|
const own = firstOwn === -1 ? [] : segments.slice(firstOwn);
|
|
38
41
|
const up = climbed.filter((segment) => segment === "..").length;
|
|
39
|
-
return [
|
|
42
|
+
return [`${isLeftOut ? NEGATED_MARK : ""}${up}`, own.join("/")];
|
|
40
43
|
};
|
|
44
|
+
/**
|
|
45
|
+
* A group of story files written for a module's id: the folder, then for each pattern how many folders it climbs and
|
|
46
|
+
* the rest of it. A Vite module's id is served under a URL path, whose normalisation would let a `..` remove the part
|
|
47
|
+
* before it (`app::../storybook`: the folder and the separator), so a pattern's leading `../` are counted, never
|
|
48
|
+
* written. The files keep their keys relative to the declaring module (`../src/x.stories.tsx`), unique in a source and
|
|
49
|
+
* the testing package's too.
|
|
50
|
+
*
|
|
51
|
+
* @param folder - the declaring module's folder.
|
|
52
|
+
* @param patterns - the patterns as the import or the configuration writes them (`../src/**\/*.stories.tsx`).
|
|
53
|
+
*/
|
|
54
|
+
export const storyFilesGroupText = (folder, patterns) => [folder, ...patterns.flatMap(patternParts)].join(GROUP_SEPARATOR);
|
|
41
55
|
/**
|
|
42
56
|
* A group read back from a module's id (`storyFilesGroupText`).
|
|
43
57
|
*
|
|
44
58
|
* @param text - the id's group part.
|
|
45
59
|
*/
|
|
46
60
|
export const storyFilesGroupOf = (text) => {
|
|
47
|
-
const [root = "",
|
|
48
|
-
|
|
61
|
+
const [root = "", ...parts] = text.split(GROUP_SEPARATOR);
|
|
62
|
+
const patterns = [];
|
|
63
|
+
for (let at = 0; at < parts.length; at += 2) {
|
|
64
|
+
const count = parts[at] ?? "0";
|
|
65
|
+
const isLeftOut = count.startsWith(NEGATED_MARK);
|
|
66
|
+
const up = Number(isLeftOut ? count.slice(NEGATED_MARK.length) : count);
|
|
67
|
+
patterns.push(`${isLeftOut ? "!" : ""}${"../".repeat(up)}${parts[at + 1] ?? ""}`);
|
|
68
|
+
}
|
|
69
|
+
return { root, patterns };
|
|
49
70
|
};
|
|
50
|
-
/**
|
|
51
|
-
const
|
|
71
|
+
/** Every file a group lists: a build's, and a test's that reads the files from the disk. */
|
|
72
|
+
export const EVERY_FILE = () => true;
|
|
52
73
|
/**
|
|
53
|
-
*
|
|
54
|
-
*
|
|
74
|
+
* The story files patterns list, as paths relative to `root` starting with `./` or `../`, each once, sorted: the union
|
|
75
|
+
* of the patterns less what the `!` ones match (`listedFiles`).
|
|
55
76
|
*
|
|
56
|
-
* @param
|
|
77
|
+
* @param root - the folder the patterns are relative to: the declaring module's.
|
|
78
|
+
* @param patterns - the patterns (`../src/**\/*.stories.tsx`, `!../src/**\/legacy/*`).
|
|
79
|
+
* @param isKept - whether a listed file may be read; every one by default.
|
|
80
|
+
* @throws when every pattern is a `!` one.
|
|
57
81
|
*/
|
|
58
|
-
const
|
|
82
|
+
export const storyFileKeys = (root, patterns, isKept = EVERY_FILE) => listedFiles(root, patterns)
|
|
83
|
+
.map((file) => file.key)
|
|
84
|
+
.filter((key) => isKept(VITE_PATHS.resolve(root, key)));
|
|
59
85
|
/**
|
|
60
|
-
* The
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
* @param patterns - the patterns (`../src/**\/*.stories.tsx`).
|
|
86
|
+
* The specifier prefix of a group of story files named by its patterns, the group following (`storyFilesGroupText`):
|
|
87
|
+
* how the module generated for a catalogue's `stories` imports them as one group, `!` patterns included, which a
|
|
88
|
+
* `?forgebook-files` import of one pattern cannot say.
|
|
64
89
|
*/
|
|
65
|
-
export const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
.map((file) => VITE_PATHS.relativeKey(root, file))
|
|
69
|
-
.sort();
|
|
90
|
+
export const FILES_SPECIFIER_PREFIX = "virtual:forgebook-files:";
|
|
91
|
+
/** The specifier prefix of a group of docs pages named by its patterns, the same way: a catalogue's `docs`. */
|
|
92
|
+
export const DOCS_PAGES_SPECIFIER_PREFIX = "virtual:forgebook-docs-pages:";
|
|
70
93
|
/**
|
|
71
94
|
* The id prefix of a group's "Used in" module: the group follows (`storyFilesGroupText`), as in a `?forgebook-files`
|
|
72
95
|
* module's id. The plugin answers it (`usageModule.ts`) only when a page asks.
|
|
@@ -89,14 +112,14 @@ export const TEXTS_PREFIX = "virtual:forgebook-texts:";
|
|
|
89
112
|
* it, so a page that shows "Used in" never downloads it.
|
|
90
113
|
*/
|
|
91
114
|
export const REACH_PREFIX = "virtual:forgebook-reach:";
|
|
92
|
-
/** Whether what a cache file holds is a
|
|
115
|
+
/** Whether what a cache file holds is a group's index. */
|
|
93
116
|
export const isKeptIndex = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
94
117
|
/**
|
|
95
|
-
* One story file a pattern
|
|
118
|
+
* One story file a pattern lists, indexed from its text on disk, with the title its path makes when it writes none.
|
|
96
119
|
*
|
|
97
120
|
* @internal
|
|
98
121
|
* @param root - the declaring module's folder.
|
|
99
|
-
* @param pattern - the pattern.
|
|
122
|
+
* @param pattern - the pattern that lists it.
|
|
100
123
|
* @param key - the file's path relative to the declaring module.
|
|
101
124
|
* @returns the index, or a message saying why the file cannot be indexed.
|
|
102
125
|
*/
|
|
@@ -106,28 +129,30 @@ export const storyFileIndex = (root, pattern, key) => {
|
|
|
106
129
|
return indexStoryFile(file, readFileSync(file, "utf8"), title === undefined ? {} : { autoTitle: title });
|
|
107
130
|
};
|
|
108
131
|
/**
|
|
109
|
-
* Every story file a
|
|
132
|
+
* Every story file a group's patterns list, indexed.
|
|
110
133
|
*
|
|
111
134
|
* @param root - the declaring module's folder.
|
|
112
|
-
* @param
|
|
135
|
+
* @param patterns - the patterns (`storyFileKeys`).
|
|
113
136
|
* @param watch - adds a file whose edit renews the module that read it.
|
|
114
137
|
* @param cache - the index kept between starts, which holds while the files it came from do; none reads them all.
|
|
138
|
+
* @param isKept - whether a listed file may be read; every one by default.
|
|
115
139
|
* @throws when a story file cannot be indexed.
|
|
116
140
|
*/
|
|
117
|
-
export const indexedStoryFiles = (root,
|
|
118
|
-
const name = storyFilesGroupText(VITE_PATHS.normalize(root),
|
|
141
|
+
export const indexedStoryFiles = (root, patterns, watch, cache, isKept = EVERY_FILE) => {
|
|
142
|
+
const name = storyFilesGroupText(VITE_PATHS.normalize(root), patterns);
|
|
119
143
|
const since = nowNs();
|
|
120
144
|
const kept = cache?.read(name);
|
|
121
|
-
const
|
|
145
|
+
const listed = listedFiles(root, patterns).filter(({ key }) => isKept(VITE_PATHS.resolve(root, key)));
|
|
146
|
+
const files = listed.map(({ key, pattern }) => {
|
|
122
147
|
const file = VITE_PATHS.resolve(root, key);
|
|
123
148
|
watch(file);
|
|
124
149
|
const known = kept?.[key];
|
|
125
150
|
if (known !== undefined)
|
|
126
|
-
return { key, file, index: known };
|
|
151
|
+
return { key, pattern, file, index: known };
|
|
127
152
|
const index = storyFileIndex(root, pattern, key);
|
|
128
153
|
if (typeof index === "string")
|
|
129
154
|
throw new Error(index);
|
|
130
|
-
return { key, file, index };
|
|
155
|
+
return { key, pattern, file, index };
|
|
131
156
|
});
|
|
132
157
|
// kept again when anything was read anew: a file added, or the kept index out of date
|
|
133
158
|
if (cache !== undefined && files.some(({ key }) => kept?.[key] === undefined)) {
|
|
@@ -139,35 +164,35 @@ export const indexedStoryFiles = (root, pattern, watch, cache) => {
|
|
|
139
164
|
return files;
|
|
140
165
|
};
|
|
141
166
|
/**
|
|
142
|
-
* The code of the module for one
|
|
167
|
+
* The code of the module for one group.
|
|
143
168
|
*
|
|
144
169
|
* @param root - the declaring module's folder.
|
|
145
|
-
* @param
|
|
170
|
+
* @param patterns - the group's patterns.
|
|
146
171
|
* @param watch - adds a file whose edit renews the module (each story file: its index is in the module).
|
|
147
|
-
* @param loaders - the group's loaders besides its story files';
|
|
172
|
+
* @param loaders - the group's loaders besides its story files'; all of them by default.
|
|
148
173
|
* @param cache - the index kept between starts.
|
|
149
174
|
* @returns the module's code, or a message saying which file cannot be indexed.
|
|
150
175
|
*/
|
|
151
|
-
export const storyFilesModule = (root,
|
|
176
|
+
export const storyFilesModule = (root, patterns, watch, loaders = { hasUsage: true, hasReach: true }, cache) => storyFilesCode(root, patterns, indexedStoryFiles(root, patterns, watch, cache), loaders);
|
|
152
177
|
/**
|
|
153
|
-
* The code of the module for one
|
|
178
|
+
* The code of the module for one group, from its story files indexed.
|
|
154
179
|
*
|
|
155
180
|
* @internal
|
|
156
181
|
* @param root - the declaring module's folder.
|
|
157
|
-
* @param
|
|
158
|
-
* @param files - the story files it
|
|
182
|
+
* @param patterns - the group's patterns.
|
|
183
|
+
* @param files - the story files it lists, indexed.
|
|
159
184
|
* @param loaders - the group's loaders besides its story files'.
|
|
160
185
|
* @param hasTexts - a build's: whether each file's docs, code and page come from the group's texts module, in the
|
|
161
186
|
* rows' order (`TEXTS_PREFIX`), and not each from its query.
|
|
162
187
|
*/
|
|
163
|
-
export const storyFilesCode = (root,
|
|
164
|
-
const { hasUsage, hasReach } = loaders;
|
|
188
|
+
export const storyFilesCode = (root, patterns, files, loaders, hasTexts = false) => {
|
|
189
|
+
const { hasDocs = true, hasUsage, hasReach } = loaders;
|
|
165
190
|
// the group's id in Vite's form, as the plugin names the declaring module's folder
|
|
166
191
|
const folder = VITE_PATHS.normalize(root);
|
|
167
|
-
const usage = codeLiteral(`${USAGE_PREFIX}${storyFilesGroupText(folder,
|
|
168
|
-
const search = codeLiteral(`${SEARCH_PREFIX}${storyFilesGroupText(folder,
|
|
169
|
-
const reach = codeLiteral(`${REACH_PREFIX}${storyFilesGroupText(folder,
|
|
170
|
-
const texts = codeLiteral(`${TEXTS_PREFIX}${storyFilesGroupText(folder,
|
|
192
|
+
const usage = codeLiteral(`${USAGE_PREFIX}${storyFilesGroupText(folder, patterns)}`);
|
|
193
|
+
const search = codeLiteral(`${SEARCH_PREFIX}${storyFilesGroupText(folder, patterns)}`);
|
|
194
|
+
const reach = codeLiteral(`${REACH_PREFIX}${storyFilesGroupText(folder, patterns)}`);
|
|
195
|
+
const texts = codeLiteral(`${TEXTS_PREFIX}${storyFilesGroupText(folder, patterns)}`);
|
|
171
196
|
// absolute paths in Vite's form: the module has no folder of its own, Vite serves them under /@fs/ in development,
|
|
172
197
|
// and the queries' ids come back to the plugin as written here
|
|
173
198
|
const absolute = (key) => VITE_PATHS.resolve(folder, key);
|
|
@@ -175,7 +200,12 @@ export const storyFilesCode = (root, pattern, files, loaders, hasTexts = false)
|
|
|
175
200
|
const query = (key, name) => `() => import(${codeLiteral(`${absolute(key)}?${name}`)}).then((module) => module.default)`;
|
|
176
201
|
const textLoaders = (key) => hasTexts
|
|
177
202
|
? []
|
|
178
|
-
:
|
|
203
|
+
: // the docs' place is kept without them, so every row's columns stay where the module reads them
|
|
204
|
+
[
|
|
205
|
+
hasDocs ? query(key, "forgebook-docs") : "null",
|
|
206
|
+
query(key, "forgebook-code"),
|
|
207
|
+
query(key, "forgebook-page")
|
|
208
|
+
];
|
|
179
209
|
// a row per file, its name written once and not once per column (its index, each loader): a large catalogue's
|
|
180
210
|
// names are a tenth of its index
|
|
181
211
|
const rows = files.map(({ key, index }) => [codeLiteral(key), codeLiteral(index), `() => import(${path(key)})`, ...textLoaders(key)].join(", "));
|
|
@@ -188,7 +218,7 @@ export const storyFilesCode = (root, pattern, files, loaders, hasTexts = false)
|
|
|
188
218
|
"export default {",
|
|
189
219
|
"index: column(1),",
|
|
190
220
|
"stories: column(2),",
|
|
191
|
-
"docs: column(3),",
|
|
221
|
+
hasDocs ? "docs: column(3)," : "docs: {},",
|
|
192
222
|
"code: column(4),",
|
|
193
223
|
"pages: column(5),",
|
|
194
224
|
`search: () => import(${search}).then((module) => module.default),`,
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { type VitePaths } from "./vitePaths.ts";
|
|
2
|
+
/**
|
|
3
|
+
* Whether a pattern leaves files out (`!src/**\/legacy/*`) rather than listing them. One that starts with an extglob's
|
|
4
|
+
* `!(…)` lists the names its group does not match, as fast-glob reads it.
|
|
5
|
+
*
|
|
6
|
+
* @param pattern - the pattern.
|
|
7
|
+
*/
|
|
8
|
+
export declare const isNegated: (pattern: string) => boolean;
|
|
9
|
+
/**
|
|
10
|
+
* Why a list of patterns lists nothing by construction, or none: every pattern in it is a `!` one, which only leaves
|
|
11
|
+
* files out of what the others list.
|
|
12
|
+
*
|
|
13
|
+
* @param patterns - the patterns.
|
|
14
|
+
*/
|
|
15
|
+
export declare const patternsProblem: (patterns: readonly string[]) => string | undefined;
|
|
16
|
+
/**
|
|
17
|
+
* The paths one pattern matches, as Node's `globSync` gives them (backslashes on Windows), relative to the folder.
|
|
18
|
+
*
|
|
19
|
+
* @param root - the folder the pattern is relative to.
|
|
20
|
+
* @param pattern - the pattern, without a `!`.
|
|
21
|
+
*/
|
|
22
|
+
export type FindFiles = (root: string, pattern: string) => readonly string[];
|
|
23
|
+
/** A file a list of patterns lists, with the pattern that lists it. */
|
|
24
|
+
export type ListedFile = {
|
|
25
|
+
/** Its path relative to the patterns' folder, starting with `./` or `../`, with forward slashes. */
|
|
26
|
+
readonly key: string;
|
|
27
|
+
/** The first pattern that lists it: its static start begins the file's title when its meta writes none. */
|
|
28
|
+
readonly pattern: string;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* The files a list of patterns lists, each once, sorted by key.
|
|
32
|
+
*
|
|
33
|
+
* @param root - the folder the patterns are relative to: the declaring module's.
|
|
34
|
+
* @param patterns - the patterns (`../src/**\/*.stories.tsx`, `!../src/**\/legacy/*`), in any order.
|
|
35
|
+
* @param find - what matches one pattern; the disk's by default, another system's in a test.
|
|
36
|
+
* @param paths - the path functions; the running system's unless a test names another.
|
|
37
|
+
* @throws when every pattern is a `!` one (`patternsProblem`).
|
|
38
|
+
*/
|
|
39
|
+
export declare const listedFiles: (root: string, patterns: readonly string[], find?: FindFiles, paths?: VitePaths) => ListedFile[];
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The files a list of patterns names, as Vite's `import.meta.glob` and fast-glob read one: the union of its patterns,
|
|
3
|
+
* less what its `!` patterns match, whatever their order. Used for a catalogue's story files and docs pages, by the
|
|
4
|
+
* plugin and by the testing package, so both list the same files.
|
|
5
|
+
*
|
|
6
|
+
* @remarks
|
|
7
|
+
* A `!` pattern is matched by the same engine as the others (Node's `globSync`), from the same folder, so it follows
|
|
8
|
+
* the same syntax: `!src/**\/legacy/*.stories.tsx` leaves out what `src/**\/legacy/*.stories.tsx` would list, and one
|
|
9
|
+
* that matches a folder (`!src/drafts`) leaves out every file in it, as fast-glob does. A file two
|
|
10
|
+
* patterns list is listed once, by the first, whose static start begins its title (as Storybook's first specifier
|
|
11
|
+
* does). A list of `!` patterns alone lists nothing, which is a mistake, never what its author meant: it is refused.
|
|
12
|
+
*
|
|
13
|
+
* @module
|
|
14
|
+
*/
|
|
15
|
+
import { globSync } from "node:fs";
|
|
16
|
+
import { VITE_PATHS } from "./vitePaths.js";
|
|
17
|
+
/** What starts a pattern that leaves files out. */
|
|
18
|
+
const NEGATION = "!";
|
|
19
|
+
/**
|
|
20
|
+
* Whether a pattern leaves files out (`!src/**\/legacy/*`) rather than listing them. One that starts with an extglob's
|
|
21
|
+
* `!(…)` lists the names its group does not match, as fast-glob reads it.
|
|
22
|
+
*
|
|
23
|
+
* @param pattern - the pattern.
|
|
24
|
+
*/
|
|
25
|
+
export const isNegated = (pattern) => pattern.startsWith(NEGATION) && !pattern.startsWith(`${NEGATION}(`);
|
|
26
|
+
/**
|
|
27
|
+
* Why a list of patterns lists nothing by construction, or none: every pattern in it is a `!` one, which only leaves
|
|
28
|
+
* files out of what the others list.
|
|
29
|
+
*
|
|
30
|
+
* @param patterns - the patterns.
|
|
31
|
+
*/
|
|
32
|
+
export const patternsProblem = (patterns) => patterns.length > 0 && patterns.every(isNegated)
|
|
33
|
+
? `every pattern is a \`!\` one (${patterns.map((pattern) => `"${pattern}"`).join(", ")}), which only leaves files out of what the others list: add a pattern that lists files beside them`
|
|
34
|
+
: undefined;
|
|
35
|
+
/** The folder of a project's dependencies. */
|
|
36
|
+
const DEPENDENCIES = "node_modules";
|
|
37
|
+
/**
|
|
38
|
+
* What a pattern leaves out: the dependencies, unless it names them. A `**` from a monorepo's root would otherwise walk
|
|
39
|
+
* every package's, seconds on the server's thread for files nobody asked for, as Storybook's own globs skip them.
|
|
40
|
+
*
|
|
41
|
+
* @param pattern - the pattern.
|
|
42
|
+
*/
|
|
43
|
+
const excludedFrom = (pattern) => pattern.includes(DEPENDENCIES) ? () => false : (name) => name === DEPENDENCIES;
|
|
44
|
+
/** The files a pattern matches on the disk, the dependencies left out unless it names them. */
|
|
45
|
+
const globbed = (root, pattern) => globSync(pattern, { cwd: root, exclude: excludedFrom(pattern) });
|
|
46
|
+
/**
|
|
47
|
+
* Whether a file is left out: a `!` pattern matched it, or one of the folders it is in (`!src/drafts`), as fast-glob
|
|
48
|
+
* leaves out a folder's every file.
|
|
49
|
+
*
|
|
50
|
+
* @param key - the file's key (`./src/drafts/x.stories.tsx`).
|
|
51
|
+
* @param left - the keys of the files and folders the `!` patterns matched.
|
|
52
|
+
*/
|
|
53
|
+
const isLeftOut = (key, left) => {
|
|
54
|
+
const segments = key.split("/");
|
|
55
|
+
return segments.some((_segment, at) => at > 0 && left.has(segments.slice(0, at + 1).join("/")));
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* A pattern without its trailing slashes (`src/drafts/`): the glob gives the folder it names without them. A loop,
|
|
59
|
+
* not a regular expression, whose backtracking a long run of slashes would make quadratic.
|
|
60
|
+
*
|
|
61
|
+
* @param pattern - the pattern.
|
|
62
|
+
*/
|
|
63
|
+
const withoutTrailingSlashes = (pattern) => {
|
|
64
|
+
let end = pattern.length;
|
|
65
|
+
while (end > 0 && pattern[end - 1] === "/")
|
|
66
|
+
end -= 1;
|
|
67
|
+
return pattern.slice(0, end);
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* The files a list of patterns lists, each once, sorted by key.
|
|
71
|
+
*
|
|
72
|
+
* @param root - the folder the patterns are relative to: the declaring module's.
|
|
73
|
+
* @param patterns - the patterns (`../src/**\/*.stories.tsx`, `!../src/**\/legacy/*`), in any order.
|
|
74
|
+
* @param find - what matches one pattern; the disk's by default, another system's in a test.
|
|
75
|
+
* @param paths - the path functions; the running system's unless a test names another.
|
|
76
|
+
* @throws when every pattern is a `!` one (`patternsProblem`).
|
|
77
|
+
*/
|
|
78
|
+
export const listedFiles = (root, patterns, find = globbed, paths = VITE_PATHS) => {
|
|
79
|
+
const problem = patternsProblem(patterns);
|
|
80
|
+
if (problem !== undefined)
|
|
81
|
+
throw new Error(problem);
|
|
82
|
+
// with forward slashes on every system, so a file has the same key wherever the catalogue runs
|
|
83
|
+
const keysOf = (pattern) => find(root, pattern).map((file) => paths.relativeKey(root, file));
|
|
84
|
+
const listing = patterns.filter((each) => !isNegated(each));
|
|
85
|
+
// a `!` pattern that names the dependencies leaves out only what a listing one names there: walked otherwise, it
|
|
86
|
+
// would read every package's folder for files no pattern lists
|
|
87
|
+
const isWalkedFor = (pattern) => !pattern.includes(DEPENDENCIES) || listing.some((each) => each.includes(DEPENDENCIES));
|
|
88
|
+
const left = new Set(patterns
|
|
89
|
+
.filter((each) => isNegated(each) && isWalkedFor(each))
|
|
90
|
+
.flatMap((pattern) => keysOf(withoutTrailingSlashes(pattern.slice(NEGATION.length)))));
|
|
91
|
+
const listed = new Map();
|
|
92
|
+
for (const pattern of listing)
|
|
93
|
+
for (const key of keysOf(pattern))
|
|
94
|
+
if (!isLeftOut(key, left) && !listed.has(key))
|
|
95
|
+
listed.set(key, pattern);
|
|
96
|
+
// by code unit, as a plain `sort()` orders the keys: the keys are unique
|
|
97
|
+
return [...listed]
|
|
98
|
+
.sort(([first], [second]) => (first < second ? -1 : 1))
|
|
99
|
+
.map(([key, pattern]) => ({ key, pattern }));
|
|
100
|
+
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The queries the plugin answers with a module made from a file
|
|
2
|
+
* The queries the plugin answers with a module made from a file: a story file's docs and code, its component's page, a
|
|
3
3
|
* source's Markdown, a docs page.
|
|
4
4
|
*
|
|
5
5
|
* @remarks
|
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
*
|
|
9
9
|
* @module
|
|
10
10
|
*/
|
|
11
|
+
/** The query that asks for a story file's component docs. */
|
|
12
|
+
export declare const DOCS_QUERY = "?forgebook-docs";
|
|
11
13
|
/** The query that asks for a story file's code: each story's `render` as written. */
|
|
12
14
|
export declare const CODE_QUERY = "?forgebook-code";
|
|
13
15
|
/** The query that asks for a story file's component page: its documentation file as HTML. */
|
|
@@ -18,3 +20,7 @@ export declare const MARKDOWN_QUERY = "?forgebook-markdown";
|
|
|
18
20
|
export declare const DOCS_PAGE_QUERY = "?forgebook-docs-page";
|
|
19
21
|
/** The query that asks for the written home (`forgebook.home.md`), rendered: its file, then this. */
|
|
20
22
|
export declare const HOME_QUERY = "?forgebook-home";
|
|
23
|
+
/** The queries `textModules.ts` answers. */
|
|
24
|
+
export declare const TEXT_QUERIES: readonly string[];
|
|
25
|
+
/** The queries of a story file's modules the shell reads, which hand an edit to it in place on a dev server. */
|
|
26
|
+
export declare const RENEWED_QUERIES: readonly string[];
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The queries the plugin answers with a module made from a file
|
|
2
|
+
* The queries the plugin answers with a module made from a file: a story file's docs and code, its component's page, a
|
|
3
3
|
* source's Markdown, a docs page.
|
|
4
4
|
*
|
|
5
5
|
* @remarks
|
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
*
|
|
9
9
|
* @module
|
|
10
10
|
*/
|
|
11
|
+
/** The query that asks for a story file's component docs. */
|
|
12
|
+
export const DOCS_QUERY = "?forgebook-docs";
|
|
11
13
|
/** The query that asks for a story file's code: each story's `render` as written. */
|
|
12
14
|
export const CODE_QUERY = "?forgebook-code";
|
|
13
15
|
/** The query that asks for a story file's component page: its documentation file as HTML. */
|
|
@@ -18,3 +20,7 @@ export const MARKDOWN_QUERY = "?forgebook-markdown";
|
|
|
18
20
|
export const DOCS_PAGE_QUERY = "?forgebook-docs-page";
|
|
19
21
|
/** The query that asks for the written home (`forgebook.home.md`), rendered: its file, then this. */
|
|
20
22
|
export const HOME_QUERY = "?forgebook-home";
|
|
23
|
+
/** The queries `textModules.ts` answers. */
|
|
24
|
+
export const TEXT_QUERIES = [CODE_QUERY, PAGE_QUERY, MARKDOWN_QUERY];
|
|
25
|
+
/** The queries of a story file's modules the shell reads, which hand an edit to it in place on a dev server. */
|
|
26
|
+
export const RENEWED_QUERIES = [DOCS_QUERY, CODE_QUERY, PAGE_QUERY];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "forgebook",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "See every component in every theme, contrast, language and width: CSF stories, one frame per story, and a props table from the components' types.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react",
|
|
@@ -240,7 +240,7 @@
|
|
|
240
240
|
"lightningcss": "1.33.0",
|
|
241
241
|
"marked": "18.1.0",
|
|
242
242
|
"typescript-6": "npm:typescript@6.0.3",
|
|
243
|
-
"@forgebook/story-format": "0.2.
|
|
243
|
+
"@forgebook/story-format": "0.2.1"
|
|
244
244
|
},
|
|
245
245
|
"peerDependencies": {
|
|
246
246
|
"@testing-library/dom": "^10.4.0",
|