@maizzle/framework 5.0.0-beta.2 → 5.0.0-beta.4
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/package.json +9 -8
- package/src/commands/build.js +78 -61
- package/src/generators/plaintext.js +13 -14
- package/src/generators/render.js +7 -4
- package/src/transformers/index.js +22 -2
- package/src/transformers/inline.js +2 -2
- package/src/utils/node.js +23 -0
- package/src/utils/string.js +46 -0
- package/types/build.d.ts +9 -3
- package/types/config.d.ts +2 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@maizzle/framework",
|
|
3
|
-
"version": "5.0.0-beta.
|
|
3
|
+
"version": "5.0.0-beta.4",
|
|
4
4
|
"description": "Maizzle is a framework that helps you quickly build HTML emails with Tailwind CSS.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -23,9 +23,10 @@
|
|
|
23
23
|
},
|
|
24
24
|
"scripts": {
|
|
25
25
|
"dev": "vitest",
|
|
26
|
-
"
|
|
26
|
+
"release": "npx np",
|
|
27
27
|
"pretest": "npm run lint",
|
|
28
|
-
"test": "vitest run --coverage"
|
|
28
|
+
"test": "vitest run --coverage",
|
|
29
|
+
"lint": "biome lint ./src ./test"
|
|
29
30
|
},
|
|
30
31
|
"repository": {
|
|
31
32
|
"type": "git",
|
|
@@ -47,7 +48,7 @@
|
|
|
47
48
|
"html-emails"
|
|
48
49
|
],
|
|
49
50
|
"dependencies": {
|
|
50
|
-
"@csstools/css-calc": "^1.2.
|
|
51
|
+
"@csstools/css-calc": "^1.2.4",
|
|
51
52
|
"@maizzle/cli": "next",
|
|
52
53
|
"cheerio": "^1.0.0-rc.12",
|
|
53
54
|
"chokidar": "^3.6.0",
|
|
@@ -63,11 +64,11 @@
|
|
|
63
64
|
"istextorbinary": "^9.5.0",
|
|
64
65
|
"juice": "^10.0.0",
|
|
65
66
|
"lodash-es": "^4.17.21",
|
|
66
|
-
"morphdom": "^2.7.
|
|
67
|
+
"morphdom": "^2.7.3",
|
|
67
68
|
"ora": "^8.0.1",
|
|
68
69
|
"pathe": "^1.1.2",
|
|
69
|
-
"postcss": "^8.4.
|
|
70
|
-
"postcss-custom-properties": "^13.3.
|
|
70
|
+
"postcss": "^8.4.39",
|
|
71
|
+
"postcss-custom-properties": "^13.3.12",
|
|
71
72
|
"postcss-import": "^16.1.0",
|
|
72
73
|
"postcss-safe-parser": "^7.0.0",
|
|
73
74
|
"posthtml": "^0.16.6",
|
|
@@ -87,7 +88,7 @@
|
|
|
87
88
|
"pretty": "^2.0.0",
|
|
88
89
|
"string-remove-widows": "^4.0.22",
|
|
89
90
|
"string-strip-html": "^13.4.8",
|
|
90
|
-
"tailwindcss": "^3.4.
|
|
91
|
+
"tailwindcss": "^3.4.5",
|
|
91
92
|
"ws": "^8.17.0"
|
|
92
93
|
},
|
|
93
94
|
"devDependencies": {
|
package/src/commands/build.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
readFile,
|
|
3
3
|
writeFile,
|
|
4
|
-
copyFile,
|
|
5
4
|
lstat,
|
|
6
5
|
mkdir,
|
|
7
6
|
rm
|
|
@@ -12,22 +11,34 @@ import { defu as merge } from 'defu'
|
|
|
12
11
|
|
|
13
12
|
import get from 'lodash/get.js'
|
|
14
13
|
import isEmpty from 'lodash-es/isEmpty.js'
|
|
15
|
-
import { isBinary } from 'istextorbinary'
|
|
16
14
|
|
|
17
15
|
import ora from 'ora'
|
|
18
16
|
import pico from 'picocolors'
|
|
19
17
|
import cliTable from 'cli-table3'
|
|
20
18
|
|
|
21
19
|
import { render } from '../generators/render.js'
|
|
22
|
-
|
|
23
|
-
import {
|
|
24
|
-
|
|
20
|
+
|
|
21
|
+
import {
|
|
22
|
+
formatTime,
|
|
23
|
+
getRootDirectories,
|
|
24
|
+
getFileExtensionsFromPattern,
|
|
25
|
+
} from '../utils/string.js'
|
|
26
|
+
|
|
27
|
+
import {
|
|
28
|
+
getColorizedFileSize,
|
|
29
|
+
copyDirectory,
|
|
30
|
+
} from '../utils/node.js'
|
|
31
|
+
|
|
25
32
|
import {
|
|
26
33
|
generatePlaintext,
|
|
27
34
|
handlePlaintextTags,
|
|
28
35
|
writePlaintextFile
|
|
29
36
|
} from '../generators/plaintext.js'
|
|
30
37
|
|
|
38
|
+
import { readFileConfig } from '../utils/getConfigByFilePath.js'
|
|
39
|
+
|
|
40
|
+
import { transformers } from '../transformers/index.js'
|
|
41
|
+
|
|
31
42
|
/**
|
|
32
43
|
* Compile templates and output to the build directory.
|
|
33
44
|
* Returns a promise containing an object with files output and the config object.
|
|
@@ -72,9 +83,19 @@ export default async (config = {}) => {
|
|
|
72
83
|
head: ['File name', 'File size', 'Build time'].map(item => pico.bold(item)),
|
|
73
84
|
})
|
|
74
85
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
86
|
+
/**
|
|
87
|
+
* Determine paths to handle
|
|
88
|
+
*
|
|
89
|
+
* 1. Resolve globs in `build.content` to folders that should be copied over to `build.output.path`
|
|
90
|
+
* 2. Check that templates to be built, actually exist
|
|
91
|
+
*/
|
|
92
|
+
const contentPaths = get(config, 'build.content', 'src/templates/**/*.html')
|
|
93
|
+
|
|
94
|
+
// 1. Resolve globs in `build.content` to folders that should be copied over to `build.output.path`
|
|
95
|
+
const rootDirs = await getRootDirectories(contentPaths)
|
|
96
|
+
|
|
97
|
+
// 2. Check that templates to be built, actually exist
|
|
98
|
+
const templateFolders = Array.isArray(contentPaths) ? contentPaths : [contentPaths]
|
|
78
99
|
const templatePaths = await fg.glob([...new Set(templateFolders)])
|
|
79
100
|
|
|
80
101
|
// If there are no templates to build, throw error
|
|
@@ -82,37 +103,44 @@ export default async (config = {}) => {
|
|
|
82
103
|
throw new Error(`No templates found in ${pico.inverse(templateFolders)}`)
|
|
83
104
|
}
|
|
84
105
|
|
|
85
|
-
const baseDirs = templateFolders.filter(p => !p.startsWith('!')).map(p => {
|
|
86
|
-
const parts = p.split('/')
|
|
87
|
-
// remove the glob part (e.g., **/*.html):
|
|
88
|
-
return parts.filter(part => !part.includes('*')).join('/')
|
|
89
|
-
})
|
|
90
|
-
|
|
91
106
|
/**
|
|
92
|
-
*
|
|
107
|
+
* Copy source directories to destination
|
|
93
108
|
*
|
|
94
|
-
*
|
|
95
|
-
* to render them. These files will be treated as static files and will
|
|
96
|
-
* be copied directly to the output directory, just like the
|
|
97
|
-
* `build.static` folders.
|
|
109
|
+
* Copies each `build.content` path to the `build.output.path` directory.
|
|
98
110
|
*/
|
|
99
|
-
|
|
100
|
-
|
|
111
|
+
for await (const rootDir of rootDirs) {
|
|
112
|
+
await copyDirectory(rootDir, buildOutputPath)
|
|
113
|
+
}
|
|
101
114
|
|
|
102
115
|
/**
|
|
103
|
-
*
|
|
116
|
+
* Get a list of files to render, from the output directory
|
|
104
117
|
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
118
|
+
* Uses all file extensions from non-negated glob paths in `build.content`
|
|
119
|
+
* to determine which files to render from the output directory.
|
|
107
120
|
*/
|
|
108
|
-
|
|
109
|
-
const templateBuildStartTime = Date.now()
|
|
121
|
+
const outputExtensions = new Set()
|
|
110
122
|
|
|
111
|
-
|
|
112
|
-
|
|
123
|
+
for (const pattern of contentPaths) {
|
|
124
|
+
outputExtensions.add(...getFileExtensionsFromPattern(pattern))
|
|
125
|
+
}
|
|
113
126
|
|
|
114
|
-
|
|
115
|
-
|
|
127
|
+
/**
|
|
128
|
+
* Create a list of templates to compile
|
|
129
|
+
*/
|
|
130
|
+
const extensions = outputExtensions.size > 1 ? `{${[...outputExtensions].join(',')}}` : 'html'
|
|
131
|
+
|
|
132
|
+
const templatesToCompile = await fg.glob(
|
|
133
|
+
path.join(
|
|
134
|
+
buildOutputPath,
|
|
135
|
+
`**/*.${extensions}`
|
|
136
|
+
)
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Render templates
|
|
141
|
+
*/
|
|
142
|
+
for await (const templatePath of templatesToCompile) {
|
|
143
|
+
const templateBuildStartTime = Date.now()
|
|
116
144
|
|
|
117
145
|
/**
|
|
118
146
|
* Add the current template path to the config
|
|
@@ -122,8 +150,6 @@ export default async (config = {}) => {
|
|
|
122
150
|
*/
|
|
123
151
|
config.build.current = {
|
|
124
152
|
path: path.parse(templatePath),
|
|
125
|
-
baseDir,
|
|
126
|
-
relativePath,
|
|
127
153
|
}
|
|
128
154
|
|
|
129
155
|
const html = await readFile(templatePath, 'utf8')
|
|
@@ -158,8 +184,9 @@ export default async (config = {}) => {
|
|
|
158
184
|
* We do this before generating plaintext, so that
|
|
159
185
|
* any paths will already have been created.
|
|
160
186
|
*/
|
|
161
|
-
const outputPathFromConfig = get(rendered.config, 'permalink',
|
|
187
|
+
const outputPathFromConfig = get(rendered.config, 'permalink', templatePath)
|
|
162
188
|
const parsedOutputPath = path.parse(outputPathFromConfig)
|
|
189
|
+
// This keeps original file extension if no output extension is set
|
|
163
190
|
const extension = get(rendered.config, 'build.output.extension', parsedOutputPath.ext.slice(1))
|
|
164
191
|
const outputPath = `${parsedOutputPath.dir}/${parsedOutputPath.name}.${extension}`
|
|
165
192
|
|
|
@@ -174,6 +201,14 @@ export default async (config = {}) => {
|
|
|
174
201
|
*/
|
|
175
202
|
await writeFile(outputPath, rendered.html)
|
|
176
203
|
|
|
204
|
+
/**
|
|
205
|
+
* Remove original file if its path is different
|
|
206
|
+
* from the final destination path.
|
|
207
|
+
*/
|
|
208
|
+
if (outputPath !== templatePath) {
|
|
209
|
+
await rm(templatePath)
|
|
210
|
+
}
|
|
211
|
+
|
|
177
212
|
/**
|
|
178
213
|
* Add file to CLI table for build summary logging
|
|
179
214
|
*/
|
|
@@ -189,48 +224,30 @@ export default async (config = {}) => {
|
|
|
189
224
|
/**
|
|
190
225
|
* Copy static files
|
|
191
226
|
*
|
|
192
|
-
*
|
|
193
|
-
*
|
|
194
|
-
*
|
|
195
|
-
* TODO: support an array of objects with source and destination, i.e. static: [{ source: 'src/assets', destination: 'assets' }, ...]
|
|
227
|
+
* TODO: support an array of objects with source and destination,
|
|
228
|
+
* i.e. static: [{ source: 'src/assets', destination: 'assets' }, ...]
|
|
196
229
|
*/
|
|
197
|
-
|
|
198
|
-
// Copy binary files that are alongside templates
|
|
199
|
-
for await (const binaryPath of binaryPaths) {
|
|
200
|
-
const relativePath = path.relative(get(config, 'build.current.baseDir'), binaryPath)
|
|
201
|
-
const outputPath = path.join(get(config, 'build.output.path'), get(config, 'build.static.destination'), relativePath)
|
|
202
|
-
|
|
203
|
-
await mkdir(path.dirname(outputPath), { recursive: true })
|
|
204
|
-
await copyFile(binaryPath, outputPath)
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
// Copy files from `build.static`
|
|
208
230
|
const staticSourcePaths = await fg.glob([...new Set(get(config, 'build.static.source', []))])
|
|
209
|
-
.then(paths => paths.filter(file => isBinary(file)))
|
|
210
|
-
|
|
211
|
-
if (!isEmpty(staticSourcePaths)) {
|
|
212
|
-
for await (const staticPath of staticSourcePaths) {
|
|
213
|
-
const relativePath = path.relative(get(config, 'build.current.baseDir'), staticPath)
|
|
214
|
-
const outputPath = path.join(get(config, 'build.output.path'), get(config, 'build.static.destination'), relativePath)
|
|
215
231
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
}
|
|
232
|
+
for await (const rootDir of getRootDirectories(staticSourcePaths)) {
|
|
233
|
+
await copyDirectory(rootDir, path.join(buildOutputPath, get(config, 'build.static.destination')))
|
|
219
234
|
}
|
|
220
235
|
|
|
221
|
-
const compiledFiles = await fg.glob(path.join(
|
|
236
|
+
const compiledFiles = await fg.glob(path.join(buildOutputPath, '**/*'))
|
|
222
237
|
|
|
223
238
|
/**
|
|
224
239
|
* Run `afterBuild` event
|
|
225
240
|
*/
|
|
226
241
|
if (typeof config.afterBuild === 'function') {
|
|
227
|
-
await config.afterBuild({
|
|
242
|
+
await config.afterBuild({
|
|
243
|
+
config,
|
|
244
|
+
files: compiledFiles,
|
|
245
|
+
transform: transformers,
|
|
246
|
+
})
|
|
228
247
|
}
|
|
229
248
|
|
|
230
249
|
/**
|
|
231
250
|
* Log a build summary if enabled in the config
|
|
232
|
-
*
|
|
233
|
-
* Need to first clear the spinner
|
|
234
251
|
*/
|
|
235
252
|
|
|
236
253
|
spinner.clear()
|
|
@@ -134,7 +134,7 @@ export async function generatePlaintext(html = '', config = {}) {
|
|
|
134
134
|
).result
|
|
135
135
|
}
|
|
136
136
|
|
|
137
|
-
export async function writePlaintextFile(plaintext = '',
|
|
137
|
+
export async function writePlaintextFile(plaintext = '', config = {}) {
|
|
138
138
|
if (!plaintext) {
|
|
139
139
|
throw new Error('Missing plaintext content.')
|
|
140
140
|
}
|
|
@@ -149,8 +149,8 @@ export async function writePlaintextFile(plaintext = '', templateConfig = {}) {
|
|
|
149
149
|
* Fall back to template's build output path and extension, for example:
|
|
150
150
|
* `config.build.output.path`
|
|
151
151
|
*/
|
|
152
|
-
const plaintextConfig = get(
|
|
153
|
-
let plaintextOutputPath = get(plaintextConfig, 'output.path', get(
|
|
152
|
+
const plaintextConfig = get(config, 'plaintext')
|
|
153
|
+
let plaintextOutputPath = get(plaintextConfig, 'output.path', get(config, 'build.output.path'))
|
|
154
154
|
const plaintextExtension = get(plaintextConfig, 'output.extension', 'txt')
|
|
155
155
|
|
|
156
156
|
/**
|
|
@@ -158,15 +158,12 @@ export async function writePlaintextFile(plaintext = '', templateConfig = {}) {
|
|
|
158
158
|
*/
|
|
159
159
|
if (plaintextConfig === true) {
|
|
160
160
|
// If the template has a `permalink` key set in the FM
|
|
161
|
-
if (typeof
|
|
161
|
+
if (typeof config.permalink === 'string') {
|
|
162
162
|
// Output plaintext at the `permalink` path
|
|
163
|
-
plaintextOutputPath =
|
|
163
|
+
plaintextOutputPath = config.permalink
|
|
164
164
|
} else {
|
|
165
165
|
// Output plaintext at the same directory as the HTML file
|
|
166
|
-
plaintextOutputPath = path
|
|
167
|
-
get(templateConfig, 'build.output.path'),
|
|
168
|
-
get(templateConfig, 'build.current.relativePath')
|
|
169
|
-
)
|
|
166
|
+
plaintextOutputPath = get(config, 'build.output.path')
|
|
170
167
|
}
|
|
171
168
|
}
|
|
172
169
|
|
|
@@ -186,8 +183,8 @@ export async function writePlaintextFile(plaintext = '', templateConfig = {}) {
|
|
|
186
183
|
*/
|
|
187
184
|
if (path.extname(plaintextOutputPath)) {
|
|
188
185
|
// Ensure the target directory exists
|
|
189
|
-
await lstat(
|
|
190
|
-
await mkdir(
|
|
186
|
+
await lstat(plaintextOutputPath).catch(async () => {
|
|
187
|
+
await mkdir(plaintextOutputPath, { recursive: true })
|
|
191
188
|
})
|
|
192
189
|
|
|
193
190
|
// Ensure correct extension is used
|
|
@@ -196,17 +193,19 @@ export async function writePlaintextFile(plaintext = '', templateConfig = {}) {
|
|
|
196
193
|
path.basename(plaintextOutputPath, path.extname(plaintextOutputPath)) + '.' + plaintextExtension
|
|
197
194
|
)
|
|
198
195
|
|
|
196
|
+
console.log('plaintextOutputPath', plaintextOutputPath);
|
|
197
|
+
|
|
199
198
|
return writeFile(plaintextOutputPath, plaintext)
|
|
200
199
|
}
|
|
201
200
|
|
|
202
201
|
/**
|
|
203
202
|
* If `plaintextOutputPath` is a directory path, output file there, using the template's name
|
|
204
203
|
*/
|
|
205
|
-
const templateFileName = get(
|
|
204
|
+
const templateFileName = get(config, 'build.current.path.name')
|
|
206
205
|
|
|
207
206
|
plaintextOutputPath = path.join(
|
|
208
|
-
plaintextOutputPath,
|
|
209
|
-
get(
|
|
207
|
+
path.dirname(plaintextOutputPath),
|
|
208
|
+
get(config, 'build.current.path.dir'),
|
|
210
209
|
templateFileName + '.' + plaintextExtension
|
|
211
210
|
)
|
|
212
211
|
|
package/src/generators/render.js
CHANGED
|
@@ -5,7 +5,7 @@ import { defu as merge } from 'defu'
|
|
|
5
5
|
import expressions from 'posthtml-expressions'
|
|
6
6
|
import { parseFrontMatter } from '../utils/node.js'
|
|
7
7
|
import { process as compilePostHTML } from '../posthtml/index.js'
|
|
8
|
-
import { run as useTransformers } from '../transformers/index.js'
|
|
8
|
+
import { run as useTransformers, transformers } from '../transformers/index.js'
|
|
9
9
|
|
|
10
10
|
export async function render(html = '', config = {}) {
|
|
11
11
|
if (typeof html !== 'string') {
|
|
@@ -65,7 +65,8 @@ export async function render(html = '', config = {}) {
|
|
|
65
65
|
content = await templateConfig.beforeRender(({
|
|
66
66
|
html: content,
|
|
67
67
|
config: templateConfig,
|
|
68
|
-
|
|
68
|
+
posthtml: compilePostHTML,
|
|
69
|
+
transform: transformers,
|
|
69
70
|
})) ?? content
|
|
70
71
|
}
|
|
71
72
|
|
|
@@ -85,7 +86,8 @@ export async function render(html = '', config = {}) {
|
|
|
85
86
|
compiled.html = await templateConfig.afterRender(({
|
|
86
87
|
html: compiled.html,
|
|
87
88
|
config: templateConfig,
|
|
88
|
-
|
|
89
|
+
posthtml: compilePostHTML,
|
|
90
|
+
transform: transformers,
|
|
89
91
|
})) ?? compiled.html
|
|
90
92
|
}
|
|
91
93
|
|
|
@@ -117,7 +119,8 @@ export async function render(html = '', config = {}) {
|
|
|
117
119
|
compiled.html = await templateConfig.afterTransformers(({
|
|
118
120
|
html: compiled.html,
|
|
119
121
|
config: templateConfig,
|
|
120
|
-
|
|
122
|
+
posthtml: compilePostHTML,
|
|
123
|
+
transform: transformers,
|
|
121
124
|
})) ?? compiled.html
|
|
122
125
|
}
|
|
123
126
|
|
|
@@ -56,9 +56,9 @@ export async function run(html = '', config = {}) {
|
|
|
56
56
|
* Rewrite Tailwind CSS class names to email-safe alternatives,
|
|
57
57
|
* unless explicitly disabled
|
|
58
58
|
*/
|
|
59
|
-
if (get(config, 'css.
|
|
59
|
+
if (get(config, 'css.safe') !== false) {
|
|
60
60
|
posthtmlPlugins.push(
|
|
61
|
-
safeClassNames(get(config, 'css.
|
|
61
|
+
safeClassNames(get(config, 'css.safe', {}))
|
|
62
62
|
)
|
|
63
63
|
}
|
|
64
64
|
|
|
@@ -267,3 +267,23 @@ export async function run(html = '', config = {}) {
|
|
|
267
267
|
html: result.html,
|
|
268
268
|
}))
|
|
269
269
|
}
|
|
270
|
+
|
|
271
|
+
export const transformers = {
|
|
272
|
+
comb,
|
|
273
|
+
sixHex,
|
|
274
|
+
minify,
|
|
275
|
+
baseUrl,
|
|
276
|
+
inlineCSS,
|
|
277
|
+
prettify,
|
|
278
|
+
filters,
|
|
279
|
+
markdown,
|
|
280
|
+
posthtmlMso,
|
|
281
|
+
shorthandCss,
|
|
282
|
+
preventWidows,
|
|
283
|
+
addAttributes,
|
|
284
|
+
urlParameters,
|
|
285
|
+
safeClassNames,
|
|
286
|
+
replaceStrings,
|
|
287
|
+
attributeToStyle,
|
|
288
|
+
removeAttributes,
|
|
289
|
+
}
|
|
@@ -116,7 +116,7 @@ export async function inline(html = '', options = {}) {
|
|
|
116
116
|
rule.walkDecls(decl => {
|
|
117
117
|
// Resolve calc() values to static values
|
|
118
118
|
if (options.resolveCalc) {
|
|
119
|
-
decl.value = decl.value.includes('calc(') ? calc(decl.value) : decl.value
|
|
119
|
+
decl.value = decl.value.includes('calc(') ? calc(decl.value, {precision: 2}) : decl.value
|
|
120
120
|
}
|
|
121
121
|
|
|
122
122
|
declarations.add(decl)
|
|
@@ -182,7 +182,7 @@ export async function inline(html = '', options = {}) {
|
|
|
182
182
|
let [property, value] = i.split(':').map(i => i.trim())
|
|
183
183
|
|
|
184
184
|
if (value && options.resolveCalc) {
|
|
185
|
-
value = value.includes('calc') ? calc(value) : value
|
|
185
|
+
value = value.includes('calc') ? calc(value, {precision: 2}) : value
|
|
186
186
|
}
|
|
187
187
|
|
|
188
188
|
if (value && options.preferUnitlessValues) {
|
package/src/utils/node.js
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
|
+
import path from 'pathe'
|
|
1
2
|
import os from 'node:os'
|
|
2
3
|
import gm from 'gray-matter'
|
|
3
4
|
import pico from 'picocolors'
|
|
4
5
|
import { humanFileSize } from './string.js'
|
|
6
|
+
import {
|
|
7
|
+
copyFile,
|
|
8
|
+
mkdir,
|
|
9
|
+
readdir
|
|
10
|
+
} from 'node:fs/promises'
|
|
5
11
|
|
|
6
12
|
// Return a local IP address
|
|
7
13
|
export function getLocalIP() {
|
|
@@ -66,3 +72,20 @@ export function parseFrontMatter(html) {
|
|
|
66
72
|
const { content, data, matter, stringify } = gm(html, {})
|
|
67
73
|
return { content, data, matter, stringify }
|
|
68
74
|
}
|
|
75
|
+
|
|
76
|
+
export async function copyDirectory(src, dest) {
|
|
77
|
+
await mkdir(dest, { recursive: true })
|
|
78
|
+
|
|
79
|
+
const entries = await readdir(src, { withFileTypes: true })
|
|
80
|
+
|
|
81
|
+
for (const entry of entries) {
|
|
82
|
+
const srcPath = path.join(src, entry.name)
|
|
83
|
+
const destPath = path.join(dest, entry.name)
|
|
84
|
+
|
|
85
|
+
if (entry.isDirectory()) {
|
|
86
|
+
await copyDirectory(srcPath, destPath) // Recursively copy subdirectories
|
|
87
|
+
} else {
|
|
88
|
+
await copyFile(srcPath, destPath) // Copy files
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
package/src/utils/string.js
CHANGED
|
@@ -115,3 +115,49 @@ export function humanFileSize(bytes, si=false, dp=2) {
|
|
|
115
115
|
|
|
116
116
|
return bytes.toFixed(dp) + ' ' + units[u]
|
|
117
117
|
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Get the root directories from a list of globs.
|
|
121
|
+
* @param {*} globs
|
|
122
|
+
* @returns
|
|
123
|
+
*/
|
|
124
|
+
export function getRootDirectories(globs) {
|
|
125
|
+
globs = Array.isArray(globs) ? globs : [globs]
|
|
126
|
+
|
|
127
|
+
const positiveGlobs = new Set(globs.filter(g => !g.startsWith('!')))
|
|
128
|
+
|
|
129
|
+
const rootDirs = new Set()
|
|
130
|
+
|
|
131
|
+
for (const pattern of positiveGlobs) {
|
|
132
|
+
rootDirs.add(pattern.split('/').slice(0, pattern.split('/').indexOf('**')).join('/'))
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return Array.from(rootDirs)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Get the file extensions from a glob pattern.
|
|
140
|
+
* @param {*} pattern
|
|
141
|
+
* @returns
|
|
142
|
+
*/
|
|
143
|
+
export function getFileExtensionsFromPattern(pattern) {
|
|
144
|
+
const starExtPattern = /\.([^\*\{\}]+)$/ // Matches .ext but not .* or .{ext}
|
|
145
|
+
const bracePattern = /\.{([^}]+)}$/ // Matches .{ext} or .{ext,ext}
|
|
146
|
+
const wildcardPattern = /\.\*$/ // Matches .*
|
|
147
|
+
|
|
148
|
+
if (wildcardPattern.test(pattern)) {
|
|
149
|
+
return ['html'] // We default to 'html' if the pattern is a wildcard
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const braceMatch = pattern.match(bracePattern);
|
|
153
|
+
if (braceMatch) {
|
|
154
|
+
return braceMatch[1].split(',') // Split and return extensions inside braces
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const starExtMatch = pattern.match(starExtPattern)
|
|
158
|
+
if (starExtMatch) {
|
|
159
|
+
return [starExtMatch[1]] // Return single extension
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return ['html'] // No recognizable extension pattern, default to 'html'
|
|
163
|
+
}
|
package/types/build.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import ComponentsConfig from './components';
|
|
2
2
|
import type { SpinnerName } from 'cli-spinners';
|
|
3
|
+
import type ExpressionsConfig from './expressions';
|
|
3
4
|
|
|
4
5
|
export default interface BuildConfig {
|
|
5
6
|
/**
|
|
@@ -8,7 +9,7 @@ export default interface BuildConfig {
|
|
|
8
9
|
components?: ComponentsConfig;
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
|
-
*
|
|
12
|
+
* Paths where Maizzle should look for Templates to compile.
|
|
12
13
|
*
|
|
13
14
|
* @default ['src/templates/**\/*.html']
|
|
14
15
|
*
|
|
@@ -16,12 +17,17 @@ export default interface BuildConfig {
|
|
|
16
17
|
* ```
|
|
17
18
|
* export default {
|
|
18
19
|
* build: {
|
|
19
|
-
*
|
|
20
|
+
* content: ['src/templates/**\/*.html']
|
|
20
21
|
* }
|
|
21
22
|
* }
|
|
22
23
|
* ```
|
|
23
24
|
*/
|
|
24
|
-
|
|
25
|
+
content?: string | string[];
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
Configure expressions.
|
|
29
|
+
*/
|
|
30
|
+
expressions?: ExpressionsConfig;
|
|
25
31
|
|
|
26
32
|
/**
|
|
27
33
|
* Define the output path for compiled Templates, and what file extension they should use.
|
package/types/config.d.ts
CHANGED
|
@@ -136,7 +136,7 @@ export default interface Config {
|
|
|
136
136
|
* ```
|
|
137
137
|
* export default {
|
|
138
138
|
* css: {
|
|
139
|
-
*
|
|
139
|
+
* safe: {
|
|
140
140
|
* ':': '__',
|
|
141
141
|
* '!': 'i-',
|
|
142
142
|
* }
|
|
@@ -144,7 +144,7 @@ export default interface Config {
|
|
|
144
144
|
* }
|
|
145
145
|
* ```
|
|
146
146
|
*/
|
|
147
|
-
|
|
147
|
+
safe?: boolean | Record<string, string>;
|
|
148
148
|
|
|
149
149
|
/**
|
|
150
150
|
* Ensure that all your HEX colors inside `bgcolor` and `color` attributes are defined with six digits.
|