mikel-cli 0.25.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/README.md +130 -0
- package/index.js +248 -0
- package/package.json +36 -0
package/README.md
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# mikel-cli
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+

|
|
5
|
+
|
|
6
|
+
A command-line interface for the [Mikel](https://github.com/jmjuanes/mikel) templating engine. This CLI tool allows you to render Mikel templates from the command line with support for data files, partials, helpers, and functions.
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
|
|
10
|
+
Install the tool using **npm** or **yarn**:
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
# Using npm
|
|
14
|
+
$ npm install mikel-cli mikel
|
|
15
|
+
|
|
16
|
+
# Using yarn
|
|
17
|
+
$ yarn add mikel-cli mikel
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
**Note:** You need to install both `mikel-cli` and `mikel` packages. The `mikel` package is a peer dependency that provides the core templating functionality.
|
|
21
|
+
|
|
22
|
+
## Usage
|
|
23
|
+
|
|
24
|
+
### Basic Usage
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
$ mikel <template> [options]
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Options
|
|
31
|
+
|
|
32
|
+
| Option | Short | Description |
|
|
33
|
+
|--------|-------|-------------|
|
|
34
|
+
| `--help` | `-h` | Display help information |
|
|
35
|
+
| `--data <file>` | `-D` | Path to JSON data file |
|
|
36
|
+
| `--output <file>` | `-o` | Output file path |
|
|
37
|
+
| `--partial <file>` | `-P` | Register a partial template (supports glob patterns, can be used multiple times) |
|
|
38
|
+
| `--helper <file>` | `-H` | Register helper functions from a JavaScript module (supports glob patterns, can be used multiple times) |
|
|
39
|
+
| `--function <file>` | `-F` | Register functions from a JavaScript module (supports glob patterns, can be used multiple times) |
|
|
40
|
+
|
|
41
|
+
### Examples
|
|
42
|
+
|
|
43
|
+
#### Simple Template Rendering
|
|
44
|
+
|
|
45
|
+
Render a template and output to console:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
mikel template.html
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
#### Using Data File
|
|
52
|
+
|
|
53
|
+
Render a template with data from a JSON file:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
mikel template.html --data data.json
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
#### Output to File
|
|
60
|
+
|
|
61
|
+
Render template and save to an output file:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
mikel template.html --data data.json --output dist/index.html
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
#### Using Partials
|
|
68
|
+
|
|
69
|
+
Register partial templates for reusable components:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
mikel template.html --data data.json --partial header.html --partial footer.html --output dist/index.html
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
#### Using Helpers and Functions
|
|
76
|
+
|
|
77
|
+
Register custom helpers and functions from JavaScript modules:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
mikel template.html --data data.json --helper helpers.js --function utils.js --output dist/index.html
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
#### Using Glob Patterns
|
|
84
|
+
|
|
85
|
+
Load multiple files using glob patterns:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
# Load all HTML partials from a directory
|
|
89
|
+
mikel template.html --partial 'partials/*.html' --output dist/index.html
|
|
90
|
+
|
|
91
|
+
# Load all JavaScript helpers from a directory
|
|
92
|
+
mikel template.html --helper 'helpers/*.js' --output dist/index.html
|
|
93
|
+
|
|
94
|
+
# Load partials from subdirectories (recursive)
|
|
95
|
+
mikel template.html --partial 'components/**/*.html' --output dist/index.html
|
|
96
|
+
|
|
97
|
+
# Mix exact files and glob patterns
|
|
98
|
+
mikel template.html --partial header.html --partial 'components/*.html' --output dist/index.html
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
#### Complex Example
|
|
102
|
+
|
|
103
|
+
A complete example combining all features:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
mikel src/template.html \
|
|
107
|
+
--data src/data.json \
|
|
108
|
+
--partial 'src/partials/*.html' \
|
|
109
|
+
--partial 'src/components/**/*.html' \
|
|
110
|
+
--helper 'src/helpers/*.js' \
|
|
111
|
+
--function 'src/utils/*.js' \
|
|
112
|
+
--output dist/index.html
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Glob Pattern Support
|
|
116
|
+
|
|
117
|
+
The `--partial`, `--helper`, and `--function` options support glob patterns for loading multiple files at once:
|
|
118
|
+
|
|
119
|
+
| Pattern | Description | Example |
|
|
120
|
+
|---------|-------------|---------|
|
|
121
|
+
| `*.ext` | All files with extension in current directory | `*.html`, `*.js` |
|
|
122
|
+
| `dir/*.ext` | All files with extension in specific directory | `partials/*.html` |
|
|
123
|
+
| `dir/**/*.ext` | All files with extension in directory and subdirectories | `components/**/*.html` |
|
|
124
|
+
| `?` | Single character wildcard | `file?.html` |
|
|
125
|
+
|
|
126
|
+
**Note:** Glob patterns should be quoted to prevent shell expansion.
|
|
127
|
+
|
|
128
|
+
## License
|
|
129
|
+
|
|
130
|
+
Licensed under the [MIT License](../../LICENSE).
|
package/index.js
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from "node:fs/promises";
|
|
4
|
+
import { existsSync } from "node:fs";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { parseArgs } from "node:util";
|
|
7
|
+
import mikel from "mikel";
|
|
8
|
+
|
|
9
|
+
// @description get the files that matches the provided patterns
|
|
10
|
+
// this is a utility function to expand glob patterns to actual file paths.
|
|
11
|
+
// it uses Node.js 24+ built-in fs.glob to handle glob patterns.
|
|
12
|
+
const expandGlobPatterns = async (patterns = []) => {
|
|
13
|
+
const files = [];
|
|
14
|
+
for (let i = 0; i < patterns.length; i++) {
|
|
15
|
+
const pattern = patterns[i];
|
|
16
|
+
if (pattern.includes("*") || pattern.includes("?") || pattern.includes("[")) {
|
|
17
|
+
try {
|
|
18
|
+
// use Node.js 24+ built-in fs.glob
|
|
19
|
+
// https://nodejs.org/api/fs.html#fspromisesglobpattern-options
|
|
20
|
+
for await (const file of fs.glob(pattern, { cwd: process.cwd() })) {
|
|
21
|
+
files.push(file);
|
|
22
|
+
}
|
|
23
|
+
} catch (error) {
|
|
24
|
+
files.push(pattern);
|
|
25
|
+
}
|
|
26
|
+
} else {
|
|
27
|
+
files.push(pattern);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
// remove duplicates and resolve to absolute paths
|
|
31
|
+
return Array.from(new Set(files)).map(file => {
|
|
32
|
+
return path.resolve(process.cwd(), file);
|
|
33
|
+
});
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// @description load JSON data from the provided path
|
|
37
|
+
const loadData = async (file = null) => {
|
|
38
|
+
if (!file) {
|
|
39
|
+
return {};
|
|
40
|
+
}
|
|
41
|
+
// build the full data file path and check if exists
|
|
42
|
+
const dataPath = path.resolve(process.cwd(), file);
|
|
43
|
+
if (!existsSync(dataPath)) {
|
|
44
|
+
throw new Error(`Data file '${dataPath}' was not found.`);
|
|
45
|
+
}
|
|
46
|
+
// read the file and parse it as JSON
|
|
47
|
+
try {
|
|
48
|
+
const content = await fs.readFile(dataPath, "utf8");
|
|
49
|
+
return JSON.parse(content);
|
|
50
|
+
} catch (error) {
|
|
51
|
+
if (error instanceof SyntaxError) {
|
|
52
|
+
throw new Error(`Invalid JSON in data file '${dataPath}': ${error.message}`);
|
|
53
|
+
}
|
|
54
|
+
throw new Error(`Failed to read data file '${dataPath}': ${error.message}`);
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// @description load partials
|
|
59
|
+
const loadPartials = async (patterns = []) => {
|
|
60
|
+
const partials = {}; // output partials object
|
|
61
|
+
const files = await expandGlobPatterns(patterns);
|
|
62
|
+
|
|
63
|
+
// load all files
|
|
64
|
+
for (let i = 0; i < files.length; i++) {
|
|
65
|
+
const file = files[i]; // path.resolve(process.cwd(), uniqueFiles[i]);
|
|
66
|
+
if (!existsSync(file)) {
|
|
67
|
+
throw new Error(`Partial file '${file}' was not found.`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// check if it's a file (not a directory)
|
|
71
|
+
const stats = await fs.stat(file);
|
|
72
|
+
if (!stats.isFile()) {
|
|
73
|
+
continue; // skip directories
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// load the file and save it as a partial
|
|
77
|
+
try {
|
|
78
|
+
partials[path.basename(file)] = await fs.readFile(file, "utf8");
|
|
79
|
+
} catch (error) {
|
|
80
|
+
throw new Error(`Failed to read partial file '${file}': ${error.message}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return partials;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
// @description load javascript modules
|
|
88
|
+
const loadModules = async (patterns = []) => {
|
|
89
|
+
const result = {};
|
|
90
|
+
const files = await expandGlobPatterns(patterns);
|
|
91
|
+
|
|
92
|
+
for (let i = 0; i < files.length; i++) {
|
|
93
|
+
const file = files[i]; // path.resolve(process.cwd(), uniqueFiles[i]);
|
|
94
|
+
if (!existsSync(file)) {
|
|
95
|
+
throw new Error(`Module '${file}' was not found.`);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Check if it's a file (not a directory)
|
|
99
|
+
const stats = await fs.stat(file);
|
|
100
|
+
if (!stats.isFile()) {
|
|
101
|
+
continue; // Skip directories
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const extension = path.extname(file);
|
|
105
|
+
if (extension !== ".js" && extension !== ".mjs") {
|
|
106
|
+
throw new Error(`Module '${file}' is not supported. Only ESM JavaScript (.js or .mjs) files are supported.`);
|
|
107
|
+
}
|
|
108
|
+
// import the module
|
|
109
|
+
try {
|
|
110
|
+
const content = (await import(file)) || {};
|
|
111
|
+
if (typeof content === "object" && !!content) {
|
|
112
|
+
Object.assign(result, content);
|
|
113
|
+
}
|
|
114
|
+
} catch (error) {
|
|
115
|
+
throw new Error(`Failed to import module '${file}': ${error.message}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return result;
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
// print the help of the tool
|
|
122
|
+
const printHelp = () => {
|
|
123
|
+
console.log("Usage: ");
|
|
124
|
+
console.log(" mikel --help");
|
|
125
|
+
console.log(" mikel <template> [...options]");
|
|
126
|
+
console.log("");
|
|
127
|
+
console.log("Options:");
|
|
128
|
+
console.log(" -h, --help Prints the usage information");
|
|
129
|
+
console.log(" -P, --partial <file> Register a partial (supports glob patterns, can be used multiple times)");
|
|
130
|
+
console.log(" -H, --helper <file> Register a helper (supports glob patterns, can be used multiple times)");
|
|
131
|
+
console.log(" -F, --function <file> Register a function (supports glob patterns, can be used multiple times)");
|
|
132
|
+
console.log(" -D, --data <file> Path to the data file to use (JSON)");
|
|
133
|
+
console.log(" -o, --output <file> Output file");
|
|
134
|
+
console.log("");
|
|
135
|
+
console.log("Examples:");
|
|
136
|
+
console.log(" mikel template.html --data data.json --output www/index.html");
|
|
137
|
+
console.log(" mikel template.html --data data.json --partial header.html --partial footer.html --output www/index.html");
|
|
138
|
+
console.log(" mikel template.html --helper helpers.js --function utils.js --output dist/index.html");
|
|
139
|
+
console.log(" mikel template.html --partial 'partials/*.html' --helper 'helpers/*.js' --output dist/index.html");
|
|
140
|
+
console.log(" mikel template.html --partial 'components/**/*.html' --output dist/index.html");
|
|
141
|
+
console.log("");
|
|
142
|
+
process.exit(0);
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
// @description main function
|
|
146
|
+
const main = async (input = "", options = {}) => {
|
|
147
|
+
// check to print help
|
|
148
|
+
if (options.help) {
|
|
149
|
+
return printHelp();
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// make sure that input file exists
|
|
153
|
+
if (!input) {
|
|
154
|
+
throw new Error(`No input template file provided.`);
|
|
155
|
+
}
|
|
156
|
+
const inputPath = path.resolve(process.cwd(), input);
|
|
157
|
+
if (!existsSync(inputPath)) {
|
|
158
|
+
throw new Error(`Template file '${inputPath}' was not found.`);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
let template;
|
|
162
|
+
try {
|
|
163
|
+
template = await fs.readFile(inputPath, "utf8");
|
|
164
|
+
} catch (error) {
|
|
165
|
+
throw new Error(`Failed to read template file '${inputPath}': ${error.message}`);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// load additional data
|
|
169
|
+
const data = await loadData(options.data);
|
|
170
|
+
const partials = await loadPartials(options.partial);
|
|
171
|
+
const helpers = await loadModules(options.helper);
|
|
172
|
+
const functions = await loadModules(options.function);
|
|
173
|
+
|
|
174
|
+
// compile the template
|
|
175
|
+
let result;
|
|
176
|
+
try {
|
|
177
|
+
result = mikel(template, data || {}, { helpers, functions, partials });
|
|
178
|
+
} catch (error) {
|
|
179
|
+
throw new Error(`Template compilation failed: ${error.message}`);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// check if output argument has been provided to write the result to a file
|
|
183
|
+
// this will also create any intermediary directory that does not exist
|
|
184
|
+
if (options.output) {
|
|
185
|
+
const outputPath = path.resolve(process.cwd(), options.output);
|
|
186
|
+
const outputDirectory = path.dirname(outputPath);
|
|
187
|
+
// make sure that any directory containing the output file exists
|
|
188
|
+
if (!existsSync(outputDirectory)) {
|
|
189
|
+
try {
|
|
190
|
+
await fs.mkdir(outputDirectory, { recursive: true });
|
|
191
|
+
} catch (error) {
|
|
192
|
+
throw new Error(`Failed to create output directory '${outputDirectory}': ${error.message}`);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
try {
|
|
196
|
+
await fs.writeFile(outputPath, result, "utf8");
|
|
197
|
+
console.error(`✓ Template rendered successfully to '${outputPath}'`);
|
|
198
|
+
} catch (error) {
|
|
199
|
+
throw new Error(`Failed to write output file '${outputPath}': ${error.message}`);
|
|
200
|
+
}
|
|
201
|
+
process.exit(0);
|
|
202
|
+
}
|
|
203
|
+
// if no output file has been provided, print the result to console
|
|
204
|
+
else {
|
|
205
|
+
process.stdout.write(result);
|
|
206
|
+
process.exit(0);
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
// process arguments
|
|
211
|
+
const { positionals, values } = parseArgs({
|
|
212
|
+
options: {
|
|
213
|
+
data: {
|
|
214
|
+
type: "string",
|
|
215
|
+
short: "D",
|
|
216
|
+
},
|
|
217
|
+
output: {
|
|
218
|
+
type: "string",
|
|
219
|
+
short: "o",
|
|
220
|
+
},
|
|
221
|
+
helper: {
|
|
222
|
+
type: "string",
|
|
223
|
+
short: "H",
|
|
224
|
+
multiple: true,
|
|
225
|
+
},
|
|
226
|
+
function: {
|
|
227
|
+
type: "string",
|
|
228
|
+
short: "F",
|
|
229
|
+
multiple: true,
|
|
230
|
+
},
|
|
231
|
+
partial: {
|
|
232
|
+
type: "string",
|
|
233
|
+
short: "P",
|
|
234
|
+
multiple: true,
|
|
235
|
+
},
|
|
236
|
+
help: {
|
|
237
|
+
type: "boolean",
|
|
238
|
+
short: "h",
|
|
239
|
+
},
|
|
240
|
+
},
|
|
241
|
+
allowPositionals: true,
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
// run main script
|
|
245
|
+
main(positionals[0], values).catch(error => {
|
|
246
|
+
console.error(`\n❌ Error: ${error.message}\n`);
|
|
247
|
+
process.exit(1);
|
|
248
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "mikel-cli",
|
|
3
|
+
"description": "The cli tool for mikel templating.",
|
|
4
|
+
"version": "0.25.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": {
|
|
8
|
+
"name": "Josemi Juanes",
|
|
9
|
+
"email": "hello@josemi.xyz"
|
|
10
|
+
},
|
|
11
|
+
"repository": "https://github.com/jmjuanes/mikel",
|
|
12
|
+
"bugs": "https://github.com/jmjuanes/mikel/issues",
|
|
13
|
+
"engines": {
|
|
14
|
+
"node": ">=24.0.0"
|
|
15
|
+
},
|
|
16
|
+
"exports": {
|
|
17
|
+
".": "./index.js",
|
|
18
|
+
"./index.js": "./index.js",
|
|
19
|
+
"./package.json": "./package.json"
|
|
20
|
+
},
|
|
21
|
+
"bin": {
|
|
22
|
+
"mikel": "index.js"
|
|
23
|
+
},
|
|
24
|
+
"peerDependencies": {
|
|
25
|
+
"mikel": "^0.25.0"
|
|
26
|
+
},
|
|
27
|
+
"files": [
|
|
28
|
+
"README.md",
|
|
29
|
+
"index.js"
|
|
30
|
+
],
|
|
31
|
+
"keywords": [
|
|
32
|
+
"mikel",
|
|
33
|
+
"cli",
|
|
34
|
+
"templating"
|
|
35
|
+
]
|
|
36
|
+
}
|