@mark1russell7/site-pages 0.1.4 → 0.1.6
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 +2 -1
- package/build/{build-BOMm-RGQ.js → build-Dr35nErd.js} +166 -65
- package/build/build-Dr35nErd.js.map +1 -0
- package/build/cli.js +2 -3
- package/build/index.d.ts +7 -32
- package/build/index.d.ts.map +1 -1
- package/build/index.js +1 -1
- package/package.json +2 -2
- package/build/build-BOMm-RGQ.js.map +0 -1
package/README.md
CHANGED
|
@@ -260,7 +260,8 @@ jobs:
|
|
|
260
260
|
build:
|
|
261
261
|
if: >-
|
|
262
262
|
github.event_name != 'workflow_run' ||
|
|
263
|
-
(github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event == 'push'
|
|
263
|
+
(github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event == 'push' &&
|
|
264
|
+
github.event.workflow_run.head_branch == github.event.repository.default_branch)
|
|
264
265
|
runs-on: ubuntu-latest
|
|
265
266
|
# Read access only: the status page reads the pull requests, the issues and their checks.
|
|
266
267
|
# A scheduled run reads the runs of CI to find the newest commit that passed.
|
|
@@ -2,7 +2,7 @@ import { execFileSync } from "node:child_process";
|
|
|
2
2
|
import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { basename, dirname, isAbsolute, join, posix, resolve } from "node:path";
|
|
4
4
|
import { parseArgs } from "node:util";
|
|
5
|
-
|
|
5
|
+
// src/data/versions.ts
|
|
6
6
|
const SEMVER = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
|
|
7
7
|
/** This function gives the parts of a version, or null when the text is not a SemVer version. */
|
|
8
8
|
function parseVersion(version) {
|
|
@@ -64,12 +64,11 @@ function versionOfTag(tag) {
|
|
|
64
64
|
package: match[1] ?? null
|
|
65
65
|
};
|
|
66
66
|
}
|
|
67
|
-
|
|
68
|
-
//#region src/data/changelog.ts
|
|
67
|
+
// src/data/changelog.ts
|
|
69
68
|
const VERSION_HEADING = /^##\s+\[?v?(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)\]?(?:\s.*)?$/;
|
|
70
69
|
const COMMIT_ENTRY = /^([0-9a-f]{7,40}):\s+([\s\S]*)$/;
|
|
71
70
|
const DEPENDENCY_ENTRY = /^(?:Updated dependencies\b|@?[\w.-]+(?:\/[\w.-]+)?@\S+\s*$)/;
|
|
72
|
-
const GENERATED_NOTE = /\
|
|
71
|
+
const GENERATED_NOTE = /\sby\s+@[\w-]+(?:\[bot\])?\s+in\s+(https:\/\/github\.com\/[\w.-]+\/[\w.-]+\/pull\/(\d+))\s*$/;
|
|
73
72
|
const FIRST_CONTRIBUTION = /\bmade their first contribution\b/;
|
|
74
73
|
/**
|
|
75
74
|
* This function gives the first sentence of a text. A full stop, a question mark or an exclamation
|
|
@@ -108,7 +107,8 @@ function entryOf(lines) {
|
|
|
108
107
|
const commit = COMMIT_ENTRY.exec(text);
|
|
109
108
|
let body = (commit ? commit[2] : text) ?? "";
|
|
110
109
|
if (body.trim() === "" || DEPENDENCY_ENTRY.test(body.trim()) || FIRST_CONTRIBUTION.test(body)) return null;
|
|
111
|
-
|
|
110
|
+
const note = GENERATED_NOTE.exec(body);
|
|
111
|
+
if (note) body = `${body.slice(0, note.index).trimEnd()} ([#${note[2] ?? ""}](${note[1] ?? ""}))`;
|
|
112
112
|
return {
|
|
113
113
|
commit: commit?.[1] ?? null,
|
|
114
114
|
text: firstSentence(body)
|
|
@@ -179,10 +179,10 @@ function releaseBodyEntries(body) {
|
|
|
179
179
|
continue;
|
|
180
180
|
}
|
|
181
181
|
if (fence !== null) continue;
|
|
182
|
-
const item = /^[-*+]\s+(.*)
|
|
183
|
-
if (item
|
|
182
|
+
const item = /^[-*+]\s+(\S.*)?$/.exec(line);
|
|
183
|
+
if (item) {
|
|
184
184
|
close();
|
|
185
|
-
entry = [item[1]];
|
|
185
|
+
entry = [item[1] ?? ""];
|
|
186
186
|
} else if (entry && (line.trim() === "" || /^\s/.test(line))) entry.push(line);
|
|
187
187
|
else close();
|
|
188
188
|
}
|
|
@@ -196,8 +196,7 @@ function releaseBodyEntries(body) {
|
|
|
196
196
|
function headingAnchor(heading) {
|
|
197
197
|
return heading.toLowerCase().trim().replace(/[^\p{L}\p{N}\s_-]/gu, "").replace(/\s/g, "-");
|
|
198
198
|
}
|
|
199
|
-
|
|
200
|
-
//#region src/data/github.ts
|
|
199
|
+
// src/data/github.ts
|
|
201
200
|
/** The GraphQL endpoint of GitHub. */
|
|
202
201
|
const GITHUB_GRAPHQL = "https://api.github.com/graphql";
|
|
203
202
|
/** The REST endpoint of GitHub. */
|
|
@@ -422,8 +421,7 @@ async function githubReleases(options) {
|
|
|
422
421
|
return failure(error, timeoutMs);
|
|
423
422
|
}
|
|
424
423
|
}
|
|
425
|
-
|
|
426
|
-
//#region src/data/npm.ts
|
|
424
|
+
// src/data/npm.ts
|
|
427
425
|
/** The public npm registry. */
|
|
428
426
|
const NPM_REGISTRY = "https://registry.npmjs.org";
|
|
429
427
|
const object = (value) => value !== null && typeof value === "object" ? value : {};
|
|
@@ -475,7 +473,7 @@ async function npmPublishTimes(options) {
|
|
|
475
473
|
const queue = [...names].sort();
|
|
476
474
|
const worker = async () => {
|
|
477
475
|
for (let name = queue.shift(); name !== void 0; name = queue.shift()) try {
|
|
478
|
-
const packument = object(await get(`${registry}/${name.
|
|
476
|
+
const packument = object(await get(`${registry}/${name.replaceAll("/", "%2f")}`));
|
|
479
477
|
const published = object(packument["versions"]);
|
|
480
478
|
for (const [version, date] of Object.entries(object(packument["time"]))) {
|
|
481
479
|
if (version === "created" || version === "modified" || typeof date !== "string") continue;
|
|
@@ -497,8 +495,7 @@ async function npmPublishTimes(options) {
|
|
|
497
495
|
problems
|
|
498
496
|
};
|
|
499
497
|
}
|
|
500
|
-
|
|
501
|
-
//#region src/data/git.ts
|
|
498
|
+
// src/data/git.ts
|
|
502
499
|
/**
|
|
503
500
|
* The git source: the tags with their dates, the commit of the build and the GitHub remote. Each
|
|
504
501
|
* function gives an empty result when git or the repository is not available.
|
|
@@ -557,8 +554,7 @@ function originRepository(repoDir) {
|
|
|
557
554
|
const match = /github\.com[:/]([^/]+)\/(.+?)(?:\.git)?\/?$/.exec(url);
|
|
558
555
|
return match ? `${match[1]}/${match[2]}` : null;
|
|
559
556
|
}
|
|
560
|
-
|
|
561
|
-
//#region src/data/glob.ts
|
|
557
|
+
// src/data/glob.ts
|
|
562
558
|
/**
|
|
563
559
|
* A small glob for the changelog files. The syntax has `*` and `?` in one path segment, and `**` for
|
|
564
560
|
* zero or more folders.
|
|
@@ -609,8 +605,7 @@ function safeList(dir) {
|
|
|
609
605
|
return [];
|
|
610
606
|
}
|
|
611
607
|
}
|
|
612
|
-
|
|
613
|
-
//#region src/data/releases.ts
|
|
608
|
+
// src/data/releases.ts
|
|
614
609
|
/**
|
|
615
610
|
* The release data: one entry for each version. The sources are the changelogs of Changesets, the
|
|
616
611
|
* GitHub releases, the git tags and the publication times on npm.
|
|
@@ -907,8 +902,7 @@ async function readReleases(options) {
|
|
|
907
902
|
warn
|
|
908
903
|
});
|
|
909
904
|
}
|
|
910
|
-
|
|
911
|
-
//#region src/data/status.ts
|
|
905
|
+
// src/data/status.ts
|
|
912
906
|
/**
|
|
913
907
|
* The status data: the open work of a roadmap file, and the open pull requests and issues of a
|
|
914
908
|
* GitHub repository. The build reads both, so the page shows the state at the time of the build.
|
|
@@ -986,13 +980,78 @@ function splitRow(line) {
|
|
|
986
980
|
cells.push(cell.trim());
|
|
987
981
|
return cells;
|
|
988
982
|
}
|
|
983
|
+
/** A whitespace character, as `\s` of a regular expression and `String.prototype.trim` read it. */
|
|
984
|
+
const SPACE = /\s/;
|
|
985
|
+
/** A character that `.` of a regular expression does not match. */
|
|
986
|
+
const LINE_TERMINATOR = /[\n\r\u2028\u2029]/;
|
|
987
|
+
/** The first whitespace character at or after `lastIndex`. */
|
|
988
|
+
const NEXT_SPACE = /\s/g;
|
|
989
|
+
/**
|
|
990
|
+
* This function replaces each Markdown link `[label](target)` of a text with the result of
|
|
991
|
+
* `replace`. The label ends at the first `]`, and the target ends at the first `)`. In `strict`
|
|
992
|
+
* mode, the label and the target are not empty, and the target has no whitespace.
|
|
993
|
+
*
|
|
994
|
+
* @remarks The function reads the text one time. A regular expression for these links can
|
|
995
|
+
* backtrack (CodeQL `js/polynomial-redos`). For a long text with many `[` characters, its time
|
|
996
|
+
* grows with the square of the length.
|
|
997
|
+
*/
|
|
998
|
+
function replaceLinks(text, strict, replace) {
|
|
999
|
+
let result = "";
|
|
1000
|
+
let copied = 0;
|
|
1001
|
+
let close = -1;
|
|
1002
|
+
let end = -1;
|
|
1003
|
+
let space = -1;
|
|
1004
|
+
for (let open = text.indexOf("["); open !== -1; open = text.indexOf("[", open + 1)) {
|
|
1005
|
+
if (close <= open) close = text.indexOf("]", open + 1);
|
|
1006
|
+
if (close === -1) break;
|
|
1007
|
+
if (text[close + 1] !== "(" || strict && close === open + 1) continue;
|
|
1008
|
+
const start = close + 2;
|
|
1009
|
+
if (end < start) end = text.indexOf(")", start);
|
|
1010
|
+
if (end === -1) break;
|
|
1011
|
+
if (strict) {
|
|
1012
|
+
if (space !== Infinity && space < start) {
|
|
1013
|
+
NEXT_SPACE.lastIndex = start;
|
|
1014
|
+
space = NEXT_SPACE.exec(text)?.index ?? Infinity;
|
|
1015
|
+
}
|
|
1016
|
+
if (end === start || space < end) continue;
|
|
1017
|
+
}
|
|
1018
|
+
result += text.slice(copied, open) + replace(text.slice(open + 1, close), text.slice(start, end));
|
|
1019
|
+
copied = end + 1;
|
|
1020
|
+
open = end;
|
|
1021
|
+
}
|
|
1022
|
+
return result + text.slice(copied);
|
|
1023
|
+
}
|
|
1024
|
+
/**
|
|
1025
|
+
* This function gives the text of an ATX heading without its closing sequence: the whitespace,
|
|
1026
|
+
* then the `#` characters, then the whitespace at the end. It gives null when the rest has a line
|
|
1027
|
+
* terminator, as the regular expression `(.*?)\s*#*\s*$` does.
|
|
1028
|
+
*/
|
|
1029
|
+
function closeHeading(text) {
|
|
1030
|
+
let end = text.length;
|
|
1031
|
+
while (end > 0 && SPACE.test(text[end - 1] ?? "")) end--;
|
|
1032
|
+
while (end > 0 && text[end - 1] === "#") end--;
|
|
1033
|
+
while (end > 0 && SPACE.test(text[end - 1] ?? "")) end--;
|
|
1034
|
+
const rest = text.slice(0, end);
|
|
1035
|
+
return LINE_TERMINATOR.test(rest) ? null : rest;
|
|
1036
|
+
}
|
|
1037
|
+
/** The start of an ATX heading. The text after the whitespace goes to `closeHeading`. */
|
|
1038
|
+
const HEADING = /^(#{1,6})\s+(\S[\s\S]*)?$/;
|
|
1039
|
+
/** This function gives the level and the text of an ATX heading line, or null for another line. */
|
|
1040
|
+
function headingOf(line) {
|
|
1041
|
+
const match = HEADING.exec(line);
|
|
1042
|
+
const text = match ? closeHeading(match[2] ?? "") : null;
|
|
1043
|
+
return match && text !== null ? {
|
|
1044
|
+
level: match[1]?.length ?? 0,
|
|
1045
|
+
text
|
|
1046
|
+
} : null;
|
|
1047
|
+
}
|
|
989
1048
|
/** This function gives the plain text of inline Markdown: no code marks, no emphasis, the text of each link. */
|
|
990
1049
|
function plainText(markdown) {
|
|
991
|
-
return String(markdown ?? "")
|
|
1050
|
+
return replaceLinks(String(markdown ?? ""), false, (label) => label).replace(/[`*_]/g, "").replace(/\s+/g, " ").trim();
|
|
992
1051
|
}
|
|
993
1052
|
/** A cell with no value: empty, or a dash. */
|
|
994
1053
|
function isEmpty(cell) {
|
|
995
|
-
return cell === void 0 ||
|
|
1054
|
+
return cell === void 0 || /^(?:[-–—]|n\/a)?$/i.test(cell.trim());
|
|
996
1055
|
}
|
|
997
1056
|
/**
|
|
998
1057
|
* This function gives the tables of a Markdown file. Each table has its header names in lowercase
|
|
@@ -1012,14 +1071,14 @@ function markdownTables(markdown) {
|
|
|
1012
1071
|
continue;
|
|
1013
1072
|
}
|
|
1014
1073
|
if (fence !== null) continue;
|
|
1015
|
-
const heading =
|
|
1074
|
+
const heading = headingOf(line);
|
|
1016
1075
|
if (heading) {
|
|
1017
|
-
section = plainText(heading
|
|
1076
|
+
section = plainText(heading.text);
|
|
1018
1077
|
continue;
|
|
1019
1078
|
}
|
|
1020
1079
|
if (!line.trim().startsWith("|")) continue;
|
|
1021
1080
|
const separator = lines[index + 1] ?? "";
|
|
1022
|
-
if (!/^\s
|
|
1081
|
+
if (!/^\s*(?:\|\s*)?:?-{3,}/.test(separator)) continue;
|
|
1023
1082
|
const header = splitRow(line).map((name) => plainText(name).toLowerCase());
|
|
1024
1083
|
const rows = [];
|
|
1025
1084
|
let next = index + 2;
|
|
@@ -1059,6 +1118,20 @@ function linkUrl(target, { file = "", repository = "", ref = "HEAD" }) {
|
|
|
1059
1118
|
return `https://github.com/${repository}/blob/${ref}/${resolved}${anchor ? `#${anchor}` : ""}`;
|
|
1060
1119
|
}
|
|
1061
1120
|
/**
|
|
1121
|
+
* This function gives the word right before the whitespace that ends `text.slice(0, end)`, as the
|
|
1122
|
+
* regular expression `([A-Za-z][\w.-]*)\s+$` does. The word starts with a letter. The function
|
|
1123
|
+
* reads only the characters of the word and of the whitespace.
|
|
1124
|
+
*/
|
|
1125
|
+
function wordBefore(text, end) {
|
|
1126
|
+
let stop = end;
|
|
1127
|
+
while (stop > 0 && SPACE.test(text[stop - 1] ?? "")) stop--;
|
|
1128
|
+
if (stop === end) return void 0;
|
|
1129
|
+
let start = stop;
|
|
1130
|
+
while (start > 0 && /[\w.-]/.test(text[start - 1] ?? "")) start--;
|
|
1131
|
+
while (start < stop && !/[A-Za-z]/.test(text[start] ?? "")) start++;
|
|
1132
|
+
return start < stop ? text.slice(start, stop) : void 0;
|
|
1133
|
+
}
|
|
1134
|
+
/**
|
|
1062
1135
|
* This function gives the links of a cell: each Markdown link, then each bare reference in the rest
|
|
1063
1136
|
* of the text. A reference is a pull request ("PR #12", "#12") or a commit hash in a code span.
|
|
1064
1137
|
*
|
|
@@ -1077,7 +1150,7 @@ function linksOf(cell, options = {}) {
|
|
|
1077
1150
|
url
|
|
1078
1151
|
});
|
|
1079
1152
|
};
|
|
1080
|
-
const rest = String(cell ?? "")
|
|
1153
|
+
const rest = replaceLinks(String(cell ?? ""), true, (label, target) => {
|
|
1081
1154
|
add(plainText(label), linkUrl(target, options));
|
|
1082
1155
|
return " ";
|
|
1083
1156
|
});
|
|
@@ -1087,7 +1160,7 @@ function linksOf(cell, options = {}) {
|
|
|
1087
1160
|
let pulls = false;
|
|
1088
1161
|
const reference = /\bPRs?\s*#(\d+)|(?<![\w/&])#(\d+)\b|`([0-9a-f]{7,40})`/g;
|
|
1089
1162
|
for (let match = reference.exec(rest); match; match = reference.exec(rest)) {
|
|
1090
|
-
const word =
|
|
1163
|
+
const word = wordBefore(rest, match.index);
|
|
1091
1164
|
if (word && !NOT_REPOSITORIES.has(word.toLowerCase())) current = word;
|
|
1092
1165
|
const prefix = current === defaultRepo ? "" : `${current} `;
|
|
1093
1166
|
if (match[1] !== void 0) pulls = true;
|
|
@@ -1097,15 +1170,30 @@ function linksOf(cell, options = {}) {
|
|
|
1097
1170
|
}
|
|
1098
1171
|
return links;
|
|
1099
1172
|
}
|
|
1100
|
-
/**
|
|
1101
|
-
|
|
1173
|
+
/**
|
|
1174
|
+
* The heading of an item: "### `id`: Title". A dash can take the place of the colon. The title goes
|
|
1175
|
+
* to `closeHeading`.
|
|
1176
|
+
*/
|
|
1177
|
+
const ITEM_HEADING = /^###\s+`([^`]+)`\s*[:–—-]\s*(\S[\s\S]*)?$/;
|
|
1102
1178
|
/** The other heading of an item: "### S1 · Title". The ID is one word before the middle dot. */
|
|
1103
|
-
const ITEM_HEADING_DOT = /^###\s+([A-Za-z0-9][\w.-]*)\s+·\s+(
|
|
1179
|
+
const ITEM_HEADING_DOT = /^###\s+([A-Za-z0-9][\w.-]*)\s+·\s+(\S[\s\S]*)?$/;
|
|
1180
|
+
/** This function gives the ID and the title of an item heading, or null for another line. */
|
|
1181
|
+
function itemHeadingOf(line) {
|
|
1182
|
+
for (const pattern of [ITEM_HEADING, ITEM_HEADING_DOT]) {
|
|
1183
|
+
const match = pattern.exec(line);
|
|
1184
|
+
const title = match ? closeHeading(match[2] ?? "") : null;
|
|
1185
|
+
if (match && title !== null) return {
|
|
1186
|
+
id: match[1] ?? "",
|
|
1187
|
+
title
|
|
1188
|
+
};
|
|
1189
|
+
}
|
|
1190
|
+
return null;
|
|
1191
|
+
}
|
|
1104
1192
|
/**
|
|
1105
1193
|
* A field of an item heading: "- Area: Docs", "- **Area:** Docs" or "- **Area**: Docs". The label
|
|
1106
1194
|
* matches without case.
|
|
1107
1195
|
*/
|
|
1108
|
-
const ITEM_FIELD = /^\s*[-*]\s+(\*\*|__)?([A-Za-z][A-Za-z ]*?)(?::\1|\1:)\s*(.*)
|
|
1196
|
+
const ITEM_FIELD = /^\s*[-*]\s+(\*\*|__)?([A-Za-z][A-Za-z ]*?)(?::\1|\1:)\s*(\S.*)?$/;
|
|
1109
1197
|
/** The fields of an item heading that give the optional fields of an item. */
|
|
1110
1198
|
const ITEM_FIELDS = {
|
|
1111
1199
|
area: "area",
|
|
@@ -1138,20 +1226,20 @@ function headingItems(markdown, options) {
|
|
|
1138
1226
|
continue;
|
|
1139
1227
|
}
|
|
1140
1228
|
if (fence !== null) continue;
|
|
1141
|
-
const heading =
|
|
1229
|
+
const heading = headingOf(line);
|
|
1142
1230
|
if (heading) {
|
|
1143
|
-
const level = heading
|
|
1231
|
+
const { level } = heading;
|
|
1144
1232
|
if (level > 3) continue;
|
|
1145
1233
|
current = void 0;
|
|
1146
|
-
if (level === 2) section = plainText(heading
|
|
1234
|
+
if (level === 2) section = plainText(heading.text);
|
|
1147
1235
|
if (level === 1) section = "";
|
|
1148
1236
|
if (level !== 3) continue;
|
|
1149
|
-
const item =
|
|
1150
|
-
const id = plainText(item?.
|
|
1237
|
+
const item = itemHeadingOf(line);
|
|
1238
|
+
const id = plainText(item?.id);
|
|
1151
1239
|
if (!item || isEmpty(id)) continue;
|
|
1152
1240
|
current = {
|
|
1153
1241
|
id,
|
|
1154
|
-
title: item
|
|
1242
|
+
title: item.title,
|
|
1155
1243
|
status: section,
|
|
1156
1244
|
statusClass: statusClassOf(section),
|
|
1157
1245
|
section,
|
|
@@ -1238,6 +1326,18 @@ function roadmapItems(markdown, options = {}) {
|
|
|
1238
1326
|
});
|
|
1239
1327
|
return headings.length === 0 ? items : [...items, ...headings].sort((a, b) => a.line - b.line);
|
|
1240
1328
|
}
|
|
1329
|
+
/**
|
|
1330
|
+
* This function gives the items of a roadmap file, as the Status page reads them. It fails when the file has no item.
|
|
1331
|
+
*
|
|
1332
|
+
* @throws RoadmapParseError when the cells of a row do not match the header
|
|
1333
|
+
* @throws RoadmapParseError when the roadmap has no item: no table with an ID and a Status column, and no item heading
|
|
1334
|
+
*/
|
|
1335
|
+
function parseRoadmap(markdown, options = {}) {
|
|
1336
|
+
const { file = "ROADMAP.md" } = options;
|
|
1337
|
+
const items = roadmapItems(markdown, options);
|
|
1338
|
+
if (items.length === 0) throw new RoadmapParseError(`${file}: no table with an ID column and a Status column, and no heading "### \`id\`: Title"`);
|
|
1339
|
+
return items;
|
|
1340
|
+
}
|
|
1241
1341
|
/** This function puts the roadmap items and the GitHub part into the shape of `status.json`. */
|
|
1242
1342
|
function buildStatusBoard(input) {
|
|
1243
1343
|
const { items, github, roadmap, repository, commit = null, generatedAt = (/* @__PURE__ */ new Date()).toISOString() } = input;
|
|
@@ -1270,14 +1370,11 @@ async function readStatusBoard(options) {
|
|
|
1270
1370
|
const { repoDir, roadmap = null, owner, repo, token, commit = null } = options;
|
|
1271
1371
|
const repository = `${owner}/${repo}`;
|
|
1272
1372
|
let items = [];
|
|
1273
|
-
if (roadmap) {
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
});
|
|
1279
|
-
if (items.length === 0) throw new RoadmapParseError(`${roadmap}: no table with an ID column and a Status column, and no heading "### \`id\`: Title"`);
|
|
1280
|
-
}
|
|
1373
|
+
if (roadmap) items = parseRoadmap(readFileSync(join(repoDir, ...roadmap.split("/")), "utf8"), {
|
|
1374
|
+
file: roadmap,
|
|
1375
|
+
repository,
|
|
1376
|
+
ref: commit ?? "HEAD"
|
|
1377
|
+
});
|
|
1281
1378
|
const github = await githubOpenWork({
|
|
1282
1379
|
owner,
|
|
1283
1380
|
repo,
|
|
@@ -1293,8 +1390,7 @@ async function readStatusBoard(options) {
|
|
|
1293
1390
|
...options.generatedAt ? { generatedAt: options.generatedAt } : {}
|
|
1294
1391
|
});
|
|
1295
1392
|
}
|
|
1296
|
-
|
|
1297
|
-
//#region src/render/html.ts
|
|
1393
|
+
// src/render/html.ts
|
|
1298
1394
|
/**
|
|
1299
1395
|
* The HTML helpers of the pages: the escape functions and a small inline Markdown renderer.
|
|
1300
1396
|
*/
|
|
@@ -1310,6 +1406,18 @@ function escapeHtml(text) {
|
|
|
1310
1406
|
return String(text).replace(/[&<>"']/g, (char) => ESCAPES[char] ?? char);
|
|
1311
1407
|
}
|
|
1312
1408
|
/**
|
|
1409
|
+
* This function removes the control characters and the spaces (U+0000 to U+0020) at the two ends
|
|
1410
|
+
* of a text. A loop does it, because the regular expression `[\u0000- ]+$` backtracks. For a long
|
|
1411
|
+
* run of these characters, its time grows with the square of the length.
|
|
1412
|
+
*/
|
|
1413
|
+
function trimControl(text) {
|
|
1414
|
+
let start = 0;
|
|
1415
|
+
let end = text.length;
|
|
1416
|
+
while (start < end && text.charCodeAt(start) <= 32) start++;
|
|
1417
|
+
while (end > start && text.charCodeAt(end - 1) <= 32) end--;
|
|
1418
|
+
return text.slice(start, end);
|
|
1419
|
+
}
|
|
1420
|
+
/**
|
|
1313
1421
|
* This function gives a URL that a link can hold, or null. It accepts `http:`, `https:` and
|
|
1314
1422
|
* `mailto:` URLs, and relative URLs. It refuses each other scheme, for example `javascript:`.
|
|
1315
1423
|
*
|
|
@@ -1318,7 +1426,7 @@ function escapeHtml(text) {
|
|
|
1318
1426
|
* Thus a control character before `javascript:` does not hide the scheme.
|
|
1319
1427
|
*/
|
|
1320
1428
|
function safeUrl(url) {
|
|
1321
|
-
const trimmed = url.trim().replace(/[\t\n\r]/g, "")
|
|
1429
|
+
const trimmed = trimControl(url.trim().replace(/[\t\n\r]/g, ""));
|
|
1322
1430
|
if (/^(?:https?:|mailto:)/i.test(trimmed)) return trimmed;
|
|
1323
1431
|
if (/^[a-z][a-z0-9+.-]*:/i.test(trimmed) || /^[/\\]{2}/.test(trimmed)) return null;
|
|
1324
1432
|
return trimmed;
|
|
@@ -1359,8 +1467,7 @@ function listText(parts) {
|
|
|
1359
1467
|
if (parts.length <= 1) return parts.join("");
|
|
1360
1468
|
return `${parts.slice(0, -1).join(", ")} and ${parts[parts.length - 1]}`;
|
|
1361
1469
|
}
|
|
1362
|
-
|
|
1363
|
-
//#region src/render/dates.ts
|
|
1470
|
+
// src/render/dates.ts
|
|
1364
1471
|
/**
|
|
1365
1472
|
* The dates of the pages. The pages show each date in UTC, so the build and the browser agree.
|
|
1366
1473
|
*/
|
|
@@ -1403,8 +1510,7 @@ function dayRange(start, end) {
|
|
|
1403
1510
|
const last = new Date(end);
|
|
1404
1511
|
return first.getUTCMonth() === last.getUTCMonth() ? `${shortDate(start)}–${last.getUTCDate()}` : `${shortDate(start)}–${shortDate(end)}`;
|
|
1405
1512
|
}
|
|
1406
|
-
|
|
1407
|
-
//#region src/render/theme.ts
|
|
1513
|
+
// src/render/theme.ts
|
|
1408
1514
|
/**
|
|
1409
1515
|
* The page shell and the CSS of the standalone pages. The CSS uses variables (`--sp-bg`, `--sp-fg`,
|
|
1410
1516
|
* `--sp-accent` and the others), so a site can change the colors and the fonts with `extraCss`.
|
|
@@ -1589,8 +1695,7 @@ ${parts.main}
|
|
|
1589
1695
|
</html>
|
|
1590
1696
|
`;
|
|
1591
1697
|
}
|
|
1592
|
-
|
|
1593
|
-
//#region src/render/timeline.ts
|
|
1698
|
+
// src/render/timeline.ts
|
|
1594
1699
|
/**
|
|
1595
1700
|
* The layout of the release timeline: one mark for each version on a time axis.
|
|
1596
1701
|
*
|
|
@@ -1705,8 +1810,7 @@ function timelineLayout(releases, options) {
|
|
|
1705
1810
|
marks
|
|
1706
1811
|
};
|
|
1707
1812
|
}
|
|
1708
|
-
|
|
1709
|
-
//#region src/render/releases.ts
|
|
1813
|
+
// src/render/releases.ts
|
|
1710
1814
|
/** The changes and the package chips that the page shows before "Show all". */
|
|
1711
1815
|
const FIRST_CHANGES = 8;
|
|
1712
1816
|
const FIRST_CHIPS = 4;
|
|
@@ -1945,8 +2049,7 @@ function renderReleasesPage(releases, options) {
|
|
|
1945
2049
|
script: RELEASES_SCRIPT
|
|
1946
2050
|
}, options);
|
|
1947
2051
|
}
|
|
1948
|
-
|
|
1949
|
-
//#region src/render/status.ts
|
|
2052
|
+
// src/render/status.ts
|
|
1950
2053
|
/**
|
|
1951
2054
|
* The standalone status page: the open work of the roadmap file, the open pull requests and the
|
|
1952
2055
|
* open issues. The build renders the HTML, and one small inline module adds the filters.
|
|
@@ -2008,7 +2111,7 @@ function statusLead(data) {
|
|
|
2008
2111
|
}
|
|
2009
2112
|
/** This function gives the part of a URL for a text: "The server stack (R37)" gives "the-server-stack-r37". */
|
|
2010
2113
|
function slugOf(text) {
|
|
2011
|
-
return text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(
|
|
2114
|
+
return text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "other";
|
|
2012
2115
|
}
|
|
2013
2116
|
/** This function gives the sections of the items, in the sequence of the file. */
|
|
2014
2117
|
function sectionsOf(items) {
|
|
@@ -2233,8 +2336,7 @@ function renderStatusPage(data, options) {
|
|
|
2233
2336
|
script: STATUS_SCRIPT
|
|
2234
2337
|
}, options);
|
|
2235
2338
|
}
|
|
2236
|
-
|
|
2237
|
-
//#region src/build.ts
|
|
2339
|
+
// src/build.ts
|
|
2238
2340
|
/**
|
|
2239
2341
|
* The `site-pages build` command. It reads the sources. Then it writes the JSON data and the two
|
|
2240
2342
|
* standalone pages into the built output folder of a site.
|
|
@@ -2425,7 +2527,6 @@ async function runCli(argv, io) {
|
|
|
2425
2527
|
throw error;
|
|
2426
2528
|
}
|
|
2427
2529
|
}
|
|
2428
|
-
|
|
2429
|
-
export { GITHUB_API as $, RoadmapParseError as A, DEFAULT_CHANGELOGS as B, longDate as C, inlineMarkdown as D, escapeHtml as E, plainText as F, globFiles as G, mergeReleases as H, readStatusBoard as I, headingDate as J, gitTagDates as K, roadmapItems as L, buildStatusBoard as M, linksOf as N, safeUrl as O, markdownTables as P, npmPublishTimes as Q, splitRow as R, dayRange as S, utcDay as T, readChangelogs as U, ReleaseDataError as V, readReleases as W, originRepository as X, isShallow as Y, NPM_REGISTRY as Z, oldestFirst as _, runCli as a, headingAnchor as at, BASE_CSS as b, openGroups as c, compareVersions as ct, slugOf as d, versionOfTag as dt, GITHUB_GRAPHQL as et, statusLead as f, laneCenter as g, renderReleasesPage as h, parseBuildArgs as i, firstSentence as it, STATUS_CLASSES as j, OPEN_CLASSES as k, renderStatusPage as l, kindOf as lt, releasesLead as m, UsageError as n, githubOpenWork as nt, STATUS_SCRIPT as o, parseChangelog as ot, RELEASES_SCRIPT as p, headCommit as q, build as r, githubReleases as rt, classCounts as s, releaseBodyEntries as st, HELP as t, checkStateOf as tt, sectionsOf as u, parseVersion as ut, releaseSummary as v, shortDate as w, pageShell as x, timelineLayout as y, statusClassOf as z };
|
|
2530
|
+
export { npmPublishTimes as $, RoadmapParseError as A, statusClassOf as B, longDate as C, inlineMarkdown as D, escapeHtml as E, parseRoadmap as F, readReleases as G, ReleaseDataError as H, plainText as I, headCommit as J, globFiles as K, readStatusBoard as L, buildStatusBoard as M, linksOf as N, safeUrl as O, markdownTables as P, NPM_REGISTRY as Q, roadmapItems as R, dayRange as S, utcDay as T, mergeReleases as U, DEFAULT_CHANGELOGS as V, readChangelogs as W, isShallow as X, headingDate as Y, originRepository as Z, oldestFirst as _, runCli as a, firstSentence as at, BASE_CSS as b, openGroups as c, releaseBodyEntries as ct, slugOf as d, parseVersion as dt, GITHUB_API as et, statusLead as f, versionOfTag as ft, laneCenter as g, renderReleasesPage as h, parseBuildArgs as i, githubReleases as it, STATUS_CLASSES as j, OPEN_CLASSES as k, renderStatusPage as l, compareVersions as lt, releasesLead as m, UsageError as n, checkStateOf as nt, STATUS_SCRIPT as o, headingAnchor as ot, RELEASES_SCRIPT as p, gitTagDates as q, build as r, githubOpenWork as rt, classCounts as s, parseChangelog as st, HELP as t, GITHUB_GRAPHQL as tt, sectionsOf as u, kindOf as ut, releaseSummary as v, shortDate as w, pageShell as x, timelineLayout as y, splitRow as z };
|
|
2430
2531
|
|
|
2431
|
-
//# sourceMappingURL=build-
|
|
2532
|
+
//# sourceMappingURL=build-Dr35nErd.js.map
|