@grainulation/mill 1.0.0 → 1.0.2
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/CODE_OF_CONDUCT.md +25 -0
- package/CONTRIBUTING.md +101 -0
- package/README.md +90 -42
- package/bin/mill.js +233 -67
- package/lib/exporters/csv.js +35 -30
- package/lib/exporters/json-ld.js +19 -13
- package/lib/exporters/markdown.js +83 -44
- package/lib/exporters/pdf.js +15 -15
- package/lib/formats/bibtex.mjs +83 -0
- package/lib/formats/{changelog.js → changelog.mjs} +27 -26
- package/lib/formats/confluence-adf.mjs +312 -0
- package/lib/formats/{csv.js → csv.mjs} +41 -37
- package/lib/formats/{dot.js → dot.mjs} +45 -34
- package/lib/formats/{evidence-matrix.js → evidence-matrix.mjs} +17 -16
- package/lib/formats/executive-summary.mjs +178 -0
- package/lib/formats/github-issues.mjs +96 -0
- package/lib/formats/{graphml.js → graphml.mjs} +45 -32
- package/lib/formats/html-report.mjs +228 -0
- package/lib/formats/{jira-csv.js → jira-csv.mjs} +30 -29
- package/lib/formats/{json-ld.js → json-ld.mjs} +6 -6
- package/lib/formats/{markdown.js → markdown.mjs} +53 -36
- package/lib/formats/{ndjson.js → ndjson.mjs} +6 -6
- package/lib/formats/{obsidian.js → obsidian.mjs} +43 -35
- package/lib/formats/{opml.js → opml.mjs} +38 -28
- package/lib/formats/ris.mjs +76 -0
- package/lib/formats/{rss.js → rss.mjs} +31 -28
- package/lib/formats/{sankey.js → sankey.mjs} +16 -15
- package/lib/formats/slide-deck.mjs +288 -0
- package/lib/formats/sql.mjs +120 -0
- package/lib/formats/{static-site.js → static-site.mjs} +64 -52
- package/lib/formats/{treemap.js → treemap.mjs} +16 -15
- package/lib/formats/typescript-defs.mjs +150 -0
- package/lib/formats/{yaml.js → yaml.mjs} +58 -40
- package/lib/formats.js +16 -16
- package/lib/index.js +5 -5
- package/lib/json-ld-common.js +37 -31
- package/lib/publishers/clipboard.js +21 -19
- package/lib/publishers/static.js +27 -12
- package/lib/serve-mcp.js +158 -83
- package/lib/server.js +252 -142
- package/package.json +6 -3
- package/lib/formats/bibtex.js +0 -76
- package/lib/formats/executive-summary.js +0 -130
- package/lib/formats/github-issues.js +0 -89
- package/lib/formats/html-report.js +0 -181
- package/lib/formats/ris.js +0 -70
- package/lib/formats/slide-deck.js +0 -200
- package/lib/formats/sql.js +0 -116
- package/lib/formats/typescript-defs.js +0 -147
|
@@ -7,10 +7,11 @@
|
|
|
7
7
|
* Zero dependencies — node built-in only.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
export const name =
|
|
11
|
-
export const extension =
|
|
12
|
-
export const mimeType =
|
|
13
|
-
export const description =
|
|
10
|
+
export const name = "static-site";
|
|
11
|
+
export const extension = ".json";
|
|
12
|
+
export const mimeType = "application/json; charset=utf-8";
|
|
13
|
+
export const description =
|
|
14
|
+
"Hugo-compatible static site manifest (file tree as JSON)";
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
17
|
* Convert a compilation object to a static site manifest.
|
|
@@ -22,148 +23,159 @@ export function convert(compilation) {
|
|
|
22
23
|
const meta = compilation.meta || {};
|
|
23
24
|
const certificate = compilation.certificate || {};
|
|
24
25
|
|
|
25
|
-
const sprintName = meta.sprint ||
|
|
26
|
-
const question = meta.question ||
|
|
27
|
-
const audience = meta.audience ||
|
|
26
|
+
const sprintName = meta.sprint || "sprint";
|
|
27
|
+
const question = meta.question || "";
|
|
28
|
+
const audience = meta.audience || "";
|
|
28
29
|
|
|
29
30
|
const files = {};
|
|
30
31
|
|
|
31
32
|
// Hugo config
|
|
32
|
-
files[
|
|
33
|
+
files["config.yaml"] = buildConfig(sprintName, question);
|
|
33
34
|
|
|
34
35
|
// Index page
|
|
35
|
-
files[
|
|
36
|
+
files["content/_index.md"] = buildIndex(
|
|
37
|
+
sprintName,
|
|
38
|
+
question,
|
|
39
|
+
audience,
|
|
40
|
+
claims,
|
|
41
|
+
);
|
|
36
42
|
|
|
37
43
|
// Per-type section pages
|
|
38
44
|
const groups = {};
|
|
39
45
|
for (const claim of claims) {
|
|
40
|
-
const type = claim.type ||
|
|
46
|
+
const type = claim.type || "other";
|
|
41
47
|
if (!groups[type]) groups[type] = [];
|
|
42
48
|
groups[type].push(claim);
|
|
43
49
|
}
|
|
44
50
|
|
|
45
51
|
for (const [type, group] of Object.entries(groups)) {
|
|
46
|
-
files[`content/claims/${type}/_index.md`] = buildSectionIndex(
|
|
52
|
+
files[`content/claims/${type}/_index.md`] = buildSectionIndex(
|
|
53
|
+
type,
|
|
54
|
+
group.length,
|
|
55
|
+
);
|
|
47
56
|
|
|
48
57
|
for (const claim of group) {
|
|
49
|
-
const id = claim.id ||
|
|
58
|
+
const id = claim.id || "unknown";
|
|
50
59
|
files[`content/claims/${type}/${id}.md`] = buildClaimPage(claim);
|
|
51
60
|
}
|
|
52
61
|
}
|
|
53
62
|
|
|
54
63
|
// Data file: full claims array
|
|
55
|
-
files[
|
|
64
|
+
files["data/claims.json"] = JSON.stringify(claims, null, 2);
|
|
56
65
|
|
|
57
66
|
// Data file: certificate
|
|
58
67
|
if (Object.keys(certificate).length > 0) {
|
|
59
|
-
files[
|
|
68
|
+
files["data/certificate.json"] = JSON.stringify(certificate, null, 2);
|
|
60
69
|
}
|
|
61
70
|
|
|
62
71
|
// Data file: meta
|
|
63
|
-
files[
|
|
72
|
+
files["data/meta.json"] = JSON.stringify(meta, null, 2);
|
|
64
73
|
|
|
65
74
|
const manifest = {
|
|
66
|
-
generator:
|
|
75
|
+
generator: "mill",
|
|
67
76
|
sprint: sprintName,
|
|
68
77
|
claimCount: claims.length,
|
|
69
78
|
files,
|
|
70
79
|
};
|
|
71
80
|
|
|
72
|
-
return JSON.stringify(manifest, null, 2) +
|
|
81
|
+
return JSON.stringify(manifest, null, 2) + "\n";
|
|
73
82
|
}
|
|
74
83
|
|
|
75
84
|
function buildConfig(sprintName, question) {
|
|
76
85
|
const lines = [];
|
|
77
|
-
lines.push(
|
|
86
|
+
lines.push(
|
|
87
|
+
`baseURL: "https://your-site.example.com/${sanitizeSlug(sprintName)}/" # Update with your domain`,
|
|
88
|
+
);
|
|
78
89
|
lines.push(`title: "${escYaml(sprintName)}"`);
|
|
79
90
|
lines.push(`theme: "mill-default"`);
|
|
80
91
|
lines.push('languageCode: "en-us"');
|
|
81
|
-
lines.push(
|
|
82
|
-
lines.push(
|
|
92
|
+
lines.push("");
|
|
93
|
+
lines.push("params:");
|
|
83
94
|
lines.push(` question: "${escYaml(question)}"`);
|
|
84
95
|
lines.push(` generator: "mill"`);
|
|
85
|
-
return lines.join(
|
|
96
|
+
return lines.join("\n") + "\n";
|
|
86
97
|
}
|
|
87
98
|
|
|
88
99
|
function buildIndex(sprintName, question, audience, claims) {
|
|
89
100
|
const lines = [];
|
|
90
|
-
lines.push(
|
|
101
|
+
lines.push("---");
|
|
91
102
|
lines.push(`title: "${escYaml(sprintName)}"`);
|
|
92
103
|
lines.push(`description: "${escYaml(question)}"`);
|
|
93
104
|
lines.push('type: "index"');
|
|
94
|
-
lines.push(
|
|
95
|
-
lines.push(
|
|
105
|
+
lines.push("---");
|
|
106
|
+
lines.push("");
|
|
96
107
|
lines.push(`# ${sprintName}`);
|
|
97
|
-
lines.push(
|
|
108
|
+
lines.push("");
|
|
98
109
|
if (question) lines.push(`**Question:** ${question}`);
|
|
99
110
|
if (audience) lines.push(`**Audience:** ${audience}`);
|
|
100
|
-
lines.push(
|
|
111
|
+
lines.push("");
|
|
101
112
|
lines.push(`**Total claims:** ${claims.length}`);
|
|
102
113
|
|
|
103
114
|
// Type summary
|
|
104
115
|
const typeCounts = {};
|
|
105
116
|
for (const c of claims) {
|
|
106
|
-
const t = c.type ||
|
|
117
|
+
const t = c.type || "other";
|
|
107
118
|
typeCounts[t] = (typeCounts[t] || 0) + 1;
|
|
108
119
|
}
|
|
109
|
-
lines.push(
|
|
120
|
+
lines.push("");
|
|
110
121
|
for (const [type, count] of Object.entries(typeCounts)) {
|
|
111
122
|
lines.push(`- ${type}: ${count}`);
|
|
112
123
|
}
|
|
113
124
|
|
|
114
|
-
return lines.join(
|
|
125
|
+
return lines.join("\n") + "\n";
|
|
115
126
|
}
|
|
116
127
|
|
|
117
128
|
function buildSectionIndex(type, count) {
|
|
118
129
|
const lines = [];
|
|
119
|
-
lines.push(
|
|
130
|
+
lines.push("---");
|
|
120
131
|
lines.push(`title: "${escYaml(type)}"`);
|
|
121
132
|
lines.push(`type: "section"`);
|
|
122
|
-
lines.push(
|
|
123
|
-
lines.push(
|
|
124
|
-
lines.push(`${count} ${type} claim${count !== 1 ?
|
|
125
|
-
return lines.join(
|
|
133
|
+
lines.push("---");
|
|
134
|
+
lines.push("");
|
|
135
|
+
lines.push(`${count} ${type} claim${count !== 1 ? "s" : ""}.`);
|
|
136
|
+
return lines.join("\n") + "\n";
|
|
126
137
|
}
|
|
127
138
|
|
|
128
139
|
function buildClaimPage(claim) {
|
|
129
|
-
const id = claim.id ||
|
|
130
|
-
const type = claim.type ||
|
|
131
|
-
const content = claim.content || claim.text ||
|
|
140
|
+
const id = claim.id || "unknown";
|
|
141
|
+
const type = claim.type || "";
|
|
142
|
+
const content = claim.content || claim.text || "";
|
|
132
143
|
const evidence = getEvidence(claim);
|
|
133
|
-
const status = claim.status ||
|
|
144
|
+
const status = claim.status || "";
|
|
134
145
|
const tags = Array.isArray(claim.tags) ? claim.tags : [];
|
|
135
|
-
const confidence = claim.confidence != null ? claim.confidence :
|
|
146
|
+
const confidence = claim.confidence != null ? claim.confidence : "";
|
|
136
147
|
|
|
137
148
|
const lines = [];
|
|
138
|
-
lines.push(
|
|
149
|
+
lines.push("---");
|
|
139
150
|
lines.push(`title: "${escYaml(id)}"`);
|
|
140
151
|
lines.push(`type: "${escYaml(type)}"`);
|
|
141
152
|
lines.push(`evidence: "${escYaml(evidence)}"`);
|
|
142
153
|
lines.push(`status: "${escYaml(status)}"`);
|
|
143
|
-
if (confidence !==
|
|
144
|
-
if (tags.length > 0)
|
|
145
|
-
|
|
146
|
-
lines.push(
|
|
154
|
+
if (confidence !== "") lines.push(`confidence: ${confidence}`);
|
|
155
|
+
if (tags.length > 0)
|
|
156
|
+
lines.push(`tags: [${tags.map((t) => `"${escYaml(t)}"`).join(", ")}]`);
|
|
157
|
+
lines.push("---");
|
|
158
|
+
lines.push("");
|
|
147
159
|
lines.push(content);
|
|
148
|
-
return lines.join(
|
|
160
|
+
return lines.join("\n") + "\n";
|
|
149
161
|
}
|
|
150
162
|
|
|
151
163
|
function getEvidence(claim) {
|
|
152
|
-
if (typeof claim.evidence ===
|
|
153
|
-
if (typeof claim.evidence ===
|
|
154
|
-
return claim.evidence.tier || claim.evidence_tier ||
|
|
164
|
+
if (typeof claim.evidence === "string") return claim.evidence;
|
|
165
|
+
if (typeof claim.evidence === "object" && claim.evidence !== null) {
|
|
166
|
+
return claim.evidence.tier || claim.evidence_tier || "";
|
|
155
167
|
}
|
|
156
|
-
return claim.evidence_tier ||
|
|
168
|
+
return claim.evidence_tier || "";
|
|
157
169
|
}
|
|
158
170
|
|
|
159
171
|
function escYaml(str) {
|
|
160
|
-
if (str == null) return
|
|
172
|
+
if (str == null) return "";
|
|
161
173
|
return String(str).replace(/"/g, '\\"');
|
|
162
174
|
}
|
|
163
175
|
|
|
164
176
|
function sanitizeSlug(str) {
|
|
165
177
|
return String(str)
|
|
166
178
|
.toLowerCase()
|
|
167
|
-
.replace(/[^a-z0-9]+/g,
|
|
168
|
-
.replace(/^-+|-+$/g,
|
|
179
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
180
|
+
.replace(/^-+|-+$/g, "");
|
|
169
181
|
}
|
|
@@ -6,10 +6,11 @@
|
|
|
6
6
|
* Zero dependencies — node built-in only.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
export const name =
|
|
10
|
-
export const extension =
|
|
11
|
-
export const mimeType =
|
|
12
|
-
export const description =
|
|
9
|
+
export const name = "treemap";
|
|
10
|
+
export const extension = ".json";
|
|
11
|
+
export const mimeType = "application/json; charset=utf-8";
|
|
12
|
+
export const description =
|
|
13
|
+
"Claims as nested JSON for D3 treemap visualization (grouped by type)";
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* Convert a compilation object to D3 treemap JSON.
|
|
@@ -18,12 +19,12 @@ export const description = 'Claims as nested JSON for D3 treemap visualization (
|
|
|
18
19
|
*/
|
|
19
20
|
export function convert(compilation) {
|
|
20
21
|
const claims = compilation.claims || [];
|
|
21
|
-
const sprintName = compilation.meta?.sprint ||
|
|
22
|
+
const sprintName = compilation.meta?.sprint || "sprint";
|
|
22
23
|
|
|
23
24
|
// Group claims by type
|
|
24
25
|
const groups = {};
|
|
25
26
|
for (const claim of claims) {
|
|
26
|
-
const type = claim.type ||
|
|
27
|
+
const type = claim.type || "other";
|
|
27
28
|
if (!groups[type]) groups[type] = [];
|
|
28
29
|
groups[type].push(claim);
|
|
29
30
|
}
|
|
@@ -32,17 +33,17 @@ export function convert(compilation) {
|
|
|
32
33
|
name: sprintName,
|
|
33
34
|
children: Object.entries(groups).map(([type, group]) => ({
|
|
34
35
|
name: type,
|
|
35
|
-
children: group.map(claim => {
|
|
36
|
+
children: group.map((claim) => {
|
|
36
37
|
const node = {
|
|
37
|
-
name: claim.id ||
|
|
38
|
+
name: claim.id || "",
|
|
38
39
|
value: claim.confidence != null ? claim.confidence : 1,
|
|
39
40
|
evidence: getEvidence(claim),
|
|
40
41
|
};
|
|
41
42
|
|
|
42
|
-
const content = claim.content || claim.text ||
|
|
43
|
+
const content = claim.content || claim.text || "";
|
|
43
44
|
if (content) node.content = content;
|
|
44
45
|
|
|
45
|
-
const status = claim.status ||
|
|
46
|
+
const status = claim.status || "";
|
|
46
47
|
if (status) node.status = status;
|
|
47
48
|
|
|
48
49
|
const tags = Array.isArray(claim.tags) ? claim.tags : [];
|
|
@@ -53,13 +54,13 @@ export function convert(compilation) {
|
|
|
53
54
|
})),
|
|
54
55
|
};
|
|
55
56
|
|
|
56
|
-
return JSON.stringify(tree, null, 2) +
|
|
57
|
+
return JSON.stringify(tree, null, 2) + "\n";
|
|
57
58
|
}
|
|
58
59
|
|
|
59
60
|
function getEvidence(claim) {
|
|
60
|
-
if (typeof claim.evidence ===
|
|
61
|
-
if (typeof claim.evidence ===
|
|
62
|
-
return claim.evidence.tier || claim.evidence_tier ||
|
|
61
|
+
if (typeof claim.evidence === "string") return claim.evidence;
|
|
62
|
+
if (typeof claim.evidence === "object" && claim.evidence !== null) {
|
|
63
|
+
return claim.evidence.tier || claim.evidence_tier || "";
|
|
63
64
|
}
|
|
64
|
-
return claim.evidence_tier ||
|
|
65
|
+
return claim.evidence_tier || "";
|
|
65
66
|
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* mill format: typescript-defs
|
|
3
|
+
*
|
|
4
|
+
* Converts compilation.json to TypeScript .d.ts definitions.
|
|
5
|
+
* Derives union types from actual claim data values.
|
|
6
|
+
* Zero dependencies — node built-in only.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export const name = "typescript-defs";
|
|
10
|
+
export const extension = ".d.ts";
|
|
11
|
+
export const mimeType = "text/plain; charset=utf-8";
|
|
12
|
+
export const description =
|
|
13
|
+
"TypeScript type definitions derived from compilation data";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Convert a compilation object to TypeScript .d.ts definitions.
|
|
17
|
+
* @param {object} compilation - The compilation.json content
|
|
18
|
+
* @returns {string} TypeScript definition output
|
|
19
|
+
*/
|
|
20
|
+
export function convert(compilation) {
|
|
21
|
+
const claims = compilation.claims || [];
|
|
22
|
+
const meta = compilation.meta || {};
|
|
23
|
+
const conflicts = compilation.conflicts || [];
|
|
24
|
+
const certificate = compilation.certificate || {};
|
|
25
|
+
|
|
26
|
+
const claimTypes = uniqueSorted(claims.map((c) => c.type).filter(Boolean));
|
|
27
|
+
const evidenceTiers = uniqueSorted(
|
|
28
|
+
claims
|
|
29
|
+
.map((c) => {
|
|
30
|
+
if (typeof c.evidence === "string") return c.evidence;
|
|
31
|
+
return c.evidence?.tier ?? c.evidence_tier ?? null;
|
|
32
|
+
})
|
|
33
|
+
.filter(Boolean),
|
|
34
|
+
);
|
|
35
|
+
const statuses = uniqueSorted(claims.map((c) => c.status).filter(Boolean));
|
|
36
|
+
|
|
37
|
+
const lines = [];
|
|
38
|
+
|
|
39
|
+
lines.push("// Auto-generated by mill typescript-defs format");
|
|
40
|
+
lines.push("// Derived from compilation data — do not edit manually");
|
|
41
|
+
lines.push("");
|
|
42
|
+
|
|
43
|
+
// ClaimType union
|
|
44
|
+
lines.push(`export type ClaimType = ${unionType(claimTypes)};`);
|
|
45
|
+
lines.push("");
|
|
46
|
+
|
|
47
|
+
// EvidenceTier union
|
|
48
|
+
lines.push(`export type EvidenceTier = ${unionType(evidenceTiers)};`);
|
|
49
|
+
lines.push("");
|
|
50
|
+
|
|
51
|
+
// ClaimStatus union
|
|
52
|
+
lines.push(`export type ClaimStatus = ${unionType(statuses)};`);
|
|
53
|
+
lines.push("");
|
|
54
|
+
|
|
55
|
+
// Evidence interface
|
|
56
|
+
lines.push("export interface Evidence {");
|
|
57
|
+
lines.push(" tier: EvidenceTier;");
|
|
58
|
+
lines.push(" source?: string;");
|
|
59
|
+
lines.push("}");
|
|
60
|
+
lines.push("");
|
|
61
|
+
|
|
62
|
+
// Source interface
|
|
63
|
+
lines.push("export interface ClaimSource {");
|
|
64
|
+
lines.push(" origin?: string;");
|
|
65
|
+
lines.push(" artifact?: string;");
|
|
66
|
+
lines.push("}");
|
|
67
|
+
lines.push("");
|
|
68
|
+
|
|
69
|
+
// Claim interface
|
|
70
|
+
lines.push("export interface Claim {");
|
|
71
|
+
lines.push(" id: string;");
|
|
72
|
+
lines.push(" type: ClaimType;");
|
|
73
|
+
lines.push(" content: string;");
|
|
74
|
+
lines.push(" evidence: EvidenceTier | Evidence;");
|
|
75
|
+
lines.push(" status: ClaimStatus;");
|
|
76
|
+
lines.push(" confidence?: number;");
|
|
77
|
+
lines.push(" source?: string | ClaimSource;");
|
|
78
|
+
lines.push(" tags?: string[];");
|
|
79
|
+
lines.push(" created?: string;");
|
|
80
|
+
lines.push("}");
|
|
81
|
+
lines.push("");
|
|
82
|
+
|
|
83
|
+
// Meta interface
|
|
84
|
+
lines.push("export interface Meta {");
|
|
85
|
+
lines.push(" sprint?: string;");
|
|
86
|
+
lines.push(" question?: string;");
|
|
87
|
+
lines.push(" audience?: string;");
|
|
88
|
+
for (const key of Object.keys(meta)) {
|
|
89
|
+
if (!["sprint", "question", "audience"].includes(key)) {
|
|
90
|
+
lines.push(` ${sanitizeKey(key)}?: ${inferTsType(meta[key])};`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
lines.push("}");
|
|
94
|
+
lines.push("");
|
|
95
|
+
|
|
96
|
+
// Conflict interface
|
|
97
|
+
lines.push("export interface Conflict {");
|
|
98
|
+
lines.push(" ids?: string[];");
|
|
99
|
+
lines.push(" description?: string;");
|
|
100
|
+
lines.push(" reason?: string;");
|
|
101
|
+
lines.push(" resolution?: string;");
|
|
102
|
+
lines.push("}");
|
|
103
|
+
lines.push("");
|
|
104
|
+
|
|
105
|
+
// Certificate interface
|
|
106
|
+
lines.push("export interface Certificate {");
|
|
107
|
+
lines.push(" compiled_at?: string;");
|
|
108
|
+
lines.push(" sha256?: string;");
|
|
109
|
+
lines.push(" claim_count?: number;");
|
|
110
|
+
for (const key of Object.keys(certificate)) {
|
|
111
|
+
if (!["compiled_at", "sha256", "claim_count"].includes(key)) {
|
|
112
|
+
lines.push(` ${sanitizeKey(key)}?: ${inferTsType(certificate[key])};`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
lines.push("}");
|
|
116
|
+
lines.push("");
|
|
117
|
+
|
|
118
|
+
// Compilation interface
|
|
119
|
+
lines.push("export interface Compilation {");
|
|
120
|
+
lines.push(" meta: Meta;");
|
|
121
|
+
lines.push(" claims: Claim[];");
|
|
122
|
+
lines.push(" conflicts: Conflict[];");
|
|
123
|
+
lines.push(" certificate: Certificate;");
|
|
124
|
+
lines.push("}");
|
|
125
|
+
lines.push("");
|
|
126
|
+
|
|
127
|
+
return lines.join("\n");
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function uniqueSorted(arr) {
|
|
131
|
+
return [...new Set(arr)].sort();
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function unionType(values) {
|
|
135
|
+
if (values.length === 0) return "string";
|
|
136
|
+
return values.map((v) => `'${v}'`).join(" | ");
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function sanitizeKey(key) {
|
|
140
|
+
return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key) ? key : `'${key}'`;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function inferTsType(value) {
|
|
144
|
+
if (value === null || value === undefined) return "unknown";
|
|
145
|
+
if (typeof value === "number") return "number";
|
|
146
|
+
if (typeof value === "boolean") return "boolean";
|
|
147
|
+
if (typeof value === "string") return "string";
|
|
148
|
+
if (Array.isArray(value)) return "unknown[]";
|
|
149
|
+
return "Record<string, unknown>";
|
|
150
|
+
}
|
|
@@ -7,10 +7,10 @@
|
|
|
7
7
|
* Zero dependencies — node built-in only.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
export const name =
|
|
11
|
-
export const extension =
|
|
12
|
-
export const mimeType =
|
|
13
|
-
export const description =
|
|
10
|
+
export const name = "yaml";
|
|
11
|
+
export const extension = ".yaml";
|
|
12
|
+
export const mimeType = "text/yaml; charset=utf-8";
|
|
13
|
+
export const description = "YAML serialization of compilation data";
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* Convert a compilation object to YAML.
|
|
@@ -19,29 +19,29 @@ export const description = 'YAML serialization of compilation data';
|
|
|
19
19
|
*/
|
|
20
20
|
export function convert(compilation) {
|
|
21
21
|
const lines = [];
|
|
22
|
-
lines.push(
|
|
23
|
-
lines.push(
|
|
22
|
+
lines.push("# Auto-generated by mill yaml format");
|
|
23
|
+
lines.push("");
|
|
24
24
|
lines.push(toYaml(compilation, 0));
|
|
25
|
-
return lines.join(
|
|
25
|
+
return lines.join("\n");
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
function toYaml(value, indent) {
|
|
29
29
|
if (value === null || value === undefined) {
|
|
30
|
-
return
|
|
30
|
+
return "null";
|
|
31
31
|
}
|
|
32
|
-
if (typeof value ===
|
|
33
|
-
return value ?
|
|
32
|
+
if (typeof value === "boolean") {
|
|
33
|
+
return value ? "true" : "false";
|
|
34
34
|
}
|
|
35
|
-
if (typeof value ===
|
|
35
|
+
if (typeof value === "number") {
|
|
36
36
|
return String(value);
|
|
37
37
|
}
|
|
38
|
-
if (typeof value ===
|
|
38
|
+
if (typeof value === "string") {
|
|
39
39
|
return yamlString(value, indent);
|
|
40
40
|
}
|
|
41
41
|
if (Array.isArray(value)) {
|
|
42
42
|
return yamlArray(value, indent);
|
|
43
43
|
}
|
|
44
|
-
if (typeof value ===
|
|
44
|
+
if (typeof value === "object") {
|
|
45
45
|
return yamlObject(value, indent);
|
|
46
46
|
}
|
|
47
47
|
return String(value);
|
|
@@ -49,10 +49,13 @@ function toYaml(value, indent) {
|
|
|
49
49
|
|
|
50
50
|
function yamlString(str, indent) {
|
|
51
51
|
// Use block scalar for multi-line strings
|
|
52
|
-
if (str.includes(
|
|
53
|
-
const pad =
|
|
54
|
-
const indented = str
|
|
55
|
-
|
|
52
|
+
if (str.includes("\n")) {
|
|
53
|
+
const pad = " ".repeat(indent + 2);
|
|
54
|
+
const indented = str
|
|
55
|
+
.split("\n")
|
|
56
|
+
.map((line) => pad + line)
|
|
57
|
+
.join("\n");
|
|
58
|
+
return "|\n" + indented;
|
|
56
59
|
}
|
|
57
60
|
// Quote if the string contains special YAML characters or could be misinterpreted
|
|
58
61
|
if (needsQuoting(str)) {
|
|
@@ -62,39 +65,46 @@ function yamlString(str, indent) {
|
|
|
62
65
|
}
|
|
63
66
|
|
|
64
67
|
function needsQuoting(str) {
|
|
65
|
-
if (str ===
|
|
66
|
-
if (
|
|
68
|
+
if (str === "") return true;
|
|
69
|
+
if (
|
|
70
|
+
str === "true" ||
|
|
71
|
+
str === "false" ||
|
|
72
|
+
str === "null" ||
|
|
73
|
+
str === "yes" ||
|
|
74
|
+
str === "no"
|
|
75
|
+
)
|
|
76
|
+
return true;
|
|
67
77
|
if (/^[0-9]/.test(str) && !isNaN(Number(str))) return true;
|
|
68
78
|
if (/[:{}\[\],&*?|>!%#@`"']/.test(str)) return true;
|
|
69
79
|
if (/^\s|\s$/.test(str)) return true;
|
|
70
|
-
if (str.startsWith(
|
|
80
|
+
if (str.startsWith("- ") || str.startsWith("? ")) return true;
|
|
71
81
|
return false;
|
|
72
82
|
}
|
|
73
83
|
|
|
74
84
|
function yamlArray(arr, indent) {
|
|
75
|
-
if (arr.length === 0) return
|
|
76
|
-
const pad =
|
|
85
|
+
if (arr.length === 0) return "[]";
|
|
86
|
+
const pad = " ".repeat(indent);
|
|
77
87
|
const items = [];
|
|
78
88
|
for (const item of arr) {
|
|
79
|
-
if (item !== null && typeof item ===
|
|
89
|
+
if (item !== null && typeof item === "object" && !Array.isArray(item)) {
|
|
80
90
|
// Object items: first key on same line as -, rest indented
|
|
81
91
|
const keys = Object.keys(item);
|
|
82
92
|
if (keys.length === 0) {
|
|
83
|
-
items.push(pad +
|
|
93
|
+
items.push(pad + "- {}");
|
|
84
94
|
} else {
|
|
85
95
|
const firstKey = keys[0];
|
|
86
96
|
const firstVal = toYaml(item[firstKey], indent + 4);
|
|
87
97
|
const firstLine = isScalar(item[firstKey])
|
|
88
98
|
? `${pad}- ${firstKey}: ${firstVal}`
|
|
89
99
|
: `${pad}- ${firstKey}:\n${indentBlock(firstVal, indent + 4)}`;
|
|
90
|
-
const rest = keys.slice(1).map(key => {
|
|
100
|
+
const rest = keys.slice(1).map((key) => {
|
|
91
101
|
const val = toYaml(item[key], indent + 4);
|
|
92
102
|
if (isScalar(item[key])) {
|
|
93
103
|
return `${pad} ${key}: ${val}`;
|
|
94
104
|
}
|
|
95
105
|
return `${pad} ${key}:\n${indentBlock(val, indent + 4)}`;
|
|
96
106
|
});
|
|
97
|
-
items.push([firstLine, ...rest].join(
|
|
107
|
+
items.push([firstLine, ...rest].join("\n"));
|
|
98
108
|
}
|
|
99
109
|
} else if (Array.isArray(item)) {
|
|
100
110
|
const nested = toYaml(item, indent + 2);
|
|
@@ -103,13 +113,13 @@ function yamlArray(arr, indent) {
|
|
|
103
113
|
items.push(`${pad}- ${toYaml(item, indent + 2)}`);
|
|
104
114
|
}
|
|
105
115
|
}
|
|
106
|
-
return
|
|
116
|
+
return "\n" + items.join("\n");
|
|
107
117
|
}
|
|
108
118
|
|
|
109
119
|
function yamlObject(obj, indent) {
|
|
110
120
|
const keys = Object.keys(obj);
|
|
111
|
-
if (keys.length === 0) return
|
|
112
|
-
const pad =
|
|
121
|
+
if (keys.length === 0) return "{}";
|
|
122
|
+
const pad = " ".repeat(indent);
|
|
113
123
|
const entries = [];
|
|
114
124
|
for (const key of keys) {
|
|
115
125
|
const val = obj[key];
|
|
@@ -118,27 +128,35 @@ function yamlObject(obj, indent) {
|
|
|
118
128
|
entries.push(`${pad}${yamlKey}: ${toYaml(val, indent + 2)}`);
|
|
119
129
|
} else {
|
|
120
130
|
const nested = toYaml(val, indent + 2);
|
|
121
|
-
if (nested.startsWith(
|
|
131
|
+
if (nested.startsWith("\n")) {
|
|
122
132
|
entries.push(`${pad}${yamlKey}:${nested}`);
|
|
123
133
|
} else {
|
|
124
134
|
entries.push(`${pad}${yamlKey}:\n${indentBlock(nested, indent + 2)}`);
|
|
125
135
|
}
|
|
126
136
|
}
|
|
127
137
|
}
|
|
128
|
-
return
|
|
138
|
+
return "\n" + entries.join("\n");
|
|
129
139
|
}
|
|
130
140
|
|
|
131
141
|
function isScalar(value) {
|
|
132
|
-
return
|
|
133
|
-
|
|
142
|
+
return (
|
|
143
|
+
value === null ||
|
|
144
|
+
value === undefined ||
|
|
145
|
+
typeof value === "string" ||
|
|
146
|
+
typeof value === "number" ||
|
|
147
|
+
typeof value === "boolean"
|
|
148
|
+
);
|
|
134
149
|
}
|
|
135
150
|
|
|
136
151
|
function indentBlock(text, indent) {
|
|
137
|
-
const pad =
|
|
138
|
-
return text
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
152
|
+
const pad = " ".repeat(indent);
|
|
153
|
+
return text
|
|
154
|
+
.split("\n")
|
|
155
|
+
.map((line) => {
|
|
156
|
+
if (line.trim() === "") return "";
|
|
157
|
+
// Only add padding if the line doesn't already have it
|
|
158
|
+
if (line.startsWith(pad)) return line;
|
|
159
|
+
return pad + line;
|
|
160
|
+
})
|
|
161
|
+
.join("\n");
|
|
144
162
|
}
|