@stacksjs/defaults 0.75.361 → 0.75.366
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/ai/skills/stacks-ai/SKILL.md +22 -0
- package/ai/skills/stacks-native/CATALOG.md +5 -5
- package/ai/skills/stacks-payments/SKILL.md +10 -0
- package/ai/skills/stacks-technical-diagrams/LICENSE +202 -22
- package/ai/skills/stacks-technical-diagrams/LICENSE-MIT +22 -0
- package/ai/skills/stacks-technical-diagrams/NOTICE +26 -0
- package/ai/skills/stacks-technical-diagrams/SKILL.md +106 -266
- package/ai/skills/stacks-technical-diagrams/THIRD_PARTY_NOTICES.md +11 -2
- package/ai/skills/stacks-technical-diagrams/UPSTREAM.json +12 -0
- package/ai/skills/stacks-technical-diagrams/agents/openai.yaml +2 -2
- package/ai/skills/stacks-technical-diagrams/assets/template.html +3655 -3798
- package/ai/skills/stacks-technical-diagrams/bin/archify.mjs +7290 -0
- package/ai/skills/stacks-technical-diagrams/bin/delivery-update.mjs +7 -0
- package/ai/skills/stacks-technical-diagrams/bin/diagrams +1 -0
- package/ai/skills/stacks-technical-diagrams/bin/finalize.mjs +1056 -0
- package/ai/skills/stacks-technical-diagrams/bin/open-artifact.mjs +72 -12
- package/ai/skills/stacks-technical-diagrams/bin/preview.mjs +348 -31
- package/ai/skills/stacks-technical-diagrams/bin/recover-output.mjs +41 -0
- package/ai/skills/stacks-technical-diagrams/bin/technical-diagrams.mjs +3 -2097
- package/ai/skills/stacks-technical-diagrams/bin/visual-check.mjs +2142 -157
- package/ai/skills/stacks-technical-diagrams/brand-marks/README.md +3 -3
- package/ai/skills/stacks-technical-diagrams/delta/architecture-delta.mjs +130 -33
- package/ai/skills/stacks-technical-diagrams/examples/agent-run.lifecycle.json +25 -30
- package/ai/skills/stacks-technical-diagrams/examples/agent-tool-call.workflow.json +0 -20
- package/ai/skills/stacks-technical-diagrams/examples/archify-dev-activity.timeline.json +176 -0
- package/ai/skills/stacks-technical-diagrams/examples/archify-repository.tree.json +197 -0
- package/ai/skills/stacks-technical-diagrams/examples/async-job-roundtrip.sequence.json +1 -6
- package/ai/skills/stacks-technical-diagrams/examples/brand-aware-delivery.architecture.json +3 -16
- package/ai/skills/stacks-technical-diagrams/examples/cache-miss-request.sequence.json +2 -7
- package/ai/skills/stacks-technical-diagrams/examples/checkout-platform.base.architecture.json +2 -1
- package/ai/skills/stacks-technical-diagrams/examples/checkout-platform.head.architecture.json +2 -1
- package/ai/skills/stacks-technical-diagrams/examples/checkout-request.waterfall.json +20 -0
- package/ai/skills/stacks-technical-diagrams/examples/class-payment-processors-rendered.html +14987 -0
- package/ai/skills/stacks-technical-diagrams/examples/class-payments-rendered.html +14787 -0
- package/ai/skills/stacks-technical-diagrams/examples/dataflow-product-analytics.html +14880 -0
- package/ai/skills/stacks-technical-diagrams/examples/delivery.explainer.json +155 -0
- package/ai/skills/stacks-technical-diagrams/examples/deployment-release.lifecycle.json +57 -34
- package/ai/skills/stacks-technical-diagrams/examples/erd-orders-rendered.html +15112 -0
- package/ai/skills/stacks-technical-diagrams/examples/event-stream.dataflow.json +1 -6
- package/ai/skills/stacks-technical-diagrams/examples/example-rebuild.waterfall.json +212 -0
- package/ai/skills/stacks-technical-diagrams/examples/holiday-planning.workflow.json +143 -0
- package/ai/skills/stacks-technical-diagrams/examples/incident-response.workflow.json +1 -6
- package/ai/skills/stacks-technical-diagrams/examples/lifecycle-agent-run.html +14892 -0
- package/ai/skills/stacks-technical-diagrams/examples/locales/de.json +463 -0
- package/ai/skills/stacks-technical-diagrams/examples/locales/fr.json +463 -0
- package/ai/skills/stacks-technical-diagrams/examples/locales/fr.partial.json +22 -0
- package/ai/skills/stacks-technical-diagrams/examples/locales/it.json +463 -0
- package/ai/skills/stacks-technical-diagrams/examples/locales/ja.json +463 -0
- package/ai/skills/stacks-technical-diagrams/examples/locales/pt.json +463 -0
- package/ai/skills/stacks-technical-diagrams/examples/locales/ru.json +463 -0
- package/ai/skills/stacks-technical-diagrams/examples/orders.erd.json +337 -0
- package/ai/skills/stacks-technical-diagrams/examples/payment-incident.timeline.json +24 -0
- package/ai/skills/stacks-technical-diagrams/examples/payment-platform.tree.json +22 -0
- package/ai/skills/stacks-technical-diagrams/examples/payment-processors.class.json +177 -0
- package/ai/skills/stacks-technical-diagrams/examples/payments.class.json +85 -0
- package/ai/skills/stacks-technical-diagrams/examples/product-analytics.dataflow.json +1 -6
- package/ai/skills/stacks-technical-diagrams/examples/production-deployment.architecture.json +3 -23
- package/ai/skills/stacks-technical-diagrams/examples/release-delivery.workflow.json +1 -6
- package/ai/skills/stacks-technical-diagrams/examples/sequence-cache-miss-request.html +14895 -0
- package/ai/skills/stacks-technical-diagrams/examples/subscription-billing-rendered.html +15369 -0
- package/ai/skills/stacks-technical-diagrams/examples/subscription-billing.erd.json +548 -0
- package/ai/skills/stacks-technical-diagrams/examples/timeline-archify-dev-activity-rendered.html +15002 -0
- package/ai/skills/stacks-technical-diagrams/examples/timeline-payment-incident-rendered.html +14772 -0
- package/ai/skills/stacks-technical-diagrams/examples/tree-archify-repository-rendered.html +15071 -0
- package/ai/skills/stacks-technical-diagrams/examples/tree-payment-platform-rendered.html +14810 -0
- package/ai/skills/stacks-technical-diagrams/examples/waterfall-checkout-request-rendered.html +14754 -0
- package/ai/skills/stacks-technical-diagrams/examples/waterfall-example-rebuild-rendered.html +14940 -0
- package/ai/skills/stacks-technical-diagrams/examples/web-app-rendered.html +14889 -0
- package/ai/skills/stacks-technical-diagrams/examples/web-app.architecture.json +1 -6
- package/ai/skills/stacks-technical-diagrams/examples/workflow-agent-tool-call-rendered.html +14907 -0
- package/ai/skills/stacks-technical-diagrams/explainer/browser-check.mjs +208 -0
- package/ai/skills/stacks-technical-diagrams/explainer/charts.mjs +70 -0
- package/ai/skills/stacks-technical-diagrams/explainer/check.mjs +168 -0
- package/ai/skills/stacks-technical-diagrams/explainer/diagrams.mjs +518 -0
- package/ai/skills/stacks-technical-diagrams/explainer/diff.mjs +124 -0
- package/ai/skills/stacks-technical-diagrams/explainer/inline.mjs +155 -0
- package/ai/skills/stacks-technical-diagrams/explainer/layout.mjs +35 -0
- package/ai/skills/stacks-technical-diagrams/explainer/page-bundle.mjs +16 -0
- package/ai/skills/stacks-technical-diagrams/explainer/parse.mjs +300 -0
- package/ai/skills/stacks-technical-diagrams/explainer/patch.mjs +117 -0
- package/ai/skills/stacks-technical-diagrams/explainer/prepare.mjs +131 -0
- package/ai/skills/stacks-technical-diagrams/explainer/reader.css +472 -0
- package/ai/skills/stacks-technical-diagrams/explainer/reader.js +667 -0
- package/ai/skills/stacks-technical-diagrams/explainer/render.mjs +224 -0
- package/ai/skills/stacks-technical-diagrams/explainer/revision.mjs +20 -0
- package/ai/skills/stacks-technical-diagrams/explainer/sources.mjs +318 -0
- package/ai/skills/stacks-technical-diagrams/explainer/validate.mjs +138 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/LICENSE +40 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/README.md +5 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/3DKXF73K.js +4 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/42UTT654.js +3 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/DKTLEL5E.js +1 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/EEIX5ZYH.js +2 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/EJ5KOXYD.js +25 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/GUJTEFZK.js +1 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/IZOKQ2GT.js +4 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/L4LY553T.js +3 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/MSKIJHB2.js +2 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/RVARI57O.js +1 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/UKSIWDDN.js +2 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/UPTXNAXN.js +2 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/XTDUIGUI.js +9 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/YAYTP6L5.js +9 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/core.js +1 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/diagrams/pie.js +1 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/diagrams/quadrantChart.js +1 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/diagrams/radar.js +1 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/diagrams/sankey.js +1 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/diagrams/venn.js +1 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/diagrams/xychart.js +1 -0
- package/ai/skills/stacks-technical-diagrams/explainer/writing.mjs +139 -0
- package/ai/skills/stacks-technical-diagrams/locales/README.md +40 -0
- package/ai/skills/stacks-technical-diagrams/locales/ar.json +488 -0
- package/ai/skills/stacks-technical-diagrams/locales/en.json +488 -0
- package/ai/skills/stacks-technical-diagrams/locales/es.json +488 -0
- package/ai/skills/stacks-technical-diagrams/locales/ko.json +488 -0
- package/ai/skills/stacks-technical-diagrams/locales/manifest.json +10 -0
- package/ai/skills/stacks-technical-diagrams/locales/zh-CN.json +488 -0
- package/ai/skills/stacks-technical-diagrams/locales/zh-TW.json +488 -0
- package/ai/skills/stacks-technical-diagrams/migrations/workflow-v2.mjs +1 -0
- package/ai/skills/stacks-technical-diagrams/recipes/scenarios.mjs +115 -51
- package/ai/skills/stacks-technical-diagrams/references/architecture-layout-repair.md +18 -0
- package/ai/skills/stacks-technical-diagrams/references/authoring-contract.md +453 -51
- package/ai/skills/stacks-technical-diagrams/references/authoring-defaults.md +57 -0
- package/ai/skills/stacks-technical-diagrams/references/brand-marks.md +57 -3
- package/ai/skills/stacks-technical-diagrams/references/delivery-contract.md +603 -58
- package/ai/skills/stacks-technical-diagrams/references/explainer-authoring.md +274 -0
- package/ai/skills/stacks-technical-diagrams/references/repository-authoring.md +126 -0
- package/ai/skills/stacks-technical-diagrams/references/update-awareness.md +13 -0
- package/ai/skills/stacks-technical-diagrams/references/viewer-runtime.md +18 -11
- package/ai/skills/stacks-technical-diagrams/renderers/architecture/grid.mjs +1 -1
- package/ai/skills/stacks-technical-diagrams/renderers/architecture/labels.mjs +2 -0
- package/ai/skills/stacks-technical-diagrams/renderers/architecture/render-architecture.mjs +550 -424
- package/ai/skills/stacks-technical-diagrams/renderers/architecture/routing.mjs +1469 -0
- package/ai/skills/stacks-technical-diagrams/renderers/class/README.md +129 -0
- package/ai/skills/stacks-technical-diagrams/renderers/class/render-class.mjs +1095 -0
- package/ai/skills/stacks-technical-diagrams/renderers/dataflow/README.md +89 -17
- package/ai/skills/stacks-technical-diagrams/renderers/dataflow/dataflow-compiler.mjs +1605 -0
- package/ai/skills/stacks-technical-diagrams/renderers/dataflow/joint-label-routing.mjs +165 -0
- package/ai/skills/stacks-technical-diagrams/renderers/dataflow/label-capacity.mjs +81 -0
- package/ai/skills/stacks-technical-diagrams/renderers/dataflow/render-dataflow.mjs +14 -474
- package/ai/skills/stacks-technical-diagrams/renderers/erd/README.md +246 -0
- package/ai/skills/stacks-technical-diagrams/renderers/erd/grid.mjs +150 -0
- package/ai/skills/stacks-technical-diagrams/renderers/erd/render-erd.mjs +1588 -0
- package/ai/skills/stacks-technical-diagrams/renderers/explainer/render-explainer.mjs +42 -0
- package/ai/skills/stacks-technical-diagrams/renderers/lifecycle/README.md +83 -94
- package/ai/skills/stacks-technical-diagrams/renderers/lifecycle/render-lifecycle.mjs +990 -417
- package/ai/skills/stacks-technical-diagrams/renderers/sequence/README.md +49 -17
- package/ai/skills/stacks-technical-diagrams/renderers/sequence/render-sequence.mjs +735 -94
- package/ai/skills/stacks-technical-diagrams/renderers/shared/atomic-output.mjs +1982 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/automatic-labels.mjs +290 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/brand-marks.mjs +169 -26
- package/ai/skills/stacks-technical-diagrams/renderers/shared/cli.mjs +266 -54
- package/ai/skills/stacks-technical-diagrams/renderers/shared/desktop-readability.mjs +106 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/diagnostics.mjs +0 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/generated-validators.mjs +268 -2
- package/ai/skills/stacks-technical-diagrams/renderers/shared/geometry.mjs +774 -124
- package/ai/skills/stacks-technical-diagrams/renderers/shared/i18n.mjs +289 -556
- package/ai/skills/stacks-technical-diagrams/renderers/shared/layout-report.mjs +18 -1
- package/ai/skills/stacks-technical-diagrams/renderers/shared/legend.mjs +16 -6
- package/ai/skills/stacks-technical-diagrams/renderers/shared/node-card-style.mjs +32 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/output-path.mjs +329 -229
- package/ai/skills/stacks-technical-diagrams/renderers/shared/owned-temp-directory.mjs +82 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/path-semantics.mjs +1017 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/portable-path.mjs +408 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/repository-evidence.mjs +235 -79
- package/ai/skills/stacks-technical-diagrams/renderers/shared/repository-location.mjs +17 -4
- package/ai/skills/stacks-technical-diagrams/renderers/shared/route-quality.mjs +670 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/sidecar-path.mjs +38 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/software-notices.mjs +6 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/spatial-grid.mjs +66 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/svg-path-analysis.mjs +135 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/text-fit.mjs +59 -3
- package/ai/skills/stacks-technical-diagrams/renderers/shared/utils.mjs +63 -35
- package/ai/skills/stacks-technical-diagrams/renderers/shared/validator.mjs +2 -5
- package/ai/skills/stacks-technical-diagrams/renderers/timeline/README.md +76 -0
- package/ai/skills/stacks-technical-diagrams/renderers/timeline/render-timeline.mjs +576 -0
- package/ai/skills/stacks-technical-diagrams/renderers/tree/README.md +96 -0
- package/ai/skills/stacks-technical-diagrams/renderers/tree/render-tree.mjs +573 -0
- package/ai/skills/stacks-technical-diagrams/renderers/waterfall/README.md +73 -0
- package/ai/skills/stacks-technical-diagrams/renderers/waterfall/render-waterfall.mjs +413 -0
- package/ai/skills/stacks-technical-diagrams/renderers/workflow/README.md +137 -23
- package/ai/skills/stacks-technical-diagrams/renderers/workflow/render-workflow.mjs +18 -6
- package/ai/skills/stacks-technical-diagrams/renderers/workflow/workflow-compiler.mjs +2230 -710
- package/ai/skills/stacks-technical-diagrams/renderers/workflow/workflow-migration-geometry.mjs +17 -0
- package/ai/skills/stacks-technical-diagrams/renderers/workflow/workflow-placement-repair.mjs +94 -0
- package/ai/skills/stacks-technical-diagrams/schemas/README.md +87 -40
- package/ai/skills/stacks-technical-diagrams/schemas/architecture.schema.json +7 -33
- package/ai/skills/stacks-technical-diagrams/schemas/class.schema.json +345 -0
- package/ai/skills/stacks-technical-diagrams/schemas/common.schema.json +68 -2
- package/ai/skills/stacks-technical-diagrams/schemas/dataflow.schema.json +14 -3
- package/ai/skills/stacks-technical-diagrams/schemas/erd.schema.json +381 -0
- package/ai/skills/stacks-technical-diagrams/schemas/explainer.schema.json +927 -0
- package/ai/skills/stacks-technical-diagrams/schemas/lifecycle.schema.json +23 -110
- package/ai/skills/stacks-technical-diagrams/schemas/sequence.schema.json +15 -4
- package/ai/skills/stacks-technical-diagrams/schemas/timeline.schema.json +160 -0
- package/ai/skills/stacks-technical-diagrams/schemas/tree.schema.json +141 -0
- package/ai/skills/stacks-technical-diagrams/schemas/waterfall.schema.json +147 -0
- package/ai/skills/stacks-technical-diagrams/schemas/workflow.schema.json +14 -3
- package/ai/skills/stacks-technical-diagrams/scripts/check-render-output.mjs +876 -243
- package/ai/skills/stacks-technical-diagrams/scripts/check-update.mjs +1717 -0
- package/ai/skills/stacks-technical-diagrams/scripts/delivery-update-child.mjs +26 -0
- package/ai/skills/stacks-technical-diagrams/scripts/generate-brand-marks.mjs +141 -0
- package/ai/skills/stacks-technical-diagrams/scripts/generate-validators.mjs +105 -0
- package/ai/skills/stacks-technical-diagrams/scripts/render-examples.mjs +11 -1
- package/ai/skills/stacks-technical-diagrams/scripts/update-contract.mjs +187 -0
- package/ai/skills/stacks-technical-diagrams/skill-release.json +10 -0
- package/ai/skills/stacks-testing/SKILL.md +8 -1
- package/ide/vscode/package.json +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { containsExplainerSecret } from './sources.mjs';
|
|
2
|
+
|
|
3
|
+
const EXAMPLE = '@@ -1,2 +1,2 @@\n unchanged\n-old value\n+new value';
|
|
4
|
+
|
|
5
|
+
function sourceLines(text) {
|
|
6
|
+
const result=[];
|
|
7
|
+
for (const match of text.matchAll(/([^\n]*)(\n|$)/gu)) {
|
|
8
|
+
if (!match[0]) continue;
|
|
9
|
+
const crlf=match[2] && match[1].endsWith('\r');
|
|
10
|
+
result.push({raw:crlf?match[1].slice(0,-1):match[1],ending:crlf?'\r\n':match[2]});
|
|
11
|
+
}
|
|
12
|
+
return result;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function fileName(value) {
|
|
16
|
+
let name=value.split('\t')[0];
|
|
17
|
+
if (name.startsWith('"') && name.endsWith('"')) {
|
|
18
|
+
try { name=JSON.parse(name); } catch { name=name.slice(1,-1); }
|
|
19
|
+
}
|
|
20
|
+
if (name==='/dev/null') return null;
|
|
21
|
+
return name.replace(/^[ab]\//u,'');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Parse unified diff without rewriting its literal bytes. Fragment line numbers stay null.
|
|
25
|
+
* File paths identify credential syntax only; this parser performs no filesystem reads.
|
|
26
|
+
*/
|
|
27
|
+
export function parseExplainerDiff(text, {line=1,component='diff',input,panelId,path} = {}) {
|
|
28
|
+
if (typeof text!=='string') throw new TypeError('Diff input must be a string');
|
|
29
|
+
const rows=[],diagnostics=[],counts={added:0,removed:0,context:0,hunks:0,files:0};
|
|
30
|
+
let hunk=null,oldPath=null,newPath=null,fileIndex=-1,oldHeader=false,newHeader=false,oldHeaderLine=line,lastBody=null,markerFor=null;
|
|
31
|
+
let format='fragment';
|
|
32
|
+
const groups=new Map();
|
|
33
|
+
function fail(code,sourceLine,reason,example=EXAMPLE) {
|
|
34
|
+
diagnostics.push({code:`explainer/diff-${code}`,severity:'error',message:reason,
|
|
35
|
+
subject:{line:sourceLine,column:1,component,...(input?{input}:{}),...(panelId?{panelId}:{}),...(path?{path}:{})},
|
|
36
|
+
evidence:{reason,example},supportedFixes:[`Use a valid unified diff, for example:\n${example}`]});
|
|
37
|
+
}
|
|
38
|
+
function finishHunk() {
|
|
39
|
+
if (!hunk) return;
|
|
40
|
+
if (hunk.usedOld!==hunk.oldCount || hunk.usedNew!==hunk.newCount) fail('hunk-count',hunk.sourceLine,
|
|
41
|
+
`Hunk declares ${hunk.oldCount} old and ${hunk.newCount} new lines, but contains ${hunk.usedOld} old and ${hunk.usedNew} new lines.`);
|
|
42
|
+
hunk=null;
|
|
43
|
+
}
|
|
44
|
+
function startFile() {
|
|
45
|
+
if(oldHeader&&!newHeader)fail('header-order',oldHeaderLine,'An old-file header needs its matching new-file header.','--- a/file.txt\n+++ b/file.txt\n'+EXAMPLE);
|
|
46
|
+
finishHunk();fileIndex++;counts.files++;oldPath=null;newPath=null;oldHeader=false;newHeader=false;lastBody=null;markerFor=null;format='unified';
|
|
47
|
+
}
|
|
48
|
+
function row(kind,value,index,extra={}) {
|
|
49
|
+
const entry={kind,raw:value.raw,text:value.raw,ending:value.ending,sourceLine:line+index,oldLine:null,newLine:null,...extra};
|
|
50
|
+
const knownPath=kind==='removed' ? oldPath||newPath : newPath||oldPath;
|
|
51
|
+
if (knownPath) entry.filePath=knownPath;
|
|
52
|
+
if (kind==='header'||kind==='hunk') {lastBody=null;markerFor=null;}
|
|
53
|
+
rows.push(entry);return entry;
|
|
54
|
+
}
|
|
55
|
+
const values=sourceLines(text);
|
|
56
|
+
for (const [index,value] of values.entries()) {
|
|
57
|
+
const raw=value.raw,sourceLine=line+index;
|
|
58
|
+
if (/^(?:diff --(?:cc|combined)\b|@@@)/u.test(raw)) {finishHunk();fail('combined',sourceLine,'Combined merge diffs are unsupported. Provide a two-way unified diff.');row('header',value,index);continue;}
|
|
59
|
+
if (/^(?:GIT binary patch|Binary files .+ differ|literal \d+$|delta \d+$)/u.test(raw)) {finishHunk();fail('binary',sourceLine,'Binary patches are unsupported. Describe the binary change separately.');row('header',value,index);continue;}
|
|
60
|
+
if (raw.startsWith('diff --git ')) {
|
|
61
|
+
if(!/^diff --git .+ .+$/u.test(raw))fail('header',sourceLine,'A git diff header must identify old and new paths.','diff --git a/file.txt b/file.txt\n--- a/file.txt\n+++ b/file.txt\n'+EXAMPLE);
|
|
62
|
+
startFile();row('header',value,index);continue;
|
|
63
|
+
}
|
|
64
|
+
if (raw.startsWith('@@')) {
|
|
65
|
+
finishHunk();format='unified';
|
|
66
|
+
const match=raw.match(/^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@(?:[ \t].*)?$/u);
|
|
67
|
+
if (!match) {fail('hunk-header',sourceLine,'A hunk header requires old and new start/count ranges.');row('hunk',value,index);continue;}
|
|
68
|
+
const [oldStart,oldCount,newStart,newCount]=[match[1],match[2]??'1',match[3],match[4]??'1'].map(Number);
|
|
69
|
+
const valid=[oldStart,oldCount,newStart,newCount].every(Number.isSafeInteger)
|
|
70
|
+
&& Number.isSafeInteger(oldStart+oldCount)&&Number.isSafeInteger(newStart+newCount)
|
|
71
|
+
&& (oldCount===0||oldStart>=1)&&(newCount===0||newStart>=1);
|
|
72
|
+
if(!valid){fail('hunk-range',sourceLine,'Hunk ranges must use safe nonnegative integers; a nonempty range starts at line 1 or later.');row('hunk',value,index);continue;}
|
|
73
|
+
counts.hunks++;hunk={oldStart,oldCount,newStart,newCount,usedOld:0,usedNew:0,sourceLine};lastBody=null;markerFor=null;
|
|
74
|
+
row('hunk',value,index,{oldStart,oldCount,newStart,newCount});continue;
|
|
75
|
+
}
|
|
76
|
+
// Once the declared hunk is complete, a file header belongs to the next file.
|
|
77
|
+
// Within an unfinished hunk, +++/--- remain ordinary additions/removals.
|
|
78
|
+
const within=hunk&&(hunk.usedOld<hunk.oldCount||hunk.usedNew<hunk.newCount);
|
|
79
|
+
if(!within && /^--- /u.test(raw)) {
|
|
80
|
+
finishHunk();if(oldHeader&&newHeader)startFile();else if(fileIndex<0)startFile();
|
|
81
|
+
oldPath=fileName(raw.slice(4));oldHeader=true;oldHeaderLine=sourceLine;newHeader=false;row('header',value,index);continue;
|
|
82
|
+
}
|
|
83
|
+
if(!within && /^\+\+\+ /u.test(raw)) {
|
|
84
|
+
finishHunk();format='unified';if(!oldHeader)fail('header-order',sourceLine,'A new-file header needs its preceding old-file header.','--- a/file.txt\n+++ b/file.txt\n'+EXAMPLE);
|
|
85
|
+
newPath=fileName(raw.slice(4));newHeader=true;row('header',value,index);continue;
|
|
86
|
+
}
|
|
87
|
+
if (/^(?:index |(?:new file|deleted file|old|new) mode |(?:similarity|dissimilarity) index |(?:rename|copy) (?:from|to) )/u.test(raw)) {
|
|
88
|
+
finishHunk();format='unified';
|
|
89
|
+
const valid=/^index [a-fA-F0-9]{1,64}\.\.[a-fA-F0-9]{1,64}(?: [0-7]{6})?$/u.test(raw)
|
|
90
|
+
||/^(?:new file|deleted file|old|new) mode [0-7]{6}$/u.test(raw)
|
|
91
|
+
||/^(?:rename|copy) (?:from|to) .+$/u.test(raw)
|
|
92
|
+
||(/^(?:similarity|dissimilarity) index (\d+)%$/u.test(raw)&&Number(raw.match(/(\d+)%$/u)[1])<=100);
|
|
93
|
+
if(!valid)fail('header',sourceLine,'This git metadata header is malformed.','diff --git a/file.txt b/file.txt\nindex 1234567..abcdef0 100644\n--- a/file.txt\n+++ b/file.txt\n'+EXAMPLE);
|
|
94
|
+
row('header',value,index);lastBody=null;markerFor=null;continue;
|
|
95
|
+
}
|
|
96
|
+
if(raw==='\') {
|
|
97
|
+
if(!lastBody||markerFor===lastBody)fail('newline-marker',sourceLine,'A no-newline marker must immediately follow one content row and cannot repeat for that row.','@@ -1 +1 @@\n-old\n\\n+new\n\');
|
|
98
|
+
row('no-newline',value,index,{...(lastBody?{forLine:lastBody.sourceLine}:{})});markerFor=lastBody;continue;
|
|
99
|
+
}
|
|
100
|
+
const kind=raw.startsWith('+')?'added':raw.startsWith('-')?'removed':raw.startsWith(' ')?'context':null;
|
|
101
|
+
if(!kind) {fail('row',sourceLine,'Diff content rows must start with a space, + or -.');row('header',value,index);lastBody=null;markerFor=null;continue;}
|
|
102
|
+
const entry=row(kind,value,index,{text:raw.slice(1)});counts[kind]++;
|
|
103
|
+
if(hunk) {
|
|
104
|
+
if(kind!=='added')entry.oldLine=hunk.oldStart+hunk.usedOld++;
|
|
105
|
+
if(kind!=='removed')entry.newLine=hunk.newStart+hunk.usedNew++;
|
|
106
|
+
}
|
|
107
|
+
lastBody=entry;markerFor=null;
|
|
108
|
+
if(!groups.has(fileIndex))groups.set(fileIndex,[]);groups.get(fileIndex).push(entry);
|
|
109
|
+
}
|
|
110
|
+
finishHunk();
|
|
111
|
+
if(oldHeader&&!newHeader)fail('header-order',rows.at(-1)?.sourceLine||line,'An old-file header needs its matching new-file header.','--- a/file.txt\n+++ b/file.txt\n'+EXAMPLE);
|
|
112
|
+
if(!rows.length)fail('empty',line,'A diff must contain content or a standard git metadata change.');
|
|
113
|
+
// Scan the whole authored content and pinpoint a matching row when possible.
|
|
114
|
+
// The shared scanner owns credential rules; diagnostics never quote rejected bytes.
|
|
115
|
+
for(const group of groups.values()) {
|
|
116
|
+
const knownPaths=[...new Set(group.map(entry=>entry.filePath).filter(Boolean))];
|
|
117
|
+
const combined=group.map(entry=>entry.text).join('\n');
|
|
118
|
+
if((knownPaths.length?knownPaths:[undefined]).some(knownPath=>containsExplainerSecret(combined,knownPath))) {
|
|
119
|
+
const matched=group.find(entry=>containsExplainerSecret(entry.text,entry.filePath));
|
|
120
|
+
fail('secret',matched?.sourceLine||group[0].sourceLine,'The diff contains a credential or sensitive secret value. Remove or redact it before rendering.','-const timeout = 100;\n+const timeout = 200;');
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return {rows,counts,diagnostics,format};
|
|
124
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/** The explainer's deliberately small inline language; never an HTML escape hatch. */
|
|
2
|
+
export const escapeHtml = value => String(value ?? '').replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
|
|
3
|
+
|
|
4
|
+
const identifier = /^[\p{L}\p{N}_-]+$/u;
|
|
5
|
+
const targetIdentifier = /^[\p{L}\p{N}_.:-]+$/u;
|
|
6
|
+
|
|
7
|
+
function referenceFor(href) {
|
|
8
|
+
if (/^https?:\/\//i.test(href)) {
|
|
9
|
+
try {
|
|
10
|
+
const url = new URL(href);
|
|
11
|
+
if (url.username || url.password || /[\u0000-\u0020\u007f]/u.test(href)) return null;
|
|
12
|
+
return {kind:'web',url:href};
|
|
13
|
+
} catch { return null; }
|
|
14
|
+
}
|
|
15
|
+
if (href.startsWith('source:') && identifier.test(href.slice(7))) return {kind:'source',id:href.slice(7)};
|
|
16
|
+
if (href.startsWith('block:') && identifier.test(href.slice(6))) return {kind:'block',id:href.slice(6)};
|
|
17
|
+
if (href.startsWith('#') && identifier.test(href.slice(1))) return {kind:'block',id:href.slice(1)};
|
|
18
|
+
if (href.startsWith('typical:') && href.slice(8).trim()) return {kind:'typical',text:href.slice(8).trim()};
|
|
19
|
+
if (href.startsWith('node:') || href.startsWith('route:')) {
|
|
20
|
+
const kind = href.startsWith('node:') ? 'node' : 'route';
|
|
21
|
+
const value = href.slice(kind.length + 1);
|
|
22
|
+
const split = value.indexOf('.');
|
|
23
|
+
if (split < 1 || !identifier.test(value.slice(0,split))) return null;
|
|
24
|
+
const diagram = value.slice(0,split), target = value.slice(split+1);
|
|
25
|
+
if (kind === 'node' && targetIdentifier.test(target)) return {kind,diagram,node:target};
|
|
26
|
+
const route = target.split('~');
|
|
27
|
+
if (kind === 'route' && route.length === 2 && route.every(id => targetIdentifier.test(id))) return {kind,diagram,from:route[0],to:route[1]};
|
|
28
|
+
}
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Returns tokens and references, with offsets for validation and actionable diagnostics. */
|
|
33
|
+
export function parseInline(value, options = {}) {
|
|
34
|
+
const text = String(value ?? '');
|
|
35
|
+
const references = [], diagnostics = [];
|
|
36
|
+
function problem(code, index, reason, example) {
|
|
37
|
+
const before = text.slice(0,index), lastBreak = before.lastIndexOf('\n');
|
|
38
|
+
diagnostics.push({code:`explainer/inline-${code}`,severity:'error',message:reason,
|
|
39
|
+
subject:{line:(Number.isInteger(options.line) ? options.line : 1) + (before.match(/\n/g)?.length || 0),column:index-lastBreak,
|
|
40
|
+
component:options.component || 'inline',...(options.panelId ? {panelId:options.panelId} : {}),...(options.input ? {input:options.input} : {})},
|
|
41
|
+
evidence:{reason,example},supportedFixes:[`Use the supported form: ${example}`]});
|
|
42
|
+
}
|
|
43
|
+
function segment(start, end, depth = 0) {
|
|
44
|
+
const tokens = [];
|
|
45
|
+
const literal = value => {
|
|
46
|
+
if (!value) return;
|
|
47
|
+
const previous = tokens[tokens.length-1];
|
|
48
|
+
if (previous?.type === 'text') previous.value += value;
|
|
49
|
+
else tokens.push({type:'text',value});
|
|
50
|
+
};
|
|
51
|
+
for (let i=start;i<end;) {
|
|
52
|
+
const c=text[i];
|
|
53
|
+
if (c === '\\' && i+1 < end && /[\\`*_[\]()!<>~|]/u.test(text[i+1])) { literal(text[i+1]);i+=2;continue; }
|
|
54
|
+
if (c === '`') {
|
|
55
|
+
const close=text.indexOf('`',i+1);
|
|
56
|
+
if (close === -1 || close >= end || text.slice(i+1,close).includes('\n')) {
|
|
57
|
+
problem('code',i,'Inline code must have one closing backtick on the same line.','`cache.get(key)`');literal(c);i++;continue;
|
|
58
|
+
}
|
|
59
|
+
tokens.push({type:'code',value:text.slice(i+1,close)});i=close+1;continue;
|
|
60
|
+
}
|
|
61
|
+
if (c === '<' && /^(?:<\/?[A-Za-z][^>]*>|<!--)/u.test(text.slice(i,end))) {
|
|
62
|
+
problem('html',i,'Raw HTML is not part of the explainer inline language.','**Important** or `[literal <tag>]`');
|
|
63
|
+
literal(c);i++;continue;
|
|
64
|
+
}
|
|
65
|
+
if (text.startsWith('![',i)) {
|
|
66
|
+
problem('image',i,'Inline Markdown images are unsupported. Use a contained image block.','@image path=diagram.png alt="Request path"');
|
|
67
|
+
literal('!');i++;continue;
|
|
68
|
+
}
|
|
69
|
+
if (text.startsWith('~~',i)) { problem('decoration',i,'Strikethrough is unsupported; use a diff or an explicit change block.','Changed: **new behavior**');literal('~~');i+=2;continue; }
|
|
70
|
+
if (c === '*') {
|
|
71
|
+
const marker=text.startsWith('**',i) ? '**' : '*';
|
|
72
|
+
let close=text.indexOf(marker,i+marker.length);
|
|
73
|
+
const escaped = index => {
|
|
74
|
+
let slashes=0;
|
|
75
|
+
while (index > start && text[--index] === '\\') slashes++;
|
|
76
|
+
return slashes % 2 === 1;
|
|
77
|
+
};
|
|
78
|
+
while (close > -1 && escaped(close)) close=text.indexOf(marker,close+marker.length);
|
|
79
|
+
if (close < 0 || close >= end || close === i+marker.length || depth >= 6) {
|
|
80
|
+
problem('emphasis',i,'Emphasis needs matching nonempty delimiters. Escape a literal asterisk.','**important** or \\*');literal(marker);i+=marker.length;continue;
|
|
81
|
+
}
|
|
82
|
+
tokens.push({type:marker.length===2?'strong':'em',children:segment(i+marker.length,close,depth+1)});i=close+marker.length;continue;
|
|
83
|
+
}
|
|
84
|
+
if (c === '[') {
|
|
85
|
+
const labelEnd=text.indexOf(']',i+1);
|
|
86
|
+
if (labelEnd > -1 && labelEnd < end && text[labelEnd+1] === '(') {
|
|
87
|
+
let close=labelEnd+2, nesting=1;
|
|
88
|
+
for (;close<end;close++) {
|
|
89
|
+
if (text[close] === '\\') { close++;continue; }
|
|
90
|
+
if (text[close] === '(') nesting++;
|
|
91
|
+
if (text[close] === ')' && --nesting === 0) break;
|
|
92
|
+
}
|
|
93
|
+
if (close >= end || nesting !== 0) {
|
|
94
|
+
problem('link',i,'The link destination is not closed.','[Evidence](source:cache-read)');literal(c);i++;continue;
|
|
95
|
+
}
|
|
96
|
+
const label=text.slice(i+1,labelEnd), href=text.slice(labelEnd+2,close).replace(/\\([()\\])/g,'$1');
|
|
97
|
+
const reference=referenceFor(href);
|
|
98
|
+
if ((!label.trim() && reference?.kind !== 'source') || !reference) {
|
|
99
|
+
problem('target',i,'Use a nonempty label and an HTTP(S), source, block, node, route or typical destination.','[Redis](node:read-path.redis)');
|
|
100
|
+
literal(text.slice(i,close+1));
|
|
101
|
+
} else {
|
|
102
|
+
const entry={...reference,offset:i,end:close+1};references.push(entry);
|
|
103
|
+
tokens.push({type:'link',label,href,reference:entry});
|
|
104
|
+
}
|
|
105
|
+
i=close+1;continue;
|
|
106
|
+
}
|
|
107
|
+
if (text[i+1] === '^') problem('footnote',i,'Arbitrary Markdown footnotes are unsupported. Use a source or typical-value reference.','[Claim](source:trace)');
|
|
108
|
+
}
|
|
109
|
+
literal(c);i++;
|
|
110
|
+
}
|
|
111
|
+
return tokens;
|
|
112
|
+
}
|
|
113
|
+
const tokens=segment(0,text.length);
|
|
114
|
+
return {tokens,references,diagnostics};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function inlineError(diagnostics) {
|
|
118
|
+
const error=new Error(diagnostics[0].message);
|
|
119
|
+
error.archifyDiagnostics=diagnostics;
|
|
120
|
+
error.diagnostics=diagnostics;
|
|
121
|
+
return error;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** Safely render the same parsed language used by semantic validation. */
|
|
125
|
+
export function renderInline(value, context = {}) {
|
|
126
|
+
const parsed=parseInline(value,context);
|
|
127
|
+
if (parsed.diagnostics.length) throw inlineError(parsed.diagnostics);
|
|
128
|
+
const render = tokens => tokens.map(token => {
|
|
129
|
+
if (token.type === 'text') return escapeHtml(token.value);
|
|
130
|
+
if (token.type === 'code') return `<code>${escapeHtml(token.value)}</code>`;
|
|
131
|
+
if (token.type === 'strong' || token.type === 'em') return `<${token.type}>${render(token.children)}</${token.type}>`;
|
|
132
|
+
const label=escapeHtml(token.label), ref=token.reference;
|
|
133
|
+
if (ref.kind === 'web') return `<a href="${escapeHtml(ref.url)}" target="_blank" rel="noopener noreferrer">${label}</a>`;
|
|
134
|
+
if (ref.kind === 'source') {
|
|
135
|
+
const citations = context.citations || (context.citations = new Map());
|
|
136
|
+
if (!citations.has(ref.id)) citations.set(ref.id,citations.size + 1);
|
|
137
|
+
return `${label}<sup class="cite"><a href="#source-${escapeHtml(ref.id)}">${citations.get(ref.id)}</a></sup>`;
|
|
138
|
+
}
|
|
139
|
+
if (ref.kind === 'block') return `<a href="#${escapeHtml(ref.id)}" data-block-ref="${escapeHtml(ref.id)}">${label}</a>`;
|
|
140
|
+
if (ref.kind === 'node' || ref.kind === 'route') {
|
|
141
|
+
const hash=ref.kind==='node' ? `focus=${encodeURIComponent(ref.node)}` : `route=${encodeURIComponent(ref.from)}~${encodeURIComponent(ref.to)}`;
|
|
142
|
+
return `<a class="diagram-link" href="#${escapeHtml(ref.diagram)}" data-diagram-link="${escapeHtml(ref.diagram)}" data-diagram-hash="${escapeHtml(hash)}">${label}</a>`;
|
|
143
|
+
}
|
|
144
|
+
if (ref.kind === 'typical') {
|
|
145
|
+
if (Array.isArray(context.footnotes)) {
|
|
146
|
+
const index=context.footnotes.length+1,id=`explainer-typical-${index}`;
|
|
147
|
+
context.footnotes.push({id,index,text:ref.text,label:token.label});
|
|
148
|
+
return `<span class="typical" title="${escapeHtml(ref.text)}">${label}<sup><a href="#${id}" role="doc-noteref" class="typical-ref">[${index}]</a></sup></span>`;
|
|
149
|
+
}
|
|
150
|
+
return `<span class="typical">${label}<small class="typical-note"> (${escapeHtml(ref.text)})</small></span>`;
|
|
151
|
+
}
|
|
152
|
+
return label;
|
|
153
|
+
}).join('');
|
|
154
|
+
return render(parsed.tokens);
|
|
155
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/** Order-preserving twelve-column packing. Explicit spans never change.
|
|
2
|
+
* A panel may shrink from its preferred width to its minimum to finish the current row,
|
|
3
|
+
* so a wide panel does not strand the conclusion on a row of its own.
|
|
4
|
+
*/
|
|
5
|
+
export function layoutExplainerPanels(document, diagrams = {}) {
|
|
6
|
+
const contentSize = value => typeof value === 'string' ? Array.from(value).length : Array.isArray(value) ? value.reduce((n,v)=>n+contentSize(v),0) : value && typeof value === 'object' ? Object.entries(value).filter(([key])=>!['id','type','span','target','source','diagramType','path','url','revision'].includes(key)).reduce((n,[,v])=>n+contentSize(v),0) : 0;
|
|
7
|
+
const widths = block => {
|
|
8
|
+
if (block.type === 'comparison') return block.columns.length + 1 > 3 ? [8,7] : [7,6];
|
|
9
|
+
if (['code','diff'].includes(block.type)) return [8,7];
|
|
10
|
+
if (block.type === 'chart') return [6,5];
|
|
11
|
+
if (block.type === 'snippet') { const longest = Math.max(...block.text.split('\n').map(line => line.length)); return longest > 72 ? [8,7] : longest > 48 ? [6,6] : [5,4]; }
|
|
12
|
+
if (block.type === 'diagram') {
|
|
13
|
+
const diagram=diagrams[block.id], viewBox=diagram?.svg?.match(/viewBox=["']([^"']+)["']/)?.[1].split(/[ ,]+/).map(Number);
|
|
14
|
+
return (viewBox?.[2] || diagram?.spec?.meta?.canvas?.width || 0) > 1000 ? [12,12] : [8,8];
|
|
15
|
+
}
|
|
16
|
+
return [4,4];
|
|
17
|
+
};
|
|
18
|
+
const sized = panel => panel.blocks.map(widths).reduce((best,next)=>next[0]>best[0]?next:best,[4,4]);
|
|
19
|
+
const items=[{id:'answer',span:5,min:4,weight:contentSize(document.answer),answer:true},...document.panels.map(panel=>{const [span,min]=sized(panel);return{id:panel.id,span:panel.span ?? span,min:panel.span ?? min,explicit:panel.span!==undefined,weight:contentSize(panel),panel};})];
|
|
20
|
+
const rows=[];let row=[],used=0;
|
|
21
|
+
function finish(){if(!row.length)return;const remaining=12-used;const flexible=row.filter(item=>!item.explicit).sort((a,b)=>b.weight-a.weight);if(remaining&&flexible.length)flexible[0].span+=remaining;else if(remaining)row.push({spacer:true,span:remaining});rows.push(row);row=[];used=0;}
|
|
22
|
+
for(const item of items){
|
|
23
|
+
if(used+item.span>12){
|
|
24
|
+
if(row.length&&row.reduce((n,x)=>n+x.min,0)+item.min<=12){
|
|
25
|
+
let deficit=used+item.span-12;
|
|
26
|
+
const cut=x=>{const d=Math.min(deficit,x.span-x.min);x.span-=d;deficit-=d;};
|
|
27
|
+
cut(item);for(const x of [...row].sort((a,b)=>(b.span-b.min)-(a.span-a.min)))cut(x);
|
|
28
|
+
used=row.reduce((n,x)=>n+x.span,0);
|
|
29
|
+
} else finish();
|
|
30
|
+
}
|
|
31
|
+
row.push(item);used+=item.span;if(used===12)finish();
|
|
32
|
+
}
|
|
33
|
+
finish();
|
|
34
|
+
return rows;
|
|
35
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { gzipSync, gunzipSync } from 'node:zlib';
|
|
2
|
+
|
|
3
|
+
// The native Viewer HTML is needed only when a reader opens "Explore", so the page stores it
|
|
4
|
+
// gzip-compressed and the reader inflates it on demand. Keys keep their order, so the prepared
|
|
5
|
+
// bundle digest is computed and verified over the same unpacked JSON.
|
|
6
|
+
const swap = (bundle, from, to, convert) => {
|
|
7
|
+
const diagrams = bundle?.preparedDiagrams?.diagrams;
|
|
8
|
+
if (!diagrams || typeof diagrams !== 'object') return bundle;
|
|
9
|
+
return { ...bundle, preparedDiagrams: { ...bundle.preparedDiagrams, diagrams: Object.fromEntries(Object.entries(diagrams).map(([id, child]) =>
|
|
10
|
+
[id, child && typeof child[from] === 'string' ? Object.fromEntries(Object.entries(child).map(([key, value]) => key === from ? [to, convert(value)] : [key, value])) : child])) } };
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const packPageBundle = bundle => swap(bundle, 'html', 'htmlGzip', html => gzipSync(html, { level: 9 }).toString('base64'));
|
|
14
|
+
|
|
15
|
+
/** Accepts packed and older unpacked pages alike. */
|
|
16
|
+
export const unpackPageBundle = bundle => swap(bundle, 'htmlGzip', 'html', data => gunzipSync(Buffer.from(data, 'base64')).toString('utf8'));
|