compmark-vue 0.3.3 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +244 -11
- package/dist/cli.mjs +413 -37
- package/dist/index.d.mts +27 -2
- package/dist/index.mjs +277 -9
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { defineCommand, runMain } from "citty";
|
|
3
|
-
import { existsSync, mkdirSync, readFileSync, statSync, watch, writeFileSync } from "node:fs";
|
|
4
|
-
import { dirname, join, resolve } from "node:path";
|
|
3
|
+
import { existsSync, mkdirSync, readFileSync, statSync, unlinkSync, watch, writeFileSync } from "node:fs";
|
|
4
|
+
import { dirname, join, relative, resolve } from "node:path";
|
|
5
5
|
import { glob } from "tinyglobby";
|
|
6
6
|
import { babelParse, compileScript, parse } from "@vue/compiler-sfc";
|
|
7
7
|
//#region src/discovery.ts
|
|
@@ -9,17 +9,44 @@ async function discoverFiles(inputs, ignore) {
|
|
|
9
9
|
const baseIgnore = ["**/node_modules/**"];
|
|
10
10
|
const userIgnore = (ignore ?? []).map(normalizeIgnorePattern);
|
|
11
11
|
const allIgnore = [...baseIgnore, ...userIgnore];
|
|
12
|
+
const hasUserIgnore = userIgnore.length > 0;
|
|
12
13
|
const found = /* @__PURE__ */ new Set();
|
|
14
|
+
let ignoredCount = 0;
|
|
13
15
|
for (const input of inputs) {
|
|
14
16
|
const resolved = resolve(input);
|
|
15
17
|
if (input.endsWith(".vue") && existsSync(resolved)) found.add(resolved);
|
|
16
|
-
else if (isDirectory$1(resolved)) {
|
|
18
|
+
else if (isDirectory$1(resolved)) if (hasUserIgnore) {
|
|
19
|
+
const allFiles = await glob("**/*.vue", {
|
|
20
|
+
cwd: resolved,
|
|
21
|
+
absolute: true,
|
|
22
|
+
ignore: baseIgnore
|
|
23
|
+
});
|
|
24
|
+
const filteredFiles = await glob("**/*.vue", {
|
|
25
|
+
cwd: resolved,
|
|
26
|
+
absolute: true,
|
|
27
|
+
ignore: allIgnore
|
|
28
|
+
});
|
|
29
|
+
ignoredCount += allFiles.length - filteredFiles.length;
|
|
30
|
+
for (const f of filteredFiles) found.add(f);
|
|
31
|
+
} else {
|
|
17
32
|
const files = await glob("**/*.vue", {
|
|
18
33
|
cwd: resolved,
|
|
19
34
|
absolute: true,
|
|
20
35
|
ignore: allIgnore
|
|
21
36
|
});
|
|
22
37
|
for (const f of files) found.add(f);
|
|
38
|
+
}
|
|
39
|
+
else if (hasUserIgnore) {
|
|
40
|
+
const allFiles = await glob(input, {
|
|
41
|
+
absolute: true,
|
|
42
|
+
ignore: baseIgnore
|
|
43
|
+
});
|
|
44
|
+
const filteredFiles = await glob(input, {
|
|
45
|
+
absolute: true,
|
|
46
|
+
ignore: allIgnore
|
|
47
|
+
});
|
|
48
|
+
ignoredCount += allFiles.length - filteredFiles.length;
|
|
49
|
+
for (const f of filteredFiles) found.add(f);
|
|
23
50
|
} else {
|
|
24
51
|
const files = await glob(input, {
|
|
25
52
|
absolute: true,
|
|
@@ -28,7 +55,25 @@ async function discoverFiles(inputs, ignore) {
|
|
|
28
55
|
for (const f of files) found.add(f);
|
|
29
56
|
}
|
|
30
57
|
}
|
|
31
|
-
|
|
58
|
+
const files = [...found].sort();
|
|
59
|
+
const basePath = computeBasePath(files);
|
|
60
|
+
return {
|
|
61
|
+
files,
|
|
62
|
+
ignoredCount,
|
|
63
|
+
basePath
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function computeBasePath(files) {
|
|
67
|
+
if (files.length === 0) return ".";
|
|
68
|
+
if (files.length === 1) return dirname(files[0]);
|
|
69
|
+
const parts = files.map((f) => dirname(f).split("/"));
|
|
70
|
+
const common = [];
|
|
71
|
+
for (let i = 0; i < parts[0].length; i++) {
|
|
72
|
+
const segment = parts[0][i];
|
|
73
|
+
if (parts.every((p) => p[i] === segment)) common.push(segment);
|
|
74
|
+
else break;
|
|
75
|
+
}
|
|
76
|
+
return common.join("/") || "/";
|
|
32
77
|
}
|
|
33
78
|
function isDirectory$1(path) {
|
|
34
79
|
try {
|
|
@@ -209,15 +254,15 @@ function inferType(node) {
|
|
|
209
254
|
if (typeParams?.params?.length > 0) return `Ref<${resolveTypeAnnotation(typeParams.params[0])}>`;
|
|
210
255
|
const arg = node.arguments[0];
|
|
211
256
|
if (!arg) return "Ref<unknown>";
|
|
212
|
-
return `Ref<${inferLiteralType(arg)}>`;
|
|
257
|
+
return `Ref<${inferLiteralType$1(arg)}>`;
|
|
213
258
|
}
|
|
214
259
|
if (node.type === "CallExpression" && node.callee.type === "Identifier" && node.callee.name === "computed") return "ComputedRef";
|
|
215
260
|
if (node.type === "CallExpression" && node.callee.type === "Identifier" && node.callee.name === "reactive") return "Object";
|
|
216
261
|
if (node.type === "ArrowFunctionExpression" || node.type === "FunctionExpression") return inferFunctionSignature(node);
|
|
217
262
|
if (node.type === "CallExpression" && node.callee.type === "Identifier" && /^use[A-Z]/.test(node.callee.name)) return "unknown";
|
|
218
|
-
return inferLiteralType(node);
|
|
263
|
+
return inferLiteralType$1(node);
|
|
219
264
|
}
|
|
220
|
-
function inferLiteralType(node) {
|
|
265
|
+
function inferLiteralType$1(node) {
|
|
221
266
|
switch (node.type) {
|
|
222
267
|
case "NumericLiteral": return "number";
|
|
223
268
|
case "StringLiteral": return "string";
|
|
@@ -430,12 +475,17 @@ function parseSFC(source, filename, sfcDir) {
|
|
|
430
475
|
else if (callee === "defineExpose" && args[0]?.type === "ObjectExpression") doc.exposes = extractExposes(args[0], scriptSource);
|
|
431
476
|
}
|
|
432
477
|
doc.composables = extractComposables(setupAst, importMap, sfcDir);
|
|
478
|
+
const { refs, computeds } = extractRefsAndComputeds(setupAst, scriptSource);
|
|
479
|
+
if (refs.length > 0) doc.refs = refs;
|
|
480
|
+
if (computeds.length > 0) doc.computeds = computeds;
|
|
433
481
|
}
|
|
434
482
|
const scriptAst = compiled.scriptAst;
|
|
435
483
|
if (scriptAst && doc.props.length === 0 && doc.emits.length === 0) {
|
|
436
484
|
const optionsDoc = extractOptionsAPI(scriptAst, compiled.content);
|
|
437
485
|
doc.props = optionsDoc.props;
|
|
438
486
|
doc.emits = optionsDoc.emits;
|
|
487
|
+
if (optionsDoc.refs.length > 0) doc.refs = optionsDoc.refs;
|
|
488
|
+
if (optionsDoc.computeds.length > 0) doc.computeds = optionsDoc.computeds;
|
|
439
489
|
}
|
|
440
490
|
return doc;
|
|
441
491
|
}
|
|
@@ -444,7 +494,8 @@ function extractComponentJSDoc(ast) {
|
|
|
444
494
|
description: "",
|
|
445
495
|
internal: false
|
|
446
496
|
};
|
|
447
|
-
const
|
|
497
|
+
const first = ast[0];
|
|
498
|
+
const comments = first.leadingComments ?? [];
|
|
448
499
|
if (comments.length === 0) return {
|
|
449
500
|
description: "",
|
|
450
501
|
internal: false
|
|
@@ -459,6 +510,13 @@ function extractComponentJSDoc(ast) {
|
|
|
459
510
|
description: result.description,
|
|
460
511
|
internal: true
|
|
461
512
|
};
|
|
513
|
+
const isImport = first.type === "ImportDeclaration";
|
|
514
|
+
const isDefineCall = first.type === "ExpressionStatement" && first.expression.type === "CallExpression" && first.expression.callee.type === "Identifier" && first.expression.callee.name.startsWith("define");
|
|
515
|
+
const isDefineVar = first.type === "VariableDeclaration" && first.declarations.some((d) => d.init?.type === "CallExpression" && d.init.callee?.type === "Identifier" && (d.init.callee.name.startsWith("define") || d.init.callee.name === "withDefaults"));
|
|
516
|
+
if (firstComment.value.includes("@component") || isImport || isDefineCall || isDefineVar) return {
|
|
517
|
+
description: result.description,
|
|
518
|
+
internal: false
|
|
519
|
+
};
|
|
462
520
|
return {
|
|
463
521
|
description: "",
|
|
464
522
|
internal: false
|
|
@@ -798,6 +856,99 @@ function extractComposables(ast, importMap, sfcDir) {
|
|
|
798
856
|
}
|
|
799
857
|
return composables;
|
|
800
858
|
}
|
|
859
|
+
const REF_CALLEES = new Set([
|
|
860
|
+
"ref",
|
|
861
|
+
"shallowRef",
|
|
862
|
+
"reactive",
|
|
863
|
+
"shallowReactive"
|
|
864
|
+
]);
|
|
865
|
+
const COMPUTED_CALLEES = new Set(["computed"]);
|
|
866
|
+
const SKIP_CALLEES = new Set([
|
|
867
|
+
"defineProps",
|
|
868
|
+
"defineEmits",
|
|
869
|
+
"defineSlots",
|
|
870
|
+
"defineExpose",
|
|
871
|
+
"withDefaults"
|
|
872
|
+
]);
|
|
873
|
+
function inferLiteralType(node) {
|
|
874
|
+
switch (node.type) {
|
|
875
|
+
case "StringLiteral": return "string";
|
|
876
|
+
case "NumericLiteral": return "number";
|
|
877
|
+
case "BooleanLiteral": return "boolean";
|
|
878
|
+
case "ArrayExpression": return "Array";
|
|
879
|
+
case "ObjectExpression": return "Object";
|
|
880
|
+
case "NullLiteral": return "null";
|
|
881
|
+
default: return null;
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
function extractRefsAndComputeds(ast, _scriptSource) {
|
|
885
|
+
const refs = [];
|
|
886
|
+
const computeds = [];
|
|
887
|
+
for (const stmt of ast) {
|
|
888
|
+
if (stmt.type !== "VariableDeclaration") continue;
|
|
889
|
+
const comments = stmt.leadingComments ?? [];
|
|
890
|
+
for (const decl of stmt.declarations) {
|
|
891
|
+
if (!decl.init || decl.init.type !== "CallExpression") continue;
|
|
892
|
+
if (decl.init.callee.type !== "Identifier") continue;
|
|
893
|
+
const calleeName = decl.init.callee.name;
|
|
894
|
+
if (/^use[A-Z]/.test(calleeName)) continue;
|
|
895
|
+
if (SKIP_CALLEES.has(calleeName)) continue;
|
|
896
|
+
const isRef = REF_CALLEES.has(calleeName);
|
|
897
|
+
const isComputed = COMPUTED_CALLEES.has(calleeName);
|
|
898
|
+
if (!isRef && !isComputed) continue;
|
|
899
|
+
const id = decl.id;
|
|
900
|
+
if (!id || id.type !== "Identifier") continue;
|
|
901
|
+
const varName = id.name;
|
|
902
|
+
const jsdoc = parseJSDocTags(comments);
|
|
903
|
+
const callExpr = decl.init;
|
|
904
|
+
const typeParams = callExpr.typeParameters;
|
|
905
|
+
const args = callExpr.arguments;
|
|
906
|
+
let type;
|
|
907
|
+
if (id.typeAnnotation?.typeAnnotation) type = resolveTypeString(id.typeAnnotation.typeAnnotation);
|
|
908
|
+
else if (typeParams?.params?.[0]) {
|
|
909
|
+
const genericType = resolveTypeString(typeParams.params[0]);
|
|
910
|
+
if (calleeName === "reactive" || calleeName === "shallowReactive") type = genericType;
|
|
911
|
+
else if (calleeName === "shallowRef") type = `ShallowRef<${genericType}>`;
|
|
912
|
+
else if (calleeName === "computed") type = `ComputedRef<${genericType}>`;
|
|
913
|
+
else type = `Ref<${genericType}>`;
|
|
914
|
+
} else if (isRef && args[0]) {
|
|
915
|
+
const literalType = inferLiteralType(args[0]);
|
|
916
|
+
if (literalType) if (calleeName === "reactive" || calleeName === "shallowReactive") type = literalType;
|
|
917
|
+
else if (calleeName === "shallowRef") type = `ShallowRef<${literalType}>`;
|
|
918
|
+
else type = `Ref<${literalType}>`;
|
|
919
|
+
else if (calleeName === "reactive") type = "Object";
|
|
920
|
+
else if (calleeName === "shallowReactive") type = "Object";
|
|
921
|
+
else if (calleeName === "shallowRef") type = "ShallowRef";
|
|
922
|
+
else type = "Ref";
|
|
923
|
+
} else if (isComputed && args[0]) {
|
|
924
|
+
let returnType = null;
|
|
925
|
+
const getter = args[0];
|
|
926
|
+
if ((getter.type === "ArrowFunctionExpression" || getter.type === "FunctionExpression") && getter.returnType?.type === "TSTypeAnnotation") returnType = resolveTypeString(getter.returnType.typeAnnotation);
|
|
927
|
+
if (returnType) type = `ComputedRef<${returnType}>`;
|
|
928
|
+
else type = "ComputedRef";
|
|
929
|
+
} else if (calleeName === "reactive") type = "Object";
|
|
930
|
+
else if (calleeName === "shallowReactive") type = "Object";
|
|
931
|
+
else if (calleeName === "shallowRef") type = "ShallowRef";
|
|
932
|
+
else if (calleeName === "computed") type = "ComputedRef";
|
|
933
|
+
else type = "Ref";
|
|
934
|
+
const docEntry = {
|
|
935
|
+
name: varName,
|
|
936
|
+
type,
|
|
937
|
+
description: jsdoc.description,
|
|
938
|
+
...jsdoc.deprecated !== void 0 && { deprecated: jsdoc.deprecated },
|
|
939
|
+
...jsdoc.since && { since: jsdoc.since },
|
|
940
|
+
...jsdoc.example && { example: jsdoc.example },
|
|
941
|
+
...jsdoc.see && { see: jsdoc.see }
|
|
942
|
+
};
|
|
943
|
+
if (isRef) refs.push(docEntry);
|
|
944
|
+
else computeds.push(docEntry);
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
return {
|
|
948
|
+
refs,
|
|
949
|
+
computeds
|
|
950
|
+
};
|
|
951
|
+
}
|
|
801
952
|
function extractTemplateSlots(templateAst) {
|
|
802
953
|
const slots = [];
|
|
803
954
|
walkTemplate(templateAst.children ?? [], slots);
|
|
@@ -827,31 +978,110 @@ function walkTemplate(children, slots) {
|
|
|
827
978
|
function extractOptionsAPI(ast, source) {
|
|
828
979
|
let props = [];
|
|
829
980
|
let emits = [];
|
|
981
|
+
let refs = [];
|
|
982
|
+
let computeds = [];
|
|
830
983
|
for (const stmt of ast) {
|
|
831
984
|
if (stmt.type !== "ExportDefaultDeclaration") continue;
|
|
832
985
|
const decl = stmt.declaration;
|
|
833
986
|
if (decl.type !== "ObjectExpression") continue;
|
|
834
987
|
for (const prop of decl.properties) {
|
|
835
|
-
if (prop.type !== "ObjectProperty") continue;
|
|
988
|
+
if (prop.type !== "ObjectProperty" && prop.type !== "ObjectMethod") continue;
|
|
836
989
|
const key = prop.key;
|
|
837
990
|
const name = key.type === "Identifier" ? key.name : "";
|
|
838
991
|
if (name === "props") {
|
|
992
|
+
if (prop.type !== "ObjectProperty") continue;
|
|
839
993
|
if (prop.value.type === "ObjectExpression") props = extractProps(prop.value, source);
|
|
840
994
|
else if (prop.value.type === "ArrayExpression") props = extractArrayProps(prop.value);
|
|
841
995
|
} else if (name === "emits") {
|
|
996
|
+
if (prop.type !== "ObjectProperty") continue;
|
|
842
997
|
if (prop.value.type === "ArrayExpression") {
|
|
843
998
|
const comments = stmt.leadingComments ?? [];
|
|
844
999
|
emits = extractEmits(prop.value, comments);
|
|
845
1000
|
} else if (prop.value.type === "ObjectExpression") emits = extractObjectEmits(prop.value);
|
|
1001
|
+
} else if (name === "data") refs = extractOptionsData(prop);
|
|
1002
|
+
else if (name === "computed") {
|
|
1003
|
+
if (prop.type !== "ObjectProperty") continue;
|
|
1004
|
+
if (prop.value.type === "ObjectExpression") computeds = extractOptionsComputed(prop.value);
|
|
846
1005
|
}
|
|
847
1006
|
}
|
|
848
1007
|
break;
|
|
849
1008
|
}
|
|
850
1009
|
return {
|
|
851
1010
|
props,
|
|
852
|
-
emits
|
|
1011
|
+
emits,
|
|
1012
|
+
refs,
|
|
1013
|
+
computeds
|
|
853
1014
|
};
|
|
854
1015
|
}
|
|
1016
|
+
function extractOptionsData(prop) {
|
|
1017
|
+
const refs = [];
|
|
1018
|
+
let body = null;
|
|
1019
|
+
if (prop.type === "ObjectMethod") body = prop.body;
|
|
1020
|
+
else if (prop.type === "ObjectProperty") {
|
|
1021
|
+
const val = prop.value;
|
|
1022
|
+
if (val.type === "ArrowFunctionExpression" || val.type === "FunctionExpression") body = val.body;
|
|
1023
|
+
}
|
|
1024
|
+
if (!body) return refs;
|
|
1025
|
+
let returnArg = null;
|
|
1026
|
+
if (body.type === "ObjectExpression") returnArg = body;
|
|
1027
|
+
else if (body.type === "BlockStatement") {
|
|
1028
|
+
for (const s of body.body) if (s.type === "ReturnStatement" && s.argument?.type === "ObjectExpression") {
|
|
1029
|
+
returnArg = s.argument;
|
|
1030
|
+
break;
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
if (!returnArg || returnArg.type !== "ObjectExpression") return refs;
|
|
1034
|
+
for (const p of returnArg.properties) {
|
|
1035
|
+
if (p.type !== "ObjectProperty") continue;
|
|
1036
|
+
const name = p.key.type === "Identifier" ? p.key.name : p.key.type === "StringLiteral" ? p.key.value : "";
|
|
1037
|
+
if (!name) continue;
|
|
1038
|
+
const jsdoc = parseJSDocTags(p.leadingComments ?? []);
|
|
1039
|
+
const type = inferLiteralType(p.value) ?? "unknown";
|
|
1040
|
+
refs.push({
|
|
1041
|
+
name,
|
|
1042
|
+
type,
|
|
1043
|
+
description: jsdoc.description,
|
|
1044
|
+
...jsdoc.deprecated !== void 0 && { deprecated: jsdoc.deprecated },
|
|
1045
|
+
...jsdoc.since && { since: jsdoc.since },
|
|
1046
|
+
...jsdoc.example && { example: jsdoc.example },
|
|
1047
|
+
...jsdoc.see && { see: jsdoc.see }
|
|
1048
|
+
});
|
|
1049
|
+
}
|
|
1050
|
+
return refs;
|
|
1051
|
+
}
|
|
1052
|
+
function extractOptionsComputed(obj) {
|
|
1053
|
+
const computeds = [];
|
|
1054
|
+
for (const prop of obj.properties) {
|
|
1055
|
+
if (prop.type !== "ObjectProperty" && prop.type !== "ObjectMethod") continue;
|
|
1056
|
+
const name = prop.key.type === "Identifier" ? prop.key.name : prop.key.type === "StringLiteral" ? prop.key.value : "";
|
|
1057
|
+
if (!name) continue;
|
|
1058
|
+
const jsdoc = parseJSDocTags(prop.leadingComments ?? []);
|
|
1059
|
+
let type = "unknown";
|
|
1060
|
+
if (prop.type === "ObjectMethod") {
|
|
1061
|
+
if (prop.returnType?.type === "TSTypeAnnotation") type = resolveTypeString(prop.returnType.typeAnnotation);
|
|
1062
|
+
} else if (prop.type === "ObjectProperty") {
|
|
1063
|
+
const val = prop.value;
|
|
1064
|
+
if (val.type === "ObjectExpression") {
|
|
1065
|
+
for (const member of val.properties) if (member.type === "ObjectMethod" && member.key?.type === "Identifier" && member.key.name === "get") {
|
|
1066
|
+
if (member.returnType?.type === "TSTypeAnnotation") type = resolveTypeString(member.returnType.typeAnnotation);
|
|
1067
|
+
break;
|
|
1068
|
+
}
|
|
1069
|
+
} else if (val.type === "ArrowFunctionExpression" || val.type === "FunctionExpression") {
|
|
1070
|
+
if (val.returnType?.type === "TSTypeAnnotation") type = resolveTypeString(val.returnType.typeAnnotation);
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
computeds.push({
|
|
1074
|
+
name,
|
|
1075
|
+
type,
|
|
1076
|
+
description: jsdoc.description,
|
|
1077
|
+
...jsdoc.deprecated !== void 0 && { deprecated: jsdoc.deprecated },
|
|
1078
|
+
...jsdoc.since && { since: jsdoc.since },
|
|
1079
|
+
...jsdoc.example && { example: jsdoc.example },
|
|
1080
|
+
...jsdoc.see && { see: jsdoc.see }
|
|
1081
|
+
});
|
|
1082
|
+
}
|
|
1083
|
+
return computeds;
|
|
1084
|
+
}
|
|
855
1085
|
function extractArrayProps(arr) {
|
|
856
1086
|
const props = [];
|
|
857
1087
|
for (const el of arr.elements) if (el?.type === "StringLiteral") props.push({
|
|
@@ -902,15 +1132,53 @@ function generateMarkdown(doc) {
|
|
|
902
1132
|
const sections = [`# ${doc.name}`];
|
|
903
1133
|
if (doc.description) sections.push("", doc.description);
|
|
904
1134
|
if (doc.scriptSetup) sections.push("", "**Note:** Uses `<script setup>` syntax.");
|
|
1135
|
+
const hasRefs = (doc.refs?.length ?? 0) > 0;
|
|
1136
|
+
const hasComputeds = (doc.computeds?.length ?? 0) > 0;
|
|
905
1137
|
const hasProps = doc.props.length > 0;
|
|
906
1138
|
const hasEmits = doc.emits.length > 0;
|
|
907
1139
|
const hasSlots = (doc.slots?.length ?? 0) > 0;
|
|
908
1140
|
const hasExposes = (doc.exposes?.length ?? 0) > 0;
|
|
909
1141
|
const hasComposables = (doc.composables?.length ?? 0) > 0;
|
|
910
|
-
if (!hasProps && !hasEmits && !hasSlots && !hasExposes && !hasComposables) {
|
|
1142
|
+
if (!hasProps && !hasEmits && !hasSlots && !hasExposes && !hasComposables && !hasRefs && !hasComputeds) {
|
|
911
1143
|
sections.push("", "No documentable API found.");
|
|
912
1144
|
return sections.join("\n") + "\n";
|
|
913
1145
|
}
|
|
1146
|
+
if (hasRefs) {
|
|
1147
|
+
sections.push("", "## Refs", "");
|
|
1148
|
+
sections.push("| Name | Type | Description |");
|
|
1149
|
+
sections.push("| --- | --- | --- |");
|
|
1150
|
+
const examples = [];
|
|
1151
|
+
for (const r of doc.refs) {
|
|
1152
|
+
let desc = r.description || "-";
|
|
1153
|
+
if (r.deprecated) desc += typeof r.deprecated === "string" && r.deprecated ? ` **Deprecated**: ${r.deprecated}` : " **Deprecated**";
|
|
1154
|
+
if (r.since) desc += ` *(since ${r.since})*`;
|
|
1155
|
+
if (r.see) desc += ` See: ${r.see}`;
|
|
1156
|
+
sections.push(`| ${esc(r.name)} | ${escHtml(esc(r.type))} | ${esc(desc)} |`);
|
|
1157
|
+
if (r.example) examples.push({
|
|
1158
|
+
name: r.name,
|
|
1159
|
+
example: r.example
|
|
1160
|
+
});
|
|
1161
|
+
}
|
|
1162
|
+
for (const { name, example } of examples) sections.push("", `**\`${name}\` example:**`, "", "```", example, "```");
|
|
1163
|
+
}
|
|
1164
|
+
if (hasComputeds) {
|
|
1165
|
+
sections.push("", "## Computed", "");
|
|
1166
|
+
sections.push("| Name | Type | Description |");
|
|
1167
|
+
sections.push("| --- | --- | --- |");
|
|
1168
|
+
const examples = [];
|
|
1169
|
+
for (const c of doc.computeds) {
|
|
1170
|
+
let desc = c.description || "-";
|
|
1171
|
+
if (c.deprecated) desc += typeof c.deprecated === "string" && c.deprecated ? ` **Deprecated**: ${c.deprecated}` : " **Deprecated**";
|
|
1172
|
+
if (c.since) desc += ` *(since ${c.since})*`;
|
|
1173
|
+
if (c.see) desc += ` See: ${c.see}`;
|
|
1174
|
+
sections.push(`| ${esc(c.name)} | ${escHtml(esc(c.type))} | ${esc(desc)} |`);
|
|
1175
|
+
if (c.example) examples.push({
|
|
1176
|
+
name: c.name,
|
|
1177
|
+
example: c.example
|
|
1178
|
+
});
|
|
1179
|
+
}
|
|
1180
|
+
for (const { name, example } of examples) sections.push("", `**\`${name}\` example:**`, "", "```", example, "```");
|
|
1181
|
+
}
|
|
914
1182
|
if (hasProps) {
|
|
915
1183
|
sections.push("", "## Props", "");
|
|
916
1184
|
sections.push("| Name | Type | Required | Default | Description |");
|
|
@@ -1041,18 +1309,28 @@ function processFiles(filePaths, options) {
|
|
|
1041
1309
|
}
|
|
1042
1310
|
//#endregion
|
|
1043
1311
|
//#region src/output.ts
|
|
1044
|
-
function writeIndividualMarkdown(results, outDir, silent) {
|
|
1312
|
+
function writeIndividualMarkdown(results, outDir, silent, options) {
|
|
1045
1313
|
mkdirSync(outDir, { recursive: true });
|
|
1314
|
+
const outputMap = /* @__PURE__ */ new Map();
|
|
1046
1315
|
const usedNames = /* @__PURE__ */ new Map();
|
|
1047
|
-
for (const { doc } of results) {
|
|
1316
|
+
for (const { path: sourcePath, doc } of results) {
|
|
1317
|
+
let targetDir = outDir;
|
|
1318
|
+
if (options?.preserveStructure && options.basePath) {
|
|
1319
|
+
targetDir = join(outDir, relative(options.basePath, dirname(sourcePath)));
|
|
1320
|
+
mkdirSync(targetDir, { recursive: true });
|
|
1321
|
+
}
|
|
1048
1322
|
const baseName = doc.name;
|
|
1049
|
-
const
|
|
1050
|
-
usedNames.
|
|
1323
|
+
const dedupKey = options?.preserveStructure ? `${targetDir}/${baseName}` : baseName;
|
|
1324
|
+
const count = usedNames.get(dedupKey) ?? 0;
|
|
1325
|
+
usedNames.set(dedupKey, count + 1);
|
|
1051
1326
|
const fileName = count === 0 ? baseName : `${baseName}-${count + 1}`;
|
|
1052
1327
|
const md = generateMarkdown(doc);
|
|
1053
|
-
|
|
1328
|
+
const outPath = join(targetDir, `${fileName}.md`);
|
|
1329
|
+
writeFileSync(outPath, md, "utf-8");
|
|
1330
|
+
outputMap.set(sourcePath, outPath);
|
|
1054
1331
|
if (!silent) console.log(` Created ${fileName}.md`);
|
|
1055
1332
|
}
|
|
1333
|
+
return outputMap;
|
|
1056
1334
|
}
|
|
1057
1335
|
function writeJoinedMarkdown(results, outDir, silent) {
|
|
1058
1336
|
mkdirSync(outDir, { recursive: true });
|
|
@@ -1077,8 +1355,9 @@ function writeJoinedMarkdown(results, outDir, silent) {
|
|
|
1077
1355
|
writeFileSync(join(outDir, "components.md"), sections.join("\n") + "\n", "utf-8");
|
|
1078
1356
|
if (!silent) console.log(` Created components.md`);
|
|
1079
1357
|
}
|
|
1080
|
-
function writeJSON(results, outDir, joined, silent) {
|
|
1358
|
+
function writeJSON(results, outDir, joined, silent, options) {
|
|
1081
1359
|
mkdirSync(outDir, { recursive: true });
|
|
1360
|
+
const outputMap = /* @__PURE__ */ new Map();
|
|
1082
1361
|
if (joined) {
|
|
1083
1362
|
const data = {
|
|
1084
1363
|
generated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -1088,42 +1367,82 @@ function writeJSON(results, outDir, joined, silent) {
|
|
|
1088
1367
|
if (!silent) console.log(` Created components.json`);
|
|
1089
1368
|
} else {
|
|
1090
1369
|
const usedNames = /* @__PURE__ */ new Map();
|
|
1091
|
-
for (const { doc } of results) {
|
|
1370
|
+
for (const { path: sourcePath, doc } of results) {
|
|
1371
|
+
let targetDir = outDir;
|
|
1372
|
+
if (options?.preserveStructure && options.basePath) {
|
|
1373
|
+
targetDir = join(outDir, relative(options.basePath, dirname(sourcePath)));
|
|
1374
|
+
mkdirSync(targetDir, { recursive: true });
|
|
1375
|
+
}
|
|
1092
1376
|
const baseName = doc.name;
|
|
1093
|
-
const
|
|
1094
|
-
usedNames.
|
|
1377
|
+
const dedupKey = options?.preserveStructure ? `${targetDir}/${baseName}` : baseName;
|
|
1378
|
+
const count = usedNames.get(dedupKey) ?? 0;
|
|
1379
|
+
usedNames.set(dedupKey, count + 1);
|
|
1095
1380
|
const fileName = count === 0 ? baseName : `${baseName}-${count + 1}`;
|
|
1096
|
-
|
|
1381
|
+
const outPath = join(targetDir, `${fileName}.json`);
|
|
1382
|
+
writeFileSync(outPath, JSON.stringify(doc, null, 2) + "\n", "utf-8");
|
|
1383
|
+
outputMap.set(sourcePath, outPath);
|
|
1097
1384
|
if (!silent) console.log(` Created ${fileName}.json`);
|
|
1098
1385
|
}
|
|
1099
1386
|
}
|
|
1387
|
+
return outputMap;
|
|
1100
1388
|
}
|
|
1101
1389
|
//#endregion
|
|
1102
1390
|
//#region src/watcher.ts
|
|
1103
|
-
function startWatcher(inputs, ignore,
|
|
1391
|
+
function startWatcher(inputs, ignore, rebuildOrOptions) {
|
|
1392
|
+
const options = typeof rebuildOrOptions === "function" ? {
|
|
1393
|
+
rebuild: rebuildOrOptions,
|
|
1394
|
+
isJoined: true
|
|
1395
|
+
} : rebuildOrOptions;
|
|
1104
1396
|
const roots = /* @__PURE__ */ new Set();
|
|
1105
1397
|
for (const input of inputs) if (input.endsWith(".vue")) roots.add(dirname(resolve(input)));
|
|
1106
1398
|
else roots.add(resolve(input));
|
|
1107
1399
|
let timer = null;
|
|
1108
|
-
const
|
|
1109
|
-
|
|
1110
|
-
|
|
1400
|
+
const changedFiles = /* @__PURE__ */ new Set();
|
|
1401
|
+
const flush = async () => {
|
|
1402
|
+
if (options.isJoined || !options.incrementalUpdate) {
|
|
1111
1403
|
console.log("[watch] Rebuilding...");
|
|
1112
1404
|
try {
|
|
1113
|
-
rebuild();
|
|
1405
|
+
await options.rebuild();
|
|
1114
1406
|
} catch (err) {
|
|
1115
1407
|
const msg = err instanceof Error ? err.message : String(err);
|
|
1116
1408
|
console.error(`[watch] Error: ${msg}`);
|
|
1117
1409
|
}
|
|
1118
1410
|
console.log("[watch] Done.");
|
|
1411
|
+
} else {
|
|
1412
|
+
const files = [...changedFiles];
|
|
1413
|
+
changedFiles.clear();
|
|
1414
|
+
for (const file of files) try {
|
|
1415
|
+
if (existsSync(file)) if (ignore.some((pattern) => file.includes(pattern))) {
|
|
1416
|
+
if (options.removeOutput) options.removeOutput(file);
|
|
1417
|
+
console.log(`[watch] Ignored: ${file.split("/").pop()}`);
|
|
1418
|
+
} else {
|
|
1419
|
+
console.log(`[watch] Updating: ${file.split("/").pop()}`);
|
|
1420
|
+
await options.incrementalUpdate(file);
|
|
1421
|
+
}
|
|
1422
|
+
else {
|
|
1423
|
+
if (options.removeOutput) options.removeOutput(file);
|
|
1424
|
+
console.log(`[watch] Removed: ${file.split("/").pop()}`);
|
|
1425
|
+
}
|
|
1426
|
+
} catch (err) {
|
|
1427
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1428
|
+
console.error(`[watch] Error processing ${file.split("/").pop()}: ${msg}`);
|
|
1429
|
+
}
|
|
1430
|
+
console.log("[watch] Done.");
|
|
1431
|
+
}
|
|
1432
|
+
};
|
|
1433
|
+
const debounce = (filePath) => {
|
|
1434
|
+
if (filePath) changedFiles.add(filePath);
|
|
1435
|
+
if (timer) clearTimeout(timer);
|
|
1436
|
+
timer = setTimeout(() => {
|
|
1437
|
+
flush();
|
|
1119
1438
|
}, 300);
|
|
1120
1439
|
};
|
|
1121
1440
|
const watchers = [];
|
|
1122
1441
|
for (const root of roots) try {
|
|
1123
1442
|
const watcher = watch(root, { recursive: true }, (_event, filename) => {
|
|
1124
1443
|
if (!filename || !filename.endsWith(".vue")) return;
|
|
1125
|
-
if (ignore.some((pattern) => filename.includes(pattern))) return;
|
|
1126
|
-
debounce();
|
|
1444
|
+
if (ignore.some((pattern) => filename.includes(pattern)) && options.isJoined) return;
|
|
1445
|
+
debounce(join(root, filename));
|
|
1127
1446
|
});
|
|
1128
1447
|
watchers.push(watcher);
|
|
1129
1448
|
} catch {
|
|
@@ -1135,15 +1454,27 @@ function startWatcher(inputs, ignore, rebuild) {
|
|
|
1135
1454
|
process.exit(0);
|
|
1136
1455
|
});
|
|
1137
1456
|
}
|
|
1457
|
+
function removeOutputFile(outputMap, sourcePath) {
|
|
1458
|
+
const outPath = outputMap.get(sourcePath);
|
|
1459
|
+
if (outPath && existsSync(outPath)) {
|
|
1460
|
+
unlinkSync(outPath);
|
|
1461
|
+
outputMap.delete(sourcePath);
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1138
1464
|
//#endregion
|
|
1139
1465
|
//#region src/cli.ts
|
|
1140
1466
|
const main = defineCommand({
|
|
1141
1467
|
meta: {
|
|
1142
1468
|
name: "compmark",
|
|
1143
|
-
version: "0.
|
|
1469
|
+
version: "0.4.0",
|
|
1144
1470
|
description: "Auto-generate Markdown documentation from Vue 3 SFCs"
|
|
1145
1471
|
},
|
|
1146
1472
|
args: {
|
|
1473
|
+
paths: {
|
|
1474
|
+
type: "positional",
|
|
1475
|
+
description: "Files, directories, or glob patterns",
|
|
1476
|
+
required: true
|
|
1477
|
+
},
|
|
1147
1478
|
out: {
|
|
1148
1479
|
type: "string",
|
|
1149
1480
|
description: "Output directory",
|
|
@@ -1169,6 +1500,10 @@ const main = defineCommand({
|
|
|
1169
1500
|
silent: {
|
|
1170
1501
|
type: "boolean",
|
|
1171
1502
|
description: "Suppress non-error output"
|
|
1503
|
+
},
|
|
1504
|
+
"preserve-structure": {
|
|
1505
|
+
type: "boolean",
|
|
1506
|
+
description: "Preserve input folder structure in output"
|
|
1172
1507
|
}
|
|
1173
1508
|
},
|
|
1174
1509
|
async run({ args }) {
|
|
@@ -1187,8 +1522,12 @@ const main = defineCommand({
|
|
|
1187
1522
|
const silent = args.silent ?? false;
|
|
1188
1523
|
const joined = args.join ?? false;
|
|
1189
1524
|
const outDir = args.out ?? ".";
|
|
1525
|
+
const preserveStructure = args["preserve-structure"] ?? false;
|
|
1526
|
+
let outputMap = /* @__PURE__ */ new Map();
|
|
1527
|
+
let lastBasePath = "";
|
|
1190
1528
|
const rebuild = async () => {
|
|
1191
|
-
const filePaths = await discoverFiles(inputPaths, ignorePatterns);
|
|
1529
|
+
const { files: filePaths, ignoredCount, basePath } = await discoverFiles(inputPaths, ignorePatterns);
|
|
1530
|
+
lastBasePath = basePath;
|
|
1192
1531
|
if (filePaths.length === 0) {
|
|
1193
1532
|
if (!args.watch) {
|
|
1194
1533
|
console.error("Error: No .vue files found");
|
|
@@ -1198,17 +1537,54 @@ const main = defineCommand({
|
|
|
1198
1537
|
return null;
|
|
1199
1538
|
}
|
|
1200
1539
|
const summary = processFiles(filePaths, { silent });
|
|
1201
|
-
|
|
1540
|
+
const outputOptions = {
|
|
1541
|
+
preserveStructure,
|
|
1542
|
+
basePath
|
|
1543
|
+
};
|
|
1544
|
+
if (format === "json") outputMap = writeJSON(summary.files, outDir, joined, silent, outputOptions);
|
|
1202
1545
|
else if (joined) writeJoinedMarkdown(summary.files, outDir, silent);
|
|
1203
|
-
else writeIndividualMarkdown(summary.files, outDir, silent);
|
|
1204
|
-
|
|
1546
|
+
else outputMap = writeIndividualMarkdown(summary.files, outDir, silent, outputOptions);
|
|
1547
|
+
const totalSkipped = summary.skipped + ignoredCount;
|
|
1548
|
+
if (!silent) console.log(`✓ ${summary.documented} components documented, ${totalSkipped} skipped, ${summary.errors} errors`);
|
|
1205
1549
|
return summary;
|
|
1206
1550
|
};
|
|
1207
1551
|
const summary = await rebuild();
|
|
1208
|
-
if (args.watch)
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1552
|
+
if (args.watch) {
|
|
1553
|
+
const outputOptions = {
|
|
1554
|
+
preserveStructure,
|
|
1555
|
+
basePath: lastBasePath
|
|
1556
|
+
};
|
|
1557
|
+
startWatcher(inputPaths, ignorePatterns, {
|
|
1558
|
+
rebuild: async () => {
|
|
1559
|
+
await rebuild();
|
|
1560
|
+
},
|
|
1561
|
+
incrementalUpdate: (changedFile) => {
|
|
1562
|
+
try {
|
|
1563
|
+
const doc = parseComponent(changedFile);
|
|
1564
|
+
if (doc.internal) {
|
|
1565
|
+
removeOutputFile(outputMap, changedFile);
|
|
1566
|
+
if (!silent) console.log(` Skipped ${changedFile.split("/").pop()} (marked @internal)`);
|
|
1567
|
+
return;
|
|
1568
|
+
}
|
|
1569
|
+
const result = [{
|
|
1570
|
+
path: changedFile,
|
|
1571
|
+
doc
|
|
1572
|
+
}];
|
|
1573
|
+
let newMap;
|
|
1574
|
+
if (format === "json") newMap = writeJSON(result, outDir, false, silent, outputOptions);
|
|
1575
|
+
else newMap = writeIndividualMarkdown(result, outDir, silent, outputOptions);
|
|
1576
|
+
for (const [k, v] of newMap) outputMap.set(k, v);
|
|
1577
|
+
} catch (err) {
|
|
1578
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1579
|
+
if (!silent) console.warn(` Warning: Could not parse ${changedFile.split("/").pop()}: ${msg}`);
|
|
1580
|
+
}
|
|
1581
|
+
},
|
|
1582
|
+
removeOutput: (changedFile) => {
|
|
1583
|
+
removeOutputFile(outputMap, changedFile);
|
|
1584
|
+
},
|
|
1585
|
+
isJoined: joined
|
|
1586
|
+
});
|
|
1587
|
+
} else if (summary && summary.errors > 0) process.exit(1);
|
|
1212
1588
|
}
|
|
1213
1589
|
});
|
|
1214
1590
|
function collectInputPaths() {
|