@jk2908/mdsrc 0.4.0 → 0.5.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/CHANGELOG.md +14 -0
- package/README.md +77 -22
- package/dist/index.d.ts +42 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +409 -177
- package/dist/index.js.map +7 -5
- package/dist/types.d.ts +9 -4
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +2 -1
- package/dist/types.js.map +1 -1
- package/dist/validate.d.ts +18 -0
- package/dist/validate.d.ts.map +1 -0
- package/dist/validate.js +366 -0
- package/dist/validate.js.map +1 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -132,11 +132,6 @@ function debounce(fn, wait) {
|
|
|
132
132
|
}, wait);
|
|
133
133
|
};
|
|
134
134
|
}
|
|
135
|
-
function dedent(str) {
|
|
136
|
-
return str.replace(/^\n/, "").replace(/\s+$/, "").split(`
|
|
137
|
-
`).filter(Boolean).map((line) => line.replace(/^\s+/, "")).join(`
|
|
138
|
-
`);
|
|
139
|
-
}
|
|
140
135
|
function isRecord(value) {
|
|
141
136
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
142
137
|
}
|
|
@@ -151,128 +146,151 @@ function deep(obj, path, value) {
|
|
|
151
146
|
cur[parts.at(-1)] = value;
|
|
152
147
|
}
|
|
153
148
|
|
|
154
|
-
// src/
|
|
155
|
-
var
|
|
156
|
-
var
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
}
|
|
160
|
-
};
|
|
161
|
-
async function parse(frontmatter) {
|
|
162
|
-
if (!frontmatter)
|
|
163
|
-
return {};
|
|
164
|
-
const { kind, value } = frontmatter;
|
|
165
|
-
switch (kind) {
|
|
166
|
-
case "yaml": {
|
|
167
|
-
return (await import("yaml")).parse(value);
|
|
168
|
-
}
|
|
169
|
-
case "toml": {
|
|
170
|
-
return (await import("smol-toml")).parse(value);
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
async function create(dir, buildContext) {
|
|
175
|
-
const { logger: logger2, compileOptions = {} } = buildContext;
|
|
176
|
-
const { features, ...restCompileOptions } = compileOptions;
|
|
177
|
-
try {
|
|
178
|
-
const files = (await fs.readdir(dir)).filter((file) => path.extname(file) === ".md");
|
|
179
|
-
const filePaths = files.map((file) => path.join(dir, file));
|
|
180
|
-
if (!files.length) {
|
|
181
|
-
console.warn(`mdsrc: ${dir} is empty`);
|
|
182
|
-
return [];
|
|
183
|
-
}
|
|
184
|
-
return Promise.all(filePaths.map(async (filePath) => {
|
|
185
|
-
const file = path.basename(filePath);
|
|
186
|
-
const { html, frontmatter: rawFrontmatter } = markdownToHtml(await fs.readFile(filePath, "utf-8"), {
|
|
187
|
-
features: {
|
|
188
|
-
...DEFAULT_COMPILE_OPTIONS.features,
|
|
189
|
-
...features
|
|
190
|
-
},
|
|
191
|
-
...restCompileOptions
|
|
192
|
-
});
|
|
193
|
-
const frontmatter = await parse(rawFrontmatter);
|
|
194
|
-
return {
|
|
195
|
-
...frontmatter,
|
|
196
|
-
__mdsrc: {
|
|
197
|
-
slug: path.basename(file, ".md").toLowerCase().replace(/\s+/g, "-"),
|
|
198
|
-
filename: file
|
|
199
|
-
},
|
|
200
|
-
body: html.trim()
|
|
201
|
-
};
|
|
202
|
-
}));
|
|
203
|
-
} catch (err) {
|
|
204
|
-
logger2.error("[create]: failed to create entries", err);
|
|
205
|
-
return [];
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
async function maybeWrite(filePath, content) {
|
|
209
|
-
const cached = fileCache.get(filePath);
|
|
210
|
-
if (cached === content) {
|
|
211
|
-
try {
|
|
212
|
-
await fs.access(filePath);
|
|
213
|
-
return false;
|
|
214
|
-
} catch (err) {
|
|
215
|
-
if (!(err instanceof Error) || !("code" in err) || err.code !== "ENOENT") {
|
|
216
|
-
throw err;
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
if (cached === undefined) {
|
|
221
|
-
try {
|
|
222
|
-
const current = await fs.readFile(filePath, "utf-8");
|
|
223
|
-
fileCache.set(filePath, current);
|
|
224
|
-
if (current === content) {
|
|
225
|
-
fileCache.set(filePath, content);
|
|
226
|
-
return false;
|
|
227
|
-
}
|
|
228
|
-
} catch (err) {
|
|
229
|
-
if (!(err instanceof Error) || !("code" in err) || err.code !== "ENOENT") {
|
|
230
|
-
throw err;
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
await fs.writeFile(filePath, content);
|
|
235
|
-
fileCache.set(filePath, content);
|
|
236
|
-
return true;
|
|
237
|
-
}
|
|
149
|
+
// src/types.ts
|
|
150
|
+
var PRIMITIVE_NAMES = ["string", "number", "boolean", "date", "array"];
|
|
151
|
+
var MODIFIER_NAMES = ["max", "min"];
|
|
152
|
+
|
|
153
|
+
// src/validate.ts
|
|
238
154
|
function validate(input, schema) {
|
|
239
155
|
const validated = {};
|
|
240
156
|
const issues = [];
|
|
241
157
|
if (typeof input !== "object" || input === null) {
|
|
242
|
-
issues.push({ message: "Input must be an object" });
|
|
158
|
+
issues.push({ message: "Input must be an object", code: "INVALID_INPUT" });
|
|
243
159
|
return { issues };
|
|
244
160
|
}
|
|
161
|
+
const schemaKeys = new Set(Object.keys(schema).map((k) => parseKey(k).key));
|
|
162
|
+
for (const key in input) {
|
|
163
|
+
if (!schemaKeys.has(key)) {
|
|
164
|
+
issues.push({ message: `Unknown key: ${key}`, code: "UNKNOWN_KEY" });
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
if (issues.length)
|
|
168
|
+
return { issues };
|
|
245
169
|
function walk(key, schemaValue, data) {
|
|
246
170
|
const { optional, key: parsedKey } = parseKey(key);
|
|
247
171
|
if (data === undefined) {
|
|
248
172
|
if (!optional) {
|
|
249
|
-
issues.push({
|
|
173
|
+
issues.push({
|
|
174
|
+
message: `Missing required key: ${parsedKey}`,
|
|
175
|
+
code: "MISSING_REQUIRED"
|
|
176
|
+
});
|
|
250
177
|
}
|
|
251
178
|
return;
|
|
252
179
|
}
|
|
253
180
|
if (typeof schemaValue === "string") {
|
|
254
|
-
|
|
255
|
-
|
|
181
|
+
const { types, modifiers } = parseSchemaValue(schemaValue);
|
|
182
|
+
for (const type of types) {
|
|
183
|
+
if (type === "string") {
|
|
256
184
|
if (typeof data !== "string") {
|
|
257
|
-
|
|
258
|
-
|
|
185
|
+
if (types.length === 1) {
|
|
186
|
+
issues.push({
|
|
187
|
+
message: `Key ${parsedKey} must be a string`,
|
|
188
|
+
code: "INVALID_TYPE"
|
|
189
|
+
});
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
if (modifiers.min) {
|
|
195
|
+
const min = Number(modifiers.min);
|
|
196
|
+
if (Number.isNaN(min)) {
|
|
197
|
+
issues.push({
|
|
198
|
+
message: `Key ${parsedKey} contains a bad modifier (${modifiers.min}) that could not be converted to type (number)`,
|
|
199
|
+
code: "BAD_MODIFIER"
|
|
200
|
+
});
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
if (data.length < min) {
|
|
204
|
+
if (types.length === 1) {
|
|
205
|
+
issues.push({
|
|
206
|
+
message: `Key ${parsedKey} must be greater than or equal to minimum length (${min})`,
|
|
207
|
+
code: "INVALID_LENGTH"
|
|
208
|
+
});
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
if (modifiers.max) {
|
|
215
|
+
const max = Number(modifiers.max);
|
|
216
|
+
if (Number.isNaN(max)) {
|
|
217
|
+
issues.push({
|
|
218
|
+
message: `Key ${parsedKey} contains a bad modifier (${modifiers.max}) that could not be converted to type (number)`,
|
|
219
|
+
code: "BAD_MODIFIER"
|
|
220
|
+
});
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
if (data.length > max) {
|
|
224
|
+
if (types.length === 1) {
|
|
225
|
+
issues.push({
|
|
226
|
+
message: `Key ${parsedKey} must be less than or equal to maximum length (${max})`,
|
|
227
|
+
code: "INVALID_LENGTH"
|
|
228
|
+
});
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
259
233
|
}
|
|
260
234
|
deep(validated, parsedKey, data);
|
|
261
|
-
|
|
262
|
-
}
|
|
263
|
-
case "number": {
|
|
235
|
+
return;
|
|
236
|
+
} else if (type === "number") {
|
|
264
237
|
let num = data;
|
|
265
238
|
if (typeof data === "string" && !Number.isNaN(Number(data))) {
|
|
266
239
|
num = Number(data);
|
|
267
240
|
}
|
|
268
241
|
if (typeof num !== "number" || Number.isNaN(num)) {
|
|
269
|
-
|
|
270
|
-
|
|
242
|
+
if (types.length === 1) {
|
|
243
|
+
issues.push({
|
|
244
|
+
message: `Key ${parsedKey} must be a number`,
|
|
245
|
+
code: "INVALID_TYPE"
|
|
246
|
+
});
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
if (modifiers.min) {
|
|
252
|
+
const min = Number(modifiers.min);
|
|
253
|
+
if (Number.isNaN(min)) {
|
|
254
|
+
issues.push({
|
|
255
|
+
message: `Key ${parsedKey} contains a bad modifier (${modifiers.min}) that could not be converted to type (number)`,
|
|
256
|
+
code: "BAD_MODIFIER"
|
|
257
|
+
});
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
if (num < min) {
|
|
261
|
+
if (types.length === 1) {
|
|
262
|
+
issues.push({
|
|
263
|
+
message: `Key ${parsedKey} must be greater than or equal to minimum size (${min})`,
|
|
264
|
+
code: "INVALID_SIZE"
|
|
265
|
+
});
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
if (modifiers.max) {
|
|
272
|
+
const max = Number(modifiers.max);
|
|
273
|
+
if (Number.isNaN(max)) {
|
|
274
|
+
issues.push({
|
|
275
|
+
message: `Key ${parsedKey} contains a bad modifier (${modifiers.max}) that could not be converted to type (number)`,
|
|
276
|
+
code: "BAD_MODIFIER"
|
|
277
|
+
});
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
if (num > max) {
|
|
281
|
+
if (types.length === 1) {
|
|
282
|
+
issues.push({
|
|
283
|
+
message: `Key ${parsedKey} must be less than or equal to maximum size (${max})`,
|
|
284
|
+
code: "INVALID_SIZE"
|
|
285
|
+
});
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
271
290
|
}
|
|
272
291
|
deep(validated, parsedKey, num);
|
|
273
|
-
|
|
274
|
-
}
|
|
275
|
-
case "boolean": {
|
|
292
|
+
return;
|
|
293
|
+
} else if (type === "boolean") {
|
|
276
294
|
let bool = data;
|
|
277
295
|
if (typeof data === "string") {
|
|
278
296
|
if (data.toLowerCase() === "true") {
|
|
@@ -282,29 +300,151 @@ function validate(input, schema) {
|
|
|
282
300
|
}
|
|
283
301
|
}
|
|
284
302
|
if (typeof bool !== "boolean") {
|
|
285
|
-
|
|
286
|
-
|
|
303
|
+
if (types.length === 1) {
|
|
304
|
+
issues.push({
|
|
305
|
+
message: `Key ${parsedKey} must be a boolean`,
|
|
306
|
+
code: "INVALID_TYPE"
|
|
307
|
+
});
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
continue;
|
|
287
311
|
}
|
|
288
312
|
deep(validated, parsedKey, bool);
|
|
289
|
-
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
if (
|
|
293
|
-
|
|
294
|
-
|
|
313
|
+
return;
|
|
314
|
+
} else if (type === "date") {
|
|
315
|
+
let date;
|
|
316
|
+
if (data instanceof Date) {
|
|
317
|
+
date = data;
|
|
318
|
+
} else if (typeof data === "string" || typeof data === "number") {
|
|
319
|
+
date = new Date(data);
|
|
320
|
+
} else {
|
|
321
|
+
if (types.length === 1) {
|
|
322
|
+
issues.push({
|
|
323
|
+
message: `Key ${parsedKey} must be a Date, string or number`,
|
|
324
|
+
code: "INVALID_TYPE"
|
|
325
|
+
});
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
continue;
|
|
295
329
|
}
|
|
296
|
-
const
|
|
297
|
-
if (Number.isNaN(
|
|
298
|
-
|
|
299
|
-
|
|
330
|
+
const dt = date.getTime();
|
|
331
|
+
if (Number.isNaN(dt)) {
|
|
332
|
+
if (types.length === 1) {
|
|
333
|
+
issues.push({
|
|
334
|
+
message: `Key ${parsedKey} must be a valid date`,
|
|
335
|
+
code: "INVALID_DATE"
|
|
336
|
+
});
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
continue;
|
|
340
|
+
}
|
|
341
|
+
if (modifiers.min) {
|
|
342
|
+
const min = new Date(Number(modifiers.min));
|
|
343
|
+
if (Number.isNaN(min.getTime())) {
|
|
344
|
+
issues.push({
|
|
345
|
+
message: `Key ${parsedKey} contains a bad modifier (${modifiers.min}) that could not be converted to instance (Date)`,
|
|
346
|
+
code: "BAD_MODIFIER"
|
|
347
|
+
});
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
if (dt < min.getTime()) {
|
|
351
|
+
if (types.length === 1) {
|
|
352
|
+
issues.push({
|
|
353
|
+
message: `Key ${parsedKey} must be greater than or equal to minimum date (${min.toISOString()})`,
|
|
354
|
+
code: "INVALID_DATE"
|
|
355
|
+
});
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
continue;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
if (modifiers.max) {
|
|
362
|
+
const max = new Date(Number(modifiers.max));
|
|
363
|
+
if (Number.isNaN(max.getTime())) {
|
|
364
|
+
issues.push({
|
|
365
|
+
message: `Key ${parsedKey} contains a bad modifier (${modifiers.max}) that could not be converted to instance (Date)`,
|
|
366
|
+
code: "BAD_MODIFIER"
|
|
367
|
+
});
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
if (dt > max.getTime()) {
|
|
371
|
+
if (types.length === 1) {
|
|
372
|
+
issues.push({
|
|
373
|
+
message: `Key ${parsedKey} must be less than or equal to maximum date (${max.toISOString()})`,
|
|
374
|
+
code: "INVALID_DATE"
|
|
375
|
+
});
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
continue;
|
|
379
|
+
}
|
|
300
380
|
}
|
|
301
381
|
deep(validated, parsedKey, date.toISOString());
|
|
302
|
-
|
|
382
|
+
return;
|
|
383
|
+
} else if (type === "array") {
|
|
384
|
+
if (!Array.isArray(data)) {
|
|
385
|
+
if (types.length === 1) {
|
|
386
|
+
issues.push({
|
|
387
|
+
message: `Key ${parsedKey} must be an array`,
|
|
388
|
+
code: "INVALID_TYPE"
|
|
389
|
+
});
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
continue;
|
|
393
|
+
}
|
|
394
|
+
if (modifiers.min) {
|
|
395
|
+
const min = Number(modifiers.min);
|
|
396
|
+
if (Number.isNaN(min)) {
|
|
397
|
+
issues.push({
|
|
398
|
+
message: `Key ${parsedKey} contains a bad modifier (${modifiers.min}) that could not be converted to type (number)`,
|
|
399
|
+
code: "BAD_MODIFIER"
|
|
400
|
+
});
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
if (data.length < min) {
|
|
404
|
+
if (types.length === 1) {
|
|
405
|
+
issues.push({
|
|
406
|
+
message: `Key ${parsedKey} must be greater than or equal to minimum array length (${min})`,
|
|
407
|
+
code: "INVALID_LENGTH"
|
|
408
|
+
});
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
continue;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
if (modifiers.max) {
|
|
415
|
+
const max = Number(modifiers.max);
|
|
416
|
+
if (Number.isNaN(max)) {
|
|
417
|
+
issues.push({
|
|
418
|
+
message: `Key ${parsedKey} contains a bad modifier (${modifiers.max}) that could not be converted to type (number)`,
|
|
419
|
+
code: "BAD_MODIFIER"
|
|
420
|
+
});
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
if (data.length > max) {
|
|
424
|
+
if (types.length === 1) {
|
|
425
|
+
issues.push({
|
|
426
|
+
message: `Key ${parsedKey} must be less than or equal to maximum array length (${max})`,
|
|
427
|
+
code: "INVALID_LENGTH"
|
|
428
|
+
});
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
continue;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
deep(validated, parsedKey, data);
|
|
435
|
+
return;
|
|
303
436
|
}
|
|
304
437
|
}
|
|
438
|
+
issues.push({
|
|
439
|
+
message: `Key ${parsedKey} must be one of: ${types.join(", ")}`,
|
|
440
|
+
code: "INVALID_TYPE"
|
|
441
|
+
});
|
|
305
442
|
} else {
|
|
306
443
|
if (!isRecord(data)) {
|
|
307
|
-
issues.push({
|
|
444
|
+
issues.push({
|
|
445
|
+
message: `Key ${parsedKey} must be an object`,
|
|
446
|
+
code: "INVALID_TYPE"
|
|
447
|
+
});
|
|
308
448
|
return;
|
|
309
449
|
}
|
|
310
450
|
const obj = data;
|
|
@@ -325,14 +465,136 @@ function parseKey(k) {
|
|
|
325
465
|
key: optional ? k.slice(0, -1) : k
|
|
326
466
|
};
|
|
327
467
|
}
|
|
328
|
-
function
|
|
468
|
+
function parseSchemaValue(value) {
|
|
469
|
+
if (isRecord(value))
|
|
470
|
+
throw new Error("Cannot parse object schema values");
|
|
471
|
+
const parts = value.split("|");
|
|
472
|
+
const types = [];
|
|
473
|
+
const modifiers = {};
|
|
474
|
+
for (const p of parts) {
|
|
475
|
+
if (p.indexOf("=") > -1) {
|
|
476
|
+
const [m, v] = p.split("=");
|
|
477
|
+
if (!isModifierName(m))
|
|
478
|
+
throw new Error(`Unrecognised modifier: ${m}`);
|
|
479
|
+
modifiers[m] = v;
|
|
480
|
+
} else {
|
|
481
|
+
if (!isPrimitive(p))
|
|
482
|
+
throw new Error(`Unrecognised type: ${p}`);
|
|
483
|
+
types.push(p);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
return { types, modifiers };
|
|
487
|
+
}
|
|
488
|
+
function isModifierName(name) {
|
|
489
|
+
return MODIFIER_NAMES.some((n) => n === name);
|
|
490
|
+
}
|
|
491
|
+
function isPrimitive(name) {
|
|
492
|
+
return PRIMITIVE_NAMES.some((n) => n === name);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// src/index.ts
|
|
496
|
+
var DEFAULT_COMPILE_OPTIONS = {
|
|
497
|
+
features: {
|
|
498
|
+
frontmatter: true
|
|
499
|
+
}
|
|
500
|
+
};
|
|
501
|
+
async function parse(frontmatter) {
|
|
502
|
+
if (!frontmatter)
|
|
503
|
+
return {};
|
|
504
|
+
const { kind, value } = frontmatter;
|
|
505
|
+
switch (kind) {
|
|
506
|
+
case "yaml": {
|
|
507
|
+
return (await import("yaml")).parse(value);
|
|
508
|
+
}
|
|
509
|
+
case "toml": {
|
|
510
|
+
return (await import("smol-toml")).parse(value);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
async function create(dir, buildContext) {
|
|
515
|
+
const { logger: logger2, compileOptions = {} } = buildContext;
|
|
516
|
+
const { features, ...restCompileOptions } = compileOptions;
|
|
517
|
+
try {
|
|
518
|
+
const files = (await fs.readdir(dir)).filter((file) => path.extname(file) === ".md");
|
|
519
|
+
const filePaths = files.map((file) => path.join(dir, file));
|
|
520
|
+
if (!files.length) {
|
|
521
|
+
logger2.warn(`mdsrc: ${dir} is empty`);
|
|
522
|
+
return [];
|
|
523
|
+
}
|
|
524
|
+
return Promise.all(filePaths.map(async (filePath) => {
|
|
525
|
+
const file = path.basename(filePath);
|
|
526
|
+
const { html, frontmatter: rawFrontmatter } = markdownToHtml(await fs.readFile(filePath, "utf-8"), {
|
|
527
|
+
features: {
|
|
528
|
+
...DEFAULT_COMPILE_OPTIONS.features,
|
|
529
|
+
...features
|
|
530
|
+
},
|
|
531
|
+
...restCompileOptions
|
|
532
|
+
});
|
|
533
|
+
const frontmatter = await parse(rawFrontmatter);
|
|
534
|
+
return {
|
|
535
|
+
...frontmatter,
|
|
536
|
+
__mdsrc: {
|
|
537
|
+
slug: path.basename(file, ".md").toLowerCase().replace(/\s+/g, "-"),
|
|
538
|
+
filename: file
|
|
539
|
+
},
|
|
540
|
+
body: html.trim()
|
|
541
|
+
};
|
|
542
|
+
}));
|
|
543
|
+
} catch (err) {
|
|
544
|
+
logger2.error("[create]: failed to create entries", err);
|
|
545
|
+
throw err;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
function isENOENT(err) {
|
|
549
|
+
return err instanceof Error && "code" in err && err.code === "ENOENT";
|
|
550
|
+
}
|
|
551
|
+
var fileCache = new Map;
|
|
552
|
+
var FILE_CACHE_MAX_SIZE = 100;
|
|
553
|
+
function setFileCache(filePath, content) {
|
|
554
|
+
fileCache.delete(filePath);
|
|
555
|
+
if (fileCache.size >= FILE_CACHE_MAX_SIZE) {
|
|
556
|
+
const lru = fileCache.keys().next().value;
|
|
557
|
+
if (lru !== undefined) {
|
|
558
|
+
fileCache.delete(lru);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
fileCache.set(filePath, content);
|
|
562
|
+
}
|
|
563
|
+
function getFileCache(filePath) {
|
|
564
|
+
const content = fileCache.get(filePath);
|
|
565
|
+
if (content !== undefined) {
|
|
566
|
+
setFileCache(filePath, content);
|
|
567
|
+
}
|
|
568
|
+
return content;
|
|
569
|
+
}
|
|
570
|
+
async function maybeWrite(filePath, content) {
|
|
571
|
+
const cached = getFileCache(filePath);
|
|
572
|
+
if (cached !== content) {
|
|
573
|
+
await fs.writeFile(filePath, content);
|
|
574
|
+
setFileCache(filePath, content);
|
|
575
|
+
return true;
|
|
576
|
+
}
|
|
577
|
+
try {
|
|
578
|
+
if (await fs.readFile(filePath, "utf-8") === content) {
|
|
579
|
+
setFileCache(filePath, content);
|
|
580
|
+
return false;
|
|
581
|
+
}
|
|
582
|
+
} catch (err) {
|
|
583
|
+
if (!isENOENT(err))
|
|
584
|
+
throw err;
|
|
585
|
+
}
|
|
586
|
+
await fs.writeFile(filePath, content);
|
|
587
|
+
setFileCache(filePath, content);
|
|
588
|
+
return true;
|
|
589
|
+
}
|
|
590
|
+
function schemaToType(schema) {
|
|
329
591
|
const fields = Object.entries(schema).map(([k, v]) => {
|
|
330
592
|
const { key, optional } = parseKey(k);
|
|
331
593
|
let type;
|
|
332
594
|
if (typeof v === "string") {
|
|
333
|
-
type = v === "date" ? "string" : v;
|
|
595
|
+
type = v === "date" ? "string" : v === "array" ? "any[]" : v;
|
|
334
596
|
} else {
|
|
335
|
-
type =
|
|
597
|
+
type = schemaToType(v);
|
|
336
598
|
}
|
|
337
599
|
return `${key}${optional ? "?" : ""}: ${type}`;
|
|
338
600
|
}).join(`
|
|
@@ -349,24 +611,19 @@ async function build(src, buildContext) {
|
|
|
349
611
|
await fs.mkdir(outDir, { recursive: true });
|
|
350
612
|
for (const collection of src) {
|
|
351
613
|
const raw = await create(path.join(process.cwd(), collection.dir), buildContext);
|
|
352
|
-
const validated =
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
} catch (err) {
|
|
364
|
-
logger2.error(`[buildStart]: failed to validate item in ${collection.name}`, err);
|
|
365
|
-
return null;
|
|
366
|
-
}
|
|
367
|
-
}));
|
|
614
|
+
const validated = raw.map((item) => {
|
|
615
|
+
const { body, __mdsrc, ...metadata } = item;
|
|
616
|
+
const res = validate(metadata, collection.schema);
|
|
617
|
+
if (res.issues)
|
|
618
|
+
throw new Error(JSON.stringify(res.issues, null, 2));
|
|
619
|
+
return {
|
|
620
|
+
...res.value,
|
|
621
|
+
body,
|
|
622
|
+
__mdsrc
|
|
623
|
+
};
|
|
624
|
+
});
|
|
368
625
|
collections[collection.name] = {
|
|
369
|
-
items: validated
|
|
626
|
+
items: validated,
|
|
370
627
|
schema: collection.schema
|
|
371
628
|
};
|
|
372
629
|
}
|
|
@@ -374,7 +631,7 @@ async function build(src, buildContext) {
|
|
|
374
631
|
const promises = [];
|
|
375
632
|
promises.push(maybeWrite(path.join(outDir, "types.ts"), `
|
|
376
633
|
${names.map((name) => `
|
|
377
|
-
export type ${capitalise(name)} = ${
|
|
634
|
+
export type ${capitalise(name)} = ${schemaToType(collections[name].schema)} & {
|
|
378
635
|
body: string,
|
|
379
636
|
__mdsrc: {
|
|
380
637
|
slug: string
|
|
@@ -506,15 +763,13 @@ function mdsrc(config) {
|
|
|
506
763
|
server.watcher.on("add", (p) => rebuild("add", p)).on("change", (p) => rebuild("change", p)).on("unlink", (p) => rebuild("unlink", p));
|
|
507
764
|
},
|
|
508
765
|
resolveId(id) {
|
|
509
|
-
if (id === PKG_NAME)
|
|
766
|
+
if (id === PKG_NAME)
|
|
510
767
|
return path.join(outDir, "index.js");
|
|
511
|
-
}
|
|
512
768
|
if (id.startsWith(`${PKG_NAME}/`)) {
|
|
513
769
|
const subpath = id.slice(PKG_NAME.length + 1);
|
|
514
770
|
const match = buildContext.names.find((name) => name === subpath || toModuleName(name) === subpath);
|
|
515
|
-
if (match)
|
|
771
|
+
if (match)
|
|
516
772
|
return path.join(outDir, `${toModuleName(match)}.js`);
|
|
517
|
-
}
|
|
518
773
|
}
|
|
519
774
|
return null;
|
|
520
775
|
}
|
|
@@ -523,42 +778,19 @@ function mdsrc(config) {
|
|
|
523
778
|
function toModuleName(name) {
|
|
524
779
|
return name.toLowerCase();
|
|
525
780
|
}
|
|
526
|
-
if (import.meta.vitest) {
|
|
527
|
-
const { it, expect, describe } = import.meta.vitest;
|
|
528
|
-
const now = Date.now();
|
|
529
|
-
const yaml = {
|
|
530
|
-
kind: "yaml",
|
|
531
|
-
value: dedent(`
|
|
532
|
-
title: mdsrc
|
|
533
|
-
date: ${now}
|
|
534
|
-
`)
|
|
535
|
-
};
|
|
536
|
-
const toml = {
|
|
537
|
-
kind: "toml",
|
|
538
|
-
value: dedent(`
|
|
539
|
-
title = "mdsrc"
|
|
540
|
-
date = ${now}
|
|
541
|
-
`)
|
|
542
|
-
};
|
|
543
|
-
describe("markdown parsing", () => {
|
|
544
|
-
it("parses frontmatter", async () => {
|
|
545
|
-
for (const f of [yaml, toml]) {
|
|
546
|
-
const frontmatter = await parse(f);
|
|
547
|
-
expect(frontmatter).toEqual({
|
|
548
|
-
title: "mdsrc",
|
|
549
|
-
date: now
|
|
550
|
-
});
|
|
551
|
-
}
|
|
552
|
-
});
|
|
553
|
-
it("returns empty object for missing frontmatter", async () => {
|
|
554
|
-
const frontmatter = await parse(null);
|
|
555
|
-
expect(frontmatter).toEqual({});
|
|
556
|
-
});
|
|
557
|
-
});
|
|
558
|
-
}
|
|
559
781
|
export {
|
|
782
|
+
toModuleName,
|
|
783
|
+
setFileCache,
|
|
784
|
+
schemaToType,
|
|
785
|
+
parse,
|
|
786
|
+
maybeWrite,
|
|
787
|
+
isENOENT,
|
|
788
|
+
getFileCache,
|
|
789
|
+
fileCache,
|
|
560
790
|
mdsrc as default,
|
|
561
|
-
create
|
|
791
|
+
create,
|
|
792
|
+
FILE_CACHE_MAX_SIZE,
|
|
793
|
+
DEFAULT_COMPILE_OPTIONS
|
|
562
794
|
};
|
|
563
795
|
|
|
564
|
-
//# debugId=
|
|
796
|
+
//# debugId=49D42D1A4493323164756E2164756E21
|