euparliamentmonitor 0.9.21 → 0.9.23
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/package.json +6 -2
- package/scripts/aggregator/article-metadata.js +69 -14
- package/scripts/aggregator/editorial-brief-resolver.js +23 -0
- package/scripts/aggregator/html/headline.d.ts +41 -9
- package/scripts/aggregator/html/headline.js +69 -10
- package/scripts/aggregator/html/shell.js +73 -17
- package/scripts/aggregator/manifest/index.d.ts +1 -1
- package/scripts/aggregator/manifest/index.js +1 -1
- package/scripts/aggregator/manifest/resolver.d.ts +28 -1
- package/scripts/aggregator/manifest/resolver.js +61 -5
- package/scripts/aggregator/markdown-renderer.js +11 -0
- package/scripts/aggregator/metadata/artifact-category-heading.d.ts +81 -0
- package/scripts/aggregator/metadata/artifact-category-heading.js +353 -0
- package/scripts/aggregator/metadata/artifact-walker.js +29 -10
- package/scripts/aggregator/metadata/brief-body.d.ts +12 -0
- package/scripts/aggregator/metadata/brief-body.js +69 -0
- package/scripts/aggregator/metadata/briefing-highlight.d.ts +47 -0
- package/scripts/aggregator/metadata/briefing-highlight.js +469 -0
- package/scripts/aggregator/metadata/editorial-highlight.d.ts +18 -0
- package/scripts/aggregator/metadata/editorial-highlight.js +40 -1
- package/scripts/aggregator/metadata/heading-rules.d.ts +2 -81
- package/scripts/aggregator/metadata/heading-rules.js +78 -269
- package/scripts/aggregator/metadata/keyword-filters.d.ts +60 -0
- package/scripts/aggregator/metadata/keyword-filters.js +156 -0
- package/scripts/aggregator/metadata/lede-extractor.js +11 -2
- package/scripts/aggregator/metadata/priority-finding-cleaning.d.ts +22 -0
- package/scripts/aggregator/metadata/priority-finding-cleaning.js +181 -0
- package/scripts/aggregator/metadata/priority-finding-highlight.js +75 -159
- package/scripts/aggregator/metadata/resolve-helpers.d.ts +34 -0
- package/scripts/aggregator/metadata/resolve-helpers.js +202 -15
- package/scripts/aggregator/metadata/seo-budgets.d.ts +140 -0
- package/scripts/aggregator/metadata/seo-budgets.js +202 -0
- package/scripts/aggregator/metadata/text-truncate.d.ts +75 -0
- package/scripts/aggregator/metadata/text-truncate.js +277 -0
- package/scripts/aggregator/metadata/text-utils-constants.d.ts +96 -0
- package/scripts/aggregator/metadata/text-utils-constants.js +209 -0
- package/scripts/aggregator/metadata/text-utils.d.ts +32 -143
- package/scripts/aggregator/metadata/text-utils.js +119 -439
- package/scripts/aggregator/metadata/title-rejection.d.ts +37 -0
- package/scripts/aggregator/metadata/title-rejection.js +179 -0
- package/scripts/copy-vendor.js +84 -112
- package/scripts/dump-article-seo.js +640 -0
- package/scripts/fix-mermaid-diagrams.js +931 -0
- package/scripts/generators/news-indexes/backfill.d.ts +6 -1
- package/scripts/generators/news-indexes/backfill.js +71 -4
- package/scripts/validate-article-seo.js +534 -0
- package/scripts/validate-mermaid-diagrams.js +306 -0
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2024-2026 Hack23 AB
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
/**
|
|
4
|
+
* Title-rejection predicates shared by the metadata resolver and the
|
|
5
|
+
* SEO validation gate.
|
|
6
|
+
*
|
|
7
|
+
* Every English `<title>` and `<meta description>` on the site is
|
|
8
|
+
* resolved from the run's `executive-brief.md` via
|
|
9
|
+
* {@link ./resolve-helpers.ts}. Bold-prose labels inside the brief
|
|
10
|
+
* (`**Strategic significance:** …`, `**Threat Level:** …`,
|
|
11
|
+
* `**Key Assumptions Check:** …`) and trailing ellipsis fragments
|
|
12
|
+
* from over-budget strong-prose paragraphs have leaked into the
|
|
13
|
+
* `<title>` surface (216-article audit, 2026-05-24). This module
|
|
14
|
+
* provides the canonical denylist + structural rejection rules so
|
|
15
|
+
* resolver and validator stay in lock-step.
|
|
16
|
+
*
|
|
17
|
+
* NEVER inline these predicates — duplicating the denylist makes the
|
|
18
|
+
* validator and resolver drift, which is exactly how the bad titles
|
|
19
|
+
* shipped in the first place.
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* Bold-prose labels that appear inside `executive-brief.md` as
|
|
23
|
+
* `**Label:** …` lines. The priority-finding extractor was treating
|
|
24
|
+
* the bold label as a headline; the resolver now rejects these as
|
|
25
|
+
* usable titles.
|
|
26
|
+
*
|
|
27
|
+
* Keep entries lowercase and exact — matching is case-insensitive
|
|
28
|
+
* after trimming and stripping a trailing `:`, `…`, `.`.
|
|
29
|
+
*/
|
|
30
|
+
const SECTION_HEADER_DENYLIST = Object.freeze([
|
|
31
|
+
'strategic significance',
|
|
32
|
+
'event description',
|
|
33
|
+
'key intelligence',
|
|
34
|
+
'threat level',
|
|
35
|
+
'close to adoption',
|
|
36
|
+
'convergence themes',
|
|
37
|
+
'convergence theme',
|
|
38
|
+
'key assumptions check',
|
|
39
|
+
'risk assessment',
|
|
40
|
+
'stakeholder map',
|
|
41
|
+
'intelligence summary',
|
|
42
|
+
'session overview',
|
|
43
|
+
'situation summary',
|
|
44
|
+
'priority analysis',
|
|
45
|
+
'priority intelligence items',
|
|
46
|
+
'priority intelligence item',
|
|
47
|
+
'lead story',
|
|
48
|
+
'bluf',
|
|
49
|
+
'tl;dr',
|
|
50
|
+
'60-second read',
|
|
51
|
+
'classification',
|
|
52
|
+
'confidence summary',
|
|
53
|
+
'methodological notes',
|
|
54
|
+
'source reliability assessment',
|
|
55
|
+
'corrections and caveats',
|
|
56
|
+
'forward look',
|
|
57
|
+
'top three action items',
|
|
58
|
+
'political landscape summary',
|
|
59
|
+
'external environment summary',
|
|
60
|
+
'coalition & bloc summary',
|
|
61
|
+
'coalition and bloc summary',
|
|
62
|
+
'week ahead',
|
|
63
|
+
'week in review',
|
|
64
|
+
'month ahead',
|
|
65
|
+
'month in review',
|
|
66
|
+
'year ahead',
|
|
67
|
+
'term outlook',
|
|
68
|
+
'quarter ahead',
|
|
69
|
+
'election cycle',
|
|
70
|
+
// single-noun outputs that occasionally surface from H2/H3 walks
|
|
71
|
+
'overview',
|
|
72
|
+
'background',
|
|
73
|
+
'context',
|
|
74
|
+
'analysis',
|
|
75
|
+
'summary',
|
|
76
|
+
'conclusion',
|
|
77
|
+
'recommendations',
|
|
78
|
+
]);
|
|
79
|
+
const SECTION_HEADER_SET = new Set(SECTION_HEADER_DENYLIST);
|
|
80
|
+
/** Ellipsis at end of string (Unicode `…` or ASCII `...`). */
|
|
81
|
+
const ELLIPSIS_TAIL_RE = /(?:\u2026|\.\.\.)\s*$/u;
|
|
82
|
+
/**
|
|
83
|
+
* Adopted-text doc-ID (`TA-10-2026-0160`) — these are procedure
|
|
84
|
+
* identifiers, never editorial titles.
|
|
85
|
+
*/
|
|
86
|
+
const DOC_ID_RE = /^TA-\d+-\d{4}-\d{3,4}$/iu;
|
|
87
|
+
/**
|
|
88
|
+
* Detect a candidate that is really a complete sentence rather than a
|
|
89
|
+
* headline (e.g. `Routine inter-sessional day, no breaking signal.`,
|
|
90
|
+
* `EP10 enters the second half of its mandate with a structurally
|
|
91
|
+
* constrained but operational grand coalition.`).
|
|
92
|
+
*
|
|
93
|
+
* Gold-standard brief H1s never end with a period — they are
|
|
94
|
+
* noun-phrase headlines (`EU Parliament Year Ahead (May 2026 – May
|
|
95
|
+
* 2027)`, `EP Committee Reports · Week of 2026-05-14–21`). A trailing
|
|
96
|
+
* single `.` (NOT `…` and NOT `...`) on a ≥4-word candidate is the
|
|
97
|
+
* cleanest signal that we are looking at sentence prose leaked from a
|
|
98
|
+
* BLUF / lede paragraph rather than an editorial headline.
|
|
99
|
+
*
|
|
100
|
+
* @param value - Title candidate
|
|
101
|
+
* @returns `true` when the candidate looks like a complete sentence.
|
|
102
|
+
*/
|
|
103
|
+
function looksLikeFullSentence(value) {
|
|
104
|
+
const trimmed = value.trim();
|
|
105
|
+
// Must end with exactly one period — `…` and `...` are caught by
|
|
106
|
+
// looksLikeEllipsisCut, and other terminal punctuation (`?`, `!`,
|
|
107
|
+
// `:`) is left to lower-priority filters.
|
|
108
|
+
if (!/[^.]\.\s*$/u.test(trimmed))
|
|
109
|
+
return false;
|
|
110
|
+
if (/\.\.\.\s*$|\u2026\s*$/u.test(trimmed))
|
|
111
|
+
return false;
|
|
112
|
+
const wordCount = trimmed.split(/\s+/u).length;
|
|
113
|
+
return wordCount >= 4;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* `true` when the candidate is a bold-prose section header that
|
|
117
|
+
* leaked through the priority-finding extractor (e.g. `Strategic
|
|
118
|
+
* significance`, `Threat Level`).
|
|
119
|
+
*
|
|
120
|
+
* @param value - Title candidate
|
|
121
|
+
* @returns `true` when the candidate matches the section-header denylist.
|
|
122
|
+
*/
|
|
123
|
+
export function looksLikeSectionHeader(value) {
|
|
124
|
+
if (!value)
|
|
125
|
+
return false;
|
|
126
|
+
const normalised = value
|
|
127
|
+
.toLowerCase()
|
|
128
|
+
.replace(/[\u2026.:!?]+\s*$/u, '')
|
|
129
|
+
.replace(/^[*_\s]+/u, '')
|
|
130
|
+
.replace(/[*_\s]+$/u, '')
|
|
131
|
+
.trim();
|
|
132
|
+
if (!normalised)
|
|
133
|
+
return false;
|
|
134
|
+
return SECTION_HEADER_SET.has(normalised);
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* `true` when the candidate ends with `…` or `...` (was truncated
|
|
138
|
+
* over the title budget).
|
|
139
|
+
*
|
|
140
|
+
* @param value - Title candidate
|
|
141
|
+
* @returns `true` when the candidate has a trailing ellipsis.
|
|
142
|
+
*/
|
|
143
|
+
export function looksLikeEllipsisCut(value) {
|
|
144
|
+
return ELLIPSIS_TAIL_RE.test(value);
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* `true` when the candidate is a bare adopted-text doc-ID.
|
|
148
|
+
*
|
|
149
|
+
* @param value - Title candidate
|
|
150
|
+
* @returns `true` when the candidate matches the `TA-NN-YYYY-NNNN` shape.
|
|
151
|
+
*/
|
|
152
|
+
export function looksLikeDocId(value) {
|
|
153
|
+
return DOC_ID_RE.test(value.trim());
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Master rejection predicate. Returns the reason code (one of
|
|
157
|
+
* `section-header`, `ellipsis-cut`, `doc-id`, `sentence-fragment`)
|
|
158
|
+
* when the candidate should be rejected, or `null` when it is
|
|
159
|
+
* usable.
|
|
160
|
+
*
|
|
161
|
+
* @param value - Title candidate
|
|
162
|
+
* @returns Reason code, or `null` when the candidate is usable.
|
|
163
|
+
*/
|
|
164
|
+
export function findTitleRejectionReason(value) {
|
|
165
|
+
if (!value)
|
|
166
|
+
return null;
|
|
167
|
+
if (looksLikeEllipsisCut(value))
|
|
168
|
+
return 'ellipsis-cut';
|
|
169
|
+
if (looksLikeDocId(value))
|
|
170
|
+
return 'doc-id';
|
|
171
|
+
if (looksLikeSectionHeader(value))
|
|
172
|
+
return 'section-header';
|
|
173
|
+
if (looksLikeFullSentence(value))
|
|
174
|
+
return 'sentence-fragment';
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
/** Exposed for unit tests + the SEO validator. */
|
|
178
|
+
export const TITLE_REJECTION_DENYLIST = SECTION_HEADER_DENYLIST;
|
|
179
|
+
//# sourceMappingURL=title-rejection.js.map
|
package/scripts/copy-vendor.js
CHANGED
|
@@ -12,15 +12,25 @@
|
|
|
12
12
|
* - chart.js → js/vendor/chart.umd.min.js
|
|
13
13
|
* - chartjs-plugin-annotation → js/vendor/chartjs-plugin-annotation.min.js
|
|
14
14
|
* - d3 → js/vendor/d3.min.js
|
|
15
|
-
* - mermaid → js/vendor/mermaid/
|
|
15
|
+
* - mermaid → js/vendor/mermaid/mermaid.esm.min.mjs
|
|
16
16
|
*
|
|
17
|
-
* Mermaid is special: v11+ ships as code-split ESM
|
|
18
|
-
* `mermaid.esm.min.mjs`
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
17
|
+
* Mermaid is special: v11+ ships as a **code-split ESM bundle**. The entry
|
|
18
|
+
* `mermaid.esm.min.mjs` (28 KB) statically imports 81 diagram-specific chunks
|
|
19
|
+
* from `dist/chunks/mermaid.esm.min/*.mjs`. Empirically (May 2026), serving
|
|
20
|
+
* those chunks through S3 + CloudFront has been unreliable — the entry returns
|
|
21
|
+
* 200 OK but every chunk URL returns 403 from CloudFront, breaking every
|
|
22
|
+
* article that references the loader.
|
|
22
23
|
*
|
|
23
|
-
*
|
|
24
|
+
* To eliminate that failure mode, we **bundle Mermaid into a single
|
|
25
|
+
* self-contained ESM file at copy-vendor time using esbuild** (devDependency).
|
|
26
|
+
* The output is written to the same path / filename that the loader and the
|
|
27
|
+
* existing article HTML already reference (`mermaid.esm.min.mjs`), so the
|
|
28
|
+
* loader (`js/mermaid-init.js`) and the generated articles continue to work
|
|
29
|
+
* unchanged — only the file's content changes (3.2 MB self-contained vs.
|
|
30
|
+
* 28 KB entry-plus-81-chunks).
|
|
31
|
+
*
|
|
32
|
+
* Idempotent: rerunning overwrites prior copies and leaves licenses in place;
|
|
33
|
+
* stale `chunks/` directories from prior layouts are pruned.
|
|
24
34
|
*
|
|
25
35
|
* Failure modes:
|
|
26
36
|
* - Missing chart.js / d3 / chartjs-plugin-annotation → hard error (these
|
|
@@ -28,11 +38,12 @@
|
|
|
28
38
|
* - Missing mermaid → soft error (logged, exit 0). Mermaid is also a pinned
|
|
29
39
|
* `devDependency`, but optional installs (e.g. `npm ci --omit=dev`) may
|
|
30
40
|
* skip it; we want the deploy to succeed without diagrams rather than fail.
|
|
41
|
+
* - Bundling failure → hard error: mermaid is present but unusable, which
|
|
42
|
+
* would silently ship a broken page; fail fast at build time instead.
|
|
31
43
|
*/
|
|
32
44
|
|
|
33
45
|
import {
|
|
34
46
|
copyFileSync,
|
|
35
|
-
cpSync,
|
|
36
47
|
existsSync,
|
|
37
48
|
mkdirSync,
|
|
38
49
|
readdirSync,
|
|
@@ -43,6 +54,7 @@ import {
|
|
|
43
54
|
} from 'node:fs';
|
|
44
55
|
import path from 'node:path';
|
|
45
56
|
import process from 'node:process';
|
|
57
|
+
import * as esbuild from 'esbuild';
|
|
46
58
|
|
|
47
59
|
const ROOT = process.cwd();
|
|
48
60
|
const NODE_MODULES = path.join(ROOT, 'node_modules');
|
|
@@ -120,7 +132,8 @@ function copyOrFail(label, srcRel, dstRel, license) {
|
|
|
120
132
|
function copyMermaid() {
|
|
121
133
|
const mermaidDist = path.join(NODE_MODULES, 'mermaid', 'dist');
|
|
122
134
|
const target = path.join(VENDOR_DIR, 'mermaid');
|
|
123
|
-
|
|
135
|
+
const entryPoint = path.join(mermaidDist, 'mermaid.esm.min.mjs');
|
|
136
|
+
if (!existsSync(entryPoint)) {
|
|
124
137
|
process.stdout.write(
|
|
125
138
|
' ⚠ mermaid not installed (devDependency); skipping diagram bundle.\n',
|
|
126
139
|
);
|
|
@@ -128,84 +141,68 @@ function copyMermaid() {
|
|
|
128
141
|
}
|
|
129
142
|
ensureDir(target);
|
|
130
143
|
|
|
131
|
-
//
|
|
132
|
-
//
|
|
133
|
-
//
|
|
134
|
-
//
|
|
135
|
-
//
|
|
136
|
-
// identical until the pinned mermaid version in package.json changes.
|
|
144
|
+
// Bundle mermaid's code-split ESM entry plus all of its dynamic-import
|
|
145
|
+
// chunks into a SINGLE self-contained ESM file. esbuild follows every
|
|
146
|
+
// static and dynamic `import` from the entry and inlines the transitive
|
|
147
|
+
// closure, so the resulting file has no external module references —
|
|
148
|
+
// exactly what the static-site origin needs.
|
|
137
149
|
//
|
|
138
|
-
//
|
|
139
|
-
// `
|
|
140
|
-
//
|
|
141
|
-
//
|
|
142
|
-
//
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
150
|
+
// We write the output under the same filename the loader and existing
|
|
151
|
+
// article HTML already reference (`mermaid.esm.min.mjs`), so this script
|
|
152
|
+
// is the only place that changes when we switch from "entry + 81 chunks"
|
|
153
|
+
// to "single bundle". The previous chunk-shipping layout (`chunks/`) is
|
|
154
|
+
// pruned below.
|
|
155
|
+
const outFile = path.join(target, 'mermaid.esm.min.mjs');
|
|
156
|
+
try {
|
|
157
|
+
esbuild.buildSync({
|
|
158
|
+
entryPoints: [entryPoint],
|
|
159
|
+
outfile: outFile,
|
|
160
|
+
bundle: true,
|
|
161
|
+
format: 'esm',
|
|
162
|
+
minify: true,
|
|
163
|
+
// `browser` keeps mermaid's runtime-detection paths (e.g. `document`
|
|
164
|
+
// checks) intact — same target as the upstream `.esm.min.mjs` build.
|
|
165
|
+
platform: 'browser',
|
|
166
|
+
target: 'es2022',
|
|
167
|
+
// Resolve `import.meta.url` at runtime (relative to the served bundle
|
|
168
|
+
// location) rather than baking in the build-time path.
|
|
169
|
+
supported: { 'import-meta': true },
|
|
170
|
+
// Drop sourcemaps; the upstream bundle ships them as `.map` siblings
|
|
171
|
+
// and we previously excluded those from vendor copy.
|
|
172
|
+
sourcemap: false,
|
|
173
|
+
legalComments: 'none',
|
|
174
|
+
// Use 'error' so esbuild prints its own detailed diagnostics (file,
|
|
175
|
+
// line, column) on failure — 'silent' previously swallowed all context.
|
|
176
|
+
logLevel: 'error',
|
|
177
|
+
});
|
|
178
|
+
} catch (err) {
|
|
179
|
+
// esbuild attaches structured diagnostics on `err.errors`; print them
|
|
180
|
+
// so CI logs are actionable without re-running locally.
|
|
181
|
+
if (err && Array.isArray(err.errors)) {
|
|
182
|
+
for (const e of err.errors) {
|
|
183
|
+
const loc = e.location
|
|
184
|
+
? `${e.location.file}:${e.location.line}:${e.location.column}: `
|
|
185
|
+
: '';
|
|
186
|
+
process.stderr.write(` ${loc}${e.text}\n`);
|
|
172
187
|
}
|
|
173
188
|
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
for (const { src, rel } of wantedFiles) {
|
|
181
|
-
const dst = path.join(target, rel);
|
|
182
|
-
ensureDir(path.dirname(dst));
|
|
183
|
-
if (copyFileIfChanged(src, dst)) {
|
|
184
|
-
copied++;
|
|
185
|
-
} else {
|
|
186
|
-
unchanged++;
|
|
187
|
-
}
|
|
189
|
+
process.stderr.write(
|
|
190
|
+
`error: mermaid bundle failed: ${err && err.message ? err.message : err}\n` +
|
|
191
|
+
' Check that node_modules/mermaid is installed (run `npm ci`) and that\n' +
|
|
192
|
+
' esbuild can resolve the ESM entry point at node_modules/mermaid/dist/mermaid.esm.min.mjs.\n',
|
|
193
|
+
);
|
|
194
|
+
process.exit(1);
|
|
188
195
|
}
|
|
189
196
|
|
|
190
|
-
//
|
|
191
|
-
//
|
|
192
|
-
//
|
|
193
|
-
|
|
194
|
-
const wantedDstSet = new Set(
|
|
195
|
-
wantedFiles.map(({ rel }) => path.join(target, rel)),
|
|
196
|
-
);
|
|
197
|
-
// Allow our REUSE sidecar files alongside their primary file.
|
|
197
|
+
// Prune the obsolete chunks layout (and any other orphans) from previous
|
|
198
|
+
// copy-vendor runs. The bundled file is fully self-contained, so anything
|
|
199
|
+
// other than the bundle itself + its REUSE sidecar is stale.
|
|
200
|
+
const wantedDstSet = new Set([outFile]);
|
|
198
201
|
function isAllowedSidecar(absPath) {
|
|
199
202
|
if (!absPath.endsWith('.license')) return false;
|
|
200
203
|
const primary = absPath.slice(0, -'.license'.length);
|
|
201
204
|
return wantedDstSet.has(primary);
|
|
202
205
|
}
|
|
203
|
-
// Also allow the chunks-dir flavour-level license sidecar we drop below.
|
|
204
|
-
const flavourLicensePath = path.join(
|
|
205
|
-
target,
|
|
206
|
-
'chunks',
|
|
207
|
-
'mermaid.esm.min.license',
|
|
208
|
-
);
|
|
209
206
|
|
|
210
207
|
function pruneOrphans(dir) {
|
|
211
208
|
if (!existsSync(dir)) return;
|
|
@@ -213,7 +210,6 @@ function copyMermaid() {
|
|
|
213
210
|
const full = path.join(dir, entry.name);
|
|
214
211
|
if (entry.isDirectory()) {
|
|
215
212
|
pruneOrphans(full);
|
|
216
|
-
// Remove now-empty directories so a flavour rename leaves no shell.
|
|
217
213
|
try {
|
|
218
214
|
if (readdirSync(full).length === 0) {
|
|
219
215
|
rmSync(full, { recursive: true, force: true });
|
|
@@ -222,11 +218,7 @@ function copyMermaid() {
|
|
|
222
218
|
// best-effort
|
|
223
219
|
}
|
|
224
220
|
} else if (entry.isFile()) {
|
|
225
|
-
if (
|
|
226
|
-
!wantedDstSet.has(full) &&
|
|
227
|
-
!isAllowedSidecar(full) &&
|
|
228
|
-
full !== flavourLicensePath
|
|
229
|
-
) {
|
|
221
|
+
if (!wantedDstSet.has(full) && !isAllowedSidecar(full)) {
|
|
230
222
|
rmSync(full, { force: true });
|
|
231
223
|
}
|
|
232
224
|
}
|
|
@@ -234,39 +226,19 @@ function copyMermaid() {
|
|
|
234
226
|
}
|
|
235
227
|
pruneOrphans(target);
|
|
236
228
|
|
|
237
|
-
// REUSE sidecar for the
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
const
|
|
245
|
-
if (existsSync(chunksDir)) {
|
|
246
|
-
writeIfChanged(
|
|
247
|
-
flavourLicensePath,
|
|
248
|
-
'SPDX-FileCopyrightText: 2014-2026 Mermaid contributors\nSPDX-License-Identifier: MIT\n',
|
|
249
|
-
);
|
|
250
|
-
}
|
|
229
|
+
// REUSE sidecar for the bundled file. The bundle contains code from
|
|
230
|
+
// mermaid + its transitive ESM deps; mermaid's own MIT license header
|
|
231
|
+
// remains intact in the dependency tree (REUSE.toml covers the vendored
|
|
232
|
+
// artifact via path-level annotation; this sidecar keeps the file
|
|
233
|
+
// self-documenting).
|
|
234
|
+
writeLicense(outFile, '2014-2026 Mermaid contributors', 'MIT');
|
|
235
|
+
|
|
236
|
+
const size = statSync(outFile).size;
|
|
251
237
|
process.stdout.write(
|
|
252
|
-
` ✓ mermaid/ (${
|
|
238
|
+
` ✓ mermaid/mermaid.esm.min.mjs (${(size / 1024).toFixed(0)} KB self-contained bundle)\n`,
|
|
253
239
|
);
|
|
254
240
|
}
|
|
255
241
|
|
|
256
|
-
function countMjs(dir) {
|
|
257
|
-
let n = 0;
|
|
258
|
-
function walk(d) {
|
|
259
|
-
if (!existsSync(d)) return;
|
|
260
|
-
for (const entry of readdirSync(d, { withFileTypes: true })) {
|
|
261
|
-
const p = path.join(d, entry.name);
|
|
262
|
-
if (entry.isDirectory()) walk(p);
|
|
263
|
-
else if (entry.isFile() && entry.name.endsWith('.mjs')) n += 1;
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
walk(dir);
|
|
267
|
-
return n;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
242
|
function main() {
|
|
271
243
|
ensureDir(VENDOR_DIR);
|
|
272
244
|
process.stdout.write(`Copying vendor JS libraries to ${path.relative(ROOT, VENDOR_DIR)}/\n`);
|