mikel-press 0.1.0 → 0.2.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/index.d.ts +18 -13
  2. package/index.js +91 -51
  3. package/package.json +1 -1
package/index.d.ts CHANGED
@@ -4,11 +4,6 @@ interface MikelTemplateOptions {
4
4
  partials: {[key: string]: string};
5
5
  }
6
6
 
7
- interface FrontmatterOptions {
8
- separator?: string;
9
- parse?: (front: string) => any;
10
- }
11
-
12
7
  interface FrontmatterResult {
13
8
  body: string;
14
9
  attributes: any;
@@ -19,15 +14,22 @@ interface VirtualPageOptions {
19
14
  file?: string;
20
15
  extname?: string;
21
16
  basename?: string;
17
+ frontmatter?: (str: string) => FrontmatterResult;
18
+ transform?: (str: string) => string;
22
19
  }
23
20
 
24
21
  interface VirtualPage {
25
- content: string,
26
- attributes: any,
27
- name: string,
28
- extname: string,
29
- basename: string,
30
- url: string,
22
+ content: string;
23
+ attributes: any;
24
+ name: string;
25
+ extname: string;
26
+ basename: string;
27
+ url: string;
28
+ }
29
+
30
+ interface PostsPluginOptions {
31
+ dir: string;
32
+ parser: (str: string) => string;
31
33
  }
32
34
 
33
35
  interface SiteConfig {
@@ -38,13 +40,16 @@ interface SiteConfig {
38
40
  dataDir: string;
39
41
  pagesDir: string;
40
42
  assetsDir: string;
41
- frontmatter: Partial<FrontmatterOptions>;
43
+ frontmatter: (str: string) => FrontmatterResult;
42
44
  mikel: Partial<MikelTemplateOptions>;
43
45
  plugins: any[];
44
46
  }
45
47
 
46
48
  declare module "mikel-press" {
47
- export function frontmatter(str: string, options?: FrontmatterOptions): FrontmatterResult;
49
+ export function frontmatter(str: string): FrontmatterResult;
48
50
  export function createVirtualPage(options: VirtualPageOptions): VirtualPage;
49
51
  export function run(config: Partial<SiteConfig>): void;
52
+
53
+ export function postsPlugin(options: PostsPluginOptions): any;
54
+ export function progressPlugin(): any;
50
55
  }
package/index.js CHANGED
@@ -2,16 +2,28 @@ import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
3
  import mikel from "mikel";
4
4
 
5
+ // @description tiny yaml parser
6
+ const parseYaml = (str = "") => {
7
+ const lines = str.split("\n").filter(line => line.trim() !== "" && !line.trim().startsWith("#"));
8
+ return Object.fromEntries(lines.map(line => {
9
+ const [key, value] = line.split(":").map(part => part.trim());
10
+ if (!isNaN(value)) {
11
+ return [key, Number(value)];
12
+ }
13
+ if (value === "true" || value === "false" || value === "null") {
14
+ return [key, JSON.parse(value)];
15
+ }
16
+ return [key, value.replaceAll(/^["']|["']$/g, "")];
17
+ }));
18
+ };
19
+
5
20
  // @description tiny front-matter parser
6
- const frontmatter = (str = "", options = {}) => {
21
+ const frontmatter = (str = "") => {
7
22
  let body = (str || "").trim(), attributes = {};
8
- if (!!options && typeof options === "object") {
9
- const matches = Array.from(body.matchAll(new RegExp("^(" + (options.separator || "---") + " *)", "gm")));
10
- if (matches?.length === 2 && matches[0].index === 0) {
11
- const front = body.substring(0 + matches[0][1].length, matches[1].index).trim();
12
- body = body.substring(matches[1].index + matches[1][1].length).trim();
13
- attributes = typeof options.parse === "function" ? options.parse(front) : front;
14
- }
23
+ const matches = Array.from(body.matchAll(/^(--- *)/gm));
24
+ if (matches?.length === 2 && matches[0].index === 0) {
25
+ attributes = parseYaml(body.substring(0 + matches[0][1].length, matches[1].index).trim());
26
+ body = body.substring(matches[1].index + matches[1][1].length).trim();
15
27
  }
16
28
  return {body, attributes};
17
29
  };
@@ -37,10 +49,11 @@ const getLayoutContent = config => {
37
49
  content = config.layoutContent || config.templateContent;
38
50
  }
39
51
  // parse with frontmatter
40
- const {body, attributes} = frontmatter(content, config.frontmatter);
52
+ const {body, attributes} = typeof config.frontmatter == "function" ? config.frontmatter(content) : {body: content, attributes: {}};
41
53
  return {
42
54
  content: body,
43
55
  data: attributes || {},
56
+ attributes: attributes || {},
44
57
  };
45
58
  };
46
59
 
@@ -49,7 +62,7 @@ const createVirtualPage = (options = {}) => {
49
62
  const content = options.content || fs.readFileSync(options.file, "utf8");
50
63
  const extname = options.extname || path.extname(options.file || "") || ".html";
51
64
  const basename = options.basename || path.basename(options.file || "", extname) || "virtual";
52
- const {body, attributes} = frontmatter(content, options.frontmatter);
65
+ const {body, attributes} = typeof options?.frontmatter == "function" ? options.frontmatter(content) : {body: content, attributes: {}};
53
66
  return {
54
67
  name: basename + extname,
55
68
  basename: basename,
@@ -57,61 +70,56 @@ const createVirtualPage = (options = {}) => {
57
70
  url: options.url || attributes?.permalink || path.join("/", basename + extname),
58
71
  data: attributes || {}, // DEPRECATED
59
72
  attributes: attributes || {},
60
- content: typeof options.parse === "function" ? options.parse(body) : body,
73
+ content: typeof options.transform === "function" ? options.transform(body) : body,
61
74
  };
62
75
  };
63
76
 
64
77
  // @description get pages from input folder
65
- const readPages = (folder, extensions = ".html", fm = null, parse = null) => {
78
+ const readPages = (folder, extensions = ".html", fm = null, transform = null) => {
66
79
  const extensionsList = new Set([extensions].flat());
67
- return fs.readdirSync(folder, "utf8")
68
- .filter(file => extensionsList.has(path.extname(file)))
69
- .map(file => {
70
- return createVirtualPage({
71
- file: path.join(folder, file),
72
- frontmatter: fm,
73
- parse: parse,
74
- extname: ".html",
80
+ if (fs.existsSync(folder) && fs.lstatSync(folder).isDirectory()) {
81
+ return fs.readdirSync(folder, "utf8")
82
+ .filter(file => extensionsList.has(path.extname(file)))
83
+ .map(file => {
84
+ return createVirtualPage({
85
+ file: path.join(folder, file),
86
+ frontmatter: fm,
87
+ transform: transform,
88
+ extname: ".html",
89
+ });
75
90
  });
76
- });
91
+ }
92
+ return [];
77
93
  };
78
94
 
79
95
  // @description get assets
80
96
  const readAssets = (folder, fm = null) => {
81
- const assetPaths = fs.readdirSync(folder, "utf8");
82
- return Object.fromEntries(assetPaths.map(file => {
83
- const asset = createVirtualPage({
84
- file: path.join(folder, file),
85
- frontmatter: fm,
86
- });
87
- const assetName = asset.basename.replaceAll(".", "_").replaceAll("-", "_");
88
- return [assetName, asset];
89
- }));
97
+ if (fs.existsSync(folder) && fs.lstatSync(folder).isDirectory()) {
98
+ const assetPaths = fs.readdirSync(folder, "utf8");
99
+ return Object.fromEntries(assetPaths.map(file => {
100
+ const asset = createVirtualPage({
101
+ file: path.join(folder, file),
102
+ frontmatter: fm,
103
+ });
104
+ const assetName = asset.basename.replaceAll(".", "_").replaceAll("-", "_");
105
+ return [assetName, asset];
106
+ }));
107
+ }
108
+ return {};
90
109
  };
91
110
 
92
111
  // @description read a data folder
93
112
  const readData = folder => {
94
- const files = fs.readdirSync(folder, "utf8")
95
- .filter(file => path.extname(file) === ".json")
96
- .map(file => path.join(folder, file))
97
- .map(file => {
98
- return [path.basename(file, ".json"), JSON.parse(fs.readFileSync(file, "utf8"))];
99
- });
100
- return Object.fromEntries(files);
101
- };
102
-
103
- // @description plugins
104
- const plugins = {
105
- // plugin to read and include posts in markdown
106
- posts: (options = {}) => {
107
- return context => {
108
- context.hooks.beforeEmit.add(() => {
109
- const posts = readPages(path.join(context.source, options?.dir || "posts"), [".md"], context.site.frontmatter, options?.parser);
110
- context.site.posts = posts; // posts will be accesible in site.posts
111
- context.site.pages = [...context.site.pages, ...posts]; // posts will be included as pages also
113
+ if (fs.existsSync(folder) && fs.lstatSync(folder).isDirectory()) {
114
+ const files = fs.readdirSync(folder, "utf8")
115
+ .filter(file => path.extname(file) === ".json")
116
+ .map(file => path.join(folder, file))
117
+ .map(file => {
118
+ return [path.basename(file, ".json"), JSON.parse(fs.readFileSync(file, "utf8"))];
112
119
  });
113
- };
114
- },
120
+ return Object.fromEntries(files);
121
+ }
122
+ return {};
115
123
  };
116
124
 
117
125
  // @description run mikel press with the provided configuration
@@ -160,5 +168,37 @@ const run = (config = {}) => {
160
168
  dispatch("done", []);
161
169
  };
162
170
 
171
+ // plugin to read and include posts in markdown
172
+ const postsPlugin = (options = {}) => {
173
+ return context => {
174
+ context.hooks.beforeEmit.add(() => {
175
+ const posts = readPages(path.join(context.source, options?.dir || "posts"), [".md"], context.site.frontmatter, options?.parser);
176
+ context.site.posts = posts; // posts will be accesible in site.posts
177
+ context.site.pages = [...context.site.pages, ...posts]; // posts will be included as pages also
178
+ });
179
+ };
180
+ };
181
+
182
+ // progress plugin
183
+ const progressPlugin = () => {
184
+ return context => {
185
+ const timeStart = Date.now();
186
+ const log = (status, msg) => console.log(`[${new Date().toISOString()}] (${status}) ${msg}`);
187
+ context.hooks.initialize.add(() => {
188
+ log("info", `source directory: ${context.source}`);
189
+ log("info", `destination directory: ${context.destination}`);
190
+ });
191
+ context.hooks.emitPage.add(page => {
192
+ log("info", `saving page: ${page.url} --> ${path.join(context.destination, page.url)}`);
193
+ });
194
+ context.hooks.emitAsset.add(asset => {
195
+ log("info", `saving asset: ${asset.url} --> ${path.join(context.destination, asset.url)}`);
196
+ });
197
+ context.hooks.done.add(() => {
198
+ log("done", `build completed in ${Date.now() - timeStart}ms`);
199
+ });
200
+ };
201
+ };
202
+
163
203
  // export
164
- export default {run, createVirtualPage, frontmatter, plugins};
204
+ export default {run, createVirtualPage, frontmatter, postsPlugin, progressPlugin};
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "mikel-press",
3
3
  "description": "A minimal static site generator based on mikel templating",
4
- "version": "0.1.0",
4
+ "version": "0.2.0",
5
5
  "type": "module",
6
6
  "author": {
7
7
  "name": "Josemi Juanes",