@zeropress/build-pages 0.6.1 → 0.6.3
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 +146 -26
- package/action.yml +3 -0
- package/dist/action.js +465 -132
- package/dist/prebuild.js +110 -5
- package/package.json +2 -2
- package/schemas/zeropress-build-pages.config.v0.1.schema.json +50 -3
- package/src/action.js +1 -0
- package/src/index.js +154 -3
- package/src/prebuild.js +134 -5
- package/themes/docs/404.html +13 -2
- package/themes/docs/assets/style.css +975 -154
- package/themes/docs/assets/theme.js +333 -0
- package/themes/docs/layout.html +73 -14
- package/themes/docs/page.html +9 -14
- package/themes/docs/partials/theme-bootstrap.html +10 -0
- package/themes/docs/partials/theme-scripts.html +1 -0
- package/themes/docs/post.html +5 -2
- package/themes/docs/theme.json +10 -7
package/src/prebuild.js
CHANGED
|
@@ -6,6 +6,7 @@ import matter from 'gray-matter';
|
|
|
6
6
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
7
7
|
const rootDir = process.cwd();
|
|
8
8
|
const sourceDir = resolveEnvPath(['ZEROPRESS_BUILD_PAGES_SOURCE'], 'docs');
|
|
9
|
+
const publicDir = resolveEnvPath(['ZEROPRESS_BUILD_PAGES_PUBLIC_DIR'], sourceDir);
|
|
9
10
|
const defaultConfigPath = path.join(sourceDir, '.zeropress', 'config.json');
|
|
10
11
|
const configPath = resolveOptionalEnvPath(['ZEROPRESS_BUILD_PAGES_CONFIG'], defaultConfigPath);
|
|
11
12
|
const outDir = path.join(rootDir, '.zeropress');
|
|
@@ -15,13 +16,14 @@ const buildReportPath = path.join(outDir, 'build-report.json');
|
|
|
15
16
|
const skipUntitledMarkdown = readBooleanEnv('ZEROPRESS_SKIP_UNTITLED_MARKDOWN');
|
|
16
17
|
const copyMarkdownSource = readBooleanEnv('ZEROPRESS_COPY_MARKDOWN_SOURCE', true);
|
|
17
18
|
const FRONT_PAGE_TYPES = new Set(['theme_index', 'markdown', 'html']);
|
|
18
|
-
const BUILD_PAGES_CONFIG_SCHEMA_URL = 'https://zeropress.dev/
|
|
19
|
-
const PREVIEW_DATA_SCHEMA_URL = 'https://zeropress.dev/
|
|
19
|
+
const BUILD_PAGES_CONFIG_SCHEMA_URL = 'https://schemas.zeropress.dev/build-pages-config/v0.1/schema.json';
|
|
20
|
+
const PREVIEW_DATA_SCHEMA_URL = 'https://schemas.zeropress.dev/preview-data/v0.6/schema.json';
|
|
20
21
|
const FRONT_MATTER_DATA_KEY_PATTERN = /^[a-zA-Z_][a-zA-Z0-9_]*(?:-[a-zA-Z0-9_]+)*$/;
|
|
21
22
|
const FRONT_MATTER_DATA_MAX_DEPTH = 4;
|
|
22
23
|
const FRONT_MATTER_DATA_MAX_KEYS = 64;
|
|
23
24
|
const FRONT_MATTER_DATA_MAX_ARRAY_LENGTH = 256;
|
|
24
25
|
const FRONT_MATTER_DISCOVERABILITY_VALUES = new Set(['default', 'noindex', 'delist']);
|
|
26
|
+
const markdownDiscoverExcludeRoots = buildMarkdownDiscoverExcludeRoots();
|
|
25
27
|
|
|
26
28
|
class PrebuildMarkdownError extends Error {
|
|
27
29
|
constructor(sourcePath, reason, expected = '', code = 'invalid_markdown') {
|
|
@@ -230,7 +232,7 @@ function buildSiteData(config, frontPage) {
|
|
|
230
232
|
description: configuredSite.description,
|
|
231
233
|
url: configuredSite.url,
|
|
232
234
|
media_base_url: '',
|
|
233
|
-
locale:
|
|
235
|
+
locale: configuredSite.locale,
|
|
234
236
|
posts_per_page: 10,
|
|
235
237
|
datetime_display: 'static',
|
|
236
238
|
date_style: 'medium',
|
|
@@ -243,9 +245,18 @@ function buildSiteData(config, frontPage) {
|
|
|
243
245
|
},
|
|
244
246
|
disallow_comments: true,
|
|
245
247
|
expose_generator: configuredSite.expose_generator !== false,
|
|
248
|
+
search: configuredSite.search !== false,
|
|
246
249
|
indexing: configuredSite.indexing !== false,
|
|
247
250
|
};
|
|
248
251
|
|
|
252
|
+
if (configuredSite.logo) {
|
|
253
|
+
site.logo = configuredSite.logo;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (configuredSite.meta !== undefined) {
|
|
257
|
+
site.meta = configuredSite.meta;
|
|
258
|
+
}
|
|
259
|
+
|
|
249
260
|
if (configuredSite.footer) {
|
|
250
261
|
site.footer = configuredSite.footer;
|
|
251
262
|
}
|
|
@@ -278,23 +289,98 @@ function normalizeSiteConfig(value) {
|
|
|
278
289
|
}
|
|
279
290
|
|
|
280
291
|
const configuredSite = isPlainObject(value) ? value : {};
|
|
281
|
-
assertKnownConfigKeys(configuredSite, ['title', 'description', 'url', 'expose_generator', 'indexing', 'footer'], 'site');
|
|
292
|
+
assertKnownConfigKeys(configuredSite, ['title', 'description', 'url', 'logo', 'locale', 'expose_generator', 'search', 'indexing', 'footer', 'meta'], 'site');
|
|
282
293
|
const site = {
|
|
283
294
|
title: readConfigString(configuredSite.title, 'Documentation'),
|
|
284
295
|
description: readConfigString(configuredSite.description, 'A documentation site.'),
|
|
285
296
|
url: readEnv('ZEROPRESS_SITE_URL', readConfigString(configuredSite.url, '')),
|
|
297
|
+
locale: normalizeSiteLocale(configuredSite.locale),
|
|
286
298
|
expose_generator: readConfigBoolean(configuredSite.expose_generator, true, 'site.expose_generator'),
|
|
299
|
+
search: readConfigBoolean(configuredSite.search, true, 'site.search'),
|
|
287
300
|
indexing: readConfigBoolean(configuredSite.indexing, true, 'site.indexing'),
|
|
288
301
|
};
|
|
289
302
|
|
|
303
|
+
const logo = normalizeSiteLogo(configuredSite.logo);
|
|
304
|
+
if (logo) {
|
|
305
|
+
site.logo = logo;
|
|
306
|
+
}
|
|
307
|
+
|
|
290
308
|
const footer = normalizeFooter(configuredSite.footer);
|
|
291
309
|
if (footer) {
|
|
292
310
|
site.footer = footer;
|
|
293
311
|
}
|
|
294
312
|
|
|
313
|
+
if (configuredSite.meta !== undefined) {
|
|
314
|
+
site.meta = normalizeSiteMeta(configuredSite.meta, 'site.meta');
|
|
315
|
+
}
|
|
316
|
+
|
|
295
317
|
return site;
|
|
296
318
|
}
|
|
297
319
|
|
|
320
|
+
function normalizeSiteLocale(value) {
|
|
321
|
+
if (value === undefined) {
|
|
322
|
+
return 'en-US';
|
|
323
|
+
}
|
|
324
|
+
if (typeof value !== 'string') {
|
|
325
|
+
throw new PrebuildConfigError('site.locale must be a string when provided.');
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const locale = value.trim();
|
|
329
|
+
if (locale.length < 2) {
|
|
330
|
+
throw new PrebuildConfigError('site.locale must be a non-empty locale string such as "en-US" or "ko-KR".');
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
return locale;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function normalizeSiteLogo(value) {
|
|
337
|
+
if (value === undefined) {
|
|
338
|
+
return undefined;
|
|
339
|
+
}
|
|
340
|
+
if (!isPlainObject(value)) {
|
|
341
|
+
throw new PrebuildConfigError('site.logo must be an object when provided.');
|
|
342
|
+
}
|
|
343
|
+
assertKnownConfigKeys(value, ['src', 'alt'], 'site.logo');
|
|
344
|
+
|
|
345
|
+
const src = readConfigString(value.src, '');
|
|
346
|
+
if (!src) {
|
|
347
|
+
throw new PrebuildConfigError(
|
|
348
|
+
'site.logo.src must be a non-empty URL-like string.',
|
|
349
|
+
' "logo": { "src": "/logo.svg", "alt": "My Site" }',
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
validateUrlLikeString(src, 'site.logo.src');
|
|
353
|
+
|
|
354
|
+
const logo = { src };
|
|
355
|
+
if (value.alt !== undefined) {
|
|
356
|
+
if (typeof value.alt !== 'string') {
|
|
357
|
+
throw new PrebuildConfigError('site.logo.alt must be a string when provided.');
|
|
358
|
+
}
|
|
359
|
+
const alt = value.alt.trim();
|
|
360
|
+
if (alt) {
|
|
361
|
+
logo.alt = alt;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
return logo;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function normalizeSiteMeta(value, pathLabel) {
|
|
369
|
+
if (!isPlainObject(value)) {
|
|
370
|
+
throw new PrebuildConfigError(`${pathLabel} must be an object when provided.`);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
const meta = {};
|
|
374
|
+
for (const [key, metaValue] of Object.entries(value)) {
|
|
375
|
+
if (!isPreviewMetaValue(metaValue)) {
|
|
376
|
+
throw new PrebuildConfigError(`${pathLabel}.${key} must be a string, number, boolean, or null.`);
|
|
377
|
+
}
|
|
378
|
+
meta[key] = metaValue;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
return meta;
|
|
382
|
+
}
|
|
383
|
+
|
|
298
384
|
function normalizeFooter(value) {
|
|
299
385
|
if (value === undefined) {
|
|
300
386
|
return undefined;
|
|
@@ -320,6 +406,25 @@ function normalizeFooter(value) {
|
|
|
320
406
|
return Object.keys(footer).length ? footer : undefined;
|
|
321
407
|
}
|
|
322
408
|
|
|
409
|
+
function validateUrlLikeString(value, pathLabel) {
|
|
410
|
+
if (value.startsWith('//')) {
|
|
411
|
+
throw new PrebuildConfigError(`${pathLabel} must be an absolute URL or a safe relative path.`);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
if (value.startsWith('/') || value.startsWith('./') || value.startsWith('../')) {
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
try {
|
|
419
|
+
const url = new URL(value);
|
|
420
|
+
if (!url.protocol || !url.hostname) {
|
|
421
|
+
throw new Error('missing host');
|
|
422
|
+
}
|
|
423
|
+
} catch {
|
|
424
|
+
throw new PrebuildConfigError(`${pathLabel} must be an absolute URL or a safe relative path.`);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
323
428
|
function readConfigBoolean(value, fallback, pathName) {
|
|
324
429
|
if (value === undefined) {
|
|
325
430
|
return fallback;
|
|
@@ -760,6 +865,7 @@ function buildPrebuildReport({
|
|
|
760
865
|
return {
|
|
761
866
|
generated_at: new Date().toISOString(),
|
|
762
867
|
source_dir: formatSourcePath(sourceDir),
|
|
868
|
+
public_dir: formatSourcePath(publicDir),
|
|
763
869
|
config_path: formatSourcePath(configPath),
|
|
764
870
|
build_pages_config_path: formatSourcePath(buildPagesConfigPath),
|
|
765
871
|
preview_data_path: formatSourcePath(previewDataPath),
|
|
@@ -786,7 +892,8 @@ function buildPrebuildReport({
|
|
|
786
892
|
function printPrebuildSummary(report) {
|
|
787
893
|
const lines = [
|
|
788
894
|
'ZeroPress build report',
|
|
789
|
-
`-
|
|
895
|
+
`- Source root: ${report.source_dir}`,
|
|
896
|
+
`- Public root: ${report.public_dir}`,
|
|
790
897
|
`- Markdown discovered: ${report.markdown.discovered}`,
|
|
791
898
|
`- Markdown pages generated: ${report.markdown.generated_pages}`,
|
|
792
899
|
`- Markdown skipped: ${report.markdown.skipped}`,
|
|
@@ -1134,6 +1241,10 @@ async function listMarkdownFiles(dir) {
|
|
|
1134
1241
|
}
|
|
1135
1242
|
|
|
1136
1243
|
const entryPath = path.join(dir, entry.name);
|
|
1244
|
+
if (isMarkdownDiscoverExcluded(entryPath)) {
|
|
1245
|
+
continue;
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1137
1248
|
if (entry.isDirectory()) {
|
|
1138
1249
|
files.push(...await listMarkdownFiles(entryPath));
|
|
1139
1250
|
continue;
|
|
@@ -1147,6 +1258,24 @@ async function listMarkdownFiles(dir) {
|
|
|
1147
1258
|
return files.sort((left, right) => left.localeCompare(right));
|
|
1148
1259
|
}
|
|
1149
1260
|
|
|
1261
|
+
function buildMarkdownDiscoverExcludeRoots() {
|
|
1262
|
+
if (samePath(sourceDir, publicDir) || !isPathInside(sourceDir, publicDir)) {
|
|
1263
|
+
return [];
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
return [publicDir];
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
function isMarkdownDiscoverExcluded(entryPath) {
|
|
1270
|
+
return markdownDiscoverExcludeRoots.some((excludeRoot) => (
|
|
1271
|
+
samePath(entryPath, excludeRoot) || isPathInside(excludeRoot, entryPath)
|
|
1272
|
+
));
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
function samePath(firstPath, secondPath) {
|
|
1276
|
+
return path.resolve(firstPath) === path.resolve(secondPath);
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1150
1279
|
function shouldIgnoreMarkdownDiscoverEntry(name) {
|
|
1151
1280
|
const basename = String(name || '');
|
|
1152
1281
|
const lowerName = basename.toLowerCase();
|
package/themes/docs/404.html
CHANGED
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
<section class="shell not-found">
|
|
2
|
-
<p class="eyebrow">
|
|
2
|
+
<p class="eyebrow eyebrow--error">
|
|
3
|
+
<span class="eyebrow__dot" aria-hidden="true"></span>
|
|
4
|
+
404
|
|
5
|
+
</p>
|
|
3
6
|
<h1>Page not found</h1>
|
|
4
7
|
<p class="lede">The page you are looking for is not part of the current documentation build.</p>
|
|
5
|
-
<
|
|
8
|
+
<div class="hero__actions">
|
|
9
|
+
<a class="button-link" href="/">
|
|
10
|
+
Return home
|
|
11
|
+
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
12
|
+
<line x1="5" y1="12" x2="19" y2="12"></line>
|
|
13
|
+
<polyline points="12 5 19 12 12 19"></polyline>
|
|
14
|
+
</svg>
|
|
15
|
+
</a>
|
|
16
|
+
</div>
|
|
6
17
|
</section>
|