mikel-press 0.28.0 → 0.30.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.
Files changed (3) hide show
  1. package/README.md +29 -0
  2. package/index.js +120 -16
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -162,6 +162,20 @@ This plugin loads additional files (aka assets) and includes them in the build f
162
162
 
163
163
  This plugin processes and parses the frontmatter in each file. The parsed frontmatter content will be available in `page.attributes` field.
164
164
 
165
+ ### `press.TransformPlugin(options)`
166
+
167
+ A generic transform plugin that will execute the provided `options.transform` function with the `context` and the current `node` object. Example:
168
+
169
+ ```javascript
170
+ press.TransformPlugin({
171
+ transform: node => {
172
+ if (node.label === press.LABEL_PAGE && node.content && path.extname(node.source) === ".md") {
173
+ node.content = `{{#markdown}}\n\n${node.content}\n\n{{/markdown}}\n`;
174
+ }
175
+ },
176
+ });
177
+ ```
178
+
165
179
  ### `press.ContentPagePlugin()`
166
180
 
167
181
  This plugin processes each page using the mikel templating.
@@ -173,6 +187,21 @@ This plugin copies static files from the source to the destination.
173
187
  Options:
174
188
  - `options.patterns` (array): List of file patterns to copy. Each pattern should have `from` and `to`.
175
189
 
190
+ ### `press.RedirectsPlugin(options)`
191
+
192
+ The RedirectsPlugin lets you define URL redirects. For each redirect rule, the plugin generates a small HTML file that forwards visitors to the target URL using both HTML and JavaScript fallbacks.
193
+
194
+ Options:
195
+ - `options.redirects` (array): list of redirections. Each redirection should have `from` and `to`.
196
+
197
+ ```javascript
198
+ press.RedirectsPlugin({
199
+ redirects: [
200
+ { from: "/socials/github.html", to: "https://github.com/jmjuanes" },
201
+ ],
202
+ });
203
+ ```
204
+
176
205
  ## API
177
206
 
178
207
  **mikel-press** exposes a single function that triggers the build with the given configuration object provided as an argument.
package/index.js CHANGED
@@ -1,6 +1,11 @@
1
1
  import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
3
 
4
+ // @description internal method to get the first node that ends with the provided string
5
+ const getNodeFromSource = (nodes = [], endingStr = "") => {
6
+ return (nodes || []).find(node => (node.source || "").endsWith(endingStr || ""));
7
+ };
8
+
4
9
  // @description get all plugins of the given type
5
10
  const getPlugins = (context, name) => {
6
11
  return context.plugins.filter(plugin => typeof plugin[name] === "function");
@@ -11,6 +16,26 @@ const applyLayout = page => {
11
16
  return `{{>>layout:${page.attributes.layout}}}\n\n${page.content}\n\n{{/layout:${page.attributes.layout}}}\n`;
12
17
  };
13
18
 
19
+ // @description generate the content for the redirection page
20
+ const generateRedirectHTML = (to, status) => {
21
+ const content = [
22
+ `<!DOCTYPE html>`,
23
+ `<html lang="en">`,
24
+ `<head>`,
25
+ ` <meta charset="utf-8" />`,
26
+ ` <title>Redirecting...</title>`,
27
+ ` <meta http-equiv="refresh" content="0; url=${to}" />`,
28
+ ` <link rel="canonical" href="${to}" />`,
29
+ ` <script>window.location.replace("${to}");</script>`,
30
+ `</head>`,
31
+ `<body>`,
32
+ ` <p>Redirectig to <a href="${to}">${to}</a>...</p>`,
33
+ `</body>`,
34
+ `</html>`,
35
+ ];
36
+ return content.join("\n");
37
+ };
38
+
14
39
  // @description press main function
15
40
  // @param {Object} config - configuration object
16
41
  // @param {String} config.source - source folder
@@ -39,7 +64,28 @@ press.createContext = (config = {}) => {
39
64
  ...plugins,
40
65
  ],
41
66
  nodes: [],
67
+ actions: {},
68
+ });
69
+ // register helpers and funcions
70
+ context.template.addFunction("getPageUrl", params => {
71
+ return getNodeFromSource(params?.variables?.root?.site?.pages || [], params.args[0])?.url || "";
72
+ });
73
+ context.template.addFunction("getAssetUrl", params => {
74
+ return getNodeFromSource(params?.variables?.root?.site?.assets || [], params.args[0])?.url || "";
75
+ });
76
+ context.template.addHelper("pages", params => {
77
+ const draft = params?.options?.draft ?? params?.opt?.draft;
78
+ const collection = params?.opt?.collection || params?.options?.collection || null;
79
+ const items = (params.data?.site?.pages || []).filter(page => {
80
+ if (typeof draft === "boolean" && draft !== !!page?.attributes?.draft) {
81
+ return false;
82
+ }
83
+ return !collection || page.attributes?.collection === collection;
84
+ });
85
+ const limit = Math.min(items.length, params.options?.limit || params.opt?.limit || items.length);
86
+ return items.slice(0, limit).reverse().map((c, i) => params.fn(c, {index: i})).join("");
42
87
  });
88
+ // initialize plugins
43
89
  getPlugins(context, "init").forEach(plugin => {
44
90
  return plugin.init(context);
45
91
  });
@@ -59,7 +105,15 @@ press.createContext = (config = {}) => {
59
105
 
60
106
  // @description build the provided context
61
107
  press.buildContext = (context, nodesToBuild = null) => {
62
- const nodes = Array.isArray(nodesToBuild) ? nodesToBuild : context.nodes;
108
+ const nodes = (Array.isArray(nodesToBuild) ? nodesToBuild : context.nodes).slice();
109
+ const createNode = (nodeLabel, nodeObject = {}) => {
110
+ nodes.push({ label: nodeLabel, content: "", ...nodeObject });
111
+ };
112
+ // 0. assign actions to context
113
+ Object.assign(context.actions, {
114
+ createPage: pageObject => createNode(press.LABEL_PAGE, pageObject),
115
+ createAsset: assetObject => createNode(press.LABEL_ASSET, assetObject),
116
+ });
63
117
  // 1. transform nodes
64
118
  getPlugins(context, "transform").forEach(plugin => {
65
119
  // special hook to initialize the transform plugin
@@ -108,6 +162,10 @@ press.watchContext = (context, options = {}) => {
108
162
 
109
163
  // @description general utilities
110
164
  press.utils = {
165
+ // @description normalize a path
166
+ normalizePath: (rawPath) => {
167
+ return path.normalize("/" + rawPath);
168
+ },
111
169
  // @description read a file from disk
112
170
  // @param {String} file path to the file to read
113
171
  read: (file, encoding = "utf8") => {
@@ -140,6 +198,26 @@ press.utils = {
140
198
  .filter(file => (extensions === "*" || extensions.includes(path.extname(file))) && !exclude.includes(file))
141
199
  .filter(file => fs.statSync(path.join(folder, file)).isFile());
142
200
  },
201
+ // @description walk through the given folder and get all files
202
+ // @params {String} folder folder to walk through
203
+ // @params {Array|String} extensions extensions to include. Default: "*"
204
+ walkdir: (folder, extensions = "*", exclude = []) => {
205
+ const walkSync = (currentFolder, files = []) => {
206
+ const fullFolderPath = path.join(folder, currentFolder);
207
+ fs.readdirSync(fullFolderPath).forEach(file => {
208
+ const filePath = path.join(currentFolder, file);
209
+ const fullFilePath = path.join(fullFolderPath, file);
210
+ if (fs.statSync(fullFilePath).isDirectory()) {
211
+ return walkSync(filePath, files);
212
+ }
213
+ if (extensions === "*" || extensions.includes(path.extname(file))) {
214
+ files.push(filePath);
215
+ }
216
+ });
217
+ return files;
218
+ };
219
+ return walkSync("./", []);
220
+ },
143
221
  // @description watch for file changes
144
222
  // @param {String} filePath path to the file to watch
145
223
  // @param {Function} listener method to listen for file changes
@@ -190,7 +268,7 @@ press.SourcePlugin = (options = {}) => {
190
268
  source: path.join(folder, file),
191
269
  label: options.label || press.LABEL_PAGE,
192
270
  path: path.join(options?.basePath || ".", file),
193
- url: path.normalize("/" + path.join(options?.basePath || ".", file)),
271
+ url: press.utils.normalizePath(path.join(options?.basePath || ".", file)),
194
272
  };
195
273
  });
196
274
  },
@@ -247,6 +325,14 @@ press.LayoutsPlugin = (options = {}) => {
247
325
  });
248
326
  };
249
327
 
328
+ // @description generic transform plugin
329
+ press.TransformPlugin = (options = {}) => {
330
+ const transformFn = typeof options?.transform === "function" ? options.transform : options;
331
+ return {
332
+ transform: (context, node) => transformFn(node, context),
333
+ };
334
+ };
335
+
250
336
  // @description frontmatter plugin
251
337
  press.FrontmatterPlugin = () => {
252
338
  return {
@@ -258,7 +344,7 @@ press.FrontmatterPlugin = () => {
258
344
  node.title = node.attributes?.title || node.path;
259
345
  if (node.attributes.permalink) {
260
346
  node.path = node.attributes.permalink;
261
- node.url = path.normalize("/" + node.path);
347
+ node.url = press.utils.normalizePath(node.path);
262
348
  }
263
349
  }
264
350
  },
@@ -326,19 +412,37 @@ press.UsePlugin = mikelPlugin => {
326
412
  };
327
413
 
328
414
  // @description copy plugin
329
- press.CopyAssetsPlugin = (options = {}) => {
330
- return {
331
- load: () => {
332
- return (options?.patterns || [])
333
- .filter(item => item.from && fs.existsSync(path.resolve(item.from)))
334
- .map(item => ({
335
- source: path.resolve(item.from),
336
- path: path.join(options?.basePath || ".", item.to || path.basename(item.from)),
337
- label: options?.label || press.LABEL_ASSET,
338
- }));
339
- },
340
- };
341
- };
415
+ press.CopyAssetsPlugin = (options = {}) => ({
416
+ name: "CopyAssetsPlugin",
417
+ load: () => {
418
+ const filesToCopy = (options?.patterns || []).filter(item => {
419
+ return item.from && fs.existsSync(path.resolve(item.from));
420
+ });
421
+ return filesToCopy.map(item => {
422
+ const filePath = path.join(options?.basePath || ".", item.to || path.basename(item.from));
423
+ return {
424
+ source: path.resolve(item.from),
425
+ path: filePath,
426
+ url: press.utils.normalizePath(filePath),
427
+ label: options?.label || press.LABEL_ASSET,
428
+ };
429
+ });
430
+ },
431
+ });
432
+
433
+ // @description redirections plugin
434
+ press.RedirectsPlugin = (options = {}) => ({
435
+ name: "RedirectsPlugin",
436
+ load: () => {
437
+ return (options.redirects || []).map(redirection => ({
438
+ source: redirection.from,
439
+ path: path.join(options?.basePath || ".", redirection.from),
440
+ url: press.utils.normalizePath(path.join(options?.basePath || ".", redirection.from)),
441
+ label: press.LABEL_ASSET,
442
+ content: generateRedirectHTML(redirection.to),
443
+ }));
444
+ },
445
+ });
342
446
 
343
447
  // export press generator
344
448
  export default press;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "mikel-press",
3
3
  "description": "A tiny and fast static site generator based on mikel templating",
4
- "version": "0.28.0",
4
+ "version": "0.30.0",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "author": {