@jk2908/mdsrc 0.1.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/LICENSE +21 -0
- package/README.md +47 -0
- package/dist/config.d.ts +3 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +423 -0
- package/dist/logger.d.ts +53 -0
- package/dist/types.d.ts +44 -0
- package/dist/utils.d.ts +3 -0
- package/package.json +53 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jerome Kenway
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# @jk2908/mdsrc
|
|
2
|
+
|
|
3
|
+
A Vite plugin for managing markdown content with type safety.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npm install @jk2908/mdsrc vite
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import plugin from '@jk2908/mdsrc'
|
|
15
|
+
import { defineConfig } from 'vite'
|
|
16
|
+
|
|
17
|
+
export default defineConfig({
|
|
18
|
+
plugins: [
|
|
19
|
+
plugin([
|
|
20
|
+
{
|
|
21
|
+
dir: 'content',
|
|
22
|
+
name: 'post',
|
|
23
|
+
schema: {
|
|
24
|
+
title: { type: 'string' },
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
]),
|
|
28
|
+
],
|
|
29
|
+
})
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The plugin reads markdown content, validates frontmatter against your schema, and generates typed modules during build and watch. Collection config currently uses `name`, `dir`, and `schema`.
|
|
33
|
+
|
|
34
|
+
If you configure a collection with `name: 'post'`, the generated module exports `allPosts` from the package root.
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
import { allPosts } from '@jk2908/mdsrc'
|
|
38
|
+
|
|
39
|
+
export const summaries = allPosts.map(post => ({
|
|
40
|
+
title: post.title,
|
|
41
|
+
slug: post.__mdsrc.slug,
|
|
42
|
+
}))
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## License
|
|
46
|
+
|
|
47
|
+
MIT
|
package/dist/config.d.ts
ADDED
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Plugin } from 'vite';
|
|
2
|
+
import type { BuildContext, Collection } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Read every markdown file in a collection and turn it into the raw entry shape
|
|
5
|
+
* add mdsrc metadata like slug and filename alongside the trimmed body
|
|
6
|
+
* return an empty list if the directory read fails
|
|
7
|
+
*/
|
|
8
|
+
export declare function create(dir: string, buildContext: BuildContext): Promise<{
|
|
9
|
+
__mdsrc: {
|
|
10
|
+
slug: string;
|
|
11
|
+
filename: string;
|
|
12
|
+
};
|
|
13
|
+
body: string;
|
|
14
|
+
}[]>;
|
|
15
|
+
/**
|
|
16
|
+
* Build the vite plugin that validates collections and writes the generated modules
|
|
17
|
+
* keep the runtime data and declaration files in the same pass
|
|
18
|
+
* resolve package imports from the generated directory
|
|
19
|
+
*/
|
|
20
|
+
export default function plugin(src: Collection[]): Plugin;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { realpathSync } from "node:fs";
|
|
3
|
+
import fs from "node:fs/promises";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
|
|
6
|
+
// src/config.ts
|
|
7
|
+
var NAME = "mdsrc";
|
|
8
|
+
var PKG_NAME = `@jk2908/${NAME}`;
|
|
9
|
+
var GENERATED_DIR = `.${NAME}`;
|
|
10
|
+
|
|
11
|
+
// src/logger.ts
|
|
12
|
+
var LEVELS = {
|
|
13
|
+
debug: 0,
|
|
14
|
+
info: 1,
|
|
15
|
+
warn: 2,
|
|
16
|
+
error: 3,
|
|
17
|
+
fatal: 4
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
class Logger {
|
|
21
|
+
static #defaultLevel = "info";
|
|
22
|
+
#level;
|
|
23
|
+
constructor(level) {
|
|
24
|
+
this.#level = level;
|
|
25
|
+
}
|
|
26
|
+
static set defaultLevel(level) {
|
|
27
|
+
Logger.#defaultLevel = level;
|
|
28
|
+
}
|
|
29
|
+
static get defaultLevel() {
|
|
30
|
+
return Logger.#defaultLevel;
|
|
31
|
+
}
|
|
32
|
+
static toError(err) {
|
|
33
|
+
return err instanceof Error ? err : new Error(String(err), { cause: err });
|
|
34
|
+
}
|
|
35
|
+
static print(err) {
|
|
36
|
+
if (err instanceof Error) {
|
|
37
|
+
return err.message + (err.stack ? `
|
|
38
|
+
${err.stack}` : "");
|
|
39
|
+
}
|
|
40
|
+
if (typeof err === "object" && err !== null) {
|
|
41
|
+
try {
|
|
42
|
+
return JSON.stringify(err, null, 2);
|
|
43
|
+
} catch {
|
|
44
|
+
return String(err);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return String(err);
|
|
48
|
+
}
|
|
49
|
+
set level(level) {
|
|
50
|
+
this.#level = level;
|
|
51
|
+
}
|
|
52
|
+
get level() {
|
|
53
|
+
return this.#level ?? Logger.#defaultLevel;
|
|
54
|
+
}
|
|
55
|
+
log(level, message, error) {
|
|
56
|
+
if (LEVELS[level] < LEVELS[this.level])
|
|
57
|
+
return;
|
|
58
|
+
const entry = {
|
|
59
|
+
ts: Date.now(),
|
|
60
|
+
level,
|
|
61
|
+
message
|
|
62
|
+
};
|
|
63
|
+
if (level === "error" || level === "fatal") {
|
|
64
|
+
entry.error = error ? Logger.toError(error) : new Error(message);
|
|
65
|
+
}
|
|
66
|
+
const line = `[${NAME}] [${entry.ts}] [${level.toUpperCase()}] ${message}`;
|
|
67
|
+
const extra = entry.error ? `
|
|
68
|
+
${Logger.print(entry.error)}` : "";
|
|
69
|
+
if (level === "warn") {
|
|
70
|
+
console.warn(line, extra);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (level === "error" || level === "fatal") {
|
|
74
|
+
console.error(line, extra);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
console.log(line, extra);
|
|
78
|
+
}
|
|
79
|
+
debug(...messages) {
|
|
80
|
+
this.log("debug", messages.join(" "));
|
|
81
|
+
}
|
|
82
|
+
info(...messages) {
|
|
83
|
+
this.log("info", messages.join(" "));
|
|
84
|
+
}
|
|
85
|
+
warn(...messages) {
|
|
86
|
+
this.log("warn", messages.join(" "));
|
|
87
|
+
}
|
|
88
|
+
error(message, error) {
|
|
89
|
+
this.log("error", message, error === undefined ? undefined : Logger.toError(error));
|
|
90
|
+
}
|
|
91
|
+
fatal(message, error) {
|
|
92
|
+
this.log("fatal", message, error === undefined ? undefined : Logger.toError(error));
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
var logger = new Logger("debug");
|
|
96
|
+
|
|
97
|
+
// src/utils.ts
|
|
98
|
+
function capitalise(str) {
|
|
99
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
100
|
+
}
|
|
101
|
+
function pluralise(str, count) {
|
|
102
|
+
return count === 1 ? str : str.endsWith("s") ? str : `${str}s`;
|
|
103
|
+
}
|
|
104
|
+
function debounce(fn, wait) {
|
|
105
|
+
let timeoutId = null;
|
|
106
|
+
return (...args) => {
|
|
107
|
+
if (timeoutId) {
|
|
108
|
+
clearTimeout(timeoutId);
|
|
109
|
+
}
|
|
110
|
+
timeoutId = setTimeout(() => {
|
|
111
|
+
fn.apply(null, args);
|
|
112
|
+
}, wait);
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// src/index.ts
|
|
117
|
+
var fileCache = new Map;
|
|
118
|
+
function parse(content) {
|
|
119
|
+
const regex = /^---\r?\n([\s\S]*?)\r?\n---([\s\S]*)$/;
|
|
120
|
+
const match = content.match(regex);
|
|
121
|
+
const metadata = {};
|
|
122
|
+
if (!match)
|
|
123
|
+
throw new Error("invalid frontmatter");
|
|
124
|
+
const [, frontmatter, body] = match;
|
|
125
|
+
if (frontmatter) {
|
|
126
|
+
for (const line of frontmatter.split(`
|
|
127
|
+
`)) {
|
|
128
|
+
const [key, value] = line.split(": ").map((str) => str.trim());
|
|
129
|
+
metadata[key] = value;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return { metadata, body };
|
|
133
|
+
}
|
|
134
|
+
async function create(dir, buildContext) {
|
|
135
|
+
const { logger: logger2 } = buildContext;
|
|
136
|
+
try {
|
|
137
|
+
const files = (await fs.readdir(dir)).filter((file) => path.extname(file) === ".md");
|
|
138
|
+
const filePaths = files.map((file) => path.join(dir, file));
|
|
139
|
+
if (!files.length) {
|
|
140
|
+
console.warn(`mdsrc: ${dir} is empty`);
|
|
141
|
+
return [];
|
|
142
|
+
}
|
|
143
|
+
return Promise.all(filePaths.map(async (filePath) => {
|
|
144
|
+
const file = path.basename(filePath);
|
|
145
|
+
const { metadata, body } = parse(await fs.readFile(filePath, "utf-8"));
|
|
146
|
+
return {
|
|
147
|
+
...metadata,
|
|
148
|
+
__mdsrc: {
|
|
149
|
+
slug: path.basename(file, ".md").toLowerCase().replace(/\s+/g, "-"),
|
|
150
|
+
filename: file
|
|
151
|
+
},
|
|
152
|
+
body: body.trim()
|
|
153
|
+
};
|
|
154
|
+
}));
|
|
155
|
+
} catch (err) {
|
|
156
|
+
logger2.error("[create]: failed to create entries", err);
|
|
157
|
+
return [];
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
async function maybeWrite(filePath, content) {
|
|
161
|
+
const cached = fileCache.get(filePath);
|
|
162
|
+
if (cached === content) {
|
|
163
|
+
try {
|
|
164
|
+
await fs.access(filePath);
|
|
165
|
+
return false;
|
|
166
|
+
} catch (err) {
|
|
167
|
+
if (!(err instanceof Error) || !("code" in err) || err.code !== "ENOENT") {
|
|
168
|
+
throw err;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
if (cached === undefined) {
|
|
173
|
+
try {
|
|
174
|
+
const current = await fs.readFile(filePath, "utf-8");
|
|
175
|
+
fileCache.set(filePath, current);
|
|
176
|
+
if (current === content) {
|
|
177
|
+
fileCache.set(filePath, content);
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
} catch (err) {
|
|
181
|
+
if (!(err instanceof Error) || !("code" in err) || err.code !== "ENOENT") {
|
|
182
|
+
throw err;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
await fs.writeFile(filePath, content);
|
|
187
|
+
fileCache.set(filePath, content);
|
|
188
|
+
return true;
|
|
189
|
+
}
|
|
190
|
+
function validate(input, schema) {
|
|
191
|
+
const validated = {};
|
|
192
|
+
const issues = [];
|
|
193
|
+
if (typeof input !== "object" || input === null) {
|
|
194
|
+
issues.push({ message: "Input must be an object" });
|
|
195
|
+
return { issues };
|
|
196
|
+
}
|
|
197
|
+
for (const key in schema) {
|
|
198
|
+
const entry = schema[key];
|
|
199
|
+
if (!(key in input)) {
|
|
200
|
+
if (!entry.optional) {
|
|
201
|
+
issues.push({ message: `Missing required key: ${key}` });
|
|
202
|
+
}
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
const value = input[key];
|
|
206
|
+
switch (entry.type) {
|
|
207
|
+
case "string": {
|
|
208
|
+
if (typeof value !== "string") {
|
|
209
|
+
issues.push({ message: `Key ${key} must be a string` });
|
|
210
|
+
} else {
|
|
211
|
+
if (entry.minLength && value.length < entry.minLength) {
|
|
212
|
+
issues.push({
|
|
213
|
+
message: `Key ${key} must be at least ${entry.minLength} characters`
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
if (entry.maxLength && value.length > entry.maxLength) {
|
|
217
|
+
issues.push({
|
|
218
|
+
message: `Key ${key} must be at most ${entry.maxLength} characters`
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
validated[key] = value;
|
|
222
|
+
}
|
|
223
|
+
break;
|
|
224
|
+
}
|
|
225
|
+
case "number": {
|
|
226
|
+
let num = value;
|
|
227
|
+
if (typeof value === "string" && !Number.isNaN(Number(value))) {
|
|
228
|
+
num = Number(value);
|
|
229
|
+
}
|
|
230
|
+
if (typeof num !== "number") {
|
|
231
|
+
issues.push({ message: `Key ${key} must be a number` });
|
|
232
|
+
} else {
|
|
233
|
+
validated[key] = num;
|
|
234
|
+
}
|
|
235
|
+
break;
|
|
236
|
+
}
|
|
237
|
+
case "boolean": {
|
|
238
|
+
let bool = value;
|
|
239
|
+
if (typeof value === "string") {
|
|
240
|
+
if (value.toLowerCase() === "true") {
|
|
241
|
+
bool = true;
|
|
242
|
+
} else if (value.toLowerCase() === "false") {
|
|
243
|
+
bool = false;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
if (typeof bool !== "boolean") {
|
|
247
|
+
issues.push({ message: `Key ${key} must be a boolean` });
|
|
248
|
+
} else {
|
|
249
|
+
validated[key] = bool;
|
|
250
|
+
}
|
|
251
|
+
break;
|
|
252
|
+
}
|
|
253
|
+
case "date": {
|
|
254
|
+
if (typeof value !== "string") {
|
|
255
|
+
issues.push({ message: `Key ${key} must be a date` });
|
|
256
|
+
} else {
|
|
257
|
+
const date = new Date(value);
|
|
258
|
+
if (Number.isNaN(date.getTime())) {
|
|
259
|
+
issues.push({ message: `Key ${key} must be a valid date` });
|
|
260
|
+
} else {
|
|
261
|
+
validated[key] = date.toISOString();
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
break;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
return issues.length ? { issues } : { value: validated };
|
|
269
|
+
}
|
|
270
|
+
async function build(src, buildContext) {
|
|
271
|
+
const { logger: logger2, outDir } = buildContext;
|
|
272
|
+
let names = [];
|
|
273
|
+
const collections = {};
|
|
274
|
+
try {
|
|
275
|
+
if (!outDir)
|
|
276
|
+
throw new Error("Output directory is not defined");
|
|
277
|
+
await fs.mkdir(outDir, { recursive: true });
|
|
278
|
+
for (const collection of src) {
|
|
279
|
+
const raw = await create(path.join(process.cwd(), collection.dir), buildContext);
|
|
280
|
+
const validated = await Promise.all(raw.map(async (item) => {
|
|
281
|
+
try {
|
|
282
|
+
const { body, __mdsrc, ...metadata } = item;
|
|
283
|
+
const res = validate(metadata, collection.schema);
|
|
284
|
+
if (res.issues)
|
|
285
|
+
throw new Error(JSON.stringify(res.issues, null, 2));
|
|
286
|
+
return {
|
|
287
|
+
body,
|
|
288
|
+
...res.value,
|
|
289
|
+
__mdsrc
|
|
290
|
+
};
|
|
291
|
+
} catch (err) {
|
|
292
|
+
logger2.error(`[buildStart]: failed to validate item in ${collection.name}`, err);
|
|
293
|
+
return null;
|
|
294
|
+
}
|
|
295
|
+
}));
|
|
296
|
+
collections[collection.name] = {
|
|
297
|
+
items: validated.filter((e) => e !== null),
|
|
298
|
+
schema: collection.schema
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
names = Object.keys(collections);
|
|
302
|
+
const promises = [];
|
|
303
|
+
promises.push(maybeWrite(path.join(outDir, "types.ts"), `
|
|
304
|
+
${names.map((name) => `
|
|
305
|
+
export type ${capitalise(name)} = {
|
|
306
|
+
body?: string
|
|
307
|
+
${Object.entries(collections[name].schema).map(([key, entry]) => `${key}${entry.optional ? "?" : ""}: ${entry.type === "date" ? "string" : entry.type}`).join(`
|
|
308
|
+
`)}
|
|
309
|
+
__mdsrc: {
|
|
310
|
+
slug: string
|
|
311
|
+
filename: string
|
|
312
|
+
},
|
|
313
|
+
}
|
|
314
|
+
`).join(`
|
|
315
|
+
|
|
316
|
+
`)}`.trim()));
|
|
317
|
+
promises.push(maybeWrite(path.join(outDir, "index.d.ts"), `
|
|
318
|
+
import type { ${names.map((name) => capitalise(name)).join(", ")} } from './types.js'
|
|
319
|
+
|
|
320
|
+
declare module '${PKG_NAME}' {
|
|
321
|
+
${names.map((name) => `
|
|
322
|
+
export const all${capitalise(pluralise(name, 2))}: ${capitalise(name)}[]
|
|
323
|
+
`).join(`
|
|
324
|
+
|
|
325
|
+
`)}
|
|
326
|
+
}
|
|
327
|
+
`.trim()));
|
|
328
|
+
for (const name of names) {
|
|
329
|
+
const collection = collections[name]?.items;
|
|
330
|
+
promises.push(maybeWrite(path.join(outDir, `${name}.js`), `export const all${capitalise(pluralise(name, 2))} = ${collection?.length ? JSON.stringify(collection) : "[]"}`.trim()));
|
|
331
|
+
}
|
|
332
|
+
promises.push(maybeWrite(path.join(outDir, "index.js"), names.map((name) => `export * from './${name}.js'`).join(`
|
|
333
|
+
`)));
|
|
334
|
+
const writes = await Promise.all(promises);
|
|
335
|
+
buildContext.names = names;
|
|
336
|
+
return writes.some((changed) => changed);
|
|
337
|
+
} catch (err) {
|
|
338
|
+
logger2.error("[build]: failed to generate data", err);
|
|
339
|
+
throw err;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
var normaliseWatchPath = (p) => p.replace(/\\/g, "/");
|
|
343
|
+
function plugin(src) {
|
|
344
|
+
const logger2 = new Logger("debug");
|
|
345
|
+
const outDir = path.join(process.cwd(), GENERATED_DIR);
|
|
346
|
+
const watchRoot = normaliseWatchPath(realpathSync.native(process.cwd()));
|
|
347
|
+
const watchedRoots = src.map((c) => `${normaliseWatchPath(path.join(watchRoot, c.dir))}/`);
|
|
348
|
+
const resolveWatchFile = (filePath) => {
|
|
349
|
+
const absolutePath = path.resolve(watchRoot, filePath);
|
|
350
|
+
const parentPath = path.dirname(absolutePath);
|
|
351
|
+
try {
|
|
352
|
+
const resolvedParentPath = normaliseWatchPath(realpathSync.native(parentPath));
|
|
353
|
+
return normaliseWatchPath(path.join(resolvedParentPath, path.basename(absolutePath)));
|
|
354
|
+
} catch {
|
|
355
|
+
return normaliseWatchPath(absolutePath);
|
|
356
|
+
}
|
|
357
|
+
};
|
|
358
|
+
const isWatchedFile = (filePath) => watchedRoots.some((root) => resolveWatchFile(filePath).startsWith(root));
|
|
359
|
+
const buildContext = {
|
|
360
|
+
logger: logger2,
|
|
361
|
+
outDir,
|
|
362
|
+
names: []
|
|
363
|
+
};
|
|
364
|
+
let rebuildRunning = false;
|
|
365
|
+
let rebuildQueued = false;
|
|
366
|
+
let rebuildReason = "change";
|
|
367
|
+
const rebuild = debounce((event, filePath) => {
|
|
368
|
+
const queue = () => {
|
|
369
|
+
(async () => {
|
|
370
|
+
if (rebuildRunning) {
|
|
371
|
+
rebuildQueued = true;
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
rebuildRunning = true;
|
|
375
|
+
do {
|
|
376
|
+
rebuildQueued = false;
|
|
377
|
+
try {
|
|
378
|
+
const changed = await build(src, buildContext);
|
|
379
|
+
if (changed)
|
|
380
|
+
logger2.info(`[watch]: route graph rebuilt (${rebuildReason})`);
|
|
381
|
+
} catch (err) {
|
|
382
|
+
logger2.error("[watch] route rebuild failed", err);
|
|
383
|
+
}
|
|
384
|
+
} while (rebuildQueued);
|
|
385
|
+
rebuildRunning = false;
|
|
386
|
+
})();
|
|
387
|
+
};
|
|
388
|
+
if (!isWatchedFile(filePath))
|
|
389
|
+
return;
|
|
390
|
+
const file = resolveWatchFile(filePath);
|
|
391
|
+
rebuildReason = `${event}: ${path.relative(watchRoot, file)}`;
|
|
392
|
+
queue();
|
|
393
|
+
}, 75);
|
|
394
|
+
return {
|
|
395
|
+
name: "mdsrc",
|
|
396
|
+
enforce: "pre",
|
|
397
|
+
async buildStart() {
|
|
398
|
+
await build(src, buildContext);
|
|
399
|
+
},
|
|
400
|
+
configureServer(server) {
|
|
401
|
+
logger2.info(`[configureServer]: Watching for changes in ./${src.map((c) => c.dir).join(", ")}...`);
|
|
402
|
+
server.watcher.on("add", (p) => rebuild("add", p)).on("change", (p) => rebuild("change", p)).on("unlink", (p) => rebuild("unlink", p));
|
|
403
|
+
},
|
|
404
|
+
resolveId(id) {
|
|
405
|
+
if (id === PKG_NAME) {
|
|
406
|
+
return path.join(outDir, "index.js");
|
|
407
|
+
}
|
|
408
|
+
if (id.startsWith(`${PKG_NAME}/`)) {
|
|
409
|
+
const subpath = id.slice(PKG_NAME.length + 1);
|
|
410
|
+
if (buildContext.names.some((name) => name === subpath)) {
|
|
411
|
+
return path.join(outDir, `${subpath}.js`);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
return null;
|
|
415
|
+
}
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
export {
|
|
419
|
+
plugin as default,
|
|
420
|
+
create
|
|
421
|
+
};
|
|
422
|
+
|
|
423
|
+
//# debugId=07D2E3DD71DADF0964756E2164756E21
|
package/dist/logger.d.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
declare const LEVELS: {
|
|
2
|
+
readonly debug: 0;
|
|
3
|
+
readonly info: 1;
|
|
4
|
+
readonly warn: 2;
|
|
5
|
+
readonly error: 3;
|
|
6
|
+
readonly fatal: 4;
|
|
7
|
+
};
|
|
8
|
+
export type LogLevel = keyof typeof LEVELS;
|
|
9
|
+
/**
|
|
10
|
+
* Log messages with different severity levels
|
|
11
|
+
*/
|
|
12
|
+
export declare class Logger {
|
|
13
|
+
#private;
|
|
14
|
+
constructor(level?: LogLevel);
|
|
15
|
+
static set defaultLevel(level: LogLevel);
|
|
16
|
+
static get defaultLevel(): LogLevel;
|
|
17
|
+
/**
|
|
18
|
+
* Convert a value to an Error instance
|
|
19
|
+
*/
|
|
20
|
+
static toError(err: unknown): Error;
|
|
21
|
+
/**
|
|
22
|
+
* Stringify the error for logging
|
|
23
|
+
*/
|
|
24
|
+
static print(err: unknown): string;
|
|
25
|
+
set level(level: LogLevel);
|
|
26
|
+
get level(): LogLevel;
|
|
27
|
+
/**
|
|
28
|
+
* Log a message with a specific level
|
|
29
|
+
*/
|
|
30
|
+
log(level: LogLevel, message: string, error?: Error): void;
|
|
31
|
+
/**
|
|
32
|
+
* Log a debug message
|
|
33
|
+
*/
|
|
34
|
+
debug(...messages: string[]): void;
|
|
35
|
+
/**
|
|
36
|
+
* Log an info message
|
|
37
|
+
*/
|
|
38
|
+
info(...messages: string[]): void;
|
|
39
|
+
/**
|
|
40
|
+
* Log a warning message
|
|
41
|
+
*/
|
|
42
|
+
warn(...messages: string[]): void;
|
|
43
|
+
/**
|
|
44
|
+
* Log an error message
|
|
45
|
+
*/
|
|
46
|
+
error(message: string, error?: unknown): void;
|
|
47
|
+
/**
|
|
48
|
+
* Log a fatal error message
|
|
49
|
+
*/
|
|
50
|
+
fatal(message: string, error?: unknown): void;
|
|
51
|
+
}
|
|
52
|
+
export declare const logger: Logger;
|
|
53
|
+
export {};
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { LogLevel, Logger } from './logger.js';
|
|
2
|
+
export type PluginConfig = {
|
|
3
|
+
logger?: {
|
|
4
|
+
level?: LogLevel;
|
|
5
|
+
};
|
|
6
|
+
};
|
|
7
|
+
export type BuildContext = {
|
|
8
|
+
logger: InstanceType<typeof Logger>;
|
|
9
|
+
outDir?: string;
|
|
10
|
+
names?: string[];
|
|
11
|
+
};
|
|
12
|
+
export type SchemaEntry = {
|
|
13
|
+
type: 'string' | 'number' | 'boolean' | 'date';
|
|
14
|
+
optional?: boolean;
|
|
15
|
+
minLength?: number;
|
|
16
|
+
maxLength?: number;
|
|
17
|
+
};
|
|
18
|
+
export type Schema = Record<string, SchemaEntry>;
|
|
19
|
+
export type Collection = {
|
|
20
|
+
name: string;
|
|
21
|
+
dir: string;
|
|
22
|
+
schema: Schema;
|
|
23
|
+
};
|
|
24
|
+
export type Entries = Record<string, unknown>;
|
|
25
|
+
export type Raw = {
|
|
26
|
+
__mdsrc: {
|
|
27
|
+
slug: string;
|
|
28
|
+
filename: string;
|
|
29
|
+
};
|
|
30
|
+
body?: string;
|
|
31
|
+
} & Entries;
|
|
32
|
+
export type Types = Record<string, string>;
|
|
33
|
+
export type Issue = {
|
|
34
|
+
readonly message: string;
|
|
35
|
+
};
|
|
36
|
+
export type Fail = {
|
|
37
|
+
readonly issues: Issue[];
|
|
38
|
+
};
|
|
39
|
+
export type Success<Output> = {
|
|
40
|
+
readonly value: Output;
|
|
41
|
+
readonly issues?: Issue[];
|
|
42
|
+
readonly types?: Record<string, string>;
|
|
43
|
+
};
|
|
44
|
+
export type Result<Output> = Success<Output> | Fail;
|
package/dist/utils.d.ts
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@jk2908/mdsrc",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"author": "Jerome Kenway",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/jk2908/mdsrc.git"
|
|
9
|
+
},
|
|
10
|
+
"main": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "bun run ./build.ts && bun x tsc --project tsconfig.json --emitDeclarationOnly",
|
|
14
|
+
"test": "bun test"
|
|
15
|
+
},
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"@types/bun": "^1.3.13",
|
|
18
|
+
"@typescript/native-preview": "^7.0.0-dev.20260421.2",
|
|
19
|
+
"@types/node": "^24.0.1",
|
|
20
|
+
"oxfmt": "^0.35.0",
|
|
21
|
+
"oxlint": "^1.50.0",
|
|
22
|
+
"vite": "^5.4.2"
|
|
23
|
+
},
|
|
24
|
+
"peerDependencies": {
|
|
25
|
+
"vite": "^4.0.0"
|
|
26
|
+
},
|
|
27
|
+
"publishConfig": {
|
|
28
|
+
"access": "public"
|
|
29
|
+
},
|
|
30
|
+
"exports": {
|
|
31
|
+
".": {
|
|
32
|
+
"types": "./dist/index.d.ts",
|
|
33
|
+
"default": "./dist/index.js"
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"bugs": {
|
|
37
|
+
"url": "https://github.com/jk2908/mdsrc/issues"
|
|
38
|
+
},
|
|
39
|
+
"description": "A Vite plugin for managing markdown content with type safety",
|
|
40
|
+
"files": [
|
|
41
|
+
"dist/*.js",
|
|
42
|
+
"dist/*.d.ts"
|
|
43
|
+
],
|
|
44
|
+
"homepage": "https://github.com/jk2908/mdsrc#readme",
|
|
45
|
+
"keywords": [
|
|
46
|
+
"markdown",
|
|
47
|
+
"vite-plugin",
|
|
48
|
+
"typescript",
|
|
49
|
+
"content-management",
|
|
50
|
+
"frontmatter"
|
|
51
|
+
],
|
|
52
|
+
"license": "MIT"
|
|
53
|
+
}
|