mikel-press 0.0.1 → 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.
- package/index.d.ts +55 -0
- package/index.js +136 -88
- package/package.json +3 -7
- package/cli.js +0 -47
package/index.d.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
interface MikelTemplateOptions {
|
|
2
|
+
functions: {[key: string]: (args: any) => string};
|
|
3
|
+
helpers: {[key: string]: (args: any) => string};
|
|
4
|
+
partials: {[key: string]: string};
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
interface FrontmatterResult {
|
|
8
|
+
body: string;
|
|
9
|
+
attributes: any;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface VirtualPageOptions {
|
|
13
|
+
content?: string;
|
|
14
|
+
file?: string;
|
|
15
|
+
extname?: string;
|
|
16
|
+
basename?: string;
|
|
17
|
+
frontmatter?: (str: string) => FrontmatterResult;
|
|
18
|
+
transform?: (str: string) => string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface VirtualPage {
|
|
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;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface SiteConfig {
|
|
36
|
+
source: string;
|
|
37
|
+
destination: string;
|
|
38
|
+
layout: string;
|
|
39
|
+
layoutContent: string;
|
|
40
|
+
dataDir: string;
|
|
41
|
+
pagesDir: string;
|
|
42
|
+
assetsDir: string;
|
|
43
|
+
frontmatter: (str: string) => FrontmatterResult;
|
|
44
|
+
mikel: Partial<MikelTemplateOptions>;
|
|
45
|
+
plugins: any[];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
declare module "mikel-press" {
|
|
49
|
+
export function frontmatter(str: string): FrontmatterResult;
|
|
50
|
+
export function createVirtualPage(options: VirtualPageOptions): VirtualPage;
|
|
51
|
+
export function run(config: Partial<SiteConfig>): void;
|
|
52
|
+
|
|
53
|
+
export function postsPlugin(options: PostsPluginOptions): any;
|
|
54
|
+
export function progressPlugin(): any;
|
|
55
|
+
}
|
package/index.js
CHANGED
|
@@ -2,74 +2,39 @@ import * as fs from "node:fs";
|
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import mikel from "mikel";
|
|
4
4
|
|
|
5
|
-
//
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const
|
|
10
|
-
if (!
|
|
11
|
-
|
|
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
12
|
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
// @description tiny front-matter parser
|
|
16
|
-
frontmatter: (str = "", parser = null) => {
|
|
17
|
-
let body = (str || "").trim(), attributes = {};
|
|
18
|
-
if (!!parser) {
|
|
19
|
-
const matches = Array.from(body.matchAll(/^(--- *)/gm))
|
|
20
|
-
if (matches?.length === 2 && matches[0].index === 0) {
|
|
21
|
-
const front = body.substring(0 + matches[0][1].length, matches[1].index).trim();
|
|
22
|
-
body = body.substring(matches[1].index + matches[1][1].length).trim();
|
|
23
|
-
attributes = typeof parser === "function" ? parser(front) : front;
|
|
24
|
-
}
|
|
13
|
+
if (value === "true" || value === "false" || value === "null") {
|
|
14
|
+
return [key, JSON.parse(value)];
|
|
25
15
|
}
|
|
26
|
-
return
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
extname: path.extname(file),
|
|
49
|
-
url: attributes?.permalink || path.join("/", path.basename(file, type) + ".html"),
|
|
50
|
-
data: attributes || {},
|
|
51
|
-
content: typeof parse === "function" ? parse(body) : body,
|
|
52
|
-
};
|
|
53
|
-
});
|
|
54
|
-
},
|
|
55
|
-
// @description get assets
|
|
56
|
-
readAssets: (folder, fm = null) => {
|
|
57
|
-
const assetPaths = fs.readdirSync(folder, "utf8");
|
|
58
|
-
return Object.fromEntries(assetPaths.map(file => {
|
|
59
|
-
const content = fs.readFileSync(path.join(folder, file), "utf8");
|
|
60
|
-
const {body, attributes} = utils.frontmatter(content, fm);
|
|
61
|
-
const asset = {
|
|
62
|
-
name: file,
|
|
63
|
-
basename: path.basename(file, path.extname(file)),
|
|
64
|
-
extname: path.extname(file),
|
|
65
|
-
url: attributes?.permalink || path.join("/", file),
|
|
66
|
-
data: attributes || {},
|
|
67
|
-
content: body,
|
|
68
|
-
};
|
|
69
|
-
const assetName = asset.basename.replaceAll(".", "_").replaceAll("-", "_");
|
|
70
|
-
return [assetName, asset];
|
|
71
|
-
}));
|
|
72
|
-
},
|
|
16
|
+
return [key, value.replaceAll(/^["']|["']$/g, "")];
|
|
17
|
+
}));
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
// @description tiny front-matter parser
|
|
21
|
+
const frontmatter = (str = "") => {
|
|
22
|
+
let body = (str || "").trim(), attributes = {};
|
|
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();
|
|
27
|
+
}
|
|
28
|
+
return {body, attributes};
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// @description utility to save a file to disk
|
|
32
|
+
const saveFile = (filePath, fileContent) => {
|
|
33
|
+
const folder = path.dirname(filePath);
|
|
34
|
+
if (!fs.existsSync(folder)) {
|
|
35
|
+
fs.mkdirSync(folder, {recursive: true});
|
|
36
|
+
}
|
|
37
|
+
fs.writeFileSync(filePath, fileContent, "utf8");
|
|
73
38
|
};
|
|
74
39
|
|
|
75
40
|
// @description returns the layout content from the given options
|
|
@@ -84,25 +49,77 @@ const getLayoutContent = config => {
|
|
|
84
49
|
content = config.layoutContent || config.templateContent;
|
|
85
50
|
}
|
|
86
51
|
// parse with frontmatter
|
|
87
|
-
const {body, attributes} =
|
|
52
|
+
const {body, attributes} = typeof config.frontmatter == "function" ? config.frontmatter(content) : {body: content, attributes: {}};
|
|
88
53
|
return {
|
|
89
54
|
content: body,
|
|
90
55
|
data: attributes || {},
|
|
56
|
+
attributes: attributes || {},
|
|
57
|
+
};
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
// @description create a virtual page object from the given options
|
|
61
|
+
const createVirtualPage = (options = {}) => {
|
|
62
|
+
const content = options.content || fs.readFileSync(options.file, "utf8");
|
|
63
|
+
const extname = options.extname || path.extname(options.file || "") || ".html";
|
|
64
|
+
const basename = options.basename || path.basename(options.file || "", extname) || "virtual";
|
|
65
|
+
const {body, attributes} = typeof options?.frontmatter == "function" ? options.frontmatter(content) : {body: content, attributes: {}};
|
|
66
|
+
return {
|
|
67
|
+
name: basename + extname,
|
|
68
|
+
basename: basename,
|
|
69
|
+
extname: extname,
|
|
70
|
+
url: options.url || attributes?.permalink || path.join("/", basename + extname),
|
|
71
|
+
data: attributes || {}, // DEPRECATED
|
|
72
|
+
attributes: attributes || {},
|
|
73
|
+
content: typeof options.transform === "function" ? options.transform(body) : body,
|
|
91
74
|
};
|
|
92
75
|
};
|
|
93
76
|
|
|
94
|
-
// @description
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
return
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
77
|
+
// @description get pages from input folder
|
|
78
|
+
const readPages = (folder, extensions = ".html", fm = null, transform = null) => {
|
|
79
|
+
const extensionsList = new Set([extensions].flat());
|
|
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
|
+
});
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
return [];
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
// @description get assets
|
|
96
|
+
const readAssets = (folder, fm = null) => {
|
|
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
103
|
});
|
|
104
|
-
|
|
105
|
-
|
|
104
|
+
const assetName = asset.basename.replaceAll(".", "_").replaceAll("-", "_");
|
|
105
|
+
return [assetName, asset];
|
|
106
|
+
}));
|
|
107
|
+
}
|
|
108
|
+
return {};
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
// @description read a data folder
|
|
112
|
+
const readData = folder => {
|
|
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"))];
|
|
119
|
+
});
|
|
120
|
+
return Object.fromEntries(files);
|
|
121
|
+
}
|
|
122
|
+
return {};
|
|
106
123
|
};
|
|
107
124
|
|
|
108
125
|
// @description run mikel press with the provided configuration
|
|
@@ -113,7 +130,6 @@ const run = (config = {}) => {
|
|
|
113
130
|
site: config || {},
|
|
114
131
|
source: path.resolve(process.cwd(), config?.source || "."),
|
|
115
132
|
destination: path.resolve(process.cwd(), config?.destination || "./www"),
|
|
116
|
-
compiler: null,
|
|
117
133
|
layout: getLayoutContent(config),
|
|
118
134
|
hooks: Object.freeze(Object.fromEntries(hooks.map(name => {
|
|
119
135
|
return [name, new Set()];
|
|
@@ -126,31 +142,63 @@ const run = (config = {}) => {
|
|
|
126
142
|
}
|
|
127
143
|
dispatch("initialize", []);
|
|
128
144
|
// 2. initialize mikel instance
|
|
129
|
-
|
|
145
|
+
const compiler = mikel.create(context.layout.content, config?.mikel || {});
|
|
130
146
|
dispatch("compiler", [context.compiler]);
|
|
131
147
|
// 3. read stuff
|
|
132
|
-
context.site.data =
|
|
133
|
-
context.site.pages =
|
|
134
|
-
context.site.assets =
|
|
148
|
+
context.site.data = readData(path.join(context.source, config?.dataDir || "data"));
|
|
149
|
+
context.site.pages = readPages(path.join(context.source, config?.pagesDir || "pages"), ".html", config?.frontmatter, c => c);
|
|
150
|
+
context.site.assets = readAssets(path.join(context.source, config?.assetsDir || "assets"), config?.frontmatter);
|
|
135
151
|
dispatch("beforeEmit", []);
|
|
136
152
|
// 4. save pages
|
|
137
153
|
context.site.pages.forEach(page => {
|
|
138
|
-
|
|
139
|
-
const content =
|
|
154
|
+
compiler.addPartial("content", page.content); // register page content as partial
|
|
155
|
+
const content = compiler({
|
|
140
156
|
site: context.site,
|
|
141
157
|
layout: context.layout,
|
|
142
158
|
page: page,
|
|
143
159
|
});
|
|
144
160
|
dispatch("emitPage", [page, content]);
|
|
145
|
-
|
|
161
|
+
saveFile(path.join(context.destination, page.url), content);
|
|
146
162
|
});
|
|
147
163
|
// 5. save assets
|
|
148
164
|
Object.values(context.site.assets).forEach(asset => {
|
|
149
165
|
dispatch("emitAsset", [asset]);
|
|
150
|
-
|
|
166
|
+
saveFile(path.join(context.destination, asset.url), asset.content);
|
|
151
167
|
});
|
|
152
168
|
dispatch("done", []);
|
|
153
169
|
};
|
|
154
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
|
+
|
|
155
203
|
// export
|
|
156
|
-
export default {
|
|
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.0
|
|
4
|
+
"version": "0.2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Josemi Juanes",
|
|
@@ -12,16 +12,12 @@
|
|
|
12
12
|
"bugs": "https://github.com/jmjuanes/mikel/issues",
|
|
13
13
|
"main": "index.js",
|
|
14
14
|
"module": "index.js",
|
|
15
|
+
"types": "index.d.ts",
|
|
15
16
|
"exports": {
|
|
16
17
|
".": "./index.js",
|
|
17
18
|
"./index.js": "./index.js",
|
|
18
|
-
"./cli.js": "./cli.js",
|
|
19
19
|
"./package.json": "./package.json"
|
|
20
20
|
},
|
|
21
|
-
"bin": {
|
|
22
|
-
"mikelpress": "./cli.js",
|
|
23
|
-
"mikel-press": "./cli.js"
|
|
24
|
-
},
|
|
25
21
|
"scripts": {
|
|
26
22
|
"test": "node test.js"
|
|
27
23
|
},
|
|
@@ -39,6 +35,6 @@
|
|
|
39
35
|
"files": [
|
|
40
36
|
"README.md",
|
|
41
37
|
"index.js",
|
|
42
|
-
"
|
|
38
|
+
"index.d.ts"
|
|
43
39
|
]
|
|
44
40
|
}
|
package/cli.js
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import * as path from "node:path";
|
|
4
|
-
import {parseArgs} from "node:util";
|
|
5
|
-
import mikelPress from "./index.js";
|
|
6
|
-
|
|
7
|
-
// get the configuration file from the provided path
|
|
8
|
-
const resolveConfig = value => {
|
|
9
|
-
const configPath = path.resolve(process.cwd(), value || "./press.config.js");
|
|
10
|
-
return import(configPath).then(config => {
|
|
11
|
-
return config?.default || {};
|
|
12
|
-
});
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
// available commands
|
|
16
|
-
const commands = {
|
|
17
|
-
build: {
|
|
18
|
-
description: "Generate the static site with the provided configuration.",
|
|
19
|
-
execute: async values => {
|
|
20
|
-
const config = await resolveConfig(values.config);
|
|
21
|
-
return mikelPress.run(config);
|
|
22
|
-
},
|
|
23
|
-
options: {
|
|
24
|
-
config: {
|
|
25
|
-
type: "string",
|
|
26
|
-
short: "c",
|
|
27
|
-
default: "press.config.js",
|
|
28
|
-
},
|
|
29
|
-
},
|
|
30
|
-
},
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
const main = async (args = []) => {
|
|
34
|
-
const [commandName, ...otherArguments] = args;
|
|
35
|
-
if (typeof commands[commandName] !== "undefined") {
|
|
36
|
-
const {values} = parseArgs({
|
|
37
|
-
args: otherArguments,
|
|
38
|
-
options: commands[commandName].options || {},
|
|
39
|
-
});
|
|
40
|
-
return commands[commandName].execute(values);
|
|
41
|
-
}
|
|
42
|
-
// if this command is not available, print help [TODO]
|
|
43
|
-
// return commands.help.execute();
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
// run
|
|
47
|
-
main(process.argv.slice(2));
|