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,5 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The modules that answer a `?forgebook-docs-pages` import
|
|
2
|
+
* The modules that answer a group of docs pages, a `?forgebook-docs-pages` import's pattern or a catalogue's `docs`
|
|
3
|
+
* list (`DOCS_PAGES_SPECIFIER_PREFIX`): every page the group lists as one module with each page's
|
|
3
4
|
* index, and the loader of the page itself, and each page's own module.
|
|
4
5
|
*
|
|
5
6
|
* @remarks
|
|
@@ -15,7 +16,7 @@ import { codeLiteral } from "../codeLiteral.js";
|
|
|
15
16
|
import { readLinkedFile } from "./docPageLinks.js";
|
|
16
17
|
import { compiledDocPage, compiledHome, docPageIndex } from "./docPageMarkdown.js";
|
|
17
18
|
import { pluginFences } from "./pluginConfig.js";
|
|
18
|
-
import { PAGES_SEARCH_PREFIX, storyFileKeys, storyFilesGroupText } from "./storyFilesModule.js";
|
|
19
|
+
import { EVERY_FILE, PAGES_SEARCH_PREFIX, storyFileKeys, storyFilesGroupText } from "./storyFilesModule.js";
|
|
19
20
|
import { DOCS_PAGE_QUERY } from "./textQueries.js";
|
|
20
21
|
import { VITE_PATHS } from "./vitePaths.js";
|
|
21
22
|
/** Reads a linked page, watching it: its title makes the link's address, so a new title renews the page that links. */
|
|
@@ -26,15 +27,16 @@ const watchedRead = (watch) => (file) => {
|
|
|
26
27
|
return text;
|
|
27
28
|
};
|
|
28
29
|
/**
|
|
29
|
-
* Every page a
|
|
30
|
+
* Every page a group's patterns list, indexed.
|
|
30
31
|
*
|
|
31
32
|
* @param root - the declaring module's folder.
|
|
32
|
-
* @param
|
|
33
|
+
* @param patterns - the patterns, `!` ones included (`listedFiles`).
|
|
33
34
|
* @param watch - adds a file whose edit renews the module that read it.
|
|
34
35
|
* @param fences - the fences the catalogue's plugins draw, which are not code; none by default.
|
|
36
|
+
* @param isKept - whether a listed page may be read; every one by default.
|
|
35
37
|
*/
|
|
36
|
-
export const indexedDocPages = (root,
|
|
37
|
-
return storyFileKeys(root,
|
|
38
|
+
export const indexedDocPages = (root, patterns, watch, fences = [], isKept = EVERY_FILE) => {
|
|
39
|
+
return storyFileKeys(root, patterns, isKept).map((key) => {
|
|
38
40
|
const file = resolve(root, key);
|
|
39
41
|
watch(file);
|
|
40
42
|
return { key, file, index: docPageIndex(file, readFileSync(file, "utf8"), watchedRead(watch), fences) };
|
|
@@ -43,20 +45,21 @@ export const indexedDocPages = (root, pattern, watch, fences = []) => {
|
|
|
43
45
|
/** An object literal of a key per page, each value written by `value`. */
|
|
44
46
|
const objectOf = (keys, value) => `{${keys.map((key) => `${codeLiteral(key)}: ${value(key)}`).join(",\n")}}`;
|
|
45
47
|
/**
|
|
46
|
-
* The code of the module for one
|
|
48
|
+
* The code of the module for one group.
|
|
47
49
|
*
|
|
48
50
|
* @param root - the declaring module's folder.
|
|
49
|
-
* @param
|
|
51
|
+
* @param patterns - the group's patterns.
|
|
50
52
|
* @param watch - adds a file whose edit renews the module (each page: its index is in the module).
|
|
51
53
|
* @param isServing - whether a dev server runs, whose tools read a page's Markdown.
|
|
52
54
|
* @param plugins - the catalogue's plugins, whose fences a page may draw; none by default.
|
|
55
|
+
* @param isKept - whether a listed page may be read; every one by default.
|
|
53
56
|
*/
|
|
54
|
-
export const docPagesModule = (root,
|
|
55
|
-
const pages = indexedDocPages(root,
|
|
57
|
+
export const docPagesModule = (root, patterns, watch, isServing, plugins = [], isKept = EVERY_FILE) => {
|
|
58
|
+
const pages = indexedDocPages(root, patterns, watch, pluginFences(plugins), isKept);
|
|
56
59
|
const keys = pages.map((page) => page.key);
|
|
57
60
|
const index = Object.fromEntries(pages.map((page) => [page.key, page.index]));
|
|
58
61
|
const page = (key) => codeLiteral(`${VITE_PATHS.resolve(root, key)}${DOCS_PAGE_QUERY}`);
|
|
59
|
-
const search = codeLiteral(`${PAGES_SEARCH_PREFIX}${storyFilesGroupText(root,
|
|
62
|
+
const search = codeLiteral(`${PAGES_SEARCH_PREFIX}${storyFilesGroupText(root, patterns)}`);
|
|
60
63
|
return [
|
|
61
64
|
"export default {",
|
|
62
65
|
`index: ${codeLiteral(index)},`,
|
|
@@ -101,6 +101,12 @@ export type DocsExtractorOptions = {
|
|
|
101
101
|
* looks keeps them, renewed after each edit rather than built again (seconds, in a large monorepo).
|
|
102
102
|
*/
|
|
103
103
|
export declare const IDLE_MS: number;
|
|
104
|
+
/**
|
|
105
|
+
* The extractor of a catalogue that reads no component docs (`componentDocs: false`): it starts no worker and builds no
|
|
106
|
+
* TypeScript program, answers every story file as one whose meta names no component, and every group's usage with why
|
|
107
|
+
* there is none.
|
|
108
|
+
*/
|
|
109
|
+
export declare const NO_DOCS_EXTRACTOR: DocsExtractor;
|
|
104
110
|
/**
|
|
105
111
|
* A docs extractor with its worker, started on its first request.
|
|
106
112
|
*
|
|
@@ -62,6 +62,19 @@ const codeStampOf = (docsModule) => {
|
|
|
62
62
|
return undefined;
|
|
63
63
|
}
|
|
64
64
|
};
|
|
65
|
+
/**
|
|
66
|
+
* The extractor of a catalogue that reads no component docs (`componentDocs: false`): it starts no worker and builds no
|
|
67
|
+
* TypeScript program, answers every story file as one whose meta names no component, and every group's usage with why
|
|
68
|
+
* there is none.
|
|
69
|
+
*/
|
|
70
|
+
export const NO_DOCS_EXTRACTOR = {
|
|
71
|
+
extract: () => Promise.resolve({ docs: { name: null } }),
|
|
72
|
+
usage: () => Promise.resolve("The catalogue reads no component docs (componentDocs: false)"),
|
|
73
|
+
warm: () => undefined,
|
|
74
|
+
changed: () => undefined,
|
|
75
|
+
listed: () => undefined,
|
|
76
|
+
close: () => Promise.resolve()
|
|
77
|
+
};
|
|
65
78
|
/**
|
|
66
79
|
* A docs extractor with its worker, started on its first request.
|
|
67
80
|
*
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { type ResolvedConfig, type ViteDevServer } from "vite";
|
|
2
|
+
import type { IsFileKept } from "./storyFilesModule.ts";
|
|
3
|
+
/**
|
|
4
|
+
* Whether Vite refuses a listed file whatever `server.fs.allow` says: `deny`, or a path it will not trust (a `:` in it).
|
|
5
|
+
* Vite offers no check of this step alone, so this asks its whole check with an `allow` that covers the file.
|
|
6
|
+
*
|
|
7
|
+
* @param config - the dev server's configuration.
|
|
8
|
+
* @param file - the file, absolute in Vite's form.
|
|
9
|
+
*/
|
|
10
|
+
export declare const isFileRefused: (config: ResolvedConfig, file: string) => boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Whether a dev server may read the file a query's id names. An id Vite resolved is normalised; one with a `..` came
|
|
13
|
+
* from an address unresolved, and Vite's check reads a path as written, so `/project/../secret` would pass for a file
|
|
14
|
+
* of `/project`.
|
|
15
|
+
*
|
|
16
|
+
* @param config - the dev server's configuration.
|
|
17
|
+
* @param id - the module's id, the file and then its query.
|
|
18
|
+
*/
|
|
19
|
+
export declare const isQueriedFileReadable: (config: ResolvedConfig, id: string) => boolean;
|
|
20
|
+
/** What a plugin's dev server lets a client read: `devServerAccess` makes it. */
|
|
21
|
+
export type DevServerAccess = {
|
|
22
|
+
/** Starts on a dev server; until then (and in a build, which has none) nothing is refused. */
|
|
23
|
+
readonly serve: (server: ViteDevServer) => void;
|
|
24
|
+
/**
|
|
25
|
+
* Whether a resolver's importer is a module, not the stand-in Vite gives an address asked for directly.
|
|
26
|
+
*
|
|
27
|
+
* @param importer - the importer the resolver was given.
|
|
28
|
+
*/
|
|
29
|
+
readonly isModule: (importer: string | undefined) => importer is string;
|
|
30
|
+
/**
|
|
31
|
+
* Notes that a module imports a group, whose id is now served.
|
|
32
|
+
*
|
|
33
|
+
* @param id - the group module's id.
|
|
34
|
+
*/
|
|
35
|
+
readonly noteImported: (id: string) => void;
|
|
36
|
+
/** Whether a file a group lists may be read; says so once when not, as nothing else would tell why it is missing. */
|
|
37
|
+
readonly isKept: IsFileKept;
|
|
38
|
+
/**
|
|
39
|
+
* Whether a module is answered with nothing: a group no module imported, or a file `server.fs` refuses. Asked of every
|
|
40
|
+
* id, since an address can name an id directly, past the plugin's `resolveId`.
|
|
41
|
+
*
|
|
42
|
+
* @param id - the module's id.
|
|
43
|
+
*/
|
|
44
|
+
readonly isRefused: (id: string) => boolean;
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* What a plugin's dev server lets a client read.
|
|
48
|
+
*
|
|
49
|
+
* @param renew - renews modules Vite keeps: a group refused before its first import would otherwise stay empty.
|
|
50
|
+
*/
|
|
51
|
+
export declare const devServerAccess: (renew: (server: ViteDevServer, ids: Iterable<string>) => void) => DevServerAccess;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a dev server's plugin may read for a client, by Vite's own `server.fs` and by what the project imports: a
|
|
3
|
+
* plugin's answer never passes through Vite's check, so each read an address can name asks here first.
|
|
4
|
+
*
|
|
5
|
+
* @remarks
|
|
6
|
+
* Vite's rule is two steps. The first refuses a file whatever else holds (`server.fs.deny`, a path it will not trust).
|
|
7
|
+
* Past it, a file is served when `allow` covers it or a module of the project imports it. A group of story files or
|
|
8
|
+
* docs pages is what a module of the project imports, so it keeps to the first step alone: a catalogue's `../src` may be
|
|
9
|
+
* outside `allow`, and Vite serves its files all the same. A query names one file in the address, so it asks both steps:
|
|
10
|
+
* Vite has recorded the file by then if a module imports it.
|
|
11
|
+
*
|
|
12
|
+
* A group's id holds its patterns, so an id alone must never choose what is read: any address could name a folder to
|
|
13
|
+
* list and read, and the imports the group's module writes would make Vite serve every file listed, past `allow`.
|
|
14
|
+
*
|
|
15
|
+
* @module
|
|
16
|
+
*/
|
|
17
|
+
import { realpathSync } from "node:fs";
|
|
18
|
+
import { join } from "node:path";
|
|
19
|
+
import { isFileLoadingAllowed } from "vite";
|
|
20
|
+
import { isGroupId } from "./pluginIds.js";
|
|
21
|
+
import { CODE_QUERY, DOCS_PAGE_QUERY, DOCS_QUERY, HOME_QUERY, MARKDOWN_QUERY, PAGE_QUERY } from "./textQueries.js";
|
|
22
|
+
import { VITE_PATHS } from "./vitePaths.js";
|
|
23
|
+
/** The queries the plugin answers with a file's text, so each read keeps to what the dev server may serve. */
|
|
24
|
+
const FILE_QUERIES = [
|
|
25
|
+
DOCS_QUERY,
|
|
26
|
+
CODE_QUERY,
|
|
27
|
+
PAGE_QUERY,
|
|
28
|
+
MARKDOWN_QUERY,
|
|
29
|
+
DOCS_PAGE_QUERY,
|
|
30
|
+
HOME_QUERY
|
|
31
|
+
];
|
|
32
|
+
/**
|
|
33
|
+
* A listed file's real path, in Vite's form. Vite refuses a Windows short name (`RUNNER~1`), which could dodge `deny`,
|
|
34
|
+
* and a system's temporary folder is often written with one: the long name lets the file be judged by what it is. So
|
|
35
|
+
* does a link's target, which is the file that would be read.
|
|
36
|
+
*
|
|
37
|
+
* @param file - the file, absolute.
|
|
38
|
+
*/
|
|
39
|
+
const realFile = (file) => {
|
|
40
|
+
try {
|
|
41
|
+
return VITE_PATHS.normalize(realpathSync.native(file));
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return file;
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Whether Vite refuses a listed file whatever `server.fs.allow` says: `deny`, or a path it will not trust (a `:` in it).
|
|
49
|
+
* Vite offers no check of this step alone, so this asks its whole check with an `allow` that covers the file.
|
|
50
|
+
*
|
|
51
|
+
* @param config - the dev server's configuration.
|
|
52
|
+
* @param file - the file, absolute in Vite's form.
|
|
53
|
+
*/
|
|
54
|
+
export const isFileRefused = (config, file) => {
|
|
55
|
+
const real = realFile(file);
|
|
56
|
+
const fs = { ...config.server.fs, allow: [VITE_PATHS.dirname(real)] };
|
|
57
|
+
return !isFileLoadingAllowed({ ...config, server: { ...config.server, fs } }, real);
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Whether a dev server may read the file a query's id names. An id Vite resolved is normalised; one with a `..` came
|
|
61
|
+
* from an address unresolved, and Vite's check reads a path as written, so `/project/../secret` would pass for a file
|
|
62
|
+
* of `/project`.
|
|
63
|
+
*
|
|
64
|
+
* @param config - the dev server's configuration.
|
|
65
|
+
* @param id - the module's id, the file and then its query.
|
|
66
|
+
*/
|
|
67
|
+
export const isQueriedFileReadable = (config, id) => {
|
|
68
|
+
const file = id.slice(0, id.lastIndexOf("?"));
|
|
69
|
+
return VITE_PATHS.normalize(file) === file && isFileLoadingAllowed(config, file);
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
* What a plugin's dev server lets a client read.
|
|
73
|
+
*
|
|
74
|
+
* @param renew - renews modules Vite keeps: a group refused before its first import would otherwise stay empty.
|
|
75
|
+
*/
|
|
76
|
+
export const devServerAccess = (renew) => {
|
|
77
|
+
let devServer;
|
|
78
|
+
let addressImporter;
|
|
79
|
+
const imported = new Set();
|
|
80
|
+
const refused = new Set();
|
|
81
|
+
return {
|
|
82
|
+
serve: (server) => {
|
|
83
|
+
devServer = server;
|
|
84
|
+
addressImporter = join(server.config.root, "index.html");
|
|
85
|
+
},
|
|
86
|
+
isModule: (importer) => importer !== undefined && importer !== addressImporter,
|
|
87
|
+
noteImported: (id) => {
|
|
88
|
+
imported.add(id);
|
|
89
|
+
if (refused.delete(id) && devServer !== undefined)
|
|
90
|
+
renew(devServer, [id]);
|
|
91
|
+
},
|
|
92
|
+
isKept: (file) => {
|
|
93
|
+
if (devServer === undefined || !isFileRefused(devServer.config, file))
|
|
94
|
+
return true;
|
|
95
|
+
devServer.config.logger.warnOnce(`Forgebook leaves out ${file}: Vite's server.fs refuses it`);
|
|
96
|
+
return false;
|
|
97
|
+
},
|
|
98
|
+
isRefused: (id) => {
|
|
99
|
+
if (devServer === undefined)
|
|
100
|
+
return false;
|
|
101
|
+
if (!isGroupId(id))
|
|
102
|
+
return (FILE_QUERIES.some((query) => id.endsWith(query)) && !isQueriedFileReadable(devServer.config, id));
|
|
103
|
+
if (imported.has(id))
|
|
104
|
+
return false;
|
|
105
|
+
refused.add(id);
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
};
|
|
@@ -13,38 +13,40 @@
|
|
|
13
13
|
* come the same way, with `?forgebook-docs-pages` (`docPagesModule.ts`), and the written home with `?forgebook-home`. It also sets Lightning CSS as the CSS transformer with the forced-states visitor (`forcedStates.ts`) and lets Sass resolve
|
|
14
14
|
* `pkg:` URLs, which the library's style sheets use.
|
|
15
15
|
*
|
|
16
|
-
* A
|
|
16
|
+
* A group's module watches its story files, so an edit renews it. Vite watches only its root for new files, and the
|
|
17
17
|
* sources' story files live in other packages: `watch` names their folders, and a story file added or removed there
|
|
18
|
-
* renews
|
|
18
|
+
* renews the modules of the groups whose list it changes and reloads the page, so the sidebar lists it at once; one no
|
|
19
|
+
* group lists (a `!` pattern leaves it out) reloads nothing.
|
|
19
20
|
*
|
|
20
21
|
* @module
|
|
21
22
|
*/
|
|
22
|
-
import { existsSync } from "node:fs";
|
|
23
23
|
import { extname, join, resolve } from "node:path";
|
|
24
|
-
import {
|
|
24
|
+
import { checksA11y, offersUsedIn, readsComponentDocs } from "../catalogue.js";
|
|
25
25
|
import { SAME_ORIGIN } from "../frameOrigins.js";
|
|
26
26
|
import { createLiveErrors, isLiveErrorReport, LIVE_ERROR_EVENT } from "../liveErrors.js";
|
|
27
27
|
import { agentMiddleware, FRAMED_AGENT_PAGES } from "./agentRoutes.js";
|
|
28
28
|
import { STORY_FILE } from "./autoTitle.js";
|
|
29
|
-
import {
|
|
29
|
+
import { axeBuildAsset } from "./axeScript.js";
|
|
30
30
|
import { groupTexts } from "./builtTexts.js";
|
|
31
31
|
import { catalogueConfig } from "./catalogueConfig.js";
|
|
32
32
|
import { currentCloseWatch } from "./closeWatch.js";
|
|
33
33
|
import { findConfigFile, loadConfigFile, packageResolver } from "./configFile.js";
|
|
34
34
|
import { codeStamp, diskCache } from "./diskCache.js";
|
|
35
|
-
import { createDocsExtractor } from "./docsExtractor.js";
|
|
35
|
+
import { createDocsExtractor, NO_DOCS_EXTRACTOR } from "./docsExtractor.js";
|
|
36
|
+
import { devServerAccess } from "./fileAccess.js";
|
|
36
37
|
import { FORCED_STATES_VISITOR } from "./forcedStates.js";
|
|
37
38
|
import { builtPlacement } from "./framesConfig.js";
|
|
38
39
|
import { serveFrames } from "./framesServer.js";
|
|
40
|
+
import { generatedId, generatedModule } from "./generatedModules.js";
|
|
39
41
|
import { missingHeightsMiddleware } from "./heightsRoute.js";
|
|
40
42
|
import { FILES_MODULE_HOT, renewedModule, servedPatterns, storyFileHotHooks } from "./hotSnippets.js";
|
|
41
43
|
import { inlineJson, packedJsonOf } from "./packedModule.js";
|
|
42
|
-
import { AGENT_SOURCES_FILE,
|
|
44
|
+
import { AGENT_SOURCES_FILE, configModule, PAGE_FILES, pageHtml, pageModulePath } from "./pages.js";
|
|
43
45
|
import { CONFIG_ID, DOCS_PAGES_PREFIX, patternId, TOKENS_ID, virtualId } from "./pluginIds.js";
|
|
44
|
-
import { docsModule, docsValue, FILES_PREFIX, patternModule, REACH_ID_PREFIX, reachModule, searchIndexes, USAGE_ID_PREFIX, usageModule } from "./pluginModules.js";
|
|
46
|
+
import { docsModule, docsValue, FILES_PREFIX, listingRenewals, patternModule, REACH_ID_PREFIX, reachModule, searchIndexes, USAGE_ID_PREFIX, usageModule } from "./pluginModules.js";
|
|
45
47
|
import { isKeptIndex, storyFilesGroupOf } from "./storyFilesModule.js";
|
|
46
48
|
import { tokensModule } from "./storylessRoutes.js";
|
|
47
|
-
import {
|
|
49
|
+
import { DOCS_PAGE_QUERY, DOCS_QUERY, HOME_QUERY, RENEWED_QUERIES, TEXT_QUERIES } from "./textQueries.js";
|
|
48
50
|
import { VITE_PATHS } from "./vitePaths.js";
|
|
49
51
|
/** This module's folder, in the core's package: the core's own dependencies resolve from here, not the project's. */
|
|
50
52
|
const PACKAGE_DIR = import.meta.dirname;
|
|
@@ -62,31 +64,6 @@ const sassOptions = async () => {
|
|
|
62
64
|
const DOCS_PAGE_FILE = /\.md$/;
|
|
63
65
|
/** A TypeScript source file, whose edit may change a component's docs. */
|
|
64
66
|
const SOURCE_FILE = /\.tsx?$/;
|
|
65
|
-
/** The query that asks for a story file's component docs. */
|
|
66
|
-
const DOCS_QUERY = "?forgebook-docs";
|
|
67
|
-
/** The queries `textModules.ts` answers. */
|
|
68
|
-
const TEXT_QUERIES = [CODE_QUERY, PAGE_QUERY, MARKDOWN_QUERY];
|
|
69
|
-
/** The queries of a story file's modules the shell reads, which hand an edit to it in place on a dev server. */
|
|
70
|
-
const RENEWED_QUERIES = [DOCS_QUERY, CODE_QUERY, PAGE_QUERY];
|
|
71
|
-
/** The queries the plugin answers with a file's text, so each read keeps to what the dev server may serve. */
|
|
72
|
-
const FILE_QUERIES = [
|
|
73
|
-
DOCS_QUERY,
|
|
74
|
-
CODE_QUERY,
|
|
75
|
-
PAGE_QUERY,
|
|
76
|
-
MARKDOWN_QUERY,
|
|
77
|
-
DOCS_PAGE_QUERY,
|
|
78
|
-
HOME_QUERY
|
|
79
|
-
];
|
|
80
|
-
/**
|
|
81
|
-
* Whether a dev server may read the file a query names: a plugin's answer never passes Vite's own check of
|
|
82
|
-
* `server.fs`, so without this any file could be asked for by adding a query to its address.
|
|
83
|
-
*
|
|
84
|
-
* @param id - the module's id.
|
|
85
|
-
* @param server - the dev server; none in a build, which reads only what the sources import.
|
|
86
|
-
*/
|
|
87
|
-
const isReadable = (id, server) => server === undefined ||
|
|
88
|
-
!FILE_QUERIES.some((query) => id.endsWith(query)) ||
|
|
89
|
-
isFileLoadingAllowed(server.config, id.slice(0, id.lastIndexOf("?")));
|
|
90
67
|
/**
|
|
91
68
|
* Renews modules in every environment that loaded them: the agents' routes load them on the server too.
|
|
92
69
|
*
|
|
@@ -120,8 +97,9 @@ const requestedPage = (url) => {
|
|
|
120
97
|
* @param watch - adds a file whose edit renews the module.
|
|
121
98
|
* @param isServing - whether a dev server runs.
|
|
122
99
|
* @param plugins - the catalogue's plugins, whose fences a page may draw.
|
|
100
|
+
* @param isKept - whether a page its group lists may be read.
|
|
123
101
|
*/
|
|
124
|
-
const docsModuleOf = async (id, watch, isServing, plugins) => {
|
|
102
|
+
const docsModuleOf = async (id, watch, isServing, plugins, isKept) => {
|
|
125
103
|
const isHome = id.endsWith(HOME_QUERY);
|
|
126
104
|
const isPage = id.endsWith(DOCS_PAGE_QUERY);
|
|
127
105
|
if (!isHome && !isPage && !id.startsWith(DOCS_PAGES_PREFIX))
|
|
@@ -133,47 +111,8 @@ const docsModuleOf = async (id, watch, isServing, plugins) => {
|
|
|
133
111
|
const file = id.slice(0, -DOCS_PAGE_QUERY.length);
|
|
134
112
|
return { code: docPageModule(file, watch, isServing, plugins), moduleType: "js" };
|
|
135
113
|
}
|
|
136
|
-
const { root,
|
|
137
|
-
return { code: docPagesModule(root,
|
|
138
|
-
};
|
|
139
|
-
/**
|
|
140
|
-
* A module the plugin generates for a catalogue's pages, when the id names one: an entry, the project's own stories'
|
|
141
|
-
* source, or a page's HTML (unless the folder has one of its own). The configuration's data waits for the frames'
|
|
142
|
-
* placement, so the plugin's `load` makes it.
|
|
143
|
-
*
|
|
144
|
-
* @param id - the module's id.
|
|
145
|
-
* @param config - the catalogue's configuration.
|
|
146
|
-
* @param command - whether a dev server runs, or a build: where the frame finds axe's script.
|
|
147
|
-
*/
|
|
148
|
-
const generatedModule = (id, config, command) => {
|
|
149
|
-
const named = pageModuleOf(id, config.dir);
|
|
150
|
-
if (named === undefined)
|
|
151
|
-
return undefined;
|
|
152
|
-
if (named.kind === "entry") {
|
|
153
|
-
const code = named.page === "index.html" ? shellEntry(config) : frameEntry(config, axeAddress(command, PACKAGE_DIR));
|
|
154
|
-
return { code, moduleType: "js" };
|
|
155
|
-
}
|
|
156
|
-
if (named.kind === "stories-source")
|
|
157
|
-
return { code: storiesSourceModule(config), moduleType: "js" };
|
|
158
|
-
if (named.kind === "agent-sources")
|
|
159
|
-
return { code: agentSourcesModule(config), moduleType: "js" };
|
|
160
|
-
return existsSync(id) ? undefined : pageHtml(named.page, config, builtPlacement(config.frames));
|
|
161
|
-
};
|
|
162
|
-
/**
|
|
163
|
-
* The id of a module the plugin generates for a catalogue's pages, when an import names one: a page or an entry by
|
|
164
|
-
* its address (`/index.html`, `/.forgebook-shell.ts`) or by its path, a source by its path.
|
|
165
|
-
*
|
|
166
|
-
* @param source - the import's specifier.
|
|
167
|
-
* @param dir - the configuration's folder.
|
|
168
|
-
*/
|
|
169
|
-
const generatedId = (source, dir) => {
|
|
170
|
-
const byAddress = PAGE_FILES.find((page) => source === `/${PAGE_ENTRIES[page]}`);
|
|
171
|
-
if (byAddress !== undefined)
|
|
172
|
-
return pageModulePath(dir, PAGE_ENTRIES[byAddress]);
|
|
173
|
-
const pageByAddress = PAGE_FILES.find((page) => source === `/${page}`);
|
|
174
|
-
if (pageByAddress !== undefined)
|
|
175
|
-
return pageModulePath(dir, pageByAddress);
|
|
176
|
-
return pageModuleOf(source, dir) === undefined ? undefined : VITE_PATHS.normalize(source);
|
|
114
|
+
const { root, patterns } = storyFilesGroupOf(id.slice(DOCS_PAGES_PREFIX.length));
|
|
115
|
+
return { code: docPagesModule(root, patterns, watch, isServing, plugins, isKept), moduleType: "js" };
|
|
177
116
|
};
|
|
178
117
|
/**
|
|
179
118
|
* The catalogue's Vite plugin: the story index, the docs, the code and the Markdown of the stories, the forced-states
|
|
@@ -184,8 +123,11 @@ const generatedId = (source, dir) => {
|
|
|
184
123
|
* @returns the plugin, for the app's `plugins`.
|
|
185
124
|
*/
|
|
186
125
|
export const forgebookPlugin = (options = {}) => {
|
|
187
|
-
/**
|
|
188
|
-
|
|
126
|
+
/**
|
|
127
|
+
* The modules of the groups of story files, with the files each listed when served (none while it could not be
|
|
128
|
+
* made), renewed when a story file is added to or removed from what one lists.
|
|
129
|
+
*/
|
|
130
|
+
const filesModules = new Map();
|
|
189
131
|
// what each pattern's module was served with: an edit of a story file that changes its list reloads the pages
|
|
190
132
|
const served = servedPatterns();
|
|
191
133
|
/** The groups' "Used in" modules, renewed when a source file changes: any edit may change what reaches what. */
|
|
@@ -194,6 +136,8 @@ export const forgebookPlugin = (options = {}) => {
|
|
|
194
136
|
const docsModules = new Set();
|
|
195
137
|
/** The groups' search indexes: any edit may change what a component's docs say. */
|
|
196
138
|
const searchModules = new Set();
|
|
139
|
+
/** What a client may read of a dev server. */
|
|
140
|
+
const access = devServerAccess(invalidateEverywhere);
|
|
197
141
|
/** The errors the open frames report, for `forgebook errors` and the agents. */
|
|
198
142
|
const live = createLiveErrors();
|
|
199
143
|
/** Where the frames run: the dev server's second port once it listens, the configuration's choice in a build. */
|
|
@@ -212,8 +156,6 @@ export const forgebookPlugin = (options = {}) => {
|
|
|
212
156
|
return { code: configModule(config, await framesPlacement()), moduleType: "js" };
|
|
213
157
|
return generatedModule(id, config, command);
|
|
214
158
|
};
|
|
215
|
-
/** The dev server; none in a build. */
|
|
216
|
-
let devServer;
|
|
217
159
|
/** The files followed one by one, so an edit reaches the dev server at once. */
|
|
218
160
|
const watching = currentCloseWatch();
|
|
219
161
|
/** Whether a dev server runs, or a build: tools ask only the former. */
|
|
@@ -223,7 +165,8 @@ export const forgebookPlugin = (options = {}) => {
|
|
|
223
165
|
const search = searchIndexes({
|
|
224
166
|
extract: (storyFile) => extractor().extract(storyFile),
|
|
225
167
|
isServing,
|
|
226
|
-
track: (id, isPages) => (isPages ? docsModules : searchModules).add(id)
|
|
168
|
+
track: (id, isPages) => (isPages ? docsModules : searchModules).add(id),
|
|
169
|
+
isKept: access.isKept
|
|
227
170
|
});
|
|
228
171
|
// a build's docs, code and pages of each group's story files, read while the build loads the stories
|
|
229
172
|
const texts = groupTexts();
|
|
@@ -258,6 +201,9 @@ export const forgebookPlugin = (options = {}) => {
|
|
|
258
201
|
// build and would hold every other answer of the dev server meanwhile
|
|
259
202
|
let docs;
|
|
260
203
|
const extractor = () => {
|
|
204
|
+
// a catalogue that reads no component docs never starts the worker: its programs are most of the server's memory
|
|
205
|
+
if (config !== undefined && !readsComponentDocs(config.lists))
|
|
206
|
+
return NO_DOCS_EXTRACTOR;
|
|
261
207
|
const framework = config?.framework ?? options.framework;
|
|
262
208
|
if (framework === undefined)
|
|
263
209
|
throw new Error("Forgebook has no framework: add forgebook.config.ts, or give the plugin `framework`");
|
|
@@ -294,21 +240,23 @@ export const forgebookPlugin = (options = {}) => {
|
|
|
294
240
|
const storiesModule = async (id, watch, pack, startTexts) => {
|
|
295
241
|
if (id.startsWith(USAGE_ID_PREFIX)) {
|
|
296
242
|
usageModules.add(id);
|
|
297
|
-
return usageModule(id, watch, extractor().usage, indexCache(), pack);
|
|
243
|
+
return usageModule(id, watch, extractor().usage, indexCache(), pack, access.isKept);
|
|
298
244
|
}
|
|
299
245
|
// what each story reaches is for the dev server's tools only: a build ships none of it
|
|
300
246
|
if (id.startsWith(REACH_ID_PREFIX)) {
|
|
301
247
|
usageModules.add(id);
|
|
302
248
|
return isServing()
|
|
303
|
-
? reachModule(id, watch, extractor().usage, indexCache())
|
|
249
|
+
? reachModule(id, watch, extractor().usage, indexCache(), access.isKept)
|
|
304
250
|
: { code: "export default [];", moduleType: "js" };
|
|
305
251
|
}
|
|
306
252
|
if (!id.startsWith(FILES_PREFIX))
|
|
307
253
|
return undefined;
|
|
308
|
-
filesModules.
|
|
309
|
-
const
|
|
254
|
+
filesModules.set(id, undefined);
|
|
255
|
+
const lists = config?.lists ?? {};
|
|
256
|
+
const hasUsage = offersUsedIn(lists);
|
|
310
257
|
// the worker starts while Vite loads the pages, unless the disk keeps the docs; a test run asks for no page
|
|
311
|
-
const { files, code } = patternModule(id, watch, { hasUsage, hasReach: hasUsage && isServing() }, indexCache(), isTestRun ? undefined : extractor().warm, startTexts);
|
|
258
|
+
const { files, code } = patternModule(id, watch, { hasDocs: readsComponentDocs(lists), hasUsage, hasReach: hasUsage && isServing() }, indexCache(), isTestRun ? undefined : extractor().warm, startTexts, access.isKept);
|
|
259
|
+
filesModules.set(id, new Set(files.map((file) => file.file)));
|
|
312
260
|
if (!isRedrawing())
|
|
313
261
|
return { code, moduleType: "js" };
|
|
314
262
|
served.keep(id, files);
|
|
@@ -333,9 +281,9 @@ export const forgebookPlugin = (options = {}) => {
|
|
|
333
281
|
* @param file - the file.
|
|
334
282
|
*/
|
|
335
283
|
const modulesListing = (file) => {
|
|
336
|
-
// the
|
|
284
|
+
// the groups' modules list the story files, their "Used in" and search indexes read them
|
|
337
285
|
if (STORY_FILE.test(file))
|
|
338
|
-
return
|
|
286
|
+
return listingRenewals(filesModules, file, [...usageModules, ...searchModules], access.isKept);
|
|
339
287
|
return DOCS_PAGE_FILE.test(file) && docsModules.size > 0 ? [...docsModules] : undefined;
|
|
340
288
|
};
|
|
341
289
|
/**
|
|
@@ -356,7 +304,7 @@ export const forgebookPlugin = (options = {}) => {
|
|
|
356
304
|
}
|
|
357
305
|
if (id.startsWith(DOCS_PAGES_PREFIX))
|
|
358
306
|
docsModules.add(id);
|
|
359
|
-
const docsPages = await docsModuleOf(id, watch, isServing(), config?.plugins ?? []);
|
|
307
|
+
const docsPages = await docsModuleOf(id, watch, isServing(), config?.plugins ?? [], access.isKept);
|
|
360
308
|
if (docsPages !== undefined)
|
|
361
309
|
return docsPages;
|
|
362
310
|
if (!TEXT_QUERIES.some((query) => id.endsWith(query)))
|
|
@@ -380,7 +328,13 @@ export const forgebookPlugin = (options = {}) => {
|
|
|
380
328
|
const pack = emit === undefined ? inlineJson : packedJsonOf(emit);
|
|
381
329
|
const componentFiles = (config?.framework ?? options.framework)?.componentFiles;
|
|
382
330
|
const docsOf = async (storyFile) => docsValue(await search.docsOf(storyFile), watch);
|
|
383
|
-
const
|
|
331
|
+
const hasDocs = config === undefined || readsComponentDocs(config.lists);
|
|
332
|
+
const sources = {
|
|
333
|
+
docsOf: hasDocs ? docsOf : undefined,
|
|
334
|
+
watch,
|
|
335
|
+
resolveImport,
|
|
336
|
+
componentFiles
|
|
337
|
+
};
|
|
384
338
|
// a build's browser files only: a dev server answers each text from its query, and no texts module
|
|
385
339
|
const startTexts = emit === undefined ? undefined : texts.starter(id, sources);
|
|
386
340
|
return ((emit === undefined ? undefined : await texts.moduleOf(id, sources, emit)) ??
|
|
@@ -410,13 +364,23 @@ export const forgebookPlugin = (options = {}) => {
|
|
|
410
364
|
};
|
|
411
365
|
},
|
|
412
366
|
resolveId(source, importer) {
|
|
367
|
+
const isModule = access.isModule(importer);
|
|
413
368
|
const virtual = virtualId(source);
|
|
414
|
-
if (virtual !== undefined)
|
|
369
|
+
if (virtual !== undefined) {
|
|
370
|
+
if (isModule)
|
|
371
|
+
access.noteImported(virtual);
|
|
415
372
|
return virtual;
|
|
373
|
+
}
|
|
416
374
|
const generated = config === undefined ? undefined : generatedId(source, config.dir);
|
|
417
375
|
if (generated !== undefined)
|
|
418
376
|
return generated;
|
|
419
|
-
|
|
377
|
+
if (!access.isModule(importer))
|
|
378
|
+
return null;
|
|
379
|
+
// the importer's file: its id may carry whatever query an address gave it
|
|
380
|
+
const pattern = patternId(source, VITE_PATHS.dirname(importer.split("?")[0] ?? importer));
|
|
381
|
+
if (pattern !== null)
|
|
382
|
+
access.noteImported(pattern);
|
|
383
|
+
return pattern;
|
|
420
384
|
},
|
|
421
385
|
// a warning and not a failure: a theme a reader can still use must not stop a catalogue from building
|
|
422
386
|
configResolved: (resolved) => {
|
|
@@ -428,7 +392,7 @@ export const forgebookPlugin = (options = {}) => {
|
|
|
428
392
|
resolved.logger.warn(`Forgebook's shell theme: ${problem}`);
|
|
429
393
|
},
|
|
430
394
|
configureServer: (server) => {
|
|
431
|
-
|
|
395
|
+
access.serve(server);
|
|
432
396
|
// no person waits for a page in a test run
|
|
433
397
|
watching.serve(isTestRun ? undefined : server);
|
|
434
398
|
server.httpServer?.on("close", () => void docs?.close());
|
|
@@ -469,7 +433,7 @@ export const forgebookPlugin = (options = {}) => {
|
|
|
469
433
|
.catch(next);
|
|
470
434
|
});
|
|
471
435
|
// folders outside the configuration's are watched only when named; a story file added or removed anywhere the
|
|
472
|
-
// server watches (its own folder, a story file it loaded) renews the lists either way
|
|
436
|
+
// server watches (its own folder, a story file it loaded) renews the lists that change either way
|
|
473
437
|
const watched = [...(config?.watch ?? []), ...(options.watch ?? [])];
|
|
474
438
|
if (watched.length > 0)
|
|
475
439
|
server.watcher.add(watched);
|
|
@@ -488,7 +452,8 @@ export const forgebookPlugin = (options = {}) => {
|
|
|
488
452
|
// axe's script, for the frames' Accessibility card: loaded by the frame, never imported; the browser's build only
|
|
489
453
|
if (this.environment.name !== "client")
|
|
490
454
|
return;
|
|
491
|
-
|
|
455
|
+
// a catalogue that checks no accessibility ships none of it
|
|
456
|
+
const axe = config === undefined || !checksA11y(config.lists) ? undefined : axeBuildAsset(PACKAGE_DIR);
|
|
492
457
|
if (axe !== undefined)
|
|
493
458
|
this.emitFile({ type: "asset", fileName: axe.fileName, source: axe.source });
|
|
494
459
|
},
|
|
@@ -506,7 +471,7 @@ export const forgebookPlugin = (options = {}) => {
|
|
|
506
471
|
if (generated !== undefined)
|
|
507
472
|
return generated;
|
|
508
473
|
// an answer, not an error: an error would raise Vite's overlay on every reader's open page
|
|
509
|
-
if (
|
|
474
|
+
if (access.isRefused(id))
|
|
510
475
|
return { code: "export default null;", moduleType: "js" };
|
|
511
476
|
// in Vite's form, the text Vite's module graph knows a file by: a Node path on Windows (`C:\app\x.md`) is another
|
|
512
477
|
const watch = (file) => this.addWatchFile(VITE_PATHS.normalize(file));
|
|
@@ -45,6 +45,20 @@ export const hostOf = (request) => headerOf(request, "host") ?? headerOf(request
|
|
|
45
45
|
* @param url - the request's address.
|
|
46
46
|
*/
|
|
47
47
|
const isProxied = (proxy, url) => Object.keys(proxy ?? {}).some((key) => key.startsWith("^") ? new RegExp(key).test(url) : url.startsWith(key));
|
|
48
|
+
/**
|
|
49
|
+
* A request's path as an address parser reads it, which is how the routes behind the guards read it: a guard that read
|
|
50
|
+
* `//x/__forgebook/api/stories` as written would pass what the route answers as `/__forgebook/api/stories`.
|
|
51
|
+
*
|
|
52
|
+
* @param url - the request's address, as it was sent.
|
|
53
|
+
*/
|
|
54
|
+
const parsedPath = (url) => {
|
|
55
|
+
try {
|
|
56
|
+
return new URL(url, "http://parsed.invalid").pathname;
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return (url.split("?")[0] ?? url).split("#")[0] ?? "/";
|
|
60
|
+
}
|
|
61
|
+
};
|
|
48
62
|
/**
|
|
49
63
|
* What the browser says of a request.
|
|
50
64
|
*
|
|
@@ -54,7 +68,7 @@ const factsOf = (request) => {
|
|
|
54
68
|
const url = request.url ?? "/";
|
|
55
69
|
const queryAt = url.indexOf("?");
|
|
56
70
|
return {
|
|
57
|
-
path: (
|
|
71
|
+
path: parsedPath(url),
|
|
58
72
|
query: queryAt === -1 ? "" : (url.slice(queryAt).split("#")[0] ?? ""),
|
|
59
73
|
site: headerOf(request, "sec-fetch-site"),
|
|
60
74
|
mode: headerOf(request, "sec-fetch-mode"),
|
|
@@ -83,14 +97,16 @@ const isPublicFile = (publicDir, path) => {
|
|
|
83
97
|
return file.startsWith(`${publicDir}${sep}`) && existsSync(file) && statSync(file).isFile();
|
|
84
98
|
};
|
|
85
99
|
/**
|
|
86
|
-
* Whether a module
|
|
100
|
+
* Whether a module the catalogue imports names an address in its code, as a whole string: how a worker's file
|
|
101
|
+
* (`?worker_file`) is started. Only an imported module counts, and only a whole string: an address asked for directly
|
|
102
|
+
* writes its own query into its module's code (its HMR context), and a story's script could plant any address there.
|
|
87
103
|
*
|
|
88
104
|
* @param modules - the graph's modules.
|
|
89
105
|
* @param url - the address, as the graph reads it; its `t=` left out.
|
|
90
106
|
*/
|
|
91
107
|
const isNamedInCode = (modules, url) => {
|
|
92
|
-
const named = url.replace(/[?&]t=\d+/, "");
|
|
93
|
-
return modules.some((module) => module.transformResult?.code.includes(named) === true);
|
|
108
|
+
const named = JSON.stringify(url.replace(/[?&]t=\d+/, ""));
|
|
109
|
+
return modules.some((module) => module.importers.size > 0 && module.transformResult?.code.includes(named) === true);
|
|
94
110
|
};
|
|
95
111
|
/**
|
|
96
112
|
* Ends a request the guards turned down, with its status and nothing a page could use.
|
|
@@ -204,7 +220,7 @@ export const framesServing = (server, frames, frameHtml, framedPages) => {
|
|
|
204
220
|
next();
|
|
205
221
|
return;
|
|
206
222
|
}
|
|
207
|
-
const answer = catalogueAnswer(factsOf(request), framedPages);
|
|
223
|
+
const answer = catalogueAnswer(factsOf(request), framedPages, (path) => isPublicFile(server.config.publicDir, path));
|
|
208
224
|
if (answer === "refuse") {
|
|
209
225
|
turnDown(response, 403);
|
|
210
226
|
return;
|