@grainulation/mill 1.0.0 → 1.0.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/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.js +41 -34
- package/lib/formats/changelog.js +27 -26
- package/lib/formats/confluence-adf.js +312 -0
- package/lib/formats/csv.js +41 -37
- package/lib/formats/dot.js +45 -34
- package/lib/formats/evidence-matrix.js +17 -16
- package/lib/formats/executive-summary.js +89 -41
- package/lib/formats/github-issues.js +40 -33
- package/lib/formats/graphml.js +45 -32
- package/lib/formats/html-report.js +110 -63
- package/lib/formats/jira-csv.js +30 -29
- package/lib/formats/json-ld.js +6 -6
- package/lib/formats/markdown.js +53 -36
- package/lib/formats/ndjson.js +6 -6
- package/lib/formats/obsidian.js +43 -35
- package/lib/formats/opml.js +38 -28
- package/lib/formats/ris.js +29 -23
- package/lib/formats/rss.js +31 -28
- package/lib/formats/sankey.js +16 -15
- package/lib/formats/slide-deck.js +145 -57
- package/lib/formats/sql.js +57 -53
- package/lib/formats/static-site.js +64 -52
- package/lib/formats/treemap.js +16 -15
- package/lib/formats/typescript-defs.js +79 -76
- package/lib/formats/yaml.js +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 +7 -3
package/lib/formats/yaml.js
CHANGED
|
@@ -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
|
}
|
package/lib/formats.js
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
|
|
1
|
+
"use strict";
|
|
2
2
|
|
|
3
|
-
const pdf = require(
|
|
4
|
-
const csv = require(
|
|
5
|
-
const markdown = require(
|
|
6
|
-
const jsonLd = require(
|
|
7
|
-
const static_ = require(
|
|
8
|
-
const clipboard = require(
|
|
3
|
+
const pdf = require("./exporters/pdf.js");
|
|
4
|
+
const csv = require("./exporters/csv.js");
|
|
5
|
+
const markdown = require("./exporters/markdown.js");
|
|
6
|
+
const jsonLd = require("./exporters/json-ld.js");
|
|
7
|
+
const static_ = require("./publishers/static.js");
|
|
8
|
+
const clipboard = require("./publishers/clipboard.js");
|
|
9
9
|
|
|
10
10
|
const EXPORTERS = {
|
|
11
11
|
pdf,
|
|
12
12
|
csv,
|
|
13
13
|
markdown,
|
|
14
|
-
|
|
14
|
+
"json-ld": jsonLd,
|
|
15
15
|
};
|
|
16
16
|
|
|
17
17
|
const PUBLISHERS = {
|
|
@@ -23,16 +23,16 @@ const PUBLISHERS = {
|
|
|
23
23
|
* Detect the likely format of an input file by extension.
|
|
24
24
|
*/
|
|
25
25
|
function detectFormat(filePath) {
|
|
26
|
-
const ext = filePath.split(
|
|
26
|
+
const ext = filePath.split(".").pop().toLowerCase();
|
|
27
27
|
const map = {
|
|
28
|
-
html:
|
|
29
|
-
htm:
|
|
30
|
-
md:
|
|
31
|
-
json:
|
|
32
|
-
csv:
|
|
33
|
-
jsonld:
|
|
28
|
+
html: "html",
|
|
29
|
+
htm: "html",
|
|
30
|
+
md: "markdown",
|
|
31
|
+
json: "json",
|
|
32
|
+
csv: "csv",
|
|
33
|
+
jsonld: "json-ld",
|
|
34
34
|
};
|
|
35
|
-
return map[ext] ||
|
|
35
|
+
return map[ext] || "unknown";
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
function getExporter(name) {
|
package/lib/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
|
|
1
|
+
"use strict";
|
|
2
2
|
|
|
3
|
-
const formats = require(
|
|
3
|
+
const formats = require("./formats.js");
|
|
4
4
|
|
|
5
|
-
const name =
|
|
6
|
-
const version = require(
|
|
7
|
-
const description =
|
|
5
|
+
const name = "mill";
|
|
6
|
+
const version = require("../package.json").version;
|
|
7
|
+
const description = "Turn wheat sprint artifacts into shareable formats";
|
|
8
8
|
|
|
9
9
|
module.exports = {
|
|
10
10
|
name,
|
package/lib/json-ld-common.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
"use strict";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* json-ld-common.js — Shared JSON-LD vocabulary for mill
|
|
@@ -11,24 +11,25 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
const CONTEXT = {
|
|
14
|
-
|
|
15
|
-
wheat:
|
|
16
|
-
claim:
|
|
17
|
-
confidence:
|
|
18
|
-
evidenceTier:
|
|
19
|
-
claimType:
|
|
20
|
-
sprintId:
|
|
14
|
+
"@vocab": "https://schema.org/",
|
|
15
|
+
wheat: "https://grainulation.com/ns/wheat#",
|
|
16
|
+
claim: "wheat:Claim",
|
|
17
|
+
confidence: "wheat:confidence",
|
|
18
|
+
evidenceTier: "wheat:evidenceTier",
|
|
19
|
+
claimType: "wheat:claimType",
|
|
20
|
+
sprintId: "wheat:sprintId",
|
|
21
21
|
};
|
|
22
22
|
|
|
23
23
|
function claimToJsonLd(claim) {
|
|
24
|
-
const body = claim.content || claim.text ||
|
|
25
|
-
const evidenceTier =
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
const body = claim.content || claim.text || "";
|
|
25
|
+
const evidenceTier =
|
|
26
|
+
typeof claim.evidence === "string"
|
|
27
|
+
? claim.evidence
|
|
28
|
+
: (claim.evidence?.tier ?? claim.evidence_tier ?? null);
|
|
28
29
|
|
|
29
30
|
return {
|
|
30
|
-
|
|
31
|
-
|
|
31
|
+
"@type": "claim",
|
|
32
|
+
"@id": `wheat:claim/${claim.id}`,
|
|
32
33
|
identifier: claim.id,
|
|
33
34
|
claimType: claim.type,
|
|
34
35
|
text: body,
|
|
@@ -36,36 +37,41 @@ function claimToJsonLd(claim) {
|
|
|
36
37
|
confidence: claim.confidence ?? null,
|
|
37
38
|
evidenceTier,
|
|
38
39
|
dateCreated: claim.created || claim.timestamp || null,
|
|
39
|
-
...(claim.tags?.length ? { keywords: claim.tags.join(
|
|
40
|
+
...(claim.tags?.length ? { keywords: claim.tags.join(", ") } : {}),
|
|
40
41
|
...(claim.status ? { status: claim.status } : {}),
|
|
41
42
|
};
|
|
42
43
|
}
|
|
43
44
|
|
|
44
45
|
function buildReport(meta, claims, certificate) {
|
|
45
46
|
return {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
name: meta.sprint || meta.question ||
|
|
50
|
-
description: meta.question ||
|
|
51
|
-
dateCreated:
|
|
52
|
-
|
|
47
|
+
"@context": CONTEXT,
|
|
48
|
+
"@type": "Report",
|
|
49
|
+
"@id": `wheat:sprint/${meta.sprint || "unknown"}`,
|
|
50
|
+
name: meta.sprint || meta.question || "Wheat Sprint Report",
|
|
51
|
+
description: meta.question || "",
|
|
52
|
+
dateCreated:
|
|
53
|
+
(certificate && certificate.compiled_at) || new Date().toISOString(),
|
|
54
|
+
...(meta.audience
|
|
55
|
+
? { audience: { "@type": "Audience", name: meta.audience } }
|
|
56
|
+
: {}),
|
|
53
57
|
hasPart: {
|
|
54
|
-
|
|
58
|
+
"@type": "ItemList",
|
|
55
59
|
numberOfItems: claims.length,
|
|
56
60
|
itemListElement: claims.map((claim, i) => ({
|
|
57
|
-
|
|
61
|
+
"@type": "ListItem",
|
|
58
62
|
position: i + 1,
|
|
59
63
|
item: claimToJsonLd(claim),
|
|
60
64
|
})),
|
|
61
65
|
},
|
|
62
|
-
...(
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
66
|
+
...(certificate && certificate.sha256
|
|
67
|
+
? {
|
|
68
|
+
identifier: {
|
|
69
|
+
"@type": "PropertyValue",
|
|
70
|
+
name: "certificate-sha256",
|
|
71
|
+
value: certificate.sha256,
|
|
72
|
+
},
|
|
73
|
+
}
|
|
74
|
+
: {}),
|
|
69
75
|
};
|
|
70
76
|
}
|
|
71
77
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
"use strict";
|
|
2
2
|
|
|
3
|
-
const fs = require(
|
|
4
|
-
const path = require(
|
|
5
|
-
const { execFile } = require(
|
|
3
|
+
const fs = require("node:fs");
|
|
4
|
+
const path = require("node:path");
|
|
5
|
+
const { execFile } = require("node:child_process");
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Copy formatted output to system clipboard.
|
|
@@ -11,12 +11,12 @@ const { execFile } = require('node:child_process');
|
|
|
11
11
|
|
|
12
12
|
function getClipboardCommand() {
|
|
13
13
|
switch (process.platform) {
|
|
14
|
-
case
|
|
15
|
-
return { cmd:
|
|
16
|
-
case
|
|
17
|
-
return { cmd:
|
|
18
|
-
case
|
|
19
|
-
return { cmd:
|
|
14
|
+
case "darwin":
|
|
15
|
+
return { cmd: "pbcopy", args: [] };
|
|
16
|
+
case "linux":
|
|
17
|
+
return { cmd: "xclip", args: ["-selection", "clipboard"] };
|
|
18
|
+
case "win32":
|
|
19
|
+
return { cmd: "clip", args: [] };
|
|
20
20
|
default:
|
|
21
21
|
return null;
|
|
22
22
|
}
|
|
@@ -46,25 +46,27 @@ async function publishClipboard(inputPath) {
|
|
|
46
46
|
let content;
|
|
47
47
|
if (stat.isDirectory()) {
|
|
48
48
|
// If directory, list the files
|
|
49
|
-
const files = fs.readdirSync(inputPath).filter((f) => !f.startsWith(
|
|
50
|
-
content = files
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
49
|
+
const files = fs.readdirSync(inputPath).filter((f) => !f.startsWith("."));
|
|
50
|
+
content = files
|
|
51
|
+
.map((f) => {
|
|
52
|
+
const full = path.join(inputPath, f);
|
|
53
|
+
return fs.readFileSync(full, "utf-8");
|
|
54
|
+
})
|
|
55
|
+
.join("\n\n---\n\n");
|
|
54
56
|
} else {
|
|
55
|
-
content = fs.readFileSync(inputPath,
|
|
57
|
+
content = fs.readFileSync(inputPath, "utf-8");
|
|
56
58
|
}
|
|
57
59
|
|
|
58
60
|
await copyToClipboard(content);
|
|
59
61
|
|
|
60
|
-
const size = Buffer.byteLength(content,
|
|
62
|
+
const size = Buffer.byteLength(content, "utf-8");
|
|
61
63
|
return {
|
|
62
64
|
message: `Copied to clipboard (${size} bytes)`,
|
|
63
65
|
};
|
|
64
66
|
}
|
|
65
67
|
|
|
66
68
|
module.exports = {
|
|
67
|
-
name:
|
|
68
|
-
description:
|
|
69
|
+
name: "clipboard",
|
|
70
|
+
description: "Copy formatted output to system clipboard",
|
|
69
71
|
publish: publishClipboard,
|
|
70
72
|
};
|
package/lib/publishers/static.js
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
|
-
|
|
1
|
+
"use strict";
|
|
2
2
|
|
|
3
|
-
const fs = require(
|
|
4
|
-
const path = require(
|
|
3
|
+
const fs = require("node:fs");
|
|
4
|
+
const path = require("node:path");
|
|
5
5
|
|
|
6
|
-
function esc(s) {
|
|
6
|
+
function esc(s) {
|
|
7
|
+
return String(s || "")
|
|
8
|
+
.replace(/&/g, "&")
|
|
9
|
+
.replace(/</g, "<")
|
|
10
|
+
.replace(/>/g, ">")
|
|
11
|
+
.replace(/"/g, """)
|
|
12
|
+
.replace(/'/g, "'");
|
|
13
|
+
}
|
|
7
14
|
|
|
8
15
|
/**
|
|
9
16
|
* Generate a static site from sprint output directory.
|
|
@@ -93,7 +100,7 @@ function scanDir(dir, base) {
|
|
|
93
100
|
const stat = fs.statSync(full);
|
|
94
101
|
const real = fs.realpathSync(full);
|
|
95
102
|
if (!real.startsWith(fs.realpathSync(base))) continue;
|
|
96
|
-
if (stat.isFile() && !name.startsWith(
|
|
103
|
+
if (stat.isFile() && !name.startsWith(".")) {
|
|
97
104
|
const rel = path.relative(base, full);
|
|
98
105
|
entries.push({
|
|
99
106
|
name,
|
|
@@ -102,7 +109,11 @@ function scanDir(dir, base) {
|
|
|
102
109
|
ext: path.extname(name).slice(1).toLowerCase(),
|
|
103
110
|
modified: stat.mtime,
|
|
104
111
|
});
|
|
105
|
-
} else if (
|
|
112
|
+
} else if (
|
|
113
|
+
stat.isDirectory() &&
|
|
114
|
+
!name.startsWith(".") &&
|
|
115
|
+
name !== "_site"
|
|
116
|
+
) {
|
|
106
117
|
entries.push(...scanDir(full, base));
|
|
107
118
|
}
|
|
108
119
|
}
|
|
@@ -110,7 +121,7 @@ function scanDir(dir, base) {
|
|
|
110
121
|
}
|
|
111
122
|
|
|
112
123
|
async function publishStatic(inputDir, outputDir) {
|
|
113
|
-
const outDir = outputDir || path.join(inputDir,
|
|
124
|
+
const outDir = outputDir || path.join(inputDir, "_site");
|
|
114
125
|
if (!fs.existsSync(outDir)) {
|
|
115
126
|
fs.mkdirSync(outDir, { recursive: true });
|
|
116
127
|
}
|
|
@@ -130,14 +141,18 @@ async function publishStatic(inputDir, outputDir) {
|
|
|
130
141
|
|
|
131
142
|
// Build index
|
|
132
143
|
const sprintName = path.basename(path.resolve(inputDir));
|
|
133
|
-
const cards = entries
|
|
144
|
+
const cards = entries
|
|
145
|
+
.map(
|
|
146
|
+
(e) => `
|
|
134
147
|
<div class="card">
|
|
135
148
|
<a href="${esc(e.path)}">${esc(e.name)}</a>
|
|
136
149
|
<div class="meta">${e.ext.toUpperCase()} · ${formatBytes(e.size)}</div>
|
|
137
|
-
</div
|
|
150
|
+
</div>`,
|
|
151
|
+
)
|
|
152
|
+
.join("\n");
|
|
138
153
|
|
|
139
154
|
const html = TEMPLATE(`Sprint: ${sprintName}`, cards);
|
|
140
|
-
fs.writeFileSync(path.join(outDir,
|
|
155
|
+
fs.writeFileSync(path.join(outDir, "index.html"), html, "utf-8");
|
|
141
156
|
|
|
142
157
|
return {
|
|
143
158
|
outputPath: outDir,
|
|
@@ -146,7 +161,7 @@ async function publishStatic(inputDir, outputDir) {
|
|
|
146
161
|
}
|
|
147
162
|
|
|
148
163
|
module.exports = {
|
|
149
|
-
name:
|
|
150
|
-
description:
|
|
164
|
+
name: "static",
|
|
165
|
+
description: "Generate a static site from sprint outputs",
|
|
151
166
|
publish: publishStatic,
|
|
152
167
|
};
|