pressy 0.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/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +835 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/config-DlVehy4M.d.ts +41 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +757 -0
- package/dist/index.js.map +1 -0
- package/dist/runtime/client.d.ts +25 -0
- package/dist/runtime/client.js +660 -0
- package/dist/runtime/client.js.map +1 -0
- package/dist/runtime/offline.d.ts +17 -0
- package/dist/runtime/offline.js +171 -0
- package/dist/runtime/offline.js.map +1 -0
- package/dist/runtime/sw.d.ts +2 -0
- package/dist/runtime/sw.js +136 -0
- package/dist/runtime/sw.js.map +1 -0
- package/dist/types-CQs_xIit.d.ts +72 -0
- package/dist/vite/plugin.d.ts +6 -0
- package/dist/vite/plugin.js +738 -0
- package/dist/vite/plugin.js.map +1 -0
- package/package.json +75 -0
|
@@ -0,0 +1,835 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// ../../node_modules/.pnpm/tsup@8.5.1_typescript@5.9.3_yaml@2.8.2/node_modules/tsup/assets/esm_shims.js
|
|
4
|
+
import path from "path";
|
|
5
|
+
import { fileURLToPath } from "url";
|
|
6
|
+
var getFilename = () => fileURLToPath(import.meta.url);
|
|
7
|
+
var getDirname = () => path.dirname(getFilename());
|
|
8
|
+
var __dirname = /* @__PURE__ */ getDirname();
|
|
9
|
+
|
|
10
|
+
// src/cli/index.ts
|
|
11
|
+
import { cac } from "cac";
|
|
12
|
+
import { createServer, build, preview } from "vite";
|
|
13
|
+
import { resolve as resolve2 } from "path";
|
|
14
|
+
import { existsSync as existsSync2, writeFileSync, rmSync } from "fs";
|
|
15
|
+
import { transform } from "esbuild";
|
|
16
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
17
|
+
|
|
18
|
+
// src/vite/plugin.ts
|
|
19
|
+
import { resolve, join, relative, dirname } from "path";
|
|
20
|
+
import { existsSync, readFileSync } from "fs";
|
|
21
|
+
import { createHash } from "crypto";
|
|
22
|
+
import { createRequire } from "module";
|
|
23
|
+
import fastGlob from "fast-glob";
|
|
24
|
+
import yaml from "yaml";
|
|
25
|
+
import matter2 from "gray-matter";
|
|
26
|
+
|
|
27
|
+
// src/mdx/processor.ts
|
|
28
|
+
import { compile } from "@mdx-js/mdx";
|
|
29
|
+
import remarkGfm from "remark-gfm";
|
|
30
|
+
import rehypeSlug from "rehype-slug";
|
|
31
|
+
import matter from "gray-matter";
|
|
32
|
+
async function compileMDX(source, filePath) {
|
|
33
|
+
const { content, data: frontmatter } = matter(source);
|
|
34
|
+
const result = await compile(content, {
|
|
35
|
+
jsxImportSource: "preact",
|
|
36
|
+
remarkPlugins: [remarkGfm],
|
|
37
|
+
rehypePlugins: [rehypeSlug],
|
|
38
|
+
development: false
|
|
39
|
+
});
|
|
40
|
+
const compiled = String(result);
|
|
41
|
+
const code = `${compiled}
|
|
42
|
+
export const frontmatter = ${JSON.stringify(frontmatter)};
|
|
43
|
+
`;
|
|
44
|
+
return {
|
|
45
|
+
code,
|
|
46
|
+
map: null,
|
|
47
|
+
frontmatter
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// src/runtime/offline-page.ts
|
|
52
|
+
function generateOfflinePage(siteTitle) {
|
|
53
|
+
return `<!DOCTYPE html>
|
|
54
|
+
<html lang="en">
|
|
55
|
+
<head>
|
|
56
|
+
<meta charset="UTF-8">
|
|
57
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
58
|
+
<title>Offline | ${siteTitle}</title>
|
|
59
|
+
<style>
|
|
60
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
61
|
+
|
|
62
|
+
body {
|
|
63
|
+
font-family: Georgia, 'Times New Roman', serif;
|
|
64
|
+
display: flex;
|
|
65
|
+
align-items: center;
|
|
66
|
+
justify-content: center;
|
|
67
|
+
min-height: 100vh;
|
|
68
|
+
padding: 2rem;
|
|
69
|
+
background: #fafafa;
|
|
70
|
+
color: #1a1a1a;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.offline-container {
|
|
74
|
+
text-align: center;
|
|
75
|
+
max-width: 420px;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.offline-icon {
|
|
79
|
+
width: 64px;
|
|
80
|
+
height: 64px;
|
|
81
|
+
margin: 0 auto 1.5rem;
|
|
82
|
+
color: #999;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
h1 {
|
|
86
|
+
font-size: 1.5rem;
|
|
87
|
+
margin-bottom: 0.75rem;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
p {
|
|
91
|
+
color: #666;
|
|
92
|
+
line-height: 1.6;
|
|
93
|
+
margin-bottom: 1.5rem;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
button {
|
|
97
|
+
font-family: inherit;
|
|
98
|
+
font-size: 1rem;
|
|
99
|
+
padding: 0.75rem 1.5rem;
|
|
100
|
+
background: #1a1a1a;
|
|
101
|
+
color: #fff;
|
|
102
|
+
border: none;
|
|
103
|
+
border-radius: 0.5rem;
|
|
104
|
+
cursor: pointer;
|
|
105
|
+
transition: background 0.15s;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
button:hover { background: #333; }
|
|
109
|
+
|
|
110
|
+
@media (prefers-color-scheme: dark) {
|
|
111
|
+
body { background: #1a1a1a; color: #e5e5e5; }
|
|
112
|
+
p { color: #999; }
|
|
113
|
+
.offline-icon { color: #666; }
|
|
114
|
+
button { background: #e5e5e5; color: #1a1a1a; }
|
|
115
|
+
button:hover { background: #ccc; }
|
|
116
|
+
}
|
|
117
|
+
</style>
|
|
118
|
+
</head>
|
|
119
|
+
<body>
|
|
120
|
+
<div class="offline-container">
|
|
121
|
+
<svg class="offline-icon" viewBox="0 0 24 24" fill="currentColor">
|
|
122
|
+
<path d="M23.64 7c-.45-.34-4.93-4-11.64-4-1.5 0-2.89.19-4.15.48L18.18 13.8 23.64 7zM3.41 1.31L2 2.72l2.05 2.05C1.91 5.76.59 6.82.36 7L12 21.5l3.91-4.87 3.32 3.32 1.41-1.41L3.41 1.31z"/>
|
|
123
|
+
</svg>
|
|
124
|
+
<h1>You\u2019re offline</h1>
|
|
125
|
+
<p>This page isn\u2019t available right now. If you\u2019ve previously downloaded this book for offline reading, try navigating to it directly.</p>
|
|
126
|
+
<button onclick="window.location.reload()">Try again</button>
|
|
127
|
+
</div>
|
|
128
|
+
</body>
|
|
129
|
+
</html>`;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// src/vite/plugin.ts
|
|
133
|
+
var _require = createRequire(import.meta.url);
|
|
134
|
+
var VIRTUAL_MODULE_ID = "virtual:pressy";
|
|
135
|
+
var RESOLVED_VIRTUAL_MODULE_ID = "\0" + VIRTUAL_MODULE_ID;
|
|
136
|
+
var VIRTUAL_ENTRY_ID = "virtual:pressy-entry";
|
|
137
|
+
var RESOLVED_VIRTUAL_ENTRY_ID = "\0" + VIRTUAL_ENTRY_ID;
|
|
138
|
+
var VIRTUAL_ROUTE_PREFIX = "virtual:pressy-route:";
|
|
139
|
+
var RESOLVED_ROUTE_PREFIX = "\0" + VIRTUAL_ROUTE_PREFIX;
|
|
140
|
+
var VIRTUAL_CHAPTER_MAP_PREFIX = "virtual:pressy-chapter-map:";
|
|
141
|
+
var RESOLVED_CHAPTER_MAP_PREFIX = "\0" + VIRTUAL_CHAPTER_MAP_PREFIX;
|
|
142
|
+
function pressyPlugin(config) {
|
|
143
|
+
let root;
|
|
144
|
+
let contentDir;
|
|
145
|
+
let manifest = { books: [], articles: [] };
|
|
146
|
+
let routes = [];
|
|
147
|
+
const pwaEnabled = config.pwa?.enabled !== false;
|
|
148
|
+
const pwaConfig = {
|
|
149
|
+
themeColor: config.pwa?.themeColor || "#ffffff",
|
|
150
|
+
backgroundColor: config.pwa?.backgroundColor || "#ffffff",
|
|
151
|
+
display: config.pwa?.display || "standalone",
|
|
152
|
+
shortName: config.pwa?.shortName || config.site.title
|
|
153
|
+
};
|
|
154
|
+
const contentDiscovery = {
|
|
155
|
+
async discoverContent() {
|
|
156
|
+
const books = await this.discoverBooks();
|
|
157
|
+
const articles = await this.discoverArticles();
|
|
158
|
+
return { books, articles };
|
|
159
|
+
},
|
|
160
|
+
async discoverBooks() {
|
|
161
|
+
const booksDir = join(contentDir, "books");
|
|
162
|
+
if (!existsSync(booksDir)) return [];
|
|
163
|
+
const bookDirs = await fastGlob("*", {
|
|
164
|
+
cwd: booksDir,
|
|
165
|
+
onlyDirectories: true
|
|
166
|
+
});
|
|
167
|
+
const books = [];
|
|
168
|
+
for (const bookSlug of bookDirs) {
|
|
169
|
+
const bookPath = join(booksDir, bookSlug);
|
|
170
|
+
const metadataPath = join(bookPath, "_book.yaml");
|
|
171
|
+
if (!existsSync(metadataPath)) continue;
|
|
172
|
+
const metadataContent = readFileSync(metadataPath, "utf-8");
|
|
173
|
+
const metadata = yaml.parse(metadataContent);
|
|
174
|
+
const chapterFiles = await fastGlob("*.mdx", {
|
|
175
|
+
cwd: bookPath,
|
|
176
|
+
ignore: ["_*.mdx"]
|
|
177
|
+
});
|
|
178
|
+
const chapters = chapterFiles.map((file) => {
|
|
179
|
+
const match = file.match(/^(\d+)-(.+)\.mdx$/);
|
|
180
|
+
if (!match) return null;
|
|
181
|
+
const order = parseInt(match[1], 10);
|
|
182
|
+
const slug = match[2];
|
|
183
|
+
const filePath = join(bookPath, file);
|
|
184
|
+
const content = readFileSync(filePath, "utf-8");
|
|
185
|
+
const { data } = matter2(content);
|
|
186
|
+
return {
|
|
187
|
+
slug,
|
|
188
|
+
title: data.title || slug.replace(/-/g, " "),
|
|
189
|
+
order,
|
|
190
|
+
filePath,
|
|
191
|
+
wordCount: content.split(/\s+/).length,
|
|
192
|
+
readingTime: Math.ceil(content.split(/\s+/).length / 200)
|
|
193
|
+
};
|
|
194
|
+
}).filter((c) => c !== null).sort((a, b) => a.order - b.order);
|
|
195
|
+
const coverPath = existsSync(join(bookPath, "cover.jpg")) ? join(bookPath, "cover.jpg") : existsSync(join(bookPath, "cover.png")) ? join(bookPath, "cover.png") : void 0;
|
|
196
|
+
books.push({
|
|
197
|
+
slug: bookSlug,
|
|
198
|
+
metadata,
|
|
199
|
+
chapters,
|
|
200
|
+
basePath: bookPath,
|
|
201
|
+
coverPath
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
return books;
|
|
205
|
+
},
|
|
206
|
+
async discoverArticles() {
|
|
207
|
+
const articlesDir = join(contentDir, "articles");
|
|
208
|
+
if (!existsSync(articlesDir)) return [];
|
|
209
|
+
const articleDirs = await fastGlob("*", {
|
|
210
|
+
cwd: articlesDir,
|
|
211
|
+
onlyDirectories: true
|
|
212
|
+
});
|
|
213
|
+
const articles = [];
|
|
214
|
+
for (const articleSlug of articleDirs) {
|
|
215
|
+
const articlePath = join(articlesDir, articleSlug);
|
|
216
|
+
const metadataPath = join(articlePath, "_article.yaml");
|
|
217
|
+
const indexPath = join(articlePath, "index.mdx");
|
|
218
|
+
if (!existsSync(indexPath)) continue;
|
|
219
|
+
let metadata;
|
|
220
|
+
if (existsSync(metadataPath)) {
|
|
221
|
+
metadata = yaml.parse(readFileSync(metadataPath, "utf-8"));
|
|
222
|
+
} else {
|
|
223
|
+
const content2 = readFileSync(indexPath, "utf-8");
|
|
224
|
+
const { data } = matter2(content2);
|
|
225
|
+
metadata = data;
|
|
226
|
+
}
|
|
227
|
+
const content = readFileSync(indexPath, "utf-8");
|
|
228
|
+
articles.push({
|
|
229
|
+
slug: articleSlug,
|
|
230
|
+
metadata,
|
|
231
|
+
filePath: indexPath,
|
|
232
|
+
basePath: articlePath,
|
|
233
|
+
wordCount: content.split(/\s+/).length,
|
|
234
|
+
readingTime: Math.ceil(content.split(/\s+/).length / 200)
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
return articles;
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
function generateRoutes(manifest2) {
|
|
241
|
+
const routes2 = [
|
|
242
|
+
{ path: "/", type: "home" },
|
|
243
|
+
{ path: "/books", type: "books" },
|
|
244
|
+
{ path: "/articles", type: "articles" }
|
|
245
|
+
];
|
|
246
|
+
for (const book of manifest2.books) {
|
|
247
|
+
routes2.push({
|
|
248
|
+
path: `/books/${book.slug}`,
|
|
249
|
+
type: "book",
|
|
250
|
+
content: book
|
|
251
|
+
});
|
|
252
|
+
for (const chapter of book.chapters) {
|
|
253
|
+
routes2.push({
|
|
254
|
+
path: `/books/${book.slug}/${chapter.slug}`,
|
|
255
|
+
type: "chapter",
|
|
256
|
+
content: chapter,
|
|
257
|
+
book
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
for (const article of manifest2.articles) {
|
|
262
|
+
routes2.push({
|
|
263
|
+
path: `/articles/${article.slug}`,
|
|
264
|
+
type: "article",
|
|
265
|
+
content: article
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
return routes2;
|
|
269
|
+
}
|
|
270
|
+
function escapeHtmlAttr(str) {
|
|
271
|
+
return str.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
272
|
+
}
|
|
273
|
+
function generateHTML(route) {
|
|
274
|
+
const title = escapeHtmlAttr(getRouteTitle(route, config));
|
|
275
|
+
const description = escapeHtmlAttr(getRouteDescription(route, config));
|
|
276
|
+
let contentImport = "";
|
|
277
|
+
let contentArg = "";
|
|
278
|
+
let chapterMapImport = "";
|
|
279
|
+
let chapterMapArg = "";
|
|
280
|
+
if (route.type === "chapter") {
|
|
281
|
+
const chapter = route.content;
|
|
282
|
+
const importPath = "/" + relative(root, chapter.filePath).split("\\").join("/");
|
|
283
|
+
contentImport = `import Content from '${importPath}';
|
|
284
|
+
`;
|
|
285
|
+
contentArg = ", Content";
|
|
286
|
+
if (route.book) {
|
|
287
|
+
chapterMapImport = `import { chapterMap, chapterOrder } from '${VIRTUAL_CHAPTER_MAP_PREFIX}${route.book.slug}';
|
|
288
|
+
`;
|
|
289
|
+
chapterMapArg = ", { chapterMap, chapterOrder }";
|
|
290
|
+
}
|
|
291
|
+
} else if (route.type === "article") {
|
|
292
|
+
const article = route.content;
|
|
293
|
+
const importPath = "/" + relative(root, article.filePath).split("\\").join("/");
|
|
294
|
+
contentImport = `import Content from '${importPath}';
|
|
295
|
+
`;
|
|
296
|
+
contentArg = ", Content";
|
|
297
|
+
}
|
|
298
|
+
const dataJson = JSON.stringify({
|
|
299
|
+
route: route.path,
|
|
300
|
+
routeType: route.type,
|
|
301
|
+
manifest,
|
|
302
|
+
pagination: config.pagination
|
|
303
|
+
});
|
|
304
|
+
const pwaTags = pwaEnabled ? `
|
|
305
|
+
<link rel="manifest" href="/manifest.webmanifest">
|
|
306
|
+
<meta name="theme-color" content="${pwaConfig.themeColor}">
|
|
307
|
+
<meta name="apple-mobile-web-app-capable" content="yes">
|
|
308
|
+
<meta name="apple-mobile-web-app-status-bar-style" content="default">
|
|
309
|
+
<meta name="apple-mobile-web-app-title" content="${pwaConfig.shortName}">
|
|
310
|
+
<link rel="apple-touch-icon" href="/icon-192.png">` : `
|
|
311
|
+
<meta name="theme-color" content="${pwaConfig.themeColor}">`;
|
|
312
|
+
return `<!DOCTYPE html>
|
|
313
|
+
<html lang="${config.site.language || "en"}">
|
|
314
|
+
<head>
|
|
315
|
+
<meta charset="UTF-8">
|
|
316
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
317
|
+
<title>${title}</title>
|
|
318
|
+
<meta name="description" content="${description}">
|
|
319
|
+
<link rel="stylesheet" href="/@pressy-pub/typography/prose.css">
|
|
320
|
+
<link rel="stylesheet" href="/@pressy-pub/typography/fluid.css">
|
|
321
|
+
<link rel="stylesheet" href="/@pressy-pub/typography/themes/light.css">
|
|
322
|
+
<link rel="stylesheet" href="/@pressy-pub/typography/themes/dark.css">
|
|
323
|
+
<link rel="stylesheet" href="/@pressy-pub/typography/themes/sepia.css">${pwaTags}
|
|
324
|
+
</head>
|
|
325
|
+
<body>
|
|
326
|
+
<div id="app"></div>
|
|
327
|
+
<script type="module">
|
|
328
|
+
import { hydrate } from '/@pressy-pub/client';
|
|
329
|
+
${contentImport}${chapterMapImport}const data = ${dataJson};
|
|
330
|
+
hydrate(data${contentArg}${chapterMapArg});
|
|
331
|
+
</script>
|
|
332
|
+
</body>
|
|
333
|
+
</html>`;
|
|
334
|
+
}
|
|
335
|
+
function getRouteTitle(route, config2) {
|
|
336
|
+
switch (route.type) {
|
|
337
|
+
case "home":
|
|
338
|
+
return config2.site.title;
|
|
339
|
+
case "books":
|
|
340
|
+
return `Books | ${config2.site.title}`;
|
|
341
|
+
case "articles":
|
|
342
|
+
return `Articles | ${config2.site.title}`;
|
|
343
|
+
case "book":
|
|
344
|
+
return `${route.content.metadata.title} | ${config2.site.title}`;
|
|
345
|
+
case "chapter":
|
|
346
|
+
return `${route.content.title} | ${route.book?.metadata.title} | ${config2.site.title}`;
|
|
347
|
+
case "article":
|
|
348
|
+
return `${route.content.metadata.title} | ${config2.site.title}`;
|
|
349
|
+
default:
|
|
350
|
+
return config2.site.title;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
function getRouteDescription(route, config2) {
|
|
354
|
+
switch (route.type) {
|
|
355
|
+
case "book":
|
|
356
|
+
return route.content.metadata.description || "";
|
|
357
|
+
case "article":
|
|
358
|
+
return route.content.metadata.description || "";
|
|
359
|
+
default:
|
|
360
|
+
return config2.site.description || "";
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
function generatePlaceholderIcon(size) {
|
|
364
|
+
const initial = config.site.title.charAt(0).toUpperCase();
|
|
365
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 ${size} ${size}">
|
|
366
|
+
<rect width="${size}" height="${size}" fill="${pwaConfig.themeColor === "#ffffff" ? "#1a1a1a" : pwaConfig.themeColor}" rx="${Math.round(size * 0.15)}"/>
|
|
367
|
+
<text x="50%" y="50%" dominant-baseline="central" text-anchor="middle" font-family="Georgia, serif" font-size="${Math.round(size * 0.45)}" fill="#ffffff">${initial}</text>
|
|
368
|
+
</svg>`;
|
|
369
|
+
}
|
|
370
|
+
return [
|
|
371
|
+
{
|
|
372
|
+
name: "pressy:config",
|
|
373
|
+
enforce: "pre",
|
|
374
|
+
async config(_, { command }) {
|
|
375
|
+
if (command === "build") {
|
|
376
|
+
const tempContentDir = resolve(config.contentDir || "content");
|
|
377
|
+
contentDir = tempContentDir;
|
|
378
|
+
manifest = await contentDiscovery.discoverContent();
|
|
379
|
+
routes = generateRoutes(manifest);
|
|
380
|
+
const input = {
|
|
381
|
+
"pressy-entry": VIRTUAL_ENTRY_ID
|
|
382
|
+
};
|
|
383
|
+
for (const route of routes) {
|
|
384
|
+
const name = route.path === "/" ? "index" : route.path.slice(1).replace(/\//g, "-");
|
|
385
|
+
input[name] = VIRTUAL_ROUTE_PREFIX + route.path;
|
|
386
|
+
}
|
|
387
|
+
return {
|
|
388
|
+
appType: "custom",
|
|
389
|
+
resolve: {
|
|
390
|
+
alias: {
|
|
391
|
+
"preact": dirname(_require.resolve("preact/package.json")),
|
|
392
|
+
"preact/jsx-runtime": dirname(_require.resolve("preact/package.json")) + "/jsx-runtime"
|
|
393
|
+
}
|
|
394
|
+
},
|
|
395
|
+
build: {
|
|
396
|
+
rollupOptions: {
|
|
397
|
+
input,
|
|
398
|
+
preserveEntrySignatures: "exports-only",
|
|
399
|
+
external: ["@pressy-pub/shopify"]
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
return { appType: "custom" };
|
|
405
|
+
},
|
|
406
|
+
configResolved(resolvedConfig) {
|
|
407
|
+
root = resolvedConfig.root;
|
|
408
|
+
contentDir = resolve(root, config.contentDir || "content");
|
|
409
|
+
}
|
|
410
|
+
},
|
|
411
|
+
{
|
|
412
|
+
name: "pressy:content",
|
|
413
|
+
async buildStart() {
|
|
414
|
+
manifest = await contentDiscovery.discoverContent();
|
|
415
|
+
routes = generateRoutes(manifest);
|
|
416
|
+
},
|
|
417
|
+
options(opts) {
|
|
418
|
+
return opts;
|
|
419
|
+
},
|
|
420
|
+
resolveId(id) {
|
|
421
|
+
if (id === VIRTUAL_MODULE_ID) {
|
|
422
|
+
return RESOLVED_VIRTUAL_MODULE_ID;
|
|
423
|
+
}
|
|
424
|
+
if (id === VIRTUAL_ENTRY_ID) {
|
|
425
|
+
return RESOLVED_VIRTUAL_ENTRY_ID;
|
|
426
|
+
}
|
|
427
|
+
if (id.startsWith(VIRTUAL_ROUTE_PREFIX)) {
|
|
428
|
+
return "\0" + id;
|
|
429
|
+
}
|
|
430
|
+
if (id.startsWith(VIRTUAL_CHAPTER_MAP_PREFIX)) {
|
|
431
|
+
return "\0" + id;
|
|
432
|
+
}
|
|
433
|
+
if (id === "@pressy-pub/shopify") {
|
|
434
|
+
return { id, external: true };
|
|
435
|
+
}
|
|
436
|
+
if (id === "/@pressy-pub/client") {
|
|
437
|
+
return resolve(__dirname, "../runtime/client.js");
|
|
438
|
+
}
|
|
439
|
+
if (id.startsWith("/@pressy-pub/typography/")) {
|
|
440
|
+
const cssFile = id.replace("/@pressy-pub/typography/", "");
|
|
441
|
+
return { id: `@pressy-pub/typography/${cssFile}`, external: false };
|
|
442
|
+
}
|
|
443
|
+
},
|
|
444
|
+
load(id) {
|
|
445
|
+
if (id === RESOLVED_VIRTUAL_MODULE_ID) {
|
|
446
|
+
return `export const manifest = ${JSON.stringify(manifest)};
|
|
447
|
+
export const routes = ${JSON.stringify(routes)};
|
|
448
|
+
export const config = ${JSON.stringify(config)};`;
|
|
449
|
+
}
|
|
450
|
+
if (id === RESOLVED_VIRTUAL_ENTRY_ID) {
|
|
451
|
+
return [
|
|
452
|
+
`import '@pressy-pub/typography/prose.css'`,
|
|
453
|
+
`import '@pressy-pub/typography/fluid.css'`,
|
|
454
|
+
`import '@pressy-pub/typography/themes/light.css'`,
|
|
455
|
+
`import '@pressy-pub/typography/themes/dark.css'`,
|
|
456
|
+
`import '@pressy-pub/typography/themes/sepia.css'`,
|
|
457
|
+
`export { hydrate } from '/@pressy-pub/client'`
|
|
458
|
+
].join("\n");
|
|
459
|
+
}
|
|
460
|
+
if (id.startsWith(RESOLVED_ROUTE_PREFIX)) {
|
|
461
|
+
const routePath = id.slice(RESOLVED_ROUTE_PREFIX.length);
|
|
462
|
+
const route = routes.find((r) => r.path === routePath);
|
|
463
|
+
if (!route) return null;
|
|
464
|
+
const dataJson = JSON.stringify({
|
|
465
|
+
route: route.path,
|
|
466
|
+
routeType: route.type,
|
|
467
|
+
manifest,
|
|
468
|
+
pagination: config.pagination
|
|
469
|
+
});
|
|
470
|
+
let contentImport = "";
|
|
471
|
+
let contentArg = "";
|
|
472
|
+
let chapterMapImport = "";
|
|
473
|
+
let chapterMapArg = "";
|
|
474
|
+
if (route.type === "chapter") {
|
|
475
|
+
const chapter = route.content;
|
|
476
|
+
const importPath = chapter.filePath;
|
|
477
|
+
contentImport = `import Content from '${importPath}';
|
|
478
|
+
`;
|
|
479
|
+
contentArg = ", Content";
|
|
480
|
+
if (route.book) {
|
|
481
|
+
chapterMapImport = `import { chapterMap, chapterOrder } from '${VIRTUAL_CHAPTER_MAP_PREFIX}${route.book.slug}';
|
|
482
|
+
`;
|
|
483
|
+
chapterMapArg = ", { chapterMap, chapterOrder }";
|
|
484
|
+
}
|
|
485
|
+
} else if (route.type === "article") {
|
|
486
|
+
const article = route.content;
|
|
487
|
+
const importPath = article.filePath;
|
|
488
|
+
contentImport = `import Content from '${importPath}';
|
|
489
|
+
`;
|
|
490
|
+
contentArg = ", Content";
|
|
491
|
+
}
|
|
492
|
+
return [
|
|
493
|
+
`import { hydrate } from '/@pressy-pub/client';`,
|
|
494
|
+
contentImport,
|
|
495
|
+
chapterMapImport,
|
|
496
|
+
`const data = ${dataJson};`,
|
|
497
|
+
`hydrate(data${contentArg}${chapterMapArg});`
|
|
498
|
+
].join("\n");
|
|
499
|
+
}
|
|
500
|
+
if (id.startsWith(RESOLVED_CHAPTER_MAP_PREFIX)) {
|
|
501
|
+
const bookSlug = id.slice(RESOLVED_CHAPTER_MAP_PREFIX.length);
|
|
502
|
+
const book = manifest.books.find((b) => b.slug === bookSlug);
|
|
503
|
+
if (!book) return null;
|
|
504
|
+
const entries = book.chapters.map((ch) => {
|
|
505
|
+
return ` ${JSON.stringify(ch.slug)}: () => import(${JSON.stringify(ch.filePath)})`;
|
|
506
|
+
});
|
|
507
|
+
return [
|
|
508
|
+
`export const chapterMap = {`,
|
|
509
|
+
entries.join(",\n"),
|
|
510
|
+
`};`,
|
|
511
|
+
`export const chapterOrder = ${JSON.stringify(book.chapters.map((ch) => ch.slug))};`
|
|
512
|
+
].join("\n");
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
},
|
|
516
|
+
{
|
|
517
|
+
name: "pressy:mdx",
|
|
518
|
+
async transform(code, id) {
|
|
519
|
+
if (!id.endsWith(".mdx")) return null;
|
|
520
|
+
const result = await compileMDX(code, id);
|
|
521
|
+
return {
|
|
522
|
+
code: result.code,
|
|
523
|
+
map: result.map
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
},
|
|
527
|
+
{
|
|
528
|
+
name: "pressy:html",
|
|
529
|
+
configureServer(server) {
|
|
530
|
+
server.middlewares.use(async (req, res, next) => {
|
|
531
|
+
const url = req.url || "/";
|
|
532
|
+
if (url === "/favicon.ico") {
|
|
533
|
+
res.statusCode = 204;
|
|
534
|
+
res.end();
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
if (url === "/sw.js") {
|
|
538
|
+
res.setHeader("Content-Type", "application/javascript");
|
|
539
|
+
res.setHeader("Service-Worker-Allowed", "/");
|
|
540
|
+
res.end([
|
|
541
|
+
"// Dev-mode service worker \u2014 no caching, passes everything through",
|
|
542
|
+
'self.addEventListener("install", () => self.skipWaiting());',
|
|
543
|
+
'self.addEventListener("activate", (e) => e.waitUntil(self.clients.claim()));',
|
|
544
|
+
'self.addEventListener("message", (e) => {',
|
|
545
|
+
' if (e.data?.type === "SKIP_WAITING") self.skipWaiting();',
|
|
546
|
+
"});"
|
|
547
|
+
].join("\n"));
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
if (url === "/manifest.webmanifest") {
|
|
551
|
+
res.setHeader("Content-Type", "application/manifest+json");
|
|
552
|
+
res.end(JSON.stringify({
|
|
553
|
+
name: config.site.title,
|
|
554
|
+
short_name: pwaConfig.shortName,
|
|
555
|
+
description: config.site.description,
|
|
556
|
+
start_url: "./",
|
|
557
|
+
display: pwaConfig.display,
|
|
558
|
+
background_color: pwaConfig.backgroundColor,
|
|
559
|
+
theme_color: pwaConfig.themeColor,
|
|
560
|
+
icons: [
|
|
561
|
+
{ src: "./icon-192.png", sizes: "192x192", type: "image/png" },
|
|
562
|
+
{ src: "./icon-512.png", sizes: "512x512", type: "image/png" }
|
|
563
|
+
]
|
|
564
|
+
}, null, 2));
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
567
|
+
if (url === "/icon-192.png" || url === "/icon-512.png") {
|
|
568
|
+
const size = url.includes("192") ? 192 : 512;
|
|
569
|
+
res.setHeader("Content-Type", "image/svg+xml");
|
|
570
|
+
res.end(generatePlaceholderIcon(size));
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
if (url === "/offline.html") {
|
|
574
|
+
res.setHeader("Content-Type", "text/html");
|
|
575
|
+
res.end(generateOfflinePage(config.site.title));
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
if (url.startsWith("/@pressy-pub/")) {
|
|
579
|
+
const bareImport = url.slice(1);
|
|
580
|
+
try {
|
|
581
|
+
const result = await server.pluginContainer.resolveId(bareImport);
|
|
582
|
+
if (result && !result.external) {
|
|
583
|
+
req.url = "/@fs" + result.id;
|
|
584
|
+
}
|
|
585
|
+
} catch {
|
|
586
|
+
}
|
|
587
|
+
next();
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
590
|
+
const pathname = url.split("?")[0];
|
|
591
|
+
const route = routes.find((r) => r.path === pathname);
|
|
592
|
+
if (route) {
|
|
593
|
+
const html = generateHTML(route);
|
|
594
|
+
const transformed = await server.transformIndexHtml(url, html);
|
|
595
|
+
res.setHeader("Content-Type", "text/html");
|
|
596
|
+
res.end(transformed);
|
|
597
|
+
return;
|
|
598
|
+
}
|
|
599
|
+
next();
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
},
|
|
603
|
+
{
|
|
604
|
+
name: "pressy:build",
|
|
605
|
+
apply: "build",
|
|
606
|
+
async generateBundle(_, bundle) {
|
|
607
|
+
const cssFiles = [];
|
|
608
|
+
const routeChunks = /* @__PURE__ */ new Map();
|
|
609
|
+
for (const [fileName, chunk] of Object.entries(bundle)) {
|
|
610
|
+
if (fileName.endsWith(".css")) {
|
|
611
|
+
cssFiles.push(fileName);
|
|
612
|
+
}
|
|
613
|
+
if (chunk.type === "chunk" && chunk.isEntry && chunk.name) {
|
|
614
|
+
routeChunks.set(chunk.name, fileName);
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
for (const route of routes) {
|
|
618
|
+
const title = escapeHtmlAttr(getRouteTitle(route, config));
|
|
619
|
+
const description = escapeHtmlAttr(getRouteDescription(route, config));
|
|
620
|
+
const routeName = route.path === "/" ? "index" : route.path.slice(1).replace(/\//g, "-");
|
|
621
|
+
const routeJsFile = routeChunks.get(routeName);
|
|
622
|
+
const depth = route.path === "/" ? 0 : route.path.slice(1).split("/").length;
|
|
623
|
+
const assetPrefix = depth === 0 ? "./" : "../".repeat(depth);
|
|
624
|
+
const cssLinks = cssFiles.map((f) => ` <link rel="stylesheet" href="${assetPrefix}${f}">`).join("\n");
|
|
625
|
+
const buildPwaTags = pwaEnabled ? `
|
|
626
|
+
<link rel="manifest" href="${assetPrefix}manifest.webmanifest">
|
|
627
|
+
<meta name="theme-color" content="${pwaConfig.themeColor}">
|
|
628
|
+
<meta name="apple-mobile-web-app-capable" content="yes">
|
|
629
|
+
<meta name="apple-mobile-web-app-status-bar-style" content="default">
|
|
630
|
+
<meta name="apple-mobile-web-app-title" content="${pwaConfig.shortName}">
|
|
631
|
+
<link rel="apple-touch-icon" href="${assetPrefix}icon-192.png">` : `
|
|
632
|
+
<meta name="theme-color" content="${pwaConfig.themeColor}">`;
|
|
633
|
+
const html = `<!DOCTYPE html>
|
|
634
|
+
<html lang="${config.site.language || "en"}">
|
|
635
|
+
<head>
|
|
636
|
+
<meta charset="UTF-8">
|
|
637
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
638
|
+
<title>${title}</title>
|
|
639
|
+
<meta name="description" content="${description}">
|
|
640
|
+
${cssLinks}${buildPwaTags}
|
|
641
|
+
</head>
|
|
642
|
+
<body>
|
|
643
|
+
<div id="app"></div>
|
|
644
|
+
<script type="module" src="${assetPrefix}${routeJsFile}"></script>
|
|
645
|
+
</body>
|
|
646
|
+
</html>`;
|
|
647
|
+
const fileName = route.path === "/" ? "index.html" : `${route.path.slice(1)}/index.html`;
|
|
648
|
+
this.emitFile({
|
|
649
|
+
type: "asset",
|
|
650
|
+
fileName,
|
|
651
|
+
source: html
|
|
652
|
+
});
|
|
653
|
+
}
|
|
654
|
+
const hasCustomIcons = config.pwa?.icon192 && config.pwa?.icon512;
|
|
655
|
+
const webManifest = {
|
|
656
|
+
name: config.site.title,
|
|
657
|
+
short_name: pwaConfig.shortName,
|
|
658
|
+
description: config.site.description,
|
|
659
|
+
start_url: "./",
|
|
660
|
+
display: pwaConfig.display,
|
|
661
|
+
background_color: pwaConfig.backgroundColor,
|
|
662
|
+
theme_color: pwaConfig.themeColor,
|
|
663
|
+
icons: [
|
|
664
|
+
{
|
|
665
|
+
src: "./icon-192.png",
|
|
666
|
+
sizes: "192x192",
|
|
667
|
+
type: "image/png"
|
|
668
|
+
},
|
|
669
|
+
{
|
|
670
|
+
src: "./icon-512.png",
|
|
671
|
+
sizes: "512x512",
|
|
672
|
+
type: "image/png",
|
|
673
|
+
purpose: "any maskable"
|
|
674
|
+
}
|
|
675
|
+
]
|
|
676
|
+
};
|
|
677
|
+
this.emitFile({
|
|
678
|
+
type: "asset",
|
|
679
|
+
fileName: "manifest.webmanifest",
|
|
680
|
+
source: JSON.stringify(webManifest, null, 2)
|
|
681
|
+
});
|
|
682
|
+
this.emitFile({
|
|
683
|
+
type: "asset",
|
|
684
|
+
fileName: "offline.html",
|
|
685
|
+
source: generateOfflinePage(config.site.title)
|
|
686
|
+
});
|
|
687
|
+
if (!hasCustomIcons) {
|
|
688
|
+
this.emitFile({
|
|
689
|
+
type: "asset",
|
|
690
|
+
fileName: "icon-192.png",
|
|
691
|
+
source: generatePlaceholderIcon(192)
|
|
692
|
+
});
|
|
693
|
+
this.emitFile({
|
|
694
|
+
type: "asset",
|
|
695
|
+
fileName: "icon-512.png",
|
|
696
|
+
source: generatePlaceholderIcon(512)
|
|
697
|
+
});
|
|
698
|
+
}
|
|
699
|
+
if (pwaEnabled) {
|
|
700
|
+
const precacheEntries = [];
|
|
701
|
+
const contentRevision = (source) => createHash("md5").update(source).digest("hex").slice(0, 8);
|
|
702
|
+
for (const route of routes) {
|
|
703
|
+
const fileName = route.path === "/" ? "index.html" : `${route.path.slice(1)}/index.html`;
|
|
704
|
+
const htmlAsset = bundle[fileName];
|
|
705
|
+
const source = htmlAsset && htmlAsset.type === "asset" ? String(htmlAsset.source) : "";
|
|
706
|
+
precacheEntries.push({
|
|
707
|
+
url: `./${fileName}`,
|
|
708
|
+
revision: contentRevision(source)
|
|
709
|
+
});
|
|
710
|
+
}
|
|
711
|
+
const offlineSource = generateOfflinePage(config.site.title);
|
|
712
|
+
precacheEntries.push({ url: "./offline.html", revision: contentRevision(offlineSource) });
|
|
713
|
+
for (const [fileName] of Object.entries(bundle)) {
|
|
714
|
+
if (fileName.endsWith(".js") || fileName.endsWith(".css")) {
|
|
715
|
+
precacheEntries.push({
|
|
716
|
+
url: `./${fileName}`,
|
|
717
|
+
revision: null
|
|
718
|
+
});
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
const swSourcePath = resolve(__dirname, "../runtime/sw.js");
|
|
722
|
+
const manifestJson = JSON.stringify(precacheEntries);
|
|
723
|
+
const { build: esbuildBuild } = await import("esbuild");
|
|
724
|
+
const esbuildResult = await esbuildBuild({
|
|
725
|
+
entryPoints: [swSourcePath],
|
|
726
|
+
bundle: true,
|
|
727
|
+
format: "iife",
|
|
728
|
+
write: false,
|
|
729
|
+
platform: "browser",
|
|
730
|
+
define: {
|
|
731
|
+
"self.__WB_MANIFEST": manifestJson
|
|
732
|
+
}
|
|
733
|
+
});
|
|
734
|
+
const swCode = esbuildResult.outputFiles[0].text;
|
|
735
|
+
this.emitFile({
|
|
736
|
+
type: "asset",
|
|
737
|
+
fileName: "sw.js",
|
|
738
|
+
source: swCode
|
|
739
|
+
});
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
];
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
// src/cli/index.ts
|
|
747
|
+
var cli = cac("pressy");
|
|
748
|
+
async function loadConfig(root) {
|
|
749
|
+
const configPath = resolve2(root, "pressy.config.ts");
|
|
750
|
+
const configPathJs = resolve2(root, "pressy.config.js");
|
|
751
|
+
let configFile = configPath;
|
|
752
|
+
if (!existsSync2(configPath)) {
|
|
753
|
+
if (existsSync2(configPathJs)) {
|
|
754
|
+
configFile = configPathJs;
|
|
755
|
+
} else {
|
|
756
|
+
console.log("No pressy.config.ts found, using defaults");
|
|
757
|
+
return {
|
|
758
|
+
site: {
|
|
759
|
+
title: "My Publication",
|
|
760
|
+
url: "http://localhost:3000"
|
|
761
|
+
},
|
|
762
|
+
pagination: { defaultMode: "scroll" },
|
|
763
|
+
outDir: "dist",
|
|
764
|
+
contentDir: "content"
|
|
765
|
+
};
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
if (configFile.endsWith(".ts")) {
|
|
769
|
+
const tempFile = configFile.replace(/\.ts$/, ".pressy-tmp.mjs");
|
|
770
|
+
try {
|
|
771
|
+
const source = readFileSync2(configFile, "utf-8");
|
|
772
|
+
const result = await transform(source, {
|
|
773
|
+
loader: "ts",
|
|
774
|
+
format: "esm"
|
|
775
|
+
});
|
|
776
|
+
writeFileSync(tempFile, result.code);
|
|
777
|
+
const mod2 = await import(`file://${tempFile}?t=${Date.now()}`);
|
|
778
|
+
return mod2.default;
|
|
779
|
+
} finally {
|
|
780
|
+
rmSync(tempFile, { force: true });
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
const mod = await import(`file://${configFile}`);
|
|
784
|
+
return mod.default;
|
|
785
|
+
}
|
|
786
|
+
cli.command("[root]", "Start dev server").alias("dev").option("--port <port>", "Port to run dev server on", { default: 3e3 }).option("--host", "Expose to network").action(async (root = ".", options) => {
|
|
787
|
+
const cwd = resolve2(process.cwd(), root);
|
|
788
|
+
const config = await loadConfig(cwd);
|
|
789
|
+
const server = await createServer({
|
|
790
|
+
root: cwd,
|
|
791
|
+
plugins: [pressyPlugin(config)],
|
|
792
|
+
server: {
|
|
793
|
+
port: options.port,
|
|
794
|
+
host: options.host
|
|
795
|
+
}
|
|
796
|
+
});
|
|
797
|
+
await server.listen();
|
|
798
|
+
console.log(`
|
|
799
|
+
Pressy dev server running at:
|
|
800
|
+
`);
|
|
801
|
+
server.printUrls();
|
|
802
|
+
});
|
|
803
|
+
cli.command("build [root]", "Build for production").option("--outDir <dir>", "Output directory").action(async (root = ".", options) => {
|
|
804
|
+
const cwd = resolve2(process.cwd(), root);
|
|
805
|
+
const config = await loadConfig(cwd);
|
|
806
|
+
console.log("Building for production...\n");
|
|
807
|
+
await build({
|
|
808
|
+
root: cwd,
|
|
809
|
+
base: "./",
|
|
810
|
+
plugins: [pressyPlugin(config)],
|
|
811
|
+
build: {
|
|
812
|
+
outDir: options.outDir || config.outDir || "dist"
|
|
813
|
+
}
|
|
814
|
+
});
|
|
815
|
+
console.log("\nBuild complete!");
|
|
816
|
+
});
|
|
817
|
+
cli.command("preview [root]", "Preview production build").option("--port <port>", "Port to run preview server on", { default: 4173 }).action(async (root = ".", options) => {
|
|
818
|
+
const cwd = resolve2(process.cwd(), root);
|
|
819
|
+
const config = await loadConfig(cwd);
|
|
820
|
+
const server = await preview({
|
|
821
|
+
root: cwd,
|
|
822
|
+
plugins: [pressyPlugin(config)],
|
|
823
|
+
preview: {
|
|
824
|
+
port: options.port
|
|
825
|
+
}
|
|
826
|
+
});
|
|
827
|
+
console.log(`
|
|
828
|
+
Preview server running at:
|
|
829
|
+
`);
|
|
830
|
+
server.printUrls();
|
|
831
|
+
});
|
|
832
|
+
cli.help();
|
|
833
|
+
cli.version("0.1.0");
|
|
834
|
+
cli.parse();
|
|
835
|
+
//# sourceMappingURL=index.js.map
|