mikel-press 0.0.1

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 (4) hide show
  1. package/README.md +52 -0
  2. package/cli.js +47 -0
  3. package/index.js +156 -0
  4. package/package.json +44 -0
package/README.md ADDED
@@ -0,0 +1,52 @@
1
+ # mikel-press
2
+
3
+ **mikel-press** is a tiny static site generator inspired by Jekyll that uses **mikel** as its templating engine. It provides a simple and efficient way to build static websites with customizable layouts and content.
4
+
5
+ ## Installation
6
+
7
+ To install **mikel-press**, ensure you have [Node.js](https://nodejs.org) installed on your system. Then, add this package as a dependency to your project using **yarn**:
8
+
9
+ ```bash
10
+ $ yarn add --dev mikel-press
11
+ ```
12
+
13
+ Or **npm**:
14
+
15
+ ```bash
16
+ $ npm install --dev mikel-press
17
+ ```
18
+
19
+ ## Usage
20
+
21
+ ### Building the Site
22
+
23
+ Run the `build` command to generate your static website:
24
+
25
+ ```bash
26
+ $ mikel-press build
27
+ ```
28
+
29
+ The generated files will be available in the `www` directory by default.
30
+
31
+ ### (WIP) Starting a Development Server
32
+
33
+ To preview your site locally, use the `serve` command:
34
+
35
+ ```bash
36
+ mikel-press serve
37
+ ```
38
+
39
+ This will start a local server at `http://localhost:4000`.
40
+
41
+ ## (WIP) Configuration
42
+
43
+ Create a file called `press.config.js` with the configuration.
44
+
45
+
46
+ ## Contributing
47
+
48
+ Contributions are welcome! If you have ideas or find a bug, feel free to open an issue or submit a pull request.
49
+
50
+ ## License
51
+
52
+ This project is licensed under the MIT License.
package/cli.js ADDED
@@ -0,0 +1,47 @@
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));
package/index.js ADDED
@@ -0,0 +1,156 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import mikel from "mikel";
4
+
5
+ // global utils
6
+ const utils = {
7
+ // @description save file
8
+ saveFile: (filePath, fileContent) => {
9
+ const folder = path.dirname(filePath);
10
+ if (!fs.existsSync(folder)) {
11
+ fs.mkdirSync(folder, {recursive: true});
12
+ }
13
+ fs.writeFileSync(filePath, fileContent, "utf8");
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
+ }
25
+ }
26
+ return {body, attributes};
27
+ },
28
+ // @description read a data folder
29
+ readData: folder => {
30
+ const files = fs.readdirSync(folder, "utf8")
31
+ .filter(file => path.extname(file) === ".json")
32
+ .map(file => path.join(folder, file))
33
+ .map(file => {
34
+ return [path.basename(file, ".json"), JSON.parse(fs.readFileSync(file, "utf8"))];
35
+ });
36
+ return Object.fromEntries(files);
37
+ },
38
+ // @description get pages from input folder
39
+ readPages: (folder, type = ".html", fm = null, parse = null) => {
40
+ return fs.readdirSync(folder, "utf8")
41
+ .filter(file => path.extname(file) === type)
42
+ .map(file => {
43
+ const content = fs.readFileSync(path.join(folder, file), "utf8");
44
+ const {body, attributes} = utils.frontmatter(content, fm);
45
+ return {
46
+ name: file,
47
+ basename: path.basename(file, type),
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
+ },
73
+ };
74
+
75
+ // @description returns the layout content from the given options
76
+ const getLayoutContent = config => {
77
+ let content = "";
78
+ // using options.template to specify the the absolute path to the template file
79
+ if (typeof config?.layout === "string" || typeof config?.template === "string") {
80
+ content = fs.readFileSync(config.layout || config.template, "utf8");
81
+ }
82
+ // using templateContent to specify the string content of the template
83
+ if (typeof config?.layoutContent === "string" || typeof config?.templateContent === "string") {
84
+ content = config.layoutContent || config.templateContent;
85
+ }
86
+ // parse with frontmatter
87
+ const {body, attributes} = utils.frontmatter(content, config.frontmatter);
88
+ return {
89
+ content: body,
90
+ data: attributes || {},
91
+ };
92
+ };
93
+
94
+ // @description plugins
95
+ const plugins = {
96
+ // plugin to read and include posts in markdown
97
+ posts: (parser = null) => {
98
+ return context => {
99
+ context.hooks.beforeEmit.add(() => {
100
+ const posts = utils.readPages(path.join(context.source, "posts"), ".md", context.site.frontmatter, parser);
101
+ context.site.posts = posts; // posts will be accesible in site.posts
102
+ context.site.pages = [...context.site.pages, ...posts]; // posts will be included as pages also
103
+ });
104
+ };
105
+ },
106
+ };
107
+
108
+ // @description run mikel press with the provided configuration
109
+ const run = (config = {}) => {
110
+ // 0. initialize context object
111
+ const hooks = ["initialize", "compiler", "beforeEmit", "emitPage", "emitAsset", "done"];
112
+ const context = {
113
+ site: config || {},
114
+ source: path.resolve(process.cwd(), config?.source || "."),
115
+ destination: path.resolve(process.cwd(), config?.destination || "./www"),
116
+ compiler: null,
117
+ layout: getLayoutContent(config),
118
+ hooks: Object.freeze(Object.fromEntries(hooks.map(name => {
119
+ return [name, new Set()];
120
+ }))),
121
+ };
122
+ const dispatch = (name, args) => Array.from(context.hooks[name]).forEach(fn => fn.apply(null, args));
123
+ // 1. execute plugins
124
+ if (config?.plugins && Array.isArray(config?.plugins)) {
125
+ config.plugins.forEach(plugin => plugin(context));
126
+ }
127
+ dispatch("initialize", []);
128
+ // 2. initialize mikel instance
129
+ context.compiler = mikel.create(context.layout.content, config?.mikel || {});
130
+ dispatch("compiler", [context.compiler]);
131
+ // 3. read stuff
132
+ context.site.data = utils.readData(path.join(context.source, config?.dataDir || "data"));
133
+ context.site.pages = utils.readPages(path.join(context.source, config?.pagesDir || "pages"), ".html", config?.frontmatter ?? true, c => c);
134
+ context.site.assets = utils.readAssets(path.join(context.source, config?.assetsDir || "assets"), config?.frontmatter ?? true);
135
+ dispatch("beforeEmit", []);
136
+ // 4. save pages
137
+ context.site.pages.forEach(page => {
138
+ context.compiler.addPartial("content", page.content); // register page content as partial
139
+ const content = context.compiler({
140
+ site: context.site,
141
+ layout: context.layout,
142
+ page: page,
143
+ });
144
+ dispatch("emitPage", [page, content]);
145
+ utils.saveFile(path.join(context.destination, page.url), content);
146
+ });
147
+ // 5. save assets
148
+ Object.values(context.site.assets).forEach(asset => {
149
+ dispatch("emitAsset", [asset]);
150
+ utils.saveFile(path.join(context.destination, asset.url), asset.content);
151
+ });
152
+ dispatch("done", []);
153
+ };
154
+
155
+ // export
156
+ export default {utils, plugins, run};
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "mikel-press",
3
+ "description": "A minimal static site generator based on mikel templating",
4
+ "version": "0.0.1",
5
+ "type": "module",
6
+ "author": {
7
+ "name": "Josemi Juanes",
8
+ "email": "hello@josemi.xyz"
9
+ },
10
+ "license": "MIT",
11
+ "repository": "https://github.com/jmjuanes/mikel",
12
+ "bugs": "https://github.com/jmjuanes/mikel/issues",
13
+ "main": "index.js",
14
+ "module": "index.js",
15
+ "exports": {
16
+ ".": "./index.js",
17
+ "./index.js": "./index.js",
18
+ "./cli.js": "./cli.js",
19
+ "./package.json": "./package.json"
20
+ },
21
+ "bin": {
22
+ "mikelpress": "./cli.js",
23
+ "mikel-press": "./cli.js"
24
+ },
25
+ "scripts": {
26
+ "test": "node test.js"
27
+ },
28
+ "keywords": [
29
+ "static",
30
+ "site",
31
+ "generator",
32
+ "mikel",
33
+ "template",
34
+ "templating"
35
+ ],
36
+ "dependencies": {
37
+ "mikel": "^0.17.0"
38
+ },
39
+ "files": [
40
+ "README.md",
41
+ "index.js",
42
+ "cli.js"
43
+ ]
44
+ }