@cosla/sensemaking-report-builder 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +117 -0
- package/bin/cli.js +12 -0
- package/build.js +308 -0
- package/data.js +724 -0
- package/package.json +54 -0
- package/src/d3.v7.min.js +2 -0
- package/src/default-translations.json +76 -0
- package/src/fonts/noto-sans-v42-latin-500.txt +1 -0
- package/src/fonts/noto-sans-v42-latin-500.woff2 +0 -0
- package/src/fonts/noto-sans-v42-latin-600.txt +1 -0
- package/src/fonts/noto-sans-v42-latin-600.woff2 +0 -0
- package/src/fonts/noto-sans-v42-latin-700.txt +1 -0
- package/src/fonts/noto-sans-v42-latin-700.woff2 +0 -0
- package/src/fonts/noto-sans-v42-latin-regular.txt +1 -0
- package/src/fonts/noto-sans-v42-latin-regular.woff2 +0 -0
- package/src/index.mustache +304 -0
- package/src/popper.min.js +6 -0
- package/src/print.css +120 -0
- package/src/reset.css +55 -0
- package/src/script.js +1251 -0
- package/src/style.css +1609 -0
- package/src/svg/check.svg +3 -0
- package/src/svg/copy.svg +10 -0
- package/src/svg/exclamation.svg +3 -0
- package/src/svg/eye.svg +10 -0
- package/src/svg/filter.svg +3 -0
- package/src/svg/group.svg +10 -0
- package/src/svg/hamburger.svg +5 -0
- package/src/svg/list.svg +10 -0
- package/src/svg/list_alt.svg +10 -0
- package/src/svg/predict.svg +10 -0
- package/src/svg/reset.svg +3 -0
- package/src/svg/share.svg +10 -0
- package/src/svg/star.svg +10 -0
- package/src/svg/thumbs.svg +10 -0
- package/src/svg/trash.svg +3 -0
- package/src/svg/vote.svg +10 -0
- package/src/svg/x.svg +3 -0
- package/src/tippy-bundle.umd.min.js +2 -0
package/README.md
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# @cosla/sensemaking-report-builder
|
|
2
|
+
|
|
3
|
+
> **Cosla fork.** Maintained by [Cosla](https://github.com/CoslaDigital/sensemaking-tools) from the interactive report UI in [Jigsaw sensemaking-tools](https://github.com/Jigsaw-Code/sensemaking-tools) (`src/report_ui`). Not an official Jigsaw or Google release. Cosla adds a path-configurable CLI so Consul and other jobs can pass pipeline artefacts without copying into a package `input/` folder.
|
|
4
|
+
|
|
5
|
+
Builds an interactive HTML report from **Python pipeline** outputs: bridging-scores / opinions CSV plus `report_data.json` (summary).
|
|
6
|
+
|
|
7
|
+
This is **not** [`@cosla/sensemaking-report-ui`](https://www.npmjs.com/package/@cosla/sensemaking-report-ui), which consumes Node advanced JSON (`topic-stats.json`, `comments.json`, etc.).
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install @cosla/sensemaking-report-builder
|
|
13
|
+
# or
|
|
14
|
+
npx @cosla/sensemaking-report-builder inline --help
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
CLI binary: `sensemaking-report-builder`.
|
|
18
|
+
|
|
19
|
+
## Quick start (CLI)
|
|
20
|
+
|
|
21
|
+
### Inline (single self-contained HTML) — use `--output`
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npx @cosla/sensemaking-report-builder inline \
|
|
25
|
+
--bridging_scores /path/to/bridging_scores.csv \
|
|
26
|
+
--summary /path/to/report_data.json \
|
|
27
|
+
--output /path/to/report.html
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
`--opinions` is an alias for `--bridging_scores`.
|
|
31
|
+
|
|
32
|
+
### Static (HTML + CSS/JS siblings) — use `--outputDir`
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
npx @cosla/sensemaking-report-builder static \
|
|
36
|
+
--bridging_scores /path/to/bridging_scores.csv \
|
|
37
|
+
--summary /path/to/report_data.json \
|
|
38
|
+
--outputDir /path/to/out
|
|
39
|
+
# → /path/to/out/report.html plus assets
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Using the wrong output flag for the mode fails with a clear error (`inline` forbids `--outputDir`; `static` forbids `--output`).
|
|
43
|
+
|
|
44
|
+
### Directory-oriented (prepared `input/`)
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
npx @cosla/sensemaking-report-builder inline \
|
|
48
|
+
--inputDir ./input \
|
|
49
|
+
--output ./output/report.html
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Defaults: opinions `<inputDir>/opinions.csv`, summary `<inputDir>/summary.json`, optional `<inputDir>/predicted.json` and `<inputDir>/config.json` if present.
|
|
53
|
+
|
|
54
|
+
## CLI options
|
|
55
|
+
|
|
56
|
+
| Arg / flag | Purpose | Default |
|
|
57
|
+
|------------|---------|---------|
|
|
58
|
+
| `inline` \| `static` | Build mode (**required**) | — |
|
|
59
|
+
| `--inputDir` | Base dir for default input paths | `./input` |
|
|
60
|
+
| `--opinions` / `--bridging_scores` | Opinions / bridging scores CSV | `<inputDir>/opinions.csv` |
|
|
61
|
+
| `--summary` | Summary JSON (`report_data.json`) | `<inputDir>/summary.json` |
|
|
62
|
+
| `--predicted` | Predicted-agreement JSON | `<inputDir>/predicted.json` if present |
|
|
63
|
+
| `--config` | Config JSON (logo, exclusions, …) | `<inputDir>/config.json` if present; else `{}` |
|
|
64
|
+
| `--output` | **inline only:** path to HTML file | `./output/report.html` |
|
|
65
|
+
| `--outputDir` | **static only:** output directory | `./output` |
|
|
66
|
+
|
|
67
|
+
Logo and translations are configured in `config.json` (not CLI flags). Place `logo.svg` / `logo.png` next to that config (under `--inputDir` when using dir mode).
|
|
68
|
+
|
|
69
|
+
## Input contracts
|
|
70
|
+
|
|
71
|
+
### Opinions / bridging CSV
|
|
72
|
+
|
|
73
|
+
Required columns: `topic`, `opinion`, `quote`, `participant_id`.
|
|
74
|
+
|
|
75
|
+
Optional: `AVERAGE_OF_2_BRIDGING` or `AVERAGE_OF_3_BRIDGING` (used to sort quotes). Demographic columns: prefix with `demo:` (e.g. `demo:Age`).
|
|
76
|
+
|
|
77
|
+
### Summary JSON
|
|
78
|
+
|
|
79
|
+
Same shape as Python `sensemaking-report-text` / `report_data.json`: `text`, `sub_contents[]` with `title` and `text`. Optional top-level `title`.
|
|
80
|
+
|
|
81
|
+
### Config JSON (optional)
|
|
82
|
+
|
|
83
|
+
| Key | Description |
|
|
84
|
+
| :--- | :--- |
|
|
85
|
+
| `logo` | Header image filename (e.g. `"logo.svg"`) |
|
|
86
|
+
| `translations` | Optional i18n filename under the input dir |
|
|
87
|
+
| `overview_chart` | `"toggle"`, `"topics"`, or `"opinions"` |
|
|
88
|
+
| `number_of_top_opinions` | Opinions overview chart size |
|
|
89
|
+
| `number_of_sample_quotes` | Quote previews per opinion |
|
|
90
|
+
| `low_sample_warning_threshold` | Low-sample warning threshold |
|
|
91
|
+
| `topic_colors` / `chart_colors` | Overview chart colours |
|
|
92
|
+
| `demographic_colors` | Participant chart colours |
|
|
93
|
+
| `excludedTopics` / `excludedOpinions` | Hide named topics/opinions |
|
|
94
|
+
|
|
95
|
+
### Predicted agreement (optional)
|
|
96
|
+
|
|
97
|
+
`predicted.json` with `text` and `sub_contents[]` containing `statements[]` with `text` and `predicted_agreement`.
|
|
98
|
+
|
|
99
|
+
### Translations (optional)
|
|
100
|
+
|
|
101
|
+
Copy `src/default-translations.json`, translate values, set `locale` / `direction` (`ltr` or `rtl`). See that file for keys.
|
|
102
|
+
|
|
103
|
+
## Local development (git checkout)
|
|
104
|
+
|
|
105
|
+
From this directory:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
npm install
|
|
109
|
+
npm run inline # → ./output/report.html
|
|
110
|
+
npm run static # → ./output/report.html + assets
|
|
111
|
+
npm run preview # browser-sync on ./output (after static)
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Related
|
|
115
|
+
|
|
116
|
+
- Python CLIs: PyPI [`cosla-sensemaking-tools`](https://pypi.org/project/cosla-sensemaking-tools/) — see repo [README.md](../../README.md) and [PACKAGING.md](../../PACKAGING.md).
|
|
117
|
+
- Node report UI (different inputs): [`@cosla/sensemaking-report-ui`](https://www.npmjs.com/package/@cosla/sensemaking-report-ui).
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { runBuild } from "../build.js";
|
|
3
|
+
|
|
4
|
+
runBuild(process.argv, process.cwd())
|
|
5
|
+
.then((result) => {
|
|
6
|
+
const out = result.output || result.outputDir;
|
|
7
|
+
console.log(`Standalone report generated: ${out}`);
|
|
8
|
+
})
|
|
9
|
+
.catch((error) => {
|
|
10
|
+
console.error(error.message);
|
|
11
|
+
process.exit(1);
|
|
12
|
+
});
|
package/build.js
ADDED
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Build automation for static and inlined HTML reports.
|
|
3
|
+
* CLI-first entry via {@link runBuild}, modeled on @cosla/sensemaking-report-ui.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import fs from "node:fs";
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
import { fileURLToPath } from "node:url";
|
|
9
|
+
import { execSync } from "node:child_process";
|
|
10
|
+
import csv from "csvtojson";
|
|
11
|
+
import mustache from "mustache";
|
|
12
|
+
import { inlineSource } from "inline-source";
|
|
13
|
+
import { processReportData, resolveBuildOptions } from "./data.js";
|
|
14
|
+
|
|
15
|
+
const WORK_DIR_NAME = ".py-report-ui-work";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Recursively removes a directory or file if it exists.
|
|
19
|
+
* @param {string} dirPath
|
|
20
|
+
*/
|
|
21
|
+
function rm(dirPath) {
|
|
22
|
+
if (fs.existsSync(dirPath)) {
|
|
23
|
+
fs.rmSync(dirPath, { recursive: true, force: true });
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Creates a directory recursively if it does not already exist.
|
|
29
|
+
* @param {string} dirPath
|
|
30
|
+
*/
|
|
31
|
+
function mkdir(dirPath) {
|
|
32
|
+
if (!fs.existsSync(dirPath)) {
|
|
33
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Copies a source file or directory to a destination.
|
|
39
|
+
* @param {string} src
|
|
40
|
+
* @param {string} dest
|
|
41
|
+
*/
|
|
42
|
+
function cp(src, dest) {
|
|
43
|
+
if (!fs.existsSync(src)) return;
|
|
44
|
+
let destination = dest;
|
|
45
|
+
if (fs.existsSync(dest) && fs.statSync(dest).isDirectory()) {
|
|
46
|
+
destination = path.join(dest, path.basename(src));
|
|
47
|
+
}
|
|
48
|
+
fs.cpSync(src, destination, { recursive: true });
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Stages caller inputs into a fixed layout under workDir.
|
|
53
|
+
* @param {import("./data.js").BuildOptions} options
|
|
54
|
+
* @param {string} workDir
|
|
55
|
+
* @returns {{
|
|
56
|
+
* opinionsCsv: string,
|
|
57
|
+
* summaryPath: string,
|
|
58
|
+
* configPath: string|null,
|
|
59
|
+
* predictedPath: string|null,
|
|
60
|
+
* stagedInputDir: string,
|
|
61
|
+
* }}
|
|
62
|
+
*/
|
|
63
|
+
function stageInputs(options, workDir) {
|
|
64
|
+
const stagedInputDir = path.join(workDir, "input");
|
|
65
|
+
mkdir(stagedInputDir);
|
|
66
|
+
|
|
67
|
+
const opinionsCsv = path.join(stagedInputDir, "opinions.csv");
|
|
68
|
+
fs.copyFileSync(options.opinionsPath, opinionsCsv);
|
|
69
|
+
|
|
70
|
+
const summaryPath = path.join(stagedInputDir, "summary.json");
|
|
71
|
+
fs.copyFileSync(options.summaryPath, summaryPath);
|
|
72
|
+
|
|
73
|
+
let configPath = null;
|
|
74
|
+
if (options.configPath) {
|
|
75
|
+
configPath = path.join(stagedInputDir, "config.json");
|
|
76
|
+
fs.copyFileSync(options.configPath, configPath);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
let predictedPath = null;
|
|
80
|
+
if (options.predictedPath) {
|
|
81
|
+
predictedPath = path.join(stagedInputDir, "predicted.json");
|
|
82
|
+
fs.copyFileSync(options.predictedPath, predictedPath);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (fs.existsSync(options.inputDir)) {
|
|
86
|
+
for (const name of fs.readdirSync(options.inputDir)) {
|
|
87
|
+
if (name.startsWith("logo.")) {
|
|
88
|
+
cp(path.join(options.inputDir, name), stagedInputDir);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
const translations = path.join(options.inputDir, "translations.json");
|
|
92
|
+
if (fs.existsSync(translations)) {
|
|
93
|
+
cp(translations, stagedInputDir);
|
|
94
|
+
}
|
|
95
|
+
if (configPath) {
|
|
96
|
+
const config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
97
|
+
if (config.translations) {
|
|
98
|
+
const t1 = path.join(options.inputDir, config.translations);
|
|
99
|
+
const t2 = path.join(options.inputDir, `${config.translations}.json`);
|
|
100
|
+
if (fs.existsSync(t1)) cp(t1, stagedInputDir);
|
|
101
|
+
else if (fs.existsSync(t2)) cp(t2, stagedInputDir);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
opinionsCsv,
|
|
108
|
+
summaryPath,
|
|
109
|
+
configPath,
|
|
110
|
+
predictedPath,
|
|
111
|
+
stagedInputDir,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Copies package src assets (except mustache) into dest.
|
|
117
|
+
* @param {string} packageRoot
|
|
118
|
+
* @param {string} dest
|
|
119
|
+
*/
|
|
120
|
+
function copySrcAssets(packageRoot, dest) {
|
|
121
|
+
const srcDir = path.join(packageRoot, "src");
|
|
122
|
+
if (!fs.existsSync(srcDir)) return;
|
|
123
|
+
for (const file of fs.readdirSync(srcDir)) {
|
|
124
|
+
if (file === "index.mustache") continue;
|
|
125
|
+
cp(path.join(srcDir, file), dest);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Copies logo.* from staged input into dest.
|
|
131
|
+
* @param {string} stagedInputDir
|
|
132
|
+
* @param {string} dest
|
|
133
|
+
*/
|
|
134
|
+
function copyLogos(stagedInputDir, dest) {
|
|
135
|
+
if (!fs.existsSync(stagedInputDir)) return;
|
|
136
|
+
for (const name of fs.readdirSync(stagedInputDir)) {
|
|
137
|
+
if (name.startsWith("logo.")) {
|
|
138
|
+
cp(path.join(stagedInputDir, name), dest);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Replaces font file URLs with base64 data URIs from sibling .txt files.
|
|
145
|
+
* @param {string} html
|
|
146
|
+
* @param {string} fontsDir
|
|
147
|
+
* @returns {string}
|
|
148
|
+
*/
|
|
149
|
+
function inlineFontDataUris(html, fontsDir) {
|
|
150
|
+
if (!fs.existsSync(fontsDir)) return html;
|
|
151
|
+
let result = html;
|
|
152
|
+
const fontFiles = fs
|
|
153
|
+
.readdirSync(fontsDir)
|
|
154
|
+
.filter((f) => f.endsWith(".woff2"));
|
|
155
|
+
for (const fontFile of fontFiles) {
|
|
156
|
+
const txtFile = path.join(
|
|
157
|
+
fontsDir,
|
|
158
|
+
fontFile.replace(/\.woff2$/, ".txt"),
|
|
159
|
+
);
|
|
160
|
+
if (!fs.existsSync(txtFile)) continue;
|
|
161
|
+
const dataUri = fs.readFileSync(txtFile, "utf-8").trim();
|
|
162
|
+
result = result.split(`fonts/${fontFile}`).join(dataUri);
|
|
163
|
+
}
|
|
164
|
+
return result;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Runs the full report build.
|
|
169
|
+
* @param {string[]} [argv=process.argv]
|
|
170
|
+
* @param {string} [cwd=process.cwd()]
|
|
171
|
+
* @returns {Promise<{output?: string, outputDir?: string}>}
|
|
172
|
+
*/
|
|
173
|
+
export async function runBuild(argv = process.argv, cwd = process.cwd()) {
|
|
174
|
+
const options = resolveBuildOptions(argv, cwd);
|
|
175
|
+
const packageRoot = path.dirname(fileURLToPath(import.meta.url));
|
|
176
|
+
const artefactRoot =
|
|
177
|
+
options.command === "inline"
|
|
178
|
+
? path.dirname(options.output)
|
|
179
|
+
: options.outputDir;
|
|
180
|
+
const workDir = path.join(artefactRoot, WORK_DIR_NAME);
|
|
181
|
+
|
|
182
|
+
mkdir(artefactRoot);
|
|
183
|
+
rm(workDir);
|
|
184
|
+
mkdir(workDir);
|
|
185
|
+
|
|
186
|
+
try {
|
|
187
|
+
console.log(
|
|
188
|
+
options.command === "inline"
|
|
189
|
+
? "\n** BUILDING REPORT (INLINE) **\n"
|
|
190
|
+
: "\n** BUILDING REPORT (STATIC) **\n",
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
console.log("...staging inputs");
|
|
194
|
+
const staged = stageInputs(options, workDir);
|
|
195
|
+
|
|
196
|
+
console.log("...converting opinions csv to json");
|
|
197
|
+
const opinionsRaw = await csv().fromFile(staged.opinionsCsv);
|
|
198
|
+
|
|
199
|
+
console.log("...processing data");
|
|
200
|
+
const { dataStatic, dataInline } = processReportData({
|
|
201
|
+
opinionsRaw,
|
|
202
|
+
summaryPath: staged.summaryPath,
|
|
203
|
+
configPath: staged.configPath,
|
|
204
|
+
predictedPath: staged.predictedPath,
|
|
205
|
+
inputDir: staged.stagedInputDir,
|
|
206
|
+
packageRoot,
|
|
207
|
+
workDir,
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
const template = fs.readFileSync(
|
|
211
|
+
path.join(packageRoot, "src", "index.mustache"),
|
|
212
|
+
"utf-8",
|
|
213
|
+
);
|
|
214
|
+
const templateData =
|
|
215
|
+
options.command === "inline" ? dataInline : dataStatic;
|
|
216
|
+
const rawHtml = mustache.render(template, templateData);
|
|
217
|
+
const rawHtmlPath = path.join(workDir, "raw.html");
|
|
218
|
+
fs.writeFileSync(rawHtmlPath, rawHtml);
|
|
219
|
+
|
|
220
|
+
if (options.command === "inline") {
|
|
221
|
+
console.log("...inlining data and assets");
|
|
222
|
+
copySrcAssets(packageRoot, workDir);
|
|
223
|
+
copyLogos(staged.stagedInputDir, workDir);
|
|
224
|
+
|
|
225
|
+
let inlined = await inlineSource(rawHtmlPath, {
|
|
226
|
+
rootpath: workDir,
|
|
227
|
+
compress: false,
|
|
228
|
+
attribute: "inline",
|
|
229
|
+
});
|
|
230
|
+
inlined = inlineFontDataUris(inlined, path.join(workDir, "fonts"));
|
|
231
|
+
fs.writeFileSync(options.output, inlined);
|
|
232
|
+
console.log("\n** BUILD COMPLETE! **\n");
|
|
233
|
+
return { output: options.output };
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
console.log("...copying static assets");
|
|
237
|
+
copySrcAssets(packageRoot, artefactRoot);
|
|
238
|
+
copyLogos(staged.stagedInputDir, artefactRoot);
|
|
239
|
+
cp(path.join(workDir, "quotes.json"), artefactRoot);
|
|
240
|
+
const reportHtml = path.join(artefactRoot, "report.html");
|
|
241
|
+
fs.writeFileSync(reportHtml, rawHtml);
|
|
242
|
+
const mustacheOut = path.join(artefactRoot, "index.mustache");
|
|
243
|
+
if (fs.existsSync(mustacheOut)) fs.rmSync(mustacheOut);
|
|
244
|
+
|
|
245
|
+
console.log("\n** BUILD COMPLETE! **\n");
|
|
246
|
+
return { outputDir: options.outputDir };
|
|
247
|
+
} finally {
|
|
248
|
+
rm(workDir);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Legacy preview helper: browser-sync on ./output.
|
|
254
|
+
*/
|
|
255
|
+
function preview() {
|
|
256
|
+
execSync(
|
|
257
|
+
'npx -y -q browser-sync start --server ./output --files "./output/**"',
|
|
258
|
+
{ stdio: "inherit" },
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Legacy github pages helper (static into docs/).
|
|
264
|
+
*/
|
|
265
|
+
async function github() {
|
|
266
|
+
await runBuild(
|
|
267
|
+
["node", "build.js", "static", "--outputDir", "./output"],
|
|
268
|
+
process.cwd(),
|
|
269
|
+
);
|
|
270
|
+
console.log("...deploying to github docs");
|
|
271
|
+
rm("docs");
|
|
272
|
+
mkdir("docs");
|
|
273
|
+
for (const file of fs.readdirSync("output")) {
|
|
274
|
+
if (file === WORK_DIR_NAME) continue;
|
|
275
|
+
cp(path.join("output", file), "docs");
|
|
276
|
+
}
|
|
277
|
+
fs.closeSync(fs.openSync("docs/.nojekyll", "w"));
|
|
278
|
+
execSync("git add -A", { stdio: "inherit" });
|
|
279
|
+
execSync('git commit -m "update github pages"', { stdio: "inherit" });
|
|
280
|
+
execSync("git push", { stdio: "inherit" });
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const isMain =
|
|
284
|
+
process.argv[1] &&
|
|
285
|
+
path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
|
|
286
|
+
|
|
287
|
+
if (isMain) {
|
|
288
|
+
const command = process.argv[2];
|
|
289
|
+
if (command === "preview") {
|
|
290
|
+
preview();
|
|
291
|
+
} else if (command === "github") {
|
|
292
|
+
github().catch((error) => {
|
|
293
|
+
console.error(error.message);
|
|
294
|
+
process.exit(1);
|
|
295
|
+
});
|
|
296
|
+
} else {
|
|
297
|
+
runBuild(process.argv, process.cwd())
|
|
298
|
+
.then((result) => {
|
|
299
|
+
console.log(
|
|
300
|
+
`Standalone report generated: ${result.output || result.outputDir}`,
|
|
301
|
+
);
|
|
302
|
+
})
|
|
303
|
+
.catch((error) => {
|
|
304
|
+
console.error(error.message);
|
|
305
|
+
process.exit(1);
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
}
|