@zeropress/build-pages 0.5.2 → 0.5.5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zeropress/build-pages",
3
- "version": "0.5.2",
3
+ "version": "0.5.5",
4
4
  "description": "ZeroPress Markdown build action and CLI for static hosting",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/action.js CHANGED
@@ -8,7 +8,8 @@ const options = {
8
8
  config: input('config'),
9
9
  siteUrl: input('site-url'),
10
10
  skipUntitledMarkdown: booleanInput('skip-untitled-markdown', false),
11
- checkLinks: booleanInput('check-links', true),
11
+ skipLinkCheck: booleanInput('skip-link-check', false),
12
+ copyMarkdownSource: falseOnlyInput('copy-markdown-source'),
12
13
  };
13
14
 
14
15
  try {
@@ -30,3 +31,7 @@ function booleanInput(name, fallback) {
30
31
  }
31
32
  return value.toLowerCase() === 'true';
32
33
  }
34
+
35
+ function falseOnlyInput(name) {
36
+ return input(name).toLowerCase() !== 'false';
37
+ }
package/src/index.js CHANGED
@@ -7,7 +7,9 @@ import { checkInternalLinks } from './check-links.js';
7
7
 
8
8
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
9
9
  const packageDir = path.resolve(__dirname, '..');
10
- const prebuildScript = path.join(packageDir, 'src', 'prebuild.js');
10
+ const prebuildScript = __dirname === path.join(packageDir, 'dist')
11
+ ? path.join(__dirname, 'prebuild.js')
12
+ : path.join(packageDir, 'src', 'prebuild.js');
11
13
  const PREVIEW_DATA_PATH = '.zeropress/preview-data.json';
12
14
  const STAGING_DIR = '.zeropress/public-assets';
13
15
  const DEFAULT_THEME = 'docs';
@@ -35,6 +37,7 @@ export async function runCli(argv = process.argv.slice(2)) {
35
37
 
36
38
  export async function runBuildPages(options) {
37
39
  const cwd = path.resolve(options.cwd || process.cwd());
40
+ const copyMarkdownSource = options.copyMarkdownSource !== false;
38
41
  const sourceDir = path.resolve(cwd, options.source);
39
42
  const destinationDir = path.resolve(cwd, options.destination);
40
43
  const generatedDir = path.join(cwd, '.zeropress');
@@ -42,6 +45,13 @@ export async function runBuildPages(options) {
42
45
  const previewDataPath = path.join(cwd, PREVIEW_DATA_PATH);
43
46
  const themeDir = resolveThemeDir(cwd, options);
44
47
 
48
+ assertBuildPagesPathLayout({
49
+ cwd,
50
+ sourceDir,
51
+ destinationDir,
52
+ themeDir,
53
+ generatedDir,
54
+ });
45
55
  await assertDirectory(sourceDir, 'Source directory');
46
56
  await fs.rm(generatedDir, { recursive: true, force: true });
47
57
  await fs.mkdir(generatedDir, { recursive: true });
@@ -51,6 +61,7 @@ export async function runBuildPages(options) {
51
61
  ZEROPRESS_BUILD_PAGES_SOURCE: sourceDir,
52
62
  ZEROPRESS_PUBLIC_DIR: sourceDir,
53
63
  ZEROPRESS_SKIP_UNTITLED_MARKDOWN: String(Boolean(options.skipUntitledMarkdown)),
64
+ ZEROPRESS_COPY_MARKDOWN_SOURCE: String(copyMarkdownSource),
54
65
  };
55
66
  if (options.config) {
56
67
  env.ZEROPRESS_BUILD_PAGES_CONFIG = path.resolve(cwd, options.config);
@@ -76,6 +87,7 @@ export async function runBuildPages(options) {
76
87
  await fs.mkdir(stagingDir, { recursive: true });
77
88
  await copyPublicStaging(sourceDir, stagingDir, {
78
89
  excludePaths: [destinationDir, themeDir, generatedDir],
90
+ copyMarkdownSource,
79
91
  });
80
92
 
81
93
  const previousPublicDir = process.env.ZEROPRESS_PUBLIC_DIR;
@@ -93,7 +105,7 @@ export async function runBuildPages(options) {
93
105
  }
94
106
  }
95
107
 
96
- if (options.checkLinks) {
108
+ if (!options.skipLinkCheck) {
97
109
  const result = await checkInternalLinks(destinationDir);
98
110
  if (result.brokenLinks.length) {
99
111
  console.warn('Warning: broken internal links found:');
@@ -105,7 +117,7 @@ export async function runBuildPages(options) {
105
117
  }
106
118
  }
107
119
 
108
- export function parseArgs(argv, env = process.env) {
120
+ export function parseArgs(argv) {
109
121
  const flags = {};
110
122
 
111
123
  for (let index = 0; index < argv.length; index += 1) {
@@ -114,8 +126,12 @@ export function parseArgs(argv, env = process.env) {
114
126
  flags.skipUntitledMarkdown = true;
115
127
  continue;
116
128
  }
117
- if (arg === '--no-check-links') {
118
- flags.checkLinks = false;
129
+ if (arg === '--skip-link-check') {
130
+ flags.skipLinkCheck = true;
131
+ continue;
132
+ }
133
+ if (arg === '--no-copy-markdown-source') {
134
+ flags.copyMarkdownSource = false;
119
135
  continue;
120
136
  }
121
137
 
@@ -140,24 +156,25 @@ export function parseArgs(argv, env = process.env) {
140
156
  throw new Error(`Invalid arguments: unknown option ${arg}`);
141
157
  }
142
158
 
143
- const source = flags.source || env.ZEROPRESS_PUBLIC_DIR || '';
144
- const destination = flags.destination || env.ZEROPRESS_OUT_DIR || '';
159
+ const source = flags.source || '';
160
+ const destination = flags.destination || '';
145
161
  if (!source) {
146
- throw new Error('Invalid arguments: --source <dir> is required. You can also set ZEROPRESS_PUBLIC_DIR.');
162
+ throw new Error('Invalid arguments: --source <dir> is required.');
147
163
  }
148
164
  if (!destination) {
149
- throw new Error('Invalid arguments: --destination <dir> is required. You can also set ZEROPRESS_OUT_DIR.');
165
+ throw new Error('Invalid arguments: --destination <dir> is required.');
150
166
  }
151
167
 
152
168
  return {
153
169
  source,
154
170
  destination,
155
171
  theme: flags.theme || DEFAULT_THEME,
156
- themePath: flags['theme-path'] || env.ZEROPRESS_THEME_DIR || '',
157
- config: flags.config || env.ZEROPRESS_BUILD_PAGES_CONFIG || '',
158
- siteUrl: flags['site-url'] || env.ZEROPRESS_SITE_URL || '',
159
- skipUntitledMarkdown: flags.skipUntitledMarkdown ?? env.ZEROPRESS_SKIP_UNTITLED_MARKDOWN === 'true',
160
- checkLinks: flags.checkLinks ?? true,
172
+ themePath: flags['theme-path'] || '',
173
+ config: flags.config || '',
174
+ siteUrl: flags['site-url'] || '',
175
+ skipUntitledMarkdown: flags.skipUntitledMarkdown === true,
176
+ skipLinkCheck: flags.skipLinkCheck === true,
177
+ copyMarkdownSource: flags.copyMarkdownSource !== false,
161
178
  };
162
179
  }
163
180
 
@@ -168,14 +185,15 @@ Usage:
168
185
  zeropress-build-pages [options]
169
186
 
170
187
  Options:
171
- --source <dir> Source directory (required, or ZEROPRESS_PUBLIC_DIR)
172
- --destination <dir> Output directory (required, or ZEROPRESS_OUT_DIR)
188
+ --source <dir> Dedicated source directory (required)
189
+ --destination <dir> Output directory (required)
173
190
  --theme docs Bundled theme name (default: docs)
174
191
  --theme-path <dir> Custom ZeroPress theme directory
175
192
  --config <path> Config file (default: <source>/.zeropress/config.json)
176
193
  --site-url <url> Canonical site URL override
177
194
  --skip-untitled-markdown Skip Markdown files without a page title
178
- --no-check-links Skip internal link checking
195
+ --skip-link-check Skip internal link checking
196
+ --no-copy-markdown-source Do not copy original Markdown files to output
179
197
  --help, -h Show help
180
198
  --version, -v Show version`);
181
199
  }
@@ -205,6 +223,32 @@ async function assertDirectory(dir, label) {
205
223
  }
206
224
  }
207
225
 
226
+ function assertBuildPagesPathLayout({ cwd, sourceDir, destinationDir, themeDir, generatedDir }) {
227
+ if (samePath(sourceDir, cwd)) {
228
+ throw new Error(
229
+ 'Source directory must be a dedicated content directory, not the current working directory. '
230
+ + `Received: ${formatPath(cwd, sourceDir)}`,
231
+ );
232
+ }
233
+
234
+ assertNoPathOverlap(cwd, 'Source directory', sourceDir, 'internal .zeropress working directory', generatedDir);
235
+ assertNoPathOverlap(cwd, 'Destination directory', destinationDir, 'internal .zeropress working directory', generatedDir);
236
+ assertNoPathOverlap(cwd, 'Theme directory', themeDir, 'internal .zeropress working directory', generatedDir);
237
+ assertNoPathOverlap(cwd, 'Source directory', sourceDir, 'destination directory', destinationDir);
238
+ assertNoPathOverlap(cwd, 'Source directory', sourceDir, 'theme directory', themeDir);
239
+ }
240
+
241
+ function assertNoPathOverlap(cwd, firstLabel, firstPath, secondLabel, secondPath) {
242
+ if (!pathsOverlap(firstPath, secondPath)) {
243
+ return;
244
+ }
245
+ throw new Error(
246
+ `${firstLabel} must not overlap the ${secondLabel}. `
247
+ + `${firstLabel}: ${formatPath(cwd, firstPath)}; `
248
+ + `${secondLabel}: ${formatPath(cwd, secondPath)}`,
249
+ );
250
+ }
251
+
208
252
  async function copyPublicStaging(sourceDir, targetDir, options) {
209
253
  const entries = await fs.readdir(sourceDir, { withFileTypes: true });
210
254
 
@@ -229,6 +273,10 @@ async function copyPublicStaging(sourceDir, targetDir, options) {
229
273
  continue;
230
274
  }
231
275
 
276
+ if (options.copyMarkdownSource === false && entry.name.toLowerCase().endsWith('.md')) {
277
+ continue;
278
+ }
279
+
232
280
  await fs.mkdir(path.dirname(targetPath), { recursive: true });
233
281
  await fs.copyFile(sourcePath, targetPath);
234
282
  }
@@ -256,6 +304,10 @@ function pathsOverlap(firstPath, secondPath) {
256
304
  return first === second || isPathInside(first, second) || isPathInside(second, first);
257
305
  }
258
306
 
307
+ function samePath(firstPath, secondPath) {
308
+ return path.resolve(firstPath) === path.resolve(secondPath);
309
+ }
310
+
259
311
  function isPathInside(parentPath, childPath) {
260
312
  const relativePath = path.relative(parentPath, childPath);
261
313
  return Boolean(relativePath) && !relativePath.startsWith('..') && !path.isAbsolute(relativePath);
package/src/prebuild.js CHANGED
@@ -5,7 +5,7 @@ import matter from 'gray-matter';
5
5
 
6
6
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
7
7
  const rootDir = process.cwd();
8
- const sourceDir = resolveEnvPath(['ZEROPRESS_BUILD_PAGES_SOURCE', 'ZEROPRESS_PUBLIC_DIR'], 'docs');
8
+ const sourceDir = resolveEnvPath(['ZEROPRESS_BUILD_PAGES_SOURCE'], 'docs');
9
9
  const defaultConfigPath = path.join(sourceDir, '.zeropress', 'config.json');
10
10
  const configPath = resolveOptionalEnvPath(['ZEROPRESS_BUILD_PAGES_CONFIG'], defaultConfigPath);
11
11
  const outDir = path.join(rootDir, '.zeropress');
@@ -13,6 +13,7 @@ const buildPagesConfigPath = path.join(outDir, 'build-pages-config.json');
13
13
  const previewDataPath = path.join(outDir, 'preview-data.json');
14
14
  const buildReportPath = path.join(outDir, 'build-report.json');
15
15
  const skipUntitledMarkdown = readBooleanEnv('ZEROPRESS_SKIP_UNTITLED_MARKDOWN');
16
+ const copyMarkdownSource = readBooleanEnv('ZEROPRESS_COPY_MARKDOWN_SOURCE', true);
16
17
  const FRONT_PAGE_TYPES = new Set(['theme_index', 'markdown', 'html']);
17
18
  const BUILD_PAGES_CONFIG_SCHEMA_URL = 'https://zeropress.dev/schemas/zeropress-build-pages.config.v0.1.schema.json';
18
19
  const PREVIEW_DATA_SCHEMA_URL = 'https://zeropress.dev/schemas/preview-data.v0.5.schema.json';
@@ -96,7 +97,7 @@ async function main() {
96
97
  path: route.path,
97
98
  meta: {
98
99
  ...frontMatter.meta,
99
- source_markdown_url: buildSourceMarkdownUrl(sourcePath),
100
+ ...(copyMarkdownSource ? { source_markdown_url: buildSourceMarkdownUrl(sourcePath) } : {}),
100
101
  },
101
102
  content: rewriteMarkdownLinks(bodyMarkdown, sourcePath, routeBySourcePath),
102
103
  document_type: 'markdown',
@@ -733,6 +734,7 @@ function buildPrebuildReport({
733
734
  preview_data_path: formatSourcePath(previewDataPath),
734
735
  report_path: formatSourcePath(buildReportPath),
735
736
  skip_untitled_markdown: skipUntitledMarkdown,
737
+ copy_markdown_source: copyMarkdownSource,
736
738
  markdown: {
737
739
  discovered: sourceFiles.length,
738
740
  generated_pages: pageInputs.length,
@@ -1305,8 +1307,12 @@ function readConfigInteger(value, fallback) {
1305
1307
  return Number.isInteger(value) && value > 0 ? value : fallback;
1306
1308
  }
1307
1309
 
1308
- function readBooleanEnv(name) {
1309
- return process.env[name]?.trim().toLowerCase() === 'true';
1310
+ function readBooleanEnv(name, fallback = false) {
1311
+ const value = process.env[name]?.trim();
1312
+ if (!value) {
1313
+ return fallback;
1314
+ }
1315
+ return value.toLowerCase() === 'true';
1310
1316
  }
1311
1317
 
1312
1318
  function resolveEnvPath(names, fallback) {