@open-press/cli 1.0.0 → 1.1.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 +11 -12
- package/dist/cli.js +298 -79
- package/package.json +9 -7
- package/template/core/AGENTS.md +0 -130
- package/template/core/CHANGELOG.md +0 -218
- package/template/core/README.md +0 -43
- package/template/core/engine/cli.mjs +0 -96
- package/template/core/engine/commands/_shared.mjs +0 -199
- package/template/core/engine/commands/deploy.mjs +0 -31
- package/template/core/engine/commands/dev.mjs +0 -49
- package/template/core/engine/commands/doctor.mjs +0 -229
- package/template/core/engine/commands/export.mjs +0 -8
- package/template/core/engine/commands/image.mjs +0 -29
- package/template/core/engine/commands/inspect.mjs +0 -35
- package/template/core/engine/commands/pdf.mjs +0 -26
- package/template/core/engine/commands/preview.mjs +0 -26
- package/template/core/engine/commands/render.mjs +0 -17
- package/template/core/engine/commands/replace.mjs +0 -41
- package/template/core/engine/commands/search.mjs +0 -33
- package/template/core/engine/commands/skills-sync.mjs +0 -71
- package/template/core/engine/commands/typecheck.mjs +0 -67
- package/template/core/engine/commands/upgrade.mjs +0 -159
- package/template/core/engine/commands/validate.mjs +0 -17
- package/template/core/engine/document-export.mjs +0 -15
- package/template/core/engine/output/chrome-pdf.d.mts +0 -34
- package/template/core/engine/output/chrome-pdf.mjs +0 -450
- package/template/core/engine/output/deploy-sync.mjs +0 -15
- package/template/core/engine/output/fonts.mjs +0 -62
- package/template/core/engine/output/katex-assets.mjs +0 -45
- package/template/core/engine/output/page-block.mjs +0 -30
- package/template/core/engine/output/pdf-media.mjs +0 -45
- package/template/core/engine/output/public-assets.mjs +0 -19
- package/template/core/engine/output/static-server.mjs +0 -571
- package/template/core/engine/react/caption-numbering.mjs +0 -73
- package/template/core/engine/react/comment-endpoint.d.mts +0 -11
- package/template/core/engine/react/comment-endpoint.mjs +0 -102
- package/template/core/engine/react/comment-marker.mjs +0 -374
- package/template/core/engine/react/document-entry.mjs +0 -331
- package/template/core/engine/react/document-export.mjs +0 -512
- package/template/core/engine/react/http-json.mjs +0 -24
- package/template/core/engine/react/mdx-compile.mjs +0 -629
- package/template/core/engine/react/measurement-css.mjs +0 -157
- package/template/core/engine/react/object-entities.mjs +0 -204
- package/template/core/engine/react/pagination/allocator.mjs +0 -167
- package/template/core/engine/react/pagination/regions.mjs +0 -81
- package/template/core/engine/react/pagination-constants.mjs +0 -3
- package/template/core/engine/react/pagination.mjs +0 -9
- package/template/core/engine/react/pipeline/allocate.mjs +0 -217
- package/template/core/engine/react/pipeline/final-render.mjs +0 -94
- package/template/core/engine/react/pipeline/frame-measurement.mjs +0 -306
- package/template/core/engine/react/pipeline/press-tree.mjs +0 -135
- package/template/core/engine/react/press-tree-inspection.mjs +0 -172
- package/template/core/engine/react/project-asset-endpoint.d.mts +0 -10
- package/template/core/engine/react/project-asset-endpoint.mjs +0 -361
- package/template/core/engine/react/section-css.mjs +0 -56
- package/template/core/engine/react/source-edit-endpoint.d.mts +0 -10
- package/template/core/engine/react/source-edit-endpoint.mjs +0 -75
- package/template/core/engine/react/sources/heading-numbering.mjs +0 -132
- package/template/core/engine/react/sources/mdx-resolver.mjs +0 -439
- package/template/core/engine/react/style-discovery.mjs +0 -160
- package/template/core/engine/runtime/config.d.mts +0 -48
- package/template/core/engine/runtime/config.mjs +0 -172
- package/template/core/engine/runtime/file-utils.mjs +0 -114
- package/template/core/engine/runtime/file-walk.mjs +0 -22
- package/template/core/engine/runtime/inspection.mjs +0 -328
- package/template/core/engine/runtime/issue-report.mjs +0 -44
- package/template/core/engine/runtime/page-geometry.mjs +0 -131
- package/template/core/engine/runtime/path-utils.mjs +0 -20
- package/template/core/engine/runtime/source-text-tools.d.mts +0 -102
- package/template/core/engine/runtime/source-text-tools.mjs +0 -832
- package/template/core/engine/runtime/source-workspace.mjs +0 -168
- package/template/core/engine/runtime/validation.mjs +0 -183
- package/template/core/index.html +0 -13
- package/template/core/openpress.config.mjs +0 -8
- package/template/core/package.json +0 -89
- package/template/core/src/main.tsx +0 -16
- package/template/core/src/openpress/app/OpenPressApp.tsx +0 -296
- package/template/core/src/openpress/app/OpenPressRuntime.tsx +0 -102
- package/template/core/src/openpress/app/WorkspaceGalleryPage.tsx +0 -219
- package/template/core/src/openpress/app/index.ts +0 -2
- package/template/core/src/openpress/core/Frame.tsx +0 -91
- package/template/core/src/openpress/core/FrameContext.tsx +0 -26
- package/template/core/src/openpress/core/MdxArea.tsx +0 -34
- package/template/core/src/openpress/core/Press.tsx +0 -55
- package/template/core/src/openpress/core/Workspace.tsx +0 -36
- package/template/core/src/openpress/core/cn.ts +0 -4
- package/template/core/src/openpress/core/index.tsx +0 -47
- package/template/core/src/openpress/core/primitives.tsx +0 -91
- package/template/core/src/openpress/core/types.ts +0 -236
- package/template/core/src/openpress/core/useSource.ts +0 -28
- package/template/core/src/openpress/document-model/anchorMapModel.ts +0 -27
- package/template/core/src/openpress/document-model/documentIndexes.ts +0 -329
- package/template/core/src/openpress/document-model/documentTypes.ts +0 -147
- package/template/core/src/openpress/document-model/index.ts +0 -7
- package/template/core/src/openpress/document-model/objectEntityModel.ts +0 -55
- package/template/core/src/openpress/document-model/projectIdentityModel.ts +0 -15
- package/template/core/src/openpress/document-model/reactDocumentMetadataModel.ts +0 -27
- package/template/core/src/openpress/document-model/workspaceManifestModel.ts +0 -57
- package/template/core/src/openpress/manuscript/index.tsx +0 -238
- package/template/core/src/openpress/mdx/index.ts +0 -96
- package/template/core/src/openpress/numbering/index.ts +0 -294
- package/template/core/src/openpress/reader/PageThumbnailsPanel.tsx +0 -168
- package/template/core/src/openpress/reader/PublicReaderPage.tsx +0 -267
- package/template/core/src/openpress/reader/ReaderNavigationPanel.tsx +0 -123
- package/template/core/src/openpress/reader/index.ts +0 -11
- package/template/core/src/openpress/reader/pageViewportScaleModel.ts +0 -73
- package/template/core/src/openpress/reader/readerPageRegistry.ts +0 -41
- package/template/core/src/openpress/reader/readerPageRoute.ts +0 -21
- package/template/core/src/openpress/reader/readerScroll.ts +0 -92
- package/template/core/src/openpress/reader/readerStateModel.ts +0 -15
- package/template/core/src/openpress/reader/readerTypes.ts +0 -4
- package/template/core/src/openpress/reader/usePageViewportScale.ts +0 -119
- package/template/core/src/openpress/reader/usePanelState.ts +0 -56
- package/template/core/src/openpress/reader/useReaderHashSync.ts +0 -61
- package/template/core/src/openpress/reader/useReaderKeyboardNav.ts +0 -48
- package/template/core/src/openpress/reader/useReaderRuntime.ts +0 -146
- package/template/core/src/openpress/reader/useReaderScrollAnchor.ts +0 -64
- package/template/core/src/openpress/shared/Panel.tsx +0 -77
- package/template/core/src/openpress/shared/frameScheduler.ts +0 -32
- package/template/core/src/openpress/shared/index.ts +0 -4
- package/template/core/src/openpress/shared/numberUtils.ts +0 -3
- package/template/core/src/openpress/shared/runtimeMode.ts +0 -11
- package/template/core/src/openpress/workbench/Workbench.tsx +0 -506
- package/template/core/src/openpress/workbench/actions/DeploymentControl.tsx +0 -157
- package/template/core/src/openpress/workbench/actions/ExportImageControl.tsx +0 -96
- package/template/core/src/openpress/workbench/actions/PageZoomControl.tsx +0 -182
- package/template/core/src/openpress/workbench/actions/SearchControl.tsx +0 -345
- package/template/core/src/openpress/workbench/actions/deploymentStatusModel.ts +0 -112
- package/template/core/src/openpress/workbench/actions/index.ts +0 -6
- package/template/core/src/openpress/workbench/actions/useDeploymentWorkbench.ts +0 -136
- package/template/core/src/openpress/workbench/dialog/WorkbenchDialog.tsx +0 -72
- package/template/core/src/openpress/workbench/dialog/index.ts +0 -1
- package/template/core/src/openpress/workbench/document/components/DocumentPanel.tsx +0 -127
- package/template/core/src/openpress/workbench/document/components/InlineSourceEditorLayer.tsx +0 -207
- package/template/core/src/openpress/workbench/document/components/ReaderStage.tsx +0 -9
- package/template/core/src/openpress/workbench/document/hooks/useDocumentWorkbenchModel.ts +0 -34
- package/template/core/src/openpress/workbench/document/hooks/useInlineDocumentEditor.ts +0 -525
- package/template/core/src/openpress/workbench/document/index.ts +0 -10
- package/template/core/src/openpress/workbench/index.ts +0 -2
- package/template/core/src/openpress/workbench/inspector/InlineInspectorLayer.tsx +0 -459
- package/template/core/src/openpress/workbench/inspector/index.ts +0 -5
- package/template/core/src/openpress/workbench/inspector/inlineCommentModel.ts +0 -125
- package/template/core/src/openpress/workbench/inspector/inspectorGeometryModel.ts +0 -160
- package/template/core/src/openpress/workbench/inspector/inspectorModel.ts +0 -408
- package/template/core/src/openpress/workbench/inspector/useInspectorComments.ts +0 -254
- package/template/core/src/openpress/workbench/mentions/MentionSuggestionList.tsx +0 -41
- package/template/core/src/openpress/workbench/mentions/index.ts +0 -2
- package/template/core/src/openpress/workbench/mentions/useComposerMentions.ts +0 -185
- package/template/core/src/openpress/workbench/panels/Panel.tsx +0 -1
- package/template/core/src/openpress/workbench/panels/PendingCommentsPanel.tsx +0 -80
- package/template/core/src/openpress/workbench/panels/WorkbenchControlPanel.tsx +0 -29
- package/template/core/src/openpress/workbench/panels/index.ts +0 -3
- package/template/core/src/openpress/workbench/project/ProjectEntryPanel.tsx +0 -525
- package/template/core/src/openpress/workbench/project/ProjectPreviewDialog.tsx +0 -35
- package/template/core/src/openpress/workbench/project/index.ts +0 -2
- package/template/core/src/openpress/workbench/project/projectPreviewTypes.ts +0 -11
- package/template/core/src/openpress/workbench/project/projectSourceModel.ts +0 -24
- package/template/core/src/openpress/workbench/shell/WorkbenchShell.tsx +0 -167
- package/template/core/src/openpress/workbench/shell/index.ts +0 -1
- package/template/core/src/openpress/workbench/workbenchFormatters.ts +0 -120
- package/template/core/src/openpress/workbench/workbenchTypes.ts +0 -35
- package/template/core/src/styles/openpress/app-shell.css +0 -251
- package/template/core/src/styles/openpress/media-workspace.css +0 -230
- package/template/core/src/styles/openpress/print-route.css +0 -184
- package/template/core/src/styles/openpress/project-preview-panel.css +0 -924
- package/template/core/src/styles/openpress/public-viewer.css +0 -688
- package/template/core/src/styles/openpress/reader-runtime.css +0 -989
- package/template/core/src/styles/openpress/responsive.css +0 -245
- package/template/core/src/styles/openpress/workbench-panels.css +0 -707
- package/template/core/src/styles/openpress/workbench.css +0 -1255
- package/template/core/src/styles/openpress/workspace-gallery.css +0 -300
- package/template/core/src/styles/openpress.css +0 -15
- package/template/core/src/vite-env.d.ts +0 -9
- package/template/core/tsconfig.json +0 -40
- package/template/core/vite.config.ts +0 -584
|
@@ -1,172 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs/promises";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { normalizePageGeometry } from "./page-geometry.mjs";
|
|
4
|
-
|
|
5
|
-
const DEFAULT_CONFIG = {
|
|
6
|
-
title: "OpenPress Document",
|
|
7
|
-
subtitle: "",
|
|
8
|
-
organization: "",
|
|
9
|
-
workspaceLabel: "",
|
|
10
|
-
documentDir: ".",
|
|
11
|
-
sourceDir: "content",
|
|
12
|
-
mediaDir: "media",
|
|
13
|
-
themeDir: "theme",
|
|
14
|
-
designDoc: "design.md",
|
|
15
|
-
componentsDir: "components",
|
|
16
|
-
publicDir: "public/openpress",
|
|
17
|
-
outputDir: "dist",
|
|
18
|
-
captionNumbering: {
|
|
19
|
-
figure: "Figure",
|
|
20
|
-
table: "Table",
|
|
21
|
-
separator: " ",
|
|
22
|
-
},
|
|
23
|
-
pdf: {
|
|
24
|
-
filename: "document.pdf",
|
|
25
|
-
},
|
|
26
|
-
deploy: {
|
|
27
|
-
adapter: "cloudflare-pages",
|
|
28
|
-
source: ".deploy/openpress",
|
|
29
|
-
projectName: null,
|
|
30
|
-
commitDirty: false,
|
|
31
|
-
requiresConfirmation: true,
|
|
32
|
-
},
|
|
33
|
-
page: null,
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
// 1.0 contract: the only user-writable config lives in package.json
|
|
37
|
-
// under the "openpress" field. The engine reads it synchronously so
|
|
38
|
-
// the deploy command can resolve its adapter before any React render.
|
|
39
|
-
//
|
|
40
|
-
// Everything else is convention (path layout) or declared on
|
|
41
|
-
// <Press> / <Workspace> JSX props (document metadata).
|
|
42
|
-
export async function loadConfig(root = ".") {
|
|
43
|
-
const workspaceRoot = path.resolve(root);
|
|
44
|
-
const packageOpenpress = await readPackageOpenpressField(workspaceRoot);
|
|
45
|
-
return normalizeConfig(workspaceRoot, packageOpenpress ?? {});
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
async function readPackageOpenpressField(workspaceRoot) {
|
|
49
|
-
const pkgPath = path.join(workspaceRoot, "package.json");
|
|
50
|
-
try {
|
|
51
|
-
const raw = await fs.readFile(pkgPath, "utf8");
|
|
52
|
-
const parsed = JSON.parse(raw);
|
|
53
|
-
const field = parsed?.openpress;
|
|
54
|
-
return (field && typeof field === "object" && !Array.isArray(field)) ? field : null;
|
|
55
|
-
} catch (error) {
|
|
56
|
-
if (error?.code === "ENOENT") return null;
|
|
57
|
-
if (error instanceof SyntaxError) {
|
|
58
|
-
throw new Error(`Malformed package.json at ${pkgPath}: ${error.message}`);
|
|
59
|
-
}
|
|
60
|
-
throw error;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// Convention-only fields. The user can't override these — they're part
|
|
65
|
-
// of OpenPress's product surface. If you don't like the layout, your
|
|
66
|
-
// project isn't an OpenPress workspace.
|
|
67
|
-
const CONVENTION = {
|
|
68
|
-
documentDir: "press",
|
|
69
|
-
sourceDir: "chapters",
|
|
70
|
-
mediaDir: "media",
|
|
71
|
-
themeDir: "theme",
|
|
72
|
-
componentsDir: "components",
|
|
73
|
-
designDoc: "design.md",
|
|
74
|
-
publicDir: "public/openpress",
|
|
75
|
-
outputDir: "dist-react",
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
export function normalizeConfig(root, userConfig = {}, configPath = path.join(root, "package.json")) {
|
|
79
|
-
const config = {
|
|
80
|
-
root: path.resolve(root),
|
|
81
|
-
configPath,
|
|
82
|
-
// Document metadata defaults — actual values are merged in by
|
|
83
|
-
// loadReactDocumentEntry from <Press> / <Workspace> JSX props.
|
|
84
|
-
title: DEFAULT_CONFIG.title,
|
|
85
|
-
subtitle: "",
|
|
86
|
-
organization: "",
|
|
87
|
-
workspaceLabel: "",
|
|
88
|
-
// Paths — fixed conventions, not user-configurable.
|
|
89
|
-
documentDir: CONVENTION.documentDir,
|
|
90
|
-
sourceDir: CONVENTION.sourceDir,
|
|
91
|
-
mediaDir: CONVENTION.mediaDir,
|
|
92
|
-
themeDir: CONVENTION.themeDir,
|
|
93
|
-
designDoc: CONVENTION.designDoc,
|
|
94
|
-
componentsDir: CONVENTION.componentsDir,
|
|
95
|
-
publicDir: CONVENTION.publicDir,
|
|
96
|
-
outputDir: CONVENTION.outputDir,
|
|
97
|
-
captionNumbering: captionNumberingValue(userConfig.captionNumbering, DEFAULT_CONFIG.captionNumbering),
|
|
98
|
-
page: normalizePageGeometry(userConfig.page ?? DEFAULT_CONFIG.page),
|
|
99
|
-
pdf: {
|
|
100
|
-
filename: fileNameValue(userConfig.pdf?.filename, DEFAULT_CONFIG.pdf.filename),
|
|
101
|
-
},
|
|
102
|
-
deploy: {
|
|
103
|
-
adapter: stringValue(userConfig.deploy?.adapter, DEFAULT_CONFIG.deploy.adapter),
|
|
104
|
-
source: relativePathValue(userConfig.deploy?.source, DEFAULT_CONFIG.deploy.source),
|
|
105
|
-
projectName: optionalStringValue(userConfig.deploy?.projectName, DEFAULT_CONFIG.deploy.projectName),
|
|
106
|
-
commitDirty: booleanValue(userConfig.deploy?.commitDirty, DEFAULT_CONFIG.deploy.commitDirty),
|
|
107
|
-
requiresConfirmation: booleanValue(userConfig.deploy?.requiresConfirmation, DEFAULT_CONFIG.deploy.requiresConfirmation),
|
|
108
|
-
},
|
|
109
|
-
};
|
|
110
|
-
|
|
111
|
-
const documentRoot = config.documentDir === "." ? config.root : path.join(config.root, config.documentDir);
|
|
112
|
-
config.paths = {
|
|
113
|
-
documentRoot,
|
|
114
|
-
sourceDir: path.join(documentRoot, config.sourceDir),
|
|
115
|
-
mediaDir: path.join(documentRoot, config.mediaDir),
|
|
116
|
-
themeDir: path.join(documentRoot, config.themeDir),
|
|
117
|
-
designDoc: path.join(documentRoot, config.designDoc),
|
|
118
|
-
componentsDir: path.join(documentRoot, config.componentsDir),
|
|
119
|
-
publicDir: path.join(config.root, config.publicDir),
|
|
120
|
-
outputDir: path.join(config.root, config.outputDir),
|
|
121
|
-
pdf: path.join(config.root, config.outputDir, config.pdf.filename),
|
|
122
|
-
deploySource: path.join(config.root, config.deploy.source),
|
|
123
|
-
deployMetadata: path.join(config.root, config.deploy.source, "openpress", "deploy.json"),
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
return config;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
export function publicPdfHref(config) {
|
|
130
|
-
return `/${config.pdf.filename}`;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
function stringValue(value, fallback) {
|
|
134
|
-
return typeof value === "string" && value.trim() ? value.trim() : fallback;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
function optionalStringValue(value, fallback) {
|
|
138
|
-
if (value === null) return null;
|
|
139
|
-
if (typeof value === "string" && value.trim()) return value.trim();
|
|
140
|
-
return fallback;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
function captionNumberingValue(value, fallback) {
|
|
144
|
-
const input = value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
145
|
-
return {
|
|
146
|
-
figure: optionalStringValue(input.figure, fallback.figure) ?? fallback.figure,
|
|
147
|
-
table: optionalStringValue(input.table, fallback.table) ?? fallback.table,
|
|
148
|
-
separator: typeof input.separator === "string" ? input.separator : fallback.separator,
|
|
149
|
-
};
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
function booleanValue(value, fallback) {
|
|
153
|
-
return typeof value === "boolean" ? value : fallback;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
function fileNameValue(value, fallback) {
|
|
157
|
-
const fileName = stringValue(value, fallback);
|
|
158
|
-
if (fileName.includes("/") || fileName.includes("\\") || fileName === "." || fileName === "..") {
|
|
159
|
-
throw new Error(`OpenPress config pdf.filename must be a file name, got: ${fileName}`);
|
|
160
|
-
}
|
|
161
|
-
return fileName;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
function relativePathValue(value, fallback) {
|
|
165
|
-
const raw = stringValue(value, fallback).replaceAll("\\", "/");
|
|
166
|
-
if (path.isAbsolute(raw)) throw new Error(`OpenPress config paths must be relative, got: ${raw}`);
|
|
167
|
-
const normalized = path.posix.normalize(raw).replace(/^\.\//, "");
|
|
168
|
-
if (!normalized || normalized === "." || normalized === ".." || normalized.startsWith("../")) {
|
|
169
|
-
throw new Error(`OpenPress config path escapes workspace: ${raw}`);
|
|
170
|
-
}
|
|
171
|
-
return normalized;
|
|
172
|
-
}
|
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs/promises";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { loadConfig } from "./config.mjs";
|
|
4
|
-
import { readKatexCss } from "../output/katex-assets.mjs";
|
|
5
|
-
|
|
6
|
-
const CONTENT_CSS_LAYERS = [
|
|
7
|
-
"base/page-contract.css",
|
|
8
|
-
"base/typography.css",
|
|
9
|
-
"page-surfaces/cover.css",
|
|
10
|
-
{ path: "page-surfaces/chapter-opener.css", optional: true },
|
|
11
|
-
"page-surfaces/back-cover.css",
|
|
12
|
-
"page-surfaces/toc.css",
|
|
13
|
-
{ path: "page-surfaces", type: "directory", exclude: ["cover.css", "chapter-opener.css", "back-cover.css", "toc.css"] },
|
|
14
|
-
"shell/reader-controls.css",
|
|
15
|
-
"base/print.css",
|
|
16
|
-
];
|
|
17
|
-
|
|
18
|
-
export async function copyDirectory(src, dst) {
|
|
19
|
-
await fs.rm(dst, { recursive: true, force: true });
|
|
20
|
-
await fs.mkdir(path.dirname(dst), { recursive: true });
|
|
21
|
-
await fs.cp(src, dst, { recursive: true });
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export async function writeContentCss(root, targetDir, config) {
|
|
25
|
-
config ??= await loadConfig(root);
|
|
26
|
-
const css = await buildContentCss(root, config);
|
|
27
|
-
await fs.mkdir(targetDir, { recursive: true });
|
|
28
|
-
await fs.writeFile(path.join(targetDir, "content.css"), css, "utf8");
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export async function buildContentCss(root, config) {
|
|
32
|
-
config ??= await loadConfig(root);
|
|
33
|
-
const contentAssetsDir = config.paths.themeDir;
|
|
34
|
-
const parts = [];
|
|
35
|
-
for (const layer of CONTENT_CSS_LAYERS) {
|
|
36
|
-
if (typeof layer !== "string" && layer.type === "directory") {
|
|
37
|
-
await appendCssDirectory(parts, path.join(contentAssetsDir, layer.path), layer.path, {
|
|
38
|
-
exclude: new Set(layer.exclude ?? []),
|
|
39
|
-
});
|
|
40
|
-
continue;
|
|
41
|
-
}
|
|
42
|
-
const relativePath = typeof layer === "string" ? layer : layer.path;
|
|
43
|
-
const cssPath = path.join(contentAssetsDir, relativePath);
|
|
44
|
-
let css;
|
|
45
|
-
try {
|
|
46
|
-
css = await fs.readFile(cssPath, "utf8");
|
|
47
|
-
} catch (error) {
|
|
48
|
-
if (typeof layer !== "string" && layer.optional && error.code === "ENOENT") continue;
|
|
49
|
-
throw error;
|
|
50
|
-
}
|
|
51
|
-
parts.push(`/* === ${relativePath} === */\n`);
|
|
52
|
-
parts.push(css.trimEnd());
|
|
53
|
-
parts.push("\n\n");
|
|
54
|
-
}
|
|
55
|
-
parts.push("/* === engine/katex.css === */\n");
|
|
56
|
-
parts.push((await readKatexCss()).trimEnd());
|
|
57
|
-
parts.push("\n\n");
|
|
58
|
-
return parts.join("");
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export async function writeComponentsCss(root, targetDir, config) {
|
|
62
|
-
config ??= await loadConfig(root);
|
|
63
|
-
const css = await buildComponentsCss(root, config);
|
|
64
|
-
await fs.mkdir(targetDir, { recursive: true });
|
|
65
|
-
await fs.writeFile(path.join(targetDir, "components.css"), css, "utf8");
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export async function buildComponentsCss(root, config) {
|
|
69
|
-
config ??= await loadConfig(root);
|
|
70
|
-
const parts = [];
|
|
71
|
-
await appendCssDirectory(parts, path.join(config.paths.themeDir, "patterns"), "theme/patterns");
|
|
72
|
-
await appendComponentScopedCss(parts, config.paths.componentsDir);
|
|
73
|
-
return parts.join("");
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
async function appendCssDirectory(parts, directory, labelPrefix, options = {}) {
|
|
77
|
-
let entries;
|
|
78
|
-
try {
|
|
79
|
-
entries = await fs.readdir(directory);
|
|
80
|
-
} catch {
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
for (const name of entries.filter((entry) => entry.endsWith(".css")).sort()) {
|
|
84
|
-
if (options.exclude?.has(name)) continue;
|
|
85
|
-
parts.push(`/* === ${labelPrefix}/${name} === */\n`);
|
|
86
|
-
parts.push((await fs.readFile(path.join(directory, name), "utf8")).trimEnd());
|
|
87
|
-
parts.push("\n\n");
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
async function appendComponentScopedCss(parts, componentsDir) {
|
|
92
|
-
let entries;
|
|
93
|
-
try {
|
|
94
|
-
entries = await fs.readdir(componentsDir, { withFileTypes: true });
|
|
95
|
-
} catch {
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
for (const entry of entries.filter((item) => item.isDirectory()).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
100
|
-
const cssPath = path.join(componentsDir, entry.name, "style.css");
|
|
101
|
-
let css;
|
|
102
|
-
try {
|
|
103
|
-
css = await fs.readFile(cssPath, "utf8");
|
|
104
|
-
} catch (error) {
|
|
105
|
-
if (error.code === "ENOENT") {
|
|
106
|
-
continue;
|
|
107
|
-
}
|
|
108
|
-
throw error;
|
|
109
|
-
}
|
|
110
|
-
parts.push(`/* === components/${entry.name}/style.css === */\n`);
|
|
111
|
-
parts.push(css.trimEnd());
|
|
112
|
-
parts.push("\n\n");
|
|
113
|
-
}
|
|
114
|
-
}
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs/promises";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
|
|
4
|
-
export async function walkFiles(directory, visit) {
|
|
5
|
-
let entries;
|
|
6
|
-
try {
|
|
7
|
-
entries = await fs.readdir(directory, { withFileTypes: true });
|
|
8
|
-
} catch (error) {
|
|
9
|
-
if (error?.code === "ENOENT") return;
|
|
10
|
-
throw error;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
for (const entry of entries) {
|
|
14
|
-
if (entry.name.startsWith(".")) continue;
|
|
15
|
-
const absolutePath = path.join(directory, entry.name);
|
|
16
|
-
if (entry.isDirectory()) {
|
|
17
|
-
await walkFiles(absolutePath, visit);
|
|
18
|
-
} else if (entry.isFile()) {
|
|
19
|
-
await visit(absolutePath);
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
}
|
|
@@ -1,328 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs/promises";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { evaluateUrlWithChrome, stopChildProcess } from "../output/chrome-pdf.mjs";
|
|
4
|
-
import { buildReactStatic, startStaticServer } from "../commands/_shared.mjs";
|
|
5
|
-
import { walkFiles } from "./file-walk.mjs";
|
|
6
|
-
import { createIssue, createIssueReport } from "./issue-report.mjs";
|
|
7
|
-
import { collectActiveContentFiles, resolveActiveSourceWorkspace } from "./source-workspace.mjs";
|
|
8
|
-
|
|
9
|
-
const MEDIA_EXTENSIONS = new Set([".avif", ".gif", ".jpeg", ".jpg", ".png", ".svg", ".webp"]);
|
|
10
|
-
const SOURCE_EXTENSIONS = new Set([".css", ".html", ".js", ".json", ".md", ".mjs", ".ts", ".tsx"]);
|
|
11
|
-
|
|
12
|
-
export async function inspectWorkspace({ root, config, options = {}, recurse = null }) {
|
|
13
|
-
const checked = [];
|
|
14
|
-
const issues = [];
|
|
15
|
-
|
|
16
|
-
const sourceScan = await collectInspectionSources(config);
|
|
17
|
-
checked.push(sourceScan.checkedName);
|
|
18
|
-
sourceScan.contentFiles.forEach((file) => {
|
|
19
|
-
if (!file.text.trim()) {
|
|
20
|
-
issues.push(createIssue({
|
|
21
|
-
level: "warning",
|
|
22
|
-
code: "react-source.empty-file",
|
|
23
|
-
message: `${sourceScan.contentLabel} \`${file.relativePath}\` is empty.`,
|
|
24
|
-
path: file.absolutePath,
|
|
25
|
-
}));
|
|
26
|
-
}
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
checked.push("media");
|
|
30
|
-
const mediaFiles = await readMediaFiles(sourceScan.config.paths.mediaDir);
|
|
31
|
-
const sourceText = sourceScan.sourceFiles.map((file) => file.text).join("\n");
|
|
32
|
-
const unusedMedia = mediaFiles.filter((file) => !sourceText.includes(file.name) && !sourceText.includes(file.relativePath));
|
|
33
|
-
unusedMedia.forEach((file) => {
|
|
34
|
-
issues.push(createIssue({
|
|
35
|
-
level: "warning",
|
|
36
|
-
code: "media.unused",
|
|
37
|
-
message: `Media asset \`${file.relativePath}\` is not referenced by document sources.`,
|
|
38
|
-
path: file.absolutePath,
|
|
39
|
-
detail: {
|
|
40
|
-
file: file.name,
|
|
41
|
-
relativePath: file.relativePath,
|
|
42
|
-
},
|
|
43
|
-
}));
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
checked.push("components");
|
|
47
|
-
const componentUsage = sourceScan.componentUsage;
|
|
48
|
-
|
|
49
|
-
const summary = {
|
|
50
|
-
...sourceScan.summary,
|
|
51
|
-
mediaFiles: mediaFiles.length,
|
|
52
|
-
unusedMedia: unusedMedia.length,
|
|
53
|
-
componentUsage,
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
checked.push("overflow");
|
|
57
|
-
const renderCode = await buildReactStatic({
|
|
58
|
-
root,
|
|
59
|
-
noBuild: options.noBuild,
|
|
60
|
-
recurse,
|
|
61
|
-
silent: options.json,
|
|
62
|
-
});
|
|
63
|
-
if (renderCode !== 0) {
|
|
64
|
-
issues.push(createIssue({
|
|
65
|
-
level: "error",
|
|
66
|
-
code: "inspect.render",
|
|
67
|
-
message: `React render failed before overflow inspection (exit code ${renderCode}).`,
|
|
68
|
-
path: config.configPath,
|
|
69
|
-
}));
|
|
70
|
-
return createIssueReport({
|
|
71
|
-
kind: "inspection",
|
|
72
|
-
checked,
|
|
73
|
-
issues,
|
|
74
|
-
summary,
|
|
75
|
-
okMessage: "OpenPress inspection OK",
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
const overflowMeasurements = await inspectRenderedOverflow({
|
|
80
|
-
root,
|
|
81
|
-
config,
|
|
82
|
-
host: options.host ?? "127.0.0.1",
|
|
83
|
-
port: options.port ?? "5186",
|
|
84
|
-
});
|
|
85
|
-
issues.push(...overflowIssuesFromMeasurements(overflowMeasurements));
|
|
86
|
-
summary.pages = overflowMeasurements.length;
|
|
87
|
-
summary.overflowPages = overflowMeasurements.filter((page) => (page.overflows ?? []).length > 0).length;
|
|
88
|
-
|
|
89
|
-
return createIssueReport({
|
|
90
|
-
kind: "inspection",
|
|
91
|
-
checked,
|
|
92
|
-
issues,
|
|
93
|
-
summary,
|
|
94
|
-
okMessage: "OpenPress inspection OK",
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
export async function collectInspectionSources(config) {
|
|
99
|
-
const sourceWorkspace = await resolveActiveSourceWorkspace(config);
|
|
100
|
-
const sourceConfig = sourceWorkspace.config;
|
|
101
|
-
const contentFiles = await collectActiveContentFiles(sourceWorkspace);
|
|
102
|
-
const sourceFiles = [
|
|
103
|
-
...contentFiles,
|
|
104
|
-
...await readSourceFiles(sourceConfig.paths.componentsDir),
|
|
105
|
-
...await readSingleFile(sourceConfig.paths.designDoc),
|
|
106
|
-
];
|
|
107
|
-
const componentUsage = summarizeComponentUsage(contentFiles);
|
|
108
|
-
|
|
109
|
-
return {
|
|
110
|
-
sourceKind: sourceWorkspace.kind,
|
|
111
|
-
checkedName: sourceWorkspace.checkedName,
|
|
112
|
-
contentLabel: sourceWorkspace.contentLabel,
|
|
113
|
-
config: sourceConfig,
|
|
114
|
-
contentFiles,
|
|
115
|
-
sourceFiles,
|
|
116
|
-
componentUsage,
|
|
117
|
-
summary: {
|
|
118
|
-
sourceKind: sourceWorkspace.kind,
|
|
119
|
-
sourceFiles: contentFiles.length,
|
|
120
|
-
mdxFiles: contentFiles.length,
|
|
121
|
-
},
|
|
122
|
-
};
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
export async function inspectRenderedOverflow({ root, config, host = "127.0.0.1", port = "5186" }) {
|
|
126
|
-
const server = await startStaticServer(root, config, host, port);
|
|
127
|
-
try {
|
|
128
|
-
return await evaluateUrlWithChrome({
|
|
129
|
-
root,
|
|
130
|
-
url: `http://${host}:${port}/?print=1`,
|
|
131
|
-
debuggingPortBase: 9900,
|
|
132
|
-
debuggingPortRange: 600,
|
|
133
|
-
profilePrefix: "chrome-inspect",
|
|
134
|
-
emulatedMedia: "print",
|
|
135
|
-
evaluate: waitForInspectionReady,
|
|
136
|
-
});
|
|
137
|
-
} finally {
|
|
138
|
-
await stopChildProcess(server);
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
export async function waitForInspectionReady(client) {
|
|
143
|
-
const deadline = Date.now() + 30000;
|
|
144
|
-
while (Date.now() < deadline) {
|
|
145
|
-
const result = await client.send("Runtime.evaluate", {
|
|
146
|
-
returnByValue: true,
|
|
147
|
-
awaitPromise: true,
|
|
148
|
-
expression: inspectionExpression(),
|
|
149
|
-
});
|
|
150
|
-
const value = result.result?.value;
|
|
151
|
-
if (Array.isArray(value)) return value;
|
|
152
|
-
await delay(100);
|
|
153
|
-
}
|
|
154
|
-
throw new Error("Timed out waiting for OpenPress pagination before inspection.");
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
export function overflowIssuesFromMeasurements(measurements) {
|
|
158
|
-
return measurements.flatMap((page) => {
|
|
159
|
-
const pageLabel = String(page.pageNumber).padStart(2, "0");
|
|
160
|
-
return (page.overflows ?? []).map((overflow) => createIssue({
|
|
161
|
-
level: "warning",
|
|
162
|
-
code: `overflow.${overflow.code}`,
|
|
163
|
-
message: `Page ${pageLabel} exceeds ${humanOverflowTarget(overflow.code)} by ${Math.ceil(overflow.overflowPx)}px.`,
|
|
164
|
-
path: page.source?.path,
|
|
165
|
-
detail: {
|
|
166
|
-
pageNumber: page.pageNumber,
|
|
167
|
-
title: page.title,
|
|
168
|
-
sourceFile: page.source?.file,
|
|
169
|
-
selector: overflow.selector,
|
|
170
|
-
tagName: overflow.tagName,
|
|
171
|
-
text: overflow.text,
|
|
172
|
-
overflowPx: Math.ceil(overflow.overflowPx),
|
|
173
|
-
},
|
|
174
|
-
}));
|
|
175
|
-
});
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
async function readSourceFiles(directory, extension = null) {
|
|
179
|
-
const files = [];
|
|
180
|
-
await walkFiles(directory, async (absolutePath) => {
|
|
181
|
-
if (extension && path.extname(absolutePath) !== extension) return;
|
|
182
|
-
if (!SOURCE_EXTENSIONS.has(path.extname(absolutePath))) return;
|
|
183
|
-
files.push({
|
|
184
|
-
absolutePath,
|
|
185
|
-
relativePath: path.relative(directory, absolutePath).replaceAll("\\", "/"),
|
|
186
|
-
text: await fs.readFile(absolutePath, "utf8"),
|
|
187
|
-
});
|
|
188
|
-
});
|
|
189
|
-
files.sort((a, b) => a.relativePath.localeCompare(b.relativePath));
|
|
190
|
-
return files;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
async function readSingleFile(absolutePath) {
|
|
194
|
-
try {
|
|
195
|
-
const text = await fs.readFile(absolutePath, "utf8");
|
|
196
|
-
return [{
|
|
197
|
-
absolutePath,
|
|
198
|
-
relativePath: path.basename(absolutePath),
|
|
199
|
-
text,
|
|
200
|
-
}];
|
|
201
|
-
} catch (error) {
|
|
202
|
-
if (error?.code === "ENOENT") return [];
|
|
203
|
-
throw error;
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
async function readMediaFiles(directory) {
|
|
208
|
-
const files = [];
|
|
209
|
-
await walkFiles(directory, async (absolutePath) => {
|
|
210
|
-
if (!MEDIA_EXTENSIONS.has(path.extname(absolutePath).toLowerCase())) return;
|
|
211
|
-
files.push({
|
|
212
|
-
absolutePath,
|
|
213
|
-
name: path.basename(absolutePath),
|
|
214
|
-
relativePath: path.relative(directory, absolutePath).replaceAll("\\", "/"),
|
|
215
|
-
});
|
|
216
|
-
});
|
|
217
|
-
files.sort((a, b) => a.relativePath.localeCompare(b.relativePath));
|
|
218
|
-
return files;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
function summarizeComponentUsage(contentFiles) {
|
|
222
|
-
const usages = new Map();
|
|
223
|
-
for (const file of contentFiles) {
|
|
224
|
-
for (const match of file.text.matchAll(/<([A-Z][A-Za-z0-9]*)\b/g)) {
|
|
225
|
-
const name = match[1];
|
|
226
|
-
const current = usages.get(name) ?? { name, count: 0, files: [] };
|
|
227
|
-
current.count += 1;
|
|
228
|
-
if (!current.files.includes(file.relativePath)) current.files.push(file.relativePath);
|
|
229
|
-
usages.set(name, current);
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
return Array.from(usages.values()).sort((a, b) => a.name.localeCompare(b.name));
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
function humanOverflowTarget(code) {
|
|
236
|
-
if (code === "page-body") return "page body";
|
|
237
|
-
if (code === "page-frame") return "page frame";
|
|
238
|
-
return code.replaceAll("-", " ");
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
function inspectionExpression() {
|
|
242
|
-
return `Promise.resolve().then(async () => {
|
|
243
|
-
const root = document.querySelector('[data-openpress-print-document="true"]');
|
|
244
|
-
if (!root || root.querySelectorAll('.openpress-html-page').length === 0) return null;
|
|
245
|
-
|
|
246
|
-
await document.fonts?.ready;
|
|
247
|
-
await Promise.all(Array.from(document.images).map(async (img) => {
|
|
248
|
-
if (!img.complete) {
|
|
249
|
-
await new Promise((resolve) => {
|
|
250
|
-
const settle = () => {
|
|
251
|
-
img.removeEventListener('load', settle);
|
|
252
|
-
img.removeEventListener('error', settle);
|
|
253
|
-
resolve();
|
|
254
|
-
};
|
|
255
|
-
img.addEventListener('load', settle, { once: true });
|
|
256
|
-
img.addEventListener('error', settle, { once: true });
|
|
257
|
-
});
|
|
258
|
-
}
|
|
259
|
-
await img.decode?.().catch(() => undefined);
|
|
260
|
-
}));
|
|
261
|
-
await new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(resolve)));
|
|
262
|
-
|
|
263
|
-
const textFor = (element) => (element?.textContent || '').replace(/\\s+/g, ' ').trim().slice(0, 120);
|
|
264
|
-
const overflowAmount = (outer, inner) => {
|
|
265
|
-
if (!outer || !inner) return 0;
|
|
266
|
-
const outerRect = outer.getBoundingClientRect();
|
|
267
|
-
const innerRect = inner.getBoundingClientRect();
|
|
268
|
-
return Math.max(0, innerRect.bottom - outerRect.bottom, innerRect.right - outerRect.right);
|
|
269
|
-
};
|
|
270
|
-
const contentBottomOverflow = (body) => {
|
|
271
|
-
if (!body) return 0;
|
|
272
|
-
const bodyRect = body.getBoundingClientRect();
|
|
273
|
-
const contentBottom = Array.from(body.children).reduce((bottom, child) => {
|
|
274
|
-
if (getComputedStyle(child).display === 'none') return bottom;
|
|
275
|
-
const marginBottom = Number.parseFloat(getComputedStyle(child).marginBottom) || 0;
|
|
276
|
-
return Math.max(bottom, child.getBoundingClientRect().bottom + marginBottom);
|
|
277
|
-
}, bodyRect.top);
|
|
278
|
-
return Math.max(0, contentBottom - bodyRect.bottom);
|
|
279
|
-
};
|
|
280
|
-
const addElementOverflow = (overflows, code, selector, container, element) => {
|
|
281
|
-
const px = overflowAmount(container, element);
|
|
282
|
-
if (px <= 1) return;
|
|
283
|
-
overflows.push({
|
|
284
|
-
code,
|
|
285
|
-
selector,
|
|
286
|
-
overflowPx: Math.ceil(px),
|
|
287
|
-
tagName: element.tagName,
|
|
288
|
-
text: textFor(element),
|
|
289
|
-
});
|
|
290
|
-
};
|
|
291
|
-
|
|
292
|
-
const wrappers = Array.from(document.querySelectorAll('.openpress-public-page > .openpress-html-page'));
|
|
293
|
-
if (wrappers.length === 0) return null;
|
|
294
|
-
return wrappers.map((wrapper, index) => {
|
|
295
|
-
const page = wrapper.querySelector('.reader-page') || wrapper;
|
|
296
|
-
const frame = page.querySelector('.page-frame') || page;
|
|
297
|
-
const body = page.querySelector('.page-body') || frame;
|
|
298
|
-
const sourcePath = wrapper.getAttribute('data-source-path') || undefined;
|
|
299
|
-
const sourceFile = wrapper.getAttribute('data-source-file') || sourcePath?.split('/').pop();
|
|
300
|
-
const overflows = [];
|
|
301
|
-
const bodyOverflow = contentBottomOverflow(body);
|
|
302
|
-
if (bodyOverflow > 1) {
|
|
303
|
-
overflows.push({
|
|
304
|
-
code: 'page-body',
|
|
305
|
-
selector: '.page-body',
|
|
306
|
-
overflowPx: Math.ceil(bodyOverflow),
|
|
307
|
-
tagName: body.tagName,
|
|
308
|
-
text: textFor(body),
|
|
309
|
-
});
|
|
310
|
-
}
|
|
311
|
-
addElementOverflow(overflows, 'page-frame', '.page-frame', page, frame);
|
|
312
|
-
body.querySelectorAll('table, img, pre, figure, [data-openpress-component]').forEach((element) => {
|
|
313
|
-
const tag = element.tagName.toLowerCase();
|
|
314
|
-
addElementOverflow(overflows, tag, tag, body, element);
|
|
315
|
-
});
|
|
316
|
-
return {
|
|
317
|
-
pageNumber: index + 1,
|
|
318
|
-
title: page.getAttribute('data-page-title') || wrapper.getAttribute('aria-label') || '',
|
|
319
|
-
source: sourcePath ? { file: sourceFile, path: sourcePath } : undefined,
|
|
320
|
-
overflows,
|
|
321
|
-
};
|
|
322
|
-
});
|
|
323
|
-
})`;
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
function delay(ms) {
|
|
327
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
328
|
-
}
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
export function createIssue({ level = "warning", code, message, path = null, detail = undefined }) {
|
|
2
|
-
return {
|
|
3
|
-
level,
|
|
4
|
-
code,
|
|
5
|
-
message,
|
|
6
|
-
...(path ? { path } : {}),
|
|
7
|
-
...(detail !== undefined ? { detail } : {}),
|
|
8
|
-
};
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export function createIssueReport({ kind, checked = [], issues = [], summary = undefined, okMessage = undefined }) {
|
|
12
|
-
const report = {
|
|
13
|
-
kind,
|
|
14
|
-
ok: issues.every((issue) => issue.level !== "error"),
|
|
15
|
-
checked,
|
|
16
|
-
issues,
|
|
17
|
-
...(summary !== undefined ? { summary } : {}),
|
|
18
|
-
};
|
|
19
|
-
return {
|
|
20
|
-
...report,
|
|
21
|
-
format() {
|
|
22
|
-
return formatIssueReport(report, { okMessage });
|
|
23
|
-
},
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export function exitCodeForIssueReport(report) {
|
|
28
|
-
return report.ok ? 0 : 1;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export function formatIssueReport(report, { okMessage } = {}) {
|
|
32
|
-
if (report.issues.length === 0) return okMessage ?? `${capitalize(report.kind)} OK`;
|
|
33
|
-
return report.issues.map(formatIssue).join("\n");
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function formatIssue(issue) {
|
|
37
|
-
const suffix = issue.path ? ` (${issue.path})` : "";
|
|
38
|
-
return `[${issue.level}] ${issue.code}: ${issue.message}${suffix}`;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function capitalize(value) {
|
|
42
|
-
const text = String(value ?? "");
|
|
43
|
-
return text ? `${text.charAt(0).toUpperCase()}${text.slice(1)}` : "Report";
|
|
44
|
-
}
|