@zeropress/build-pages 0.5.1 → 0.5.4

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.1",
3
+ "version": "0.5.4",
4
4
  "description": "ZeroPress Markdown build action and CLI for static hosting",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -40,7 +40,8 @@
40
40
  "node": ">=18.18.0"
41
41
  },
42
42
  "dependencies": {
43
- "@zeropress/build": "0.5.1"
43
+ "@zeropress/build": "0.5.1",
44
+ "gray-matter": "4.0.3"
44
45
  },
45
46
  "devDependencies": {
46
47
  "esbuild": "0.28.0"
@@ -35,8 +35,8 @@
35
35
  "site": {
36
36
  "type": "object",
37
37
  "additionalProperties": false,
38
- "description": "Site metadata and output policy defaults used while generating preview-data.",
39
- "markdownDescription": "Site metadata and output policy defaults used while generating preview-data.",
38
+ "description": "User-facing site metadata used by Build Pages.",
39
+ "markdownDescription": "User-facing site metadata used by Build Pages.",
40
40
  "properties": {
41
41
  "title": {
42
42
  "type": "string",
@@ -50,37 +50,8 @@
50
50
  "description": "Canonical site URL. Use an empty string or omit this field for local builds without canonical output.",
51
51
  "markdownDescription": "Canonical site URL. Use an empty string or omit this field for local builds without canonical output."
52
52
  },
53
- "mediaBaseUrl": {
54
- "type": "string"
55
- },
56
- "locale": {
57
- "type": "string",
58
- "minLength": 1
59
- },
60
- "postsPerPage": {
61
- "type": "integer",
62
- "minimum": 1
63
- },
64
- "dateFormat": {
65
- "type": "string",
66
- "minLength": 1
67
- },
68
- "timeFormat": {
69
- "type": "string",
70
- "minLength": 1
71
- },
72
- "timezone": {
73
- "type": "string",
74
- "minLength": 1
75
- },
76
- "permalinks": {
77
- "$ref": "#/$defs/permalinks"
78
- },
79
53
  "footer": {
80
54
  "$ref": "#/$defs/siteFooter"
81
- },
82
- "disallowComments": {
83
- "type": "boolean"
84
55
  }
85
56
  }
86
57
  },
@@ -114,37 +85,6 @@
114
85
  }
115
86
  }
116
87
  },
117
- "permalinks": {
118
- "type": "object",
119
- "additionalProperties": false,
120
- "description": "Preview-data permalink defaults emitted by Build Pages.",
121
- "markdownDescription": "Preview-data permalink defaults emitted by Build Pages.",
122
- "properties": {
123
- "output_style": {
124
- "type": "string",
125
- "enum": ["directory", "html-extension"]
126
- },
127
- "posts": {
128
- "$ref": "#/$defs/permalinkPattern"
129
- },
130
- "pages": {
131
- "$ref": "#/$defs/permalinkPattern"
132
- },
133
- "categories": {
134
- "$ref": "#/$defs/permalinkPattern"
135
- },
136
- "tags": {
137
- "$ref": "#/$defs/permalinkPattern"
138
- }
139
- }
140
- },
141
- "permalinkPattern": {
142
- "type": "string",
143
- "minLength": 1,
144
- "pattern": "^/",
145
- "description": "Absolute URL path pattern passed through to preview-data site.permalinks.",
146
- "markdownDescription": "Absolute URL path pattern passed through to preview-data `site.permalinks`."
147
- },
148
88
  "frontPage": {
149
89
  "type": "object",
150
90
  "additionalProperties": false,
package/src/action.js CHANGED
@@ -1,14 +1,14 @@
1
1
  import { runBuildPages } from './index.js';
2
2
 
3
3
  const options = {
4
- source: input('source') || './',
4
+ source: input('source') || './docs',
5
5
  destination: input('destination') || './_site',
6
6
  theme: input('theme') || 'docs',
7
7
  themePath: input('theme-path'),
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
12
  };
13
13
 
14
14
  try {
package/src/index.js CHANGED
@@ -7,9 +7,11 @@ 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
- const STAGING_DIR = '.zeropress/build-pages-public';
14
+ const STAGING_DIR = '.zeropress/public-assets';
13
15
  const DEFAULT_THEME = 'docs';
14
16
 
15
17
  export async function runCli(argv = process.argv.slice(2)) {
@@ -42,6 +44,13 @@ export async function runBuildPages(options) {
42
44
  const previewDataPath = path.join(cwd, PREVIEW_DATA_PATH);
43
45
  const themeDir = resolveThemeDir(cwd, options);
44
46
 
47
+ assertBuildPagesPathLayout({
48
+ cwd,
49
+ sourceDir,
50
+ destinationDir,
51
+ themeDir,
52
+ generatedDir,
53
+ });
45
54
  await assertDirectory(sourceDir, 'Source directory');
46
55
  await fs.rm(generatedDir, { recursive: true, force: true });
47
56
  await fs.mkdir(generatedDir, { recursive: true });
@@ -93,7 +102,7 @@ export async function runBuildPages(options) {
93
102
  }
94
103
  }
95
104
 
96
- if (options.checkLinks) {
105
+ if (!options.skipLinkCheck) {
97
106
  const result = await checkInternalLinks(destinationDir);
98
107
  if (result.brokenLinks.length) {
99
108
  console.warn('Warning: broken internal links found:');
@@ -105,7 +114,7 @@ export async function runBuildPages(options) {
105
114
  }
106
115
  }
107
116
 
108
- export function parseArgs(argv, env = process.env) {
117
+ export function parseArgs(argv) {
109
118
  const flags = {};
110
119
 
111
120
  for (let index = 0; index < argv.length; index += 1) {
@@ -114,19 +123,14 @@ export function parseArgs(argv, env = process.env) {
114
123
  flags.skipUntitledMarkdown = true;
115
124
  continue;
116
125
  }
117
- if (arg === '--check-links') {
118
- flags.checkLinks = true;
119
- continue;
120
- }
121
- if (arg === '--no-check-links') {
122
- flags.checkLinks = false;
126
+ if (arg === '--skip-link-check') {
127
+ flags.skipLinkCheck = true;
123
128
  continue;
124
129
  }
125
130
 
126
131
  const valueOptions = new Set([
127
132
  '--source',
128
133
  '--destination',
129
- '--out',
130
134
  '--theme',
131
135
  '--theme-path',
132
136
  '--config',
@@ -145,15 +149,24 @@ export function parseArgs(argv, env = process.env) {
145
149
  throw new Error(`Invalid arguments: unknown option ${arg}`);
146
150
  }
147
151
 
152
+ const source = flags.source || '';
153
+ const destination = flags.destination || '';
154
+ if (!source) {
155
+ throw new Error('Invalid arguments: --source <dir> is required.');
156
+ }
157
+ if (!destination) {
158
+ throw new Error('Invalid arguments: --destination <dir> is required.');
159
+ }
160
+
148
161
  return {
149
- source: flags.source || env.ZEROPRESS_PUBLIC_DIR || '.',
150
- destination: flags.destination || flags.out || env.ZEROPRESS_OUT_DIR || '_site',
162
+ source,
163
+ destination,
151
164
  theme: flags.theme || DEFAULT_THEME,
152
- themePath: flags['theme-path'] || env.ZEROPRESS_THEME_DIR || '',
153
- config: flags.config || env.ZEROPRESS_BUILD_PAGES_CONFIG || '',
154
- siteUrl: flags['site-url'] || env.ZEROPRESS_SITE_URL || '',
155
- skipUntitledMarkdown: flags.skipUntitledMarkdown ?? env.ZEROPRESS_SKIP_UNTITLED_MARKDOWN === 'true',
156
- checkLinks: flags.checkLinks ?? true,
165
+ themePath: flags['theme-path'] || '',
166
+ config: flags.config || '',
167
+ siteUrl: flags['site-url'] || '',
168
+ skipUntitledMarkdown: flags.skipUntitledMarkdown === true,
169
+ skipLinkCheck: flags.skipLinkCheck === true,
157
170
  };
158
171
  }
159
172
 
@@ -164,16 +177,14 @@ Usage:
164
177
  zeropress-build-pages [options]
165
178
 
166
179
  Options:
167
- --source <dir> Source directory (default: .)
168
- --destination <dir> Output directory (default: _site)
169
- --out <dir> Alias for --destination
180
+ --source <dir> Dedicated source directory (required)
181
+ --destination <dir> Output directory (required)
170
182
  --theme docs Bundled theme name (default: docs)
171
183
  --theme-path <dir> Custom ZeroPress theme directory
172
184
  --config <path> Config file (default: <source>/.zeropress/config.json)
173
185
  --site-url <url> Canonical site URL override
174
- --skip-untitled-markdown Skip Markdown files without an H1
175
- --check-links Warn about broken internal links (default)
176
- --no-check-links Skip internal link checking
186
+ --skip-untitled-markdown Skip Markdown files without a page title
187
+ --skip-link-check Skip internal link checking
177
188
  --help, -h Show help
178
189
  --version, -v Show version`);
179
190
  }
@@ -203,6 +214,32 @@ async function assertDirectory(dir, label) {
203
214
  }
204
215
  }
205
216
 
217
+ function assertBuildPagesPathLayout({ cwd, sourceDir, destinationDir, themeDir, generatedDir }) {
218
+ if (samePath(sourceDir, cwd)) {
219
+ throw new Error(
220
+ 'Source directory must be a dedicated content directory, not the current working directory. '
221
+ + `Received: ${formatPath(cwd, sourceDir)}`,
222
+ );
223
+ }
224
+
225
+ assertNoPathOverlap(cwd, 'Source directory', sourceDir, 'internal .zeropress working directory', generatedDir);
226
+ assertNoPathOverlap(cwd, 'Destination directory', destinationDir, 'internal .zeropress working directory', generatedDir);
227
+ assertNoPathOverlap(cwd, 'Theme directory', themeDir, 'internal .zeropress working directory', generatedDir);
228
+ assertNoPathOverlap(cwd, 'Source directory', sourceDir, 'destination directory', destinationDir);
229
+ assertNoPathOverlap(cwd, 'Source directory', sourceDir, 'theme directory', themeDir);
230
+ }
231
+
232
+ function assertNoPathOverlap(cwd, firstLabel, firstPath, secondLabel, secondPath) {
233
+ if (!pathsOverlap(firstPath, secondPath)) {
234
+ return;
235
+ }
236
+ throw new Error(
237
+ `${firstLabel} must not overlap the ${secondLabel}. `
238
+ + `${firstLabel}: ${formatPath(cwd, firstPath)}; `
239
+ + `${secondLabel}: ${formatPath(cwd, secondPath)}`,
240
+ );
241
+ }
242
+
206
243
  async function copyPublicStaging(sourceDir, targetDir, options) {
207
244
  const entries = await fs.readdir(sourceDir, { withFileTypes: true });
208
245
 
@@ -254,6 +291,10 @@ function pathsOverlap(firstPath, secondPath) {
254
291
  return first === second || isPathInside(first, second) || isPathInside(second, first);
255
292
  }
256
293
 
294
+ function samePath(firstPath, secondPath) {
295
+ return path.resolve(firstPath) === path.resolve(secondPath);
296
+ }
297
+
257
298
  function isPathInside(parentPath, childPath) {
258
299
  const relativePath = path.relative(parentPath, childPath);
259
300
  return Boolean(relativePath) && !relativePath.startsWith('..') && !path.isAbsolute(relativePath);