compmark-vue 0.1.3 → 0.2.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.
package/dist/cli.mjs CHANGED
@@ -3,6 +3,25 @@ import { existsSync, readFileSync, writeFileSync } from "node:fs";
3
3
  import { join, resolve } from "node:path";
4
4
  import { compileScript, parse } from "@vue/compiler-sfc";
5
5
  //#region src/parser.ts
6
+ function parseJSDocTags(comments) {
7
+ const result = { description: "" };
8
+ const descLines = [];
9
+ for (let i = comments.length - 1; i >= 0; i--) {
10
+ const c = comments[i];
11
+ if (c.type !== "CommentBlock") continue;
12
+ const lines = c.value.split("\n").map((l) => l.replace(/^\s*\*\s?/, "").trim()).filter((l) => l && !l.startsWith("/"));
13
+ for (const line of lines) if (line.startsWith("@deprecated")) result.deprecated = line.replace(/^@deprecated\s*/, "").trim() || true;
14
+ else if (line.startsWith("@since")) result.since = line.replace(/^@since\s*/, "").trim();
15
+ else if (line.startsWith("@example")) result.example = line.replace(/^@example\s*/, "").trim();
16
+ else if (line.startsWith("@see")) result.see = line.replace(/^@see\s*/, "").trim();
17
+ else if (line.startsWith("@default")) result.defaultOverride = line.replace(/^@default\s*/, "").trim();
18
+ else if (line.startsWith("@internal")) result.internal = true;
19
+ else if (!line.startsWith("@")) descLines.push(line);
20
+ break;
21
+ }
22
+ result.description = descLines.join(" ");
23
+ return result;
24
+ }
6
25
  function parseSFC(source, filename) {
7
26
  const doc = {
8
27
  name: filename.replace(/\.vue$/, "").split("/").pop() ?? "Unknown",
@@ -12,17 +31,60 @@ function parseSFC(source, filename) {
12
31
  const { descriptor } = parse(source, { filename });
13
32
  if (!descriptor.scriptSetup && !descriptor.script) return doc;
14
33
  const compiled = compileScript(descriptor, { id: filename });
15
- const ast = compiled.scriptSetupAst;
16
- if (!ast) return doc;
17
- const scriptSource = descriptor.scriptSetup?.content ?? compiled.content;
18
- for (const stmt of ast) {
19
- const calls = extractDefineCalls(stmt);
20
- for (const { callee, args, leadingComments, typeParams, defaultsArg } of calls) if (callee === "defineProps" && args[0]?.type === "ObjectExpression") doc.props = extractProps(args[0], scriptSource);
21
- else if (callee === "defineProps" && typeParams?.params[0]?.type === "TSTypeLiteral") doc.props = extractTypeProps(typeParams.params[0], defaultsArg, scriptSource);
22
- else if (callee === "defineEmits" && args[0]?.type === "ArrayExpression") doc.emits = extractEmits(args[0], leadingComments);
34
+ const componentJSDoc = extractComponentJSDoc(compiled.scriptSetupAst ?? compiled.scriptAst ?? []);
35
+ doc.description = componentJSDoc.description;
36
+ doc.internal = componentJSDoc.internal;
37
+ const setupAst = compiled.scriptSetupAst;
38
+ if (setupAst) {
39
+ const scriptSource = descriptor.scriptSetup?.content ?? compiled.content;
40
+ for (const stmt of setupAst) {
41
+ const calls = extractDefineCalls(stmt);
42
+ for (const { callee, args, leadingComments, typeParams, defaultsArg } of calls) if (callee === "defineProps" && args[0]?.type === "ObjectExpression") doc.props = extractProps(args[0], scriptSource);
43
+ else if (callee === "defineProps" && typeParams?.params[0]?.type === "TSTypeLiteral") doc.props = extractTypeProps(typeParams.params[0], defaultsArg, scriptSource);
44
+ else if (callee === "defineEmits" && args[0]?.type === "ArrayExpression") doc.emits = extractEmits(args[0], leadingComments);
45
+ else if (callee === "defineEmits" && typeParams?.params[0]?.type === "TSTypeLiteral") doc.emits = extractTypeEmits(typeParams.params[0]);
46
+ else if (callee === "defineSlots" && typeParams?.params[0]?.type === "TSTypeLiteral") doc.slots = extractTypeSlots(typeParams.params[0]);
47
+ else if (callee === "defineExpose" && args[0]?.type === "ObjectExpression") doc.exposes = extractExposes(args[0], scriptSource);
48
+ }
49
+ doc.composables = extractComposables(setupAst);
50
+ }
51
+ const scriptAst = compiled.scriptAst;
52
+ if (scriptAst && doc.props.length === 0 && doc.emits.length === 0) {
53
+ const optionsDoc = extractOptionsAPI(scriptAst, compiled.content);
54
+ doc.props = optionsDoc.props;
55
+ doc.emits = optionsDoc.emits;
56
+ }
57
+ if (descriptor.template?.ast) {
58
+ const templateSlots = extractTemplateSlots(descriptor.template.ast);
59
+ doc.slots = mergeSlots(doc.slots ?? [], templateSlots);
23
60
  }
24
61
  return doc;
25
62
  }
63
+ function extractComponentJSDoc(ast) {
64
+ if (ast.length === 0) return {
65
+ description: "",
66
+ internal: false
67
+ };
68
+ const comments = ast[0].leadingComments ?? [];
69
+ if (comments.length === 0) return {
70
+ description: "",
71
+ internal: false
72
+ };
73
+ const firstComment = comments[0];
74
+ if (firstComment.type !== "CommentBlock") return {
75
+ description: "",
76
+ internal: false
77
+ };
78
+ const result = parseJSDocTags([firstComment]);
79
+ if (result.internal) return {
80
+ description: result.description,
81
+ internal: true
82
+ };
83
+ return {
84
+ description: "",
85
+ internal: false
86
+ };
87
+ }
26
88
  function processCallExpression(callExpr, leadingComments) {
27
89
  if (callExpr.callee.type === "Identifier" && callExpr.callee.name === "withDefaults" && callExpr.arguments[0]?.type === "CallExpression" && callExpr.arguments[0].callee.type === "Identifier" && callExpr.arguments[0].callee.name === "defineProps") {
28
90
  const innerCall = callExpr.arguments[0];
@@ -58,13 +120,17 @@ function extractTypeProps(typeLiteral, defaultsArg, source) {
58
120
  const name = member.key.type === "Identifier" ? member.key.name : member.key.type === "StringLiteral" ? member.key.value : "";
59
121
  if (!name) continue;
60
122
  const type = member.typeAnnotation?.typeAnnotation ? resolveTypeString(member.typeAnnotation.typeAnnotation) : "unknown";
61
- const description = extractJSDoc(member.leadingComments ?? []);
123
+ const jsdoc = parseJSDocTags(member.leadingComments ?? []);
62
124
  props.push({
63
125
  name,
64
126
  type,
65
127
  required: !member.optional,
66
128
  default: defaults.get(name),
67
- description
129
+ description: jsdoc.description,
130
+ ...jsdoc.deprecated !== void 0 && { deprecated: jsdoc.deprecated },
131
+ ...jsdoc.since && { since: jsdoc.since },
132
+ ...jsdoc.example && { example: jsdoc.example },
133
+ ...jsdoc.see && { see: jsdoc.see }
68
134
  });
69
135
  }
70
136
  return props;
@@ -76,6 +142,9 @@ function resolveTypeString(node) {
76
142
  case "TSBooleanKeyword": return "boolean";
77
143
  case "TSObjectKeyword": return "object";
78
144
  case "TSAnyKeyword": return "any";
145
+ case "TSVoidKeyword": return "void";
146
+ case "TSNullKeyword": return "null";
147
+ case "TSUndefinedKeyword": return "undefined";
79
148
  case "TSUnionType": return node.types.map((t) => resolveTypeString(t)).join(" | ");
80
149
  case "TSIntersectionType": return node.types.map((t) => resolveTypeString(t)).join(" & ");
81
150
  case "TSLiteralType":
@@ -90,6 +159,9 @@ function resolveTypeString(node) {
90
159
  return name;
91
160
  }
92
161
  case "TSFunctionType": return "Function";
162
+ case "TSTupleType": return `[${node.elementTypes.map((t) => resolveTypeString(t)).join(", ")}]`;
163
+ case "TSNamedTupleMember": return resolveTypeString(node.elementType);
164
+ case "TSParenthesizedType": return resolveTypeString(node.typeAnnotation);
93
165
  default: return "unknown";
94
166
  }
95
167
  }
@@ -110,13 +182,20 @@ function extractProps(obj, source) {
110
182
  const p = prop;
111
183
  const name = p.key.type === "Identifier" ? p.key.name : p.key.type === "StringLiteral" ? p.key.value : "";
112
184
  if (!name) continue;
113
- const description = extractJSDoc(p.leadingComments ?? []);
185
+ const jsdoc = parseJSDocTags(p.leadingComments ?? []);
186
+ const tagFields = {
187
+ ...jsdoc.deprecated !== void 0 && { deprecated: jsdoc.deprecated },
188
+ ...jsdoc.since && { since: jsdoc.since },
189
+ ...jsdoc.example && { example: jsdoc.example },
190
+ ...jsdoc.see && { see: jsdoc.see }
191
+ };
114
192
  if (p.value.type === "Identifier") props.push({
115
193
  name,
116
194
  type: p.value.name,
117
195
  required: false,
118
196
  default: void 0,
119
- description
197
+ description: jsdoc.description,
198
+ ...tagFields
120
199
  });
121
200
  else if (p.value.type === "ArrayExpression") {
122
201
  const types = p.value.elements.filter((el) => el?.type === "Identifier").map((el) => el.name);
@@ -125,7 +204,8 @@ function extractProps(obj, source) {
125
204
  type: types.join(" | "),
126
205
  required: false,
127
206
  default: void 0,
128
- description
207
+ description: jsdoc.description,
208
+ ...tagFields
129
209
  });
130
210
  } else if (p.value.type === "ObjectExpression") {
131
211
  let type = "unknown";
@@ -145,7 +225,8 @@ function extractProps(obj, source) {
145
225
  type,
146
226
  required,
147
227
  default: defaultVal,
148
- description
228
+ description: jsdoc.description,
229
+ ...tagFields
149
230
  });
150
231
  }
151
232
  }
@@ -172,14 +253,188 @@ function parseEmitJSDoc(comments) {
172
253
  }
173
254
  return map;
174
255
  }
175
- function extractJSDoc(comments) {
176
- for (let i = comments.length - 1; i >= 0; i--) {
177
- const c = comments[i];
178
- if (c.type !== "CommentBlock") continue;
179
- const lines = c.value.split("\n").map((l) => l.replace(/^\s*\*\s?/, "").trim()).filter((l) => l && !l.startsWith("@") && !l.startsWith("/"));
180
- if (lines.length > 0) return lines.join(" ");
256
+ function extractTypeEmits(typeLiteral) {
257
+ const emits = [];
258
+ for (const member of typeLiteral.members) if (member.type === "TSPropertySignature") {
259
+ const name = member.key.type === "Identifier" ? member.key.name : member.key.type === "StringLiteral" ? member.key.value : "";
260
+ if (!name) continue;
261
+ const jsdoc = parseJSDocTags(member.leadingComments ?? []);
262
+ let payload;
263
+ const typeAnnotation = member.typeAnnotation?.typeAnnotation;
264
+ if (typeAnnotation?.type === "TSTupleType") payload = typeAnnotation.elementTypes.map((el) => {
265
+ if (el.type === "TSNamedTupleMember") return `${el.label?.name ?? "arg"}: ${resolveTypeString(el.elementType)}`;
266
+ return resolveTypeString(el);
267
+ }).join(", ");
268
+ emits.push({
269
+ name,
270
+ description: jsdoc.description,
271
+ ...payload && { payload }
272
+ });
273
+ } else if (member.type === "TSCallSignatureDeclaration") {
274
+ const params = member.parameters ?? [];
275
+ if (params.length === 0) continue;
276
+ const firstParam = params[0];
277
+ if (firstParam?.typeAnnotation?.typeAnnotation?.type !== "TSLiteralType" || firstParam.typeAnnotation.typeAnnotation.literal.type !== "StringLiteral") continue;
278
+ const name = firstParam.typeAnnotation.typeAnnotation.literal.value;
279
+ const jsdoc = parseJSDocTags(member.leadingComments ?? []);
280
+ const payloadParams = params.slice(1);
281
+ let payload;
282
+ if (payloadParams.length > 0) payload = payloadParams.map((p) => {
283
+ return `${p.type === "Identifier" ? p.name : "arg"}: ${p.typeAnnotation?.typeAnnotation ? resolveTypeString(p.typeAnnotation.typeAnnotation) : "unknown"}`;
284
+ }).join(", ");
285
+ emits.push({
286
+ name,
287
+ description: jsdoc.description,
288
+ ...payload && { payload }
289
+ });
290
+ }
291
+ return emits;
292
+ }
293
+ function extractTypeSlots(typeLiteral) {
294
+ const slots = [];
295
+ for (const member of typeLiteral.members) {
296
+ const name = member.key?.type === "Identifier" ? member.key.name : member.key?.type === "StringLiteral" ? member.key.value : "";
297
+ if (!name) continue;
298
+ const jsdoc = parseJSDocTags(member.leadingComments ?? []);
299
+ const bindings = [];
300
+ if (member.type === "TSMethodSignature" && member.parameters?.length > 0) {
301
+ const propsType = member.parameters[0]?.typeAnnotation?.typeAnnotation;
302
+ if (propsType?.type === "TSTypeLiteral") {
303
+ for (const prop of propsType.members) if (prop.type === "TSPropertySignature") {
304
+ const propName = prop.key.type === "Identifier" ? prop.key.name : prop.key.type === "StringLiteral" ? prop.key.value : "";
305
+ const propType = prop.typeAnnotation?.typeAnnotation ? resolveTypeString(prop.typeAnnotation.typeAnnotation) : "unknown";
306
+ if (propName) bindings.push(`${propName}: ${propType}`);
307
+ }
308
+ }
309
+ }
310
+ slots.push({
311
+ name,
312
+ description: jsdoc.description,
313
+ bindings
314
+ });
315
+ }
316
+ return slots;
317
+ }
318
+ function extractExposes(obj, _source) {
319
+ const exposes = [];
320
+ for (const prop of obj.properties) {
321
+ if (prop.type !== "ObjectProperty" && prop.type !== "ObjectMethod") continue;
322
+ const key = prop.key;
323
+ const name = key.type === "Identifier" ? key.name : key.type === "StringLiteral" ? key.value : "";
324
+ if (!name) continue;
325
+ const jsdoc = parseJSDocTags(prop.leadingComments ?? []);
326
+ exposes.push({
327
+ name,
328
+ type: "unknown",
329
+ description: jsdoc.description
330
+ });
331
+ }
332
+ return exposes;
333
+ }
334
+ function extractComposables(ast) {
335
+ const seen = /* @__PURE__ */ new Set();
336
+ const composables = [];
337
+ for (const stmt of ast) {
338
+ const callNames = extractComposableCallNames(stmt);
339
+ for (const name of callNames) if (!seen.has(name)) {
340
+ seen.add(name);
341
+ composables.push({ name });
342
+ }
181
343
  }
182
- return "";
344
+ return composables;
345
+ }
346
+ function extractComposableCallNames(stmt) {
347
+ const names = [];
348
+ if (stmt.type === "ExpressionStatement" && stmt.expression.type === "CallExpression" && stmt.expression.callee.type === "Identifier" && /^use[A-Z]/.test(stmt.expression.callee.name)) names.push(stmt.expression.callee.name);
349
+ if (stmt.type === "VariableDeclaration") {
350
+ for (const decl of stmt.declarations) if (decl.init?.type === "CallExpression" && decl.init.callee.type === "Identifier" && /^use[A-Z]/.test(decl.init.callee.name)) names.push(decl.init.callee.name);
351
+ }
352
+ return names;
353
+ }
354
+ function extractTemplateSlots(templateAst) {
355
+ const slots = [];
356
+ walkTemplate(templateAst.children ?? [], slots);
357
+ return slots;
358
+ }
359
+ function walkTemplate(children, slots) {
360
+ for (const node of children) {
361
+ if (node.type === 1 && node.tag === "slot") {
362
+ let name = "default";
363
+ const bindings = [];
364
+ for (const prop of node.props ?? []) {
365
+ if (prop.type === 6 && prop.name === "name" && prop.value?.content) name = prop.value.content;
366
+ if (prop.type === 7 && prop.name === "bind" && prop.arg?.content) bindings.push(prop.arg.content);
367
+ }
368
+ slots.push({
369
+ name,
370
+ description: "",
371
+ bindings
372
+ });
373
+ }
374
+ if (node.children) walkTemplate(node.children, slots);
375
+ if (node.branches) {
376
+ for (const branch of node.branches) if (branch.children) walkTemplate(branch.children, slots);
377
+ }
378
+ }
379
+ }
380
+ function mergeSlots(typedSlots, templateSlots) {
381
+ const merged = [...typedSlots];
382
+ const typedNames = new Set(typedSlots.map((s) => s.name));
383
+ for (const ts of templateSlots) if (!typedNames.has(ts.name)) merged.push(ts);
384
+ return merged;
385
+ }
386
+ function extractOptionsAPI(ast, source) {
387
+ let props = [];
388
+ let emits = [];
389
+ for (const stmt of ast) {
390
+ if (stmt.type !== "ExportDefaultDeclaration") continue;
391
+ const decl = stmt.declaration;
392
+ if (decl.type !== "ObjectExpression") continue;
393
+ for (const prop of decl.properties) {
394
+ if (prop.type !== "ObjectProperty") continue;
395
+ const key = prop.key;
396
+ const name = key.type === "Identifier" ? key.name : "";
397
+ if (name === "props") {
398
+ if (prop.value.type === "ObjectExpression") props = extractProps(prop.value, source);
399
+ else if (prop.value.type === "ArrayExpression") props = extractArrayProps(prop.value);
400
+ } else if (name === "emits") {
401
+ if (prop.value.type === "ArrayExpression") {
402
+ const comments = stmt.leadingComments ?? [];
403
+ emits = extractEmits(prop.value, comments);
404
+ } else if (prop.value.type === "ObjectExpression") emits = extractObjectEmits(prop.value);
405
+ }
406
+ }
407
+ break;
408
+ }
409
+ return {
410
+ props,
411
+ emits
412
+ };
413
+ }
414
+ function extractArrayProps(arr) {
415
+ const props = [];
416
+ for (const el of arr.elements) if (el?.type === "StringLiteral") props.push({
417
+ name: el.value,
418
+ type: "unknown",
419
+ required: false,
420
+ default: void 0,
421
+ description: ""
422
+ });
423
+ return props;
424
+ }
425
+ function extractObjectEmits(obj) {
426
+ const emits = [];
427
+ for (const prop of obj.properties) {
428
+ if (prop.type !== "ObjectProperty") continue;
429
+ const name = prop.key.type === "Identifier" ? prop.key.name : prop.key.type === "StringLiteral" ? prop.key.value : "";
430
+ if (!name) continue;
431
+ const jsdoc = parseJSDocTags(prop.leadingComments ?? []);
432
+ emits.push({
433
+ name,
434
+ description: jsdoc.description
435
+ });
436
+ }
437
+ return emits;
183
438
  }
184
439
  function stringifyDefault(node, source) {
185
440
  switch (node.type) {
@@ -198,22 +453,46 @@ function stringifyDefault(node, source) {
198
453
  //#region src/markdown.ts
199
454
  function generateMarkdown(doc) {
200
455
  const sections = [`# ${doc.name}`];
201
- if (doc.props.length === 0 && doc.emits.length === 0) {
202
- sections.push("", "No documentable props or emits found.");
456
+ if (doc.description) sections.push("", doc.description);
457
+ const hasProps = doc.props.length > 0;
458
+ const hasEmits = doc.emits.length > 0;
459
+ const hasSlots = (doc.slots?.length ?? 0) > 0;
460
+ const hasExposes = (doc.exposes?.length ?? 0) > 0;
461
+ const hasComposables = (doc.composables?.length ?? 0) > 0;
462
+ if (!hasProps && !hasEmits && !hasSlots && !hasExposes && !hasComposables) {
463
+ sections.push("", "No documentable API found.");
203
464
  return sections.join("\n") + "\n";
204
465
  }
205
- if (doc.props.length > 0) {
466
+ if (hasProps) {
206
467
  sections.push("", "## Props", "");
207
468
  sections.push("| Name | Type | Required | Default | Description |");
208
469
  sections.push("| --- | --- | --- | --- | --- |");
470
+ const examples = [];
209
471
  for (const p of doc.props) {
210
472
  const def = p.default !== void 0 ? `\`${p.default}\`` : "-";
211
- const desc = p.description || "-";
473
+ let desc = p.description || "-";
474
+ if (p.deprecated) desc += typeof p.deprecated === "string" && p.deprecated ? ` **Deprecated**: ${p.deprecated}` : " **Deprecated**";
475
+ if (p.since) desc += ` *(since ${p.since})*`;
476
+ if (p.see) desc += ` See: ${p.see}`;
212
477
  const req = p.required ? "Yes" : "No";
213
478
  sections.push(`| ${p.name} | ${p.type} | ${req} | ${def} | ${desc} |`);
479
+ if (p.example) examples.push({
480
+ name: p.name,
481
+ example: p.example
482
+ });
214
483
  }
484
+ for (const { name, example } of examples) sections.push("", `**\`${name}\` example:**`, "", "```", example, "```");
215
485
  }
216
- if (doc.emits.length > 0) {
486
+ if (hasEmits) if (doc.emits.some((e) => e.payload)) {
487
+ sections.push("", "## Emits", "");
488
+ sections.push("| Name | Payload | Description |");
489
+ sections.push("| --- | --- | --- |");
490
+ for (const e of doc.emits) {
491
+ const desc = e.description || "-";
492
+ const payload = e.payload || "-";
493
+ sections.push(`| ${e.name} | ${payload} | ${desc} |`);
494
+ }
495
+ } else {
217
496
  sections.push("", "## Emits", "");
218
497
  sections.push("| Name | Description |");
219
498
  sections.push("| --- | --- |");
@@ -222,6 +501,29 @@ function generateMarkdown(doc) {
222
501
  sections.push(`| ${e.name} | ${desc} |`);
223
502
  }
224
503
  }
504
+ if (hasSlots) {
505
+ sections.push("", "## Slots", "");
506
+ sections.push("| Name | Bindings | Description |");
507
+ sections.push("| --- | --- | --- |");
508
+ for (const s of doc.slots) {
509
+ const desc = s.description || "-";
510
+ const bindings = s.bindings.length > 0 ? s.bindings.join(", ") : "-";
511
+ sections.push(`| ${s.name} | ${bindings} | ${desc} |`);
512
+ }
513
+ }
514
+ if (hasExposes) {
515
+ sections.push("", "## Exposed", "");
516
+ sections.push("| Name | Type | Description |");
517
+ sections.push("| --- | --- | --- |");
518
+ for (const e of doc.exposes) {
519
+ const desc = e.description || "-";
520
+ sections.push(`| ${e.name} | ${e.type} | ${desc} |`);
521
+ }
522
+ }
523
+ if (hasComposables) {
524
+ sections.push("", "## Composables Used", "");
525
+ for (const c of doc.composables) sections.push(`- \`${c.name}\``);
526
+ }
225
527
  return sections.join("\n") + "\n";
226
528
  }
227
529
  //#endregion
@@ -234,7 +536,7 @@ function parseComponent(filePath) {
234
536
  //#region src/cli.ts
235
537
  const filePath = process.argv[2];
236
538
  if (!filePath) {
237
- console.error("Usage: compmark-vue <path-to-component.vue>");
539
+ console.error("Usage: compmark <path-to-component.vue>");
238
540
  process.exit(1);
239
541
  }
240
542
  if (!filePath.endsWith(".vue")) {
@@ -248,6 +550,11 @@ if (!existsSync(abs)) {
248
550
  }
249
551
  try {
250
552
  const doc = parseComponent(abs);
553
+ if (doc.internal) {
554
+ const name = abs.split("/").pop() ?? filePath;
555
+ console.log(`Skipped ${name} (marked @internal)`);
556
+ process.exit(0);
557
+ }
251
558
  const md = generateMarkdown(doc);
252
559
  const outFile = `${doc.name}.md`;
253
560
  writeFileSync(join(process.cwd(), outFile), md, "utf-8");
package/dist/index.d.mts CHANGED
@@ -5,15 +5,38 @@ interface PropDoc {
5
5
  required: boolean;
6
6
  default: string | undefined;
7
7
  description: string;
8
+ deprecated?: string | boolean;
9
+ since?: string;
10
+ example?: string;
11
+ see?: string;
8
12
  }
9
13
  interface EmitDoc {
10
14
  name: string;
11
15
  description: string;
16
+ payload?: string;
17
+ }
18
+ interface SlotDoc {
19
+ name: string;
20
+ description: string;
21
+ bindings: string[];
22
+ }
23
+ interface ExposeDoc {
24
+ name: string;
25
+ type: string;
26
+ description: string;
27
+ }
28
+ interface ComposableDoc {
29
+ name: string;
12
30
  }
13
31
  interface ComponentDoc {
14
32
  name: string;
33
+ description?: string;
34
+ internal?: boolean;
15
35
  props: PropDoc[];
16
36
  emits: EmitDoc[];
37
+ slots?: SlotDoc[];
38
+ exposes?: ExposeDoc[];
39
+ composables?: ComposableDoc[];
17
40
  }
18
41
  //#endregion
19
42
  //#region src/parser.d.ts
@@ -25,4 +48,4 @@ declare function generateMarkdown(doc: ComponentDoc): string;
25
48
  //#region src/index.d.ts
26
49
  declare function parseComponent(filePath: string): ComponentDoc;
27
50
  //#endregion
28
- export { type ComponentDoc, type EmitDoc, type PropDoc, generateMarkdown, parseComponent, parseSFC };
51
+ export { type ComponentDoc, type ComposableDoc, type EmitDoc, type ExposeDoc, type PropDoc, type SlotDoc, generateMarkdown, parseComponent, parseSFC };
package/dist/index.mjs CHANGED
@@ -2,6 +2,25 @@ import { readFileSync } from "node:fs";
2
2
  import { resolve } from "node:path";
3
3
  import { compileScript, parse } from "@vue/compiler-sfc";
4
4
  //#region src/parser.ts
5
+ function parseJSDocTags(comments) {
6
+ const result = { description: "" };
7
+ const descLines = [];
8
+ for (let i = comments.length - 1; i >= 0; i--) {
9
+ const c = comments[i];
10
+ if (c.type !== "CommentBlock") continue;
11
+ const lines = c.value.split("\n").map((l) => l.replace(/^\s*\*\s?/, "").trim()).filter((l) => l && !l.startsWith("/"));
12
+ for (const line of lines) if (line.startsWith("@deprecated")) result.deprecated = line.replace(/^@deprecated\s*/, "").trim() || true;
13
+ else if (line.startsWith("@since")) result.since = line.replace(/^@since\s*/, "").trim();
14
+ else if (line.startsWith("@example")) result.example = line.replace(/^@example\s*/, "").trim();
15
+ else if (line.startsWith("@see")) result.see = line.replace(/^@see\s*/, "").trim();
16
+ else if (line.startsWith("@default")) result.defaultOverride = line.replace(/^@default\s*/, "").trim();
17
+ else if (line.startsWith("@internal")) result.internal = true;
18
+ else if (!line.startsWith("@")) descLines.push(line);
19
+ break;
20
+ }
21
+ result.description = descLines.join(" ");
22
+ return result;
23
+ }
5
24
  function parseSFC(source, filename) {
6
25
  const doc = {
7
26
  name: filename.replace(/\.vue$/, "").split("/").pop() ?? "Unknown",
@@ -11,17 +30,60 @@ function parseSFC(source, filename) {
11
30
  const { descriptor } = parse(source, { filename });
12
31
  if (!descriptor.scriptSetup && !descriptor.script) return doc;
13
32
  const compiled = compileScript(descriptor, { id: filename });
14
- const ast = compiled.scriptSetupAst;
15
- if (!ast) return doc;
16
- const scriptSource = descriptor.scriptSetup?.content ?? compiled.content;
17
- for (const stmt of ast) {
18
- const calls = extractDefineCalls(stmt);
19
- for (const { callee, args, leadingComments, typeParams, defaultsArg } of calls) if (callee === "defineProps" && args[0]?.type === "ObjectExpression") doc.props = extractProps(args[0], scriptSource);
20
- else if (callee === "defineProps" && typeParams?.params[0]?.type === "TSTypeLiteral") doc.props = extractTypeProps(typeParams.params[0], defaultsArg, scriptSource);
21
- else if (callee === "defineEmits" && args[0]?.type === "ArrayExpression") doc.emits = extractEmits(args[0], leadingComments);
33
+ const componentJSDoc = extractComponentJSDoc(compiled.scriptSetupAst ?? compiled.scriptAst ?? []);
34
+ doc.description = componentJSDoc.description;
35
+ doc.internal = componentJSDoc.internal;
36
+ const setupAst = compiled.scriptSetupAst;
37
+ if (setupAst) {
38
+ const scriptSource = descriptor.scriptSetup?.content ?? compiled.content;
39
+ for (const stmt of setupAst) {
40
+ const calls = extractDefineCalls(stmt);
41
+ for (const { callee, args, leadingComments, typeParams, defaultsArg } of calls) if (callee === "defineProps" && args[0]?.type === "ObjectExpression") doc.props = extractProps(args[0], scriptSource);
42
+ else if (callee === "defineProps" && typeParams?.params[0]?.type === "TSTypeLiteral") doc.props = extractTypeProps(typeParams.params[0], defaultsArg, scriptSource);
43
+ else if (callee === "defineEmits" && args[0]?.type === "ArrayExpression") doc.emits = extractEmits(args[0], leadingComments);
44
+ else if (callee === "defineEmits" && typeParams?.params[0]?.type === "TSTypeLiteral") doc.emits = extractTypeEmits(typeParams.params[0]);
45
+ else if (callee === "defineSlots" && typeParams?.params[0]?.type === "TSTypeLiteral") doc.slots = extractTypeSlots(typeParams.params[0]);
46
+ else if (callee === "defineExpose" && args[0]?.type === "ObjectExpression") doc.exposes = extractExposes(args[0], scriptSource);
47
+ }
48
+ doc.composables = extractComposables(setupAst);
49
+ }
50
+ const scriptAst = compiled.scriptAst;
51
+ if (scriptAst && doc.props.length === 0 && doc.emits.length === 0) {
52
+ const optionsDoc = extractOptionsAPI(scriptAst, compiled.content);
53
+ doc.props = optionsDoc.props;
54
+ doc.emits = optionsDoc.emits;
55
+ }
56
+ if (descriptor.template?.ast) {
57
+ const templateSlots = extractTemplateSlots(descriptor.template.ast);
58
+ doc.slots = mergeSlots(doc.slots ?? [], templateSlots);
22
59
  }
23
60
  return doc;
24
61
  }
62
+ function extractComponentJSDoc(ast) {
63
+ if (ast.length === 0) return {
64
+ description: "",
65
+ internal: false
66
+ };
67
+ const comments = ast[0].leadingComments ?? [];
68
+ if (comments.length === 0) return {
69
+ description: "",
70
+ internal: false
71
+ };
72
+ const firstComment = comments[0];
73
+ if (firstComment.type !== "CommentBlock") return {
74
+ description: "",
75
+ internal: false
76
+ };
77
+ const result = parseJSDocTags([firstComment]);
78
+ if (result.internal) return {
79
+ description: result.description,
80
+ internal: true
81
+ };
82
+ return {
83
+ description: "",
84
+ internal: false
85
+ };
86
+ }
25
87
  function processCallExpression(callExpr, leadingComments) {
26
88
  if (callExpr.callee.type === "Identifier" && callExpr.callee.name === "withDefaults" && callExpr.arguments[0]?.type === "CallExpression" && callExpr.arguments[0].callee.type === "Identifier" && callExpr.arguments[0].callee.name === "defineProps") {
27
89
  const innerCall = callExpr.arguments[0];
@@ -57,13 +119,17 @@ function extractTypeProps(typeLiteral, defaultsArg, source) {
57
119
  const name = member.key.type === "Identifier" ? member.key.name : member.key.type === "StringLiteral" ? member.key.value : "";
58
120
  if (!name) continue;
59
121
  const type = member.typeAnnotation?.typeAnnotation ? resolveTypeString(member.typeAnnotation.typeAnnotation) : "unknown";
60
- const description = extractJSDoc(member.leadingComments ?? []);
122
+ const jsdoc = parseJSDocTags(member.leadingComments ?? []);
61
123
  props.push({
62
124
  name,
63
125
  type,
64
126
  required: !member.optional,
65
127
  default: defaults.get(name),
66
- description
128
+ description: jsdoc.description,
129
+ ...jsdoc.deprecated !== void 0 && { deprecated: jsdoc.deprecated },
130
+ ...jsdoc.since && { since: jsdoc.since },
131
+ ...jsdoc.example && { example: jsdoc.example },
132
+ ...jsdoc.see && { see: jsdoc.see }
67
133
  });
68
134
  }
69
135
  return props;
@@ -75,6 +141,9 @@ function resolveTypeString(node) {
75
141
  case "TSBooleanKeyword": return "boolean";
76
142
  case "TSObjectKeyword": return "object";
77
143
  case "TSAnyKeyword": return "any";
144
+ case "TSVoidKeyword": return "void";
145
+ case "TSNullKeyword": return "null";
146
+ case "TSUndefinedKeyword": return "undefined";
78
147
  case "TSUnionType": return node.types.map((t) => resolveTypeString(t)).join(" | ");
79
148
  case "TSIntersectionType": return node.types.map((t) => resolveTypeString(t)).join(" & ");
80
149
  case "TSLiteralType":
@@ -89,6 +158,9 @@ function resolveTypeString(node) {
89
158
  return name;
90
159
  }
91
160
  case "TSFunctionType": return "Function";
161
+ case "TSTupleType": return `[${node.elementTypes.map((t) => resolveTypeString(t)).join(", ")}]`;
162
+ case "TSNamedTupleMember": return resolveTypeString(node.elementType);
163
+ case "TSParenthesizedType": return resolveTypeString(node.typeAnnotation);
92
164
  default: return "unknown";
93
165
  }
94
166
  }
@@ -109,13 +181,20 @@ function extractProps(obj, source) {
109
181
  const p = prop;
110
182
  const name = p.key.type === "Identifier" ? p.key.name : p.key.type === "StringLiteral" ? p.key.value : "";
111
183
  if (!name) continue;
112
- const description = extractJSDoc(p.leadingComments ?? []);
184
+ const jsdoc = parseJSDocTags(p.leadingComments ?? []);
185
+ const tagFields = {
186
+ ...jsdoc.deprecated !== void 0 && { deprecated: jsdoc.deprecated },
187
+ ...jsdoc.since && { since: jsdoc.since },
188
+ ...jsdoc.example && { example: jsdoc.example },
189
+ ...jsdoc.see && { see: jsdoc.see }
190
+ };
113
191
  if (p.value.type === "Identifier") props.push({
114
192
  name,
115
193
  type: p.value.name,
116
194
  required: false,
117
195
  default: void 0,
118
- description
196
+ description: jsdoc.description,
197
+ ...tagFields
119
198
  });
120
199
  else if (p.value.type === "ArrayExpression") {
121
200
  const types = p.value.elements.filter((el) => el?.type === "Identifier").map((el) => el.name);
@@ -124,7 +203,8 @@ function extractProps(obj, source) {
124
203
  type: types.join(" | "),
125
204
  required: false,
126
205
  default: void 0,
127
- description
206
+ description: jsdoc.description,
207
+ ...tagFields
128
208
  });
129
209
  } else if (p.value.type === "ObjectExpression") {
130
210
  let type = "unknown";
@@ -144,7 +224,8 @@ function extractProps(obj, source) {
144
224
  type,
145
225
  required,
146
226
  default: defaultVal,
147
- description
227
+ description: jsdoc.description,
228
+ ...tagFields
148
229
  });
149
230
  }
150
231
  }
@@ -171,14 +252,188 @@ function parseEmitJSDoc(comments) {
171
252
  }
172
253
  return map;
173
254
  }
174
- function extractJSDoc(comments) {
175
- for (let i = comments.length - 1; i >= 0; i--) {
176
- const c = comments[i];
177
- if (c.type !== "CommentBlock") continue;
178
- const lines = c.value.split("\n").map((l) => l.replace(/^\s*\*\s?/, "").trim()).filter((l) => l && !l.startsWith("@") && !l.startsWith("/"));
179
- if (lines.length > 0) return lines.join(" ");
255
+ function extractTypeEmits(typeLiteral) {
256
+ const emits = [];
257
+ for (const member of typeLiteral.members) if (member.type === "TSPropertySignature") {
258
+ const name = member.key.type === "Identifier" ? member.key.name : member.key.type === "StringLiteral" ? member.key.value : "";
259
+ if (!name) continue;
260
+ const jsdoc = parseJSDocTags(member.leadingComments ?? []);
261
+ let payload;
262
+ const typeAnnotation = member.typeAnnotation?.typeAnnotation;
263
+ if (typeAnnotation?.type === "TSTupleType") payload = typeAnnotation.elementTypes.map((el) => {
264
+ if (el.type === "TSNamedTupleMember") return `${el.label?.name ?? "arg"}: ${resolveTypeString(el.elementType)}`;
265
+ return resolveTypeString(el);
266
+ }).join(", ");
267
+ emits.push({
268
+ name,
269
+ description: jsdoc.description,
270
+ ...payload && { payload }
271
+ });
272
+ } else if (member.type === "TSCallSignatureDeclaration") {
273
+ const params = member.parameters ?? [];
274
+ if (params.length === 0) continue;
275
+ const firstParam = params[0];
276
+ if (firstParam?.typeAnnotation?.typeAnnotation?.type !== "TSLiteralType" || firstParam.typeAnnotation.typeAnnotation.literal.type !== "StringLiteral") continue;
277
+ const name = firstParam.typeAnnotation.typeAnnotation.literal.value;
278
+ const jsdoc = parseJSDocTags(member.leadingComments ?? []);
279
+ const payloadParams = params.slice(1);
280
+ let payload;
281
+ if (payloadParams.length > 0) payload = payloadParams.map((p) => {
282
+ return `${p.type === "Identifier" ? p.name : "arg"}: ${p.typeAnnotation?.typeAnnotation ? resolveTypeString(p.typeAnnotation.typeAnnotation) : "unknown"}`;
283
+ }).join(", ");
284
+ emits.push({
285
+ name,
286
+ description: jsdoc.description,
287
+ ...payload && { payload }
288
+ });
289
+ }
290
+ return emits;
291
+ }
292
+ function extractTypeSlots(typeLiteral) {
293
+ const slots = [];
294
+ for (const member of typeLiteral.members) {
295
+ const name = member.key?.type === "Identifier" ? member.key.name : member.key?.type === "StringLiteral" ? member.key.value : "";
296
+ if (!name) continue;
297
+ const jsdoc = parseJSDocTags(member.leadingComments ?? []);
298
+ const bindings = [];
299
+ if (member.type === "TSMethodSignature" && member.parameters?.length > 0) {
300
+ const propsType = member.parameters[0]?.typeAnnotation?.typeAnnotation;
301
+ if (propsType?.type === "TSTypeLiteral") {
302
+ for (const prop of propsType.members) if (prop.type === "TSPropertySignature") {
303
+ const propName = prop.key.type === "Identifier" ? prop.key.name : prop.key.type === "StringLiteral" ? prop.key.value : "";
304
+ const propType = prop.typeAnnotation?.typeAnnotation ? resolveTypeString(prop.typeAnnotation.typeAnnotation) : "unknown";
305
+ if (propName) bindings.push(`${propName}: ${propType}`);
306
+ }
307
+ }
308
+ }
309
+ slots.push({
310
+ name,
311
+ description: jsdoc.description,
312
+ bindings
313
+ });
314
+ }
315
+ return slots;
316
+ }
317
+ function extractExposes(obj, _source) {
318
+ const exposes = [];
319
+ for (const prop of obj.properties) {
320
+ if (prop.type !== "ObjectProperty" && prop.type !== "ObjectMethod") continue;
321
+ const key = prop.key;
322
+ const name = key.type === "Identifier" ? key.name : key.type === "StringLiteral" ? key.value : "";
323
+ if (!name) continue;
324
+ const jsdoc = parseJSDocTags(prop.leadingComments ?? []);
325
+ exposes.push({
326
+ name,
327
+ type: "unknown",
328
+ description: jsdoc.description
329
+ });
330
+ }
331
+ return exposes;
332
+ }
333
+ function extractComposables(ast) {
334
+ const seen = /* @__PURE__ */ new Set();
335
+ const composables = [];
336
+ for (const stmt of ast) {
337
+ const callNames = extractComposableCallNames(stmt);
338
+ for (const name of callNames) if (!seen.has(name)) {
339
+ seen.add(name);
340
+ composables.push({ name });
341
+ }
180
342
  }
181
- return "";
343
+ return composables;
344
+ }
345
+ function extractComposableCallNames(stmt) {
346
+ const names = [];
347
+ if (stmt.type === "ExpressionStatement" && stmt.expression.type === "CallExpression" && stmt.expression.callee.type === "Identifier" && /^use[A-Z]/.test(stmt.expression.callee.name)) names.push(stmt.expression.callee.name);
348
+ if (stmt.type === "VariableDeclaration") {
349
+ for (const decl of stmt.declarations) if (decl.init?.type === "CallExpression" && decl.init.callee.type === "Identifier" && /^use[A-Z]/.test(decl.init.callee.name)) names.push(decl.init.callee.name);
350
+ }
351
+ return names;
352
+ }
353
+ function extractTemplateSlots(templateAst) {
354
+ const slots = [];
355
+ walkTemplate(templateAst.children ?? [], slots);
356
+ return slots;
357
+ }
358
+ function walkTemplate(children, slots) {
359
+ for (const node of children) {
360
+ if (node.type === 1 && node.tag === "slot") {
361
+ let name = "default";
362
+ const bindings = [];
363
+ for (const prop of node.props ?? []) {
364
+ if (prop.type === 6 && prop.name === "name" && prop.value?.content) name = prop.value.content;
365
+ if (prop.type === 7 && prop.name === "bind" && prop.arg?.content) bindings.push(prop.arg.content);
366
+ }
367
+ slots.push({
368
+ name,
369
+ description: "",
370
+ bindings
371
+ });
372
+ }
373
+ if (node.children) walkTemplate(node.children, slots);
374
+ if (node.branches) {
375
+ for (const branch of node.branches) if (branch.children) walkTemplate(branch.children, slots);
376
+ }
377
+ }
378
+ }
379
+ function mergeSlots(typedSlots, templateSlots) {
380
+ const merged = [...typedSlots];
381
+ const typedNames = new Set(typedSlots.map((s) => s.name));
382
+ for (const ts of templateSlots) if (!typedNames.has(ts.name)) merged.push(ts);
383
+ return merged;
384
+ }
385
+ function extractOptionsAPI(ast, source) {
386
+ let props = [];
387
+ let emits = [];
388
+ for (const stmt of ast) {
389
+ if (stmt.type !== "ExportDefaultDeclaration") continue;
390
+ const decl = stmt.declaration;
391
+ if (decl.type !== "ObjectExpression") continue;
392
+ for (const prop of decl.properties) {
393
+ if (prop.type !== "ObjectProperty") continue;
394
+ const key = prop.key;
395
+ const name = key.type === "Identifier" ? key.name : "";
396
+ if (name === "props") {
397
+ if (prop.value.type === "ObjectExpression") props = extractProps(prop.value, source);
398
+ else if (prop.value.type === "ArrayExpression") props = extractArrayProps(prop.value);
399
+ } else if (name === "emits") {
400
+ if (prop.value.type === "ArrayExpression") {
401
+ const comments = stmt.leadingComments ?? [];
402
+ emits = extractEmits(prop.value, comments);
403
+ } else if (prop.value.type === "ObjectExpression") emits = extractObjectEmits(prop.value);
404
+ }
405
+ }
406
+ break;
407
+ }
408
+ return {
409
+ props,
410
+ emits
411
+ };
412
+ }
413
+ function extractArrayProps(arr) {
414
+ const props = [];
415
+ for (const el of arr.elements) if (el?.type === "StringLiteral") props.push({
416
+ name: el.value,
417
+ type: "unknown",
418
+ required: false,
419
+ default: void 0,
420
+ description: ""
421
+ });
422
+ return props;
423
+ }
424
+ function extractObjectEmits(obj) {
425
+ const emits = [];
426
+ for (const prop of obj.properties) {
427
+ if (prop.type !== "ObjectProperty") continue;
428
+ const name = prop.key.type === "Identifier" ? prop.key.name : prop.key.type === "StringLiteral" ? prop.key.value : "";
429
+ if (!name) continue;
430
+ const jsdoc = parseJSDocTags(prop.leadingComments ?? []);
431
+ emits.push({
432
+ name,
433
+ description: jsdoc.description
434
+ });
435
+ }
436
+ return emits;
182
437
  }
183
438
  function stringifyDefault(node, source) {
184
439
  switch (node.type) {
@@ -197,22 +452,46 @@ function stringifyDefault(node, source) {
197
452
  //#region src/markdown.ts
198
453
  function generateMarkdown(doc) {
199
454
  const sections = [`# ${doc.name}`];
200
- if (doc.props.length === 0 && doc.emits.length === 0) {
201
- sections.push("", "No documentable props or emits found.");
455
+ if (doc.description) sections.push("", doc.description);
456
+ const hasProps = doc.props.length > 0;
457
+ const hasEmits = doc.emits.length > 0;
458
+ const hasSlots = (doc.slots?.length ?? 0) > 0;
459
+ const hasExposes = (doc.exposes?.length ?? 0) > 0;
460
+ const hasComposables = (doc.composables?.length ?? 0) > 0;
461
+ if (!hasProps && !hasEmits && !hasSlots && !hasExposes && !hasComposables) {
462
+ sections.push("", "No documentable API found.");
202
463
  return sections.join("\n") + "\n";
203
464
  }
204
- if (doc.props.length > 0) {
465
+ if (hasProps) {
205
466
  sections.push("", "## Props", "");
206
467
  sections.push("| Name | Type | Required | Default | Description |");
207
468
  sections.push("| --- | --- | --- | --- | --- |");
469
+ const examples = [];
208
470
  for (const p of doc.props) {
209
471
  const def = p.default !== void 0 ? `\`${p.default}\`` : "-";
210
- const desc = p.description || "-";
472
+ let desc = p.description || "-";
473
+ if (p.deprecated) desc += typeof p.deprecated === "string" && p.deprecated ? ` **Deprecated**: ${p.deprecated}` : " **Deprecated**";
474
+ if (p.since) desc += ` *(since ${p.since})*`;
475
+ if (p.see) desc += ` See: ${p.see}`;
211
476
  const req = p.required ? "Yes" : "No";
212
477
  sections.push(`| ${p.name} | ${p.type} | ${req} | ${def} | ${desc} |`);
478
+ if (p.example) examples.push({
479
+ name: p.name,
480
+ example: p.example
481
+ });
213
482
  }
483
+ for (const { name, example } of examples) sections.push("", `**\`${name}\` example:**`, "", "```", example, "```");
214
484
  }
215
- if (doc.emits.length > 0) {
485
+ if (hasEmits) if (doc.emits.some((e) => e.payload)) {
486
+ sections.push("", "## Emits", "");
487
+ sections.push("| Name | Payload | Description |");
488
+ sections.push("| --- | --- | --- |");
489
+ for (const e of doc.emits) {
490
+ const desc = e.description || "-";
491
+ const payload = e.payload || "-";
492
+ sections.push(`| ${e.name} | ${payload} | ${desc} |`);
493
+ }
494
+ } else {
216
495
  sections.push("", "## Emits", "");
217
496
  sections.push("| Name | Description |");
218
497
  sections.push("| --- | --- |");
@@ -221,6 +500,29 @@ function generateMarkdown(doc) {
221
500
  sections.push(`| ${e.name} | ${desc} |`);
222
501
  }
223
502
  }
503
+ if (hasSlots) {
504
+ sections.push("", "## Slots", "");
505
+ sections.push("| Name | Bindings | Description |");
506
+ sections.push("| --- | --- | --- |");
507
+ for (const s of doc.slots) {
508
+ const desc = s.description || "-";
509
+ const bindings = s.bindings.length > 0 ? s.bindings.join(", ") : "-";
510
+ sections.push(`| ${s.name} | ${bindings} | ${desc} |`);
511
+ }
512
+ }
513
+ if (hasExposes) {
514
+ sections.push("", "## Exposed", "");
515
+ sections.push("| Name | Type | Description |");
516
+ sections.push("| --- | --- | --- |");
517
+ for (const e of doc.exposes) {
518
+ const desc = e.description || "-";
519
+ sections.push(`| ${e.name} | ${e.type} | ${desc} |`);
520
+ }
521
+ }
522
+ if (hasComposables) {
523
+ sections.push("", "## Composables Used", "");
524
+ for (const c of doc.composables) sections.push(`- \`${c.name}\``);
525
+ }
224
526
  return sections.join("\n") + "\n";
225
527
  }
226
528
  //#endregion
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "compmark-vue",
3
- "version": "0.1.3",
3
+ "version": "0.2.1",
4
4
  "description": "Auto-generate Markdown documentation from Vue 3 SFCs",
5
5
  "license": "MIT",
6
6
  "repository": "noopurphalak/compmark-vue",
7
7
  "bin": {
8
- "compmark-vue": "./dist/cli.mjs"
8
+ "compmark": "./dist/cli.mjs"
9
9
  },
10
10
  "files": [
11
11
  "dist"
@@ -24,7 +24,8 @@
24
24
  "prepack": "pnpm build",
25
25
  "release": "pnpm test && pnpm build && changelogen --release && npm publish && git push --follow-tags",
26
26
  "test": "pnpm lint && pnpm typecheck && vitest run --coverage",
27
- "typecheck": "tsgo --noEmit --skipLibCheck"
27
+ "typecheck": "tsgo --noEmit --skipLibCheck",
28
+ "prepare": "husky"
28
29
  },
29
30
  "dependencies": {
30
31
  "@vue/compiler-sfc": "^3.5.0"
@@ -36,11 +37,19 @@
36
37
  "@vitest/coverage-v8": "latest",
37
38
  "automd": "latest",
38
39
  "changelogen": "latest",
40
+ "husky": "^9.1.7",
41
+ "lint-staged": "^16.3.3",
39
42
  "obuild": "latest",
40
43
  "oxfmt": "latest",
41
44
  "oxlint": "latest",
42
45
  "typescript": "latest",
43
46
  "vitest": "latest"
44
47
  },
48
+ "lint-staged": {
49
+ "*.{ts,vue}": [
50
+ "oxlint --fix",
51
+ "oxfmt"
52
+ ]
53
+ },
45
54
  "packageManager": "pnpm@10.29.3"
46
55
  }