@chengxianglibra/dsh-data-analysis 0.1.1 → 0.2.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 +139 -53
- package/cordis.patch.yml +1 -1
- package/lib/bin/environment.js +9 -7
- package/lib/bin/presentation-lint.js +21 -0
- package/lib/bridges.js +2 -2
- package/lib/client/credentials/changes.js +74 -0
- package/lib/client/credentials/create-datasource.js +171 -0
- package/lib/client/credentials/credential-draft.js +42 -0
- package/lib/client/credentials/defaults.js +11 -0
- package/lib/client/credentials/descriptions.js +46 -0
- package/lib/client/credentials/install.js +319 -0
- package/lib/client/credentials/model.js +766 -0
- package/lib/client/credentials/report-publishing.js +96 -0
- package/lib/client/credentials/styles.js +21 -0
- package/lib/client/i18n/context.js +22 -0
- package/lib/client/i18n/copy.js +57 -0
- package/lib/client/i18n/credentials.js +475 -0
- package/lib/client/i18n/host.js +27 -0
- package/lib/client/i18n/navigation.js +41 -0
- package/lib/client/i18n/presentation.js +577 -0
- package/lib/client/i18n/python.js +36 -0
- package/lib/client/i18n/semantic.js +441 -0
- package/lib/client/input-context.js +21 -0
- package/lib/client/python-tool/card.js +35 -0
- package/lib/client/python-tool/execution-error.js +35 -0
- package/lib/client/python-tool/install.js +9 -0
- package/lib/client/python-tool/model.js +101 -0
- package/lib/client/right-tabs/install.js +535 -0
- package/lib/client/right-tabs/live-delivery.js +58 -0
- package/lib/client/right-tabs/navigation.js +47 -0
- package/lib/client/right-tabs/page.js +178 -0
- package/lib/client/right-tabs/styles.js +11 -0
- package/lib/client/rpc.js +5 -0
- package/lib/client/semantic-reference.js +10 -0
- package/lib/client/workspace-header-action.js +24 -0
- package/lib/client/workspace-shortcuts.js +26 -0
- package/lib/client.js +1250 -730
- package/lib/compatibility.js +2 -2
- package/lib/datasource/authoring.js +109 -0
- package/lib/datasource/bridge-programs.js +42 -27
- package/lib/datasource/bridge.js +144 -9
- package/lib/datasource/changes-contract.js +41 -0
- package/lib/datasource/changes.js +49 -0
- package/lib/datasource/configure.js +39 -0
- package/lib/datasource/defaults.js +25 -0
- package/lib/datasource/index.js +4 -2
- package/lib/datasource/python-options.js +21 -0
- package/lib/datasource/python.js +206 -0
- package/lib/datasource/resolver-program.js +112 -0
- package/lib/datasource/rpc.js +150 -0
- package/lib/datasource/service.js +962 -0
- package/lib/datasource/shell-env.js +2 -333
- package/lib/datasource/test.js +25 -51
- package/lib/disclosure/activation.js +19 -7
- package/lib/disclosure/execution-guidance.js +52 -0
- package/lib/disclosure/help.js +4 -1
- package/lib/environment/binding.js +54 -9
- package/lib/environment/runtime.js +120 -108
- package/lib/environment/subprocess.js +6 -1
- package/lib/environment/summary.js +3 -3
- package/lib/environment/workspace.js +23 -1
- package/lib/index.js +1 -1
- package/lib/lifecycle.js +33 -0
- package/lib/plugin-agents.js +154 -0
- package/lib/plugin.js +172 -133
- package/lib/presentation/asset.js +34 -0
- package/lib/presentation/assets/portable.js +606 -0
- package/lib/presentation/assets/static.js +1216 -0
- package/lib/presentation/build/index.js +72 -0
- package/lib/presentation/commit.js +163 -0
- package/lib/presentation/contracts/asset-path.js +9 -0
- package/lib/presentation/contracts/catalog.js +62 -0
- package/lib/presentation/contracts/charts.js +453 -0
- package/lib/presentation/contracts/editing.js +58 -0
- package/lib/presentation/contracts/index.js +663 -0
- package/lib/presentation/contracts/interaction.js +62 -0
- package/lib/presentation/contracts/types.js +15 -0
- package/lib/presentation/contracts/values.js +28 -0
- package/lib/presentation/delivery.js +71 -0
- package/lib/presentation/export-html.js +214 -0
- package/lib/presentation/files.js +82 -0
- package/lib/presentation/index.js +6 -0
- package/lib/presentation/lint.js +46 -0
- package/lib/presentation/projection/files.js +85 -0
- package/lib/presentation/projection/index.js +196 -0
- package/lib/presentation/projection/program.js +137 -0
- package/lib/presentation/projection/sql-code.js +79 -0
- package/lib/presentation/receipt.js +34 -0
- package/lib/presentation/reports.js +219 -0
- package/lib/presentation/rpc.js +186 -0
- package/lib/presentation/tool.js +118 -0
- package/lib/python-execution-contracts.js +1 -0
- package/lib/python-execution.js +232 -0
- package/lib/report-publishing/adapters.js +111 -0
- package/lib/report-publishing/config.js +69 -0
- package/lib/report-publishing/service.js +180 -0
- package/lib/rpc.js +49 -0
- package/lib/semantic-browser/contracts.js +97 -0
- package/lib/semantic-browser/definition.js +64 -0
- package/lib/semantic-browser/program.js +129 -0
- package/lib/semantic-browser/service.js +106 -0
- package/lib/semantic-reference/bridge.js +127 -0
- package/lib/semantic-reference/contracts.js +127 -0
- package/lib/semantic-reference/environment.js +56 -0
- package/lib/semantic-reference/index.js +3 -0
- package/lib/semantic-reference/labels.js +16 -0
- package/lib/semantic-reference/rpc.js +143 -0
- package/lib/semantic-reference/search.js +95 -0
- package/lib/semantic-reference/usage.js +152 -0
- package/lib/tool-lifecycle.js +22 -0
- package/lib/types/bridges.d.ts +2 -2
- package/lib/types/client/credentials/changes.d.ts +9 -0
- package/lib/types/client/credentials/create-datasource.d.ts +7 -0
- package/lib/types/client/credentials/credential-draft.d.ts +14 -0
- package/lib/types/client/credentials/defaults.d.ts +2 -0
- package/lib/types/client/credentials/descriptions.d.ts +1 -0
- package/lib/types/client/credentials/install.d.ts +19 -0
- package/lib/types/client/credentials/model.d.ts +79 -0
- package/lib/types/client/credentials/report-publishing.d.ts +5 -0
- package/lib/types/client/credentials/styles.d.ts +1 -0
- package/lib/types/client/i18n/context.d.ts +17 -0
- package/lib/types/client/i18n/copy.d.ts +806 -0
- package/lib/types/client/i18n/credentials.d.ts +475 -0
- package/lib/types/client/i18n/host.d.ts +16 -0
- package/lib/types/client/i18n/navigation.d.ts +41 -0
- package/lib/types/client/i18n/presentation.d.ts +577 -0
- package/lib/types/client/i18n/python.d.ts +19 -0
- package/lib/types/client/i18n/semantic.d.ts +441 -0
- package/lib/types/client/input-context.d.ts +19 -0
- package/lib/types/client/presentation/ask-dsh.d.ts +5 -0
- package/lib/types/client/presentation/catalog-model.d.ts +24 -0
- package/lib/types/client/presentation/catalog.d.ts +20 -0
- package/lib/types/client/presentation/chart-explorer.d.ts +12 -0
- package/lib/types/client/presentation/chart-geometry.d.ts +30 -0
- package/lib/types/client/presentation/chart-renderer.d.ts +10 -0
- package/lib/types/client/presentation/chart-specials.d.ts +10 -0
- package/lib/types/client/presentation/chart-styles.d.ts +1 -0
- package/lib/types/client/presentation/chart-tooltip.d.ts +13 -0
- package/lib/types/client/presentation/chart-view.d.ts +23 -0
- package/lib/types/client/presentation/context-reference.d.ts +12 -0
- package/lib/types/client/presentation/copy-context.d.ts +5 -0
- package/lib/types/client/presentation/delivery-model.d.ts +75 -0
- package/lib/types/client/presentation/delivery.d.ts +78 -0
- package/lib/types/client/presentation/download.d.ts +2 -0
- package/lib/types/client/presentation/editor-controls.d.ts +12 -0
- package/lib/types/client/presentation/export-menu.d.ts +26 -0
- package/lib/types/client/presentation/export-view.d.ts +63 -0
- package/lib/types/client/presentation/global-filters.d.ts +6 -0
- package/lib/types/client/presentation/host-entry.d.ts +18 -0
- package/lib/types/client/presentation/icons.d.ts +4 -0
- package/lib/types/client/presentation/markdown.d.ts +5 -0
- package/lib/types/client/presentation/model.d.ts +61 -0
- package/lib/types/client/presentation/reader.d.ts +24 -0
- package/lib/types/client/presentation/reference-source.d.ts +9 -0
- package/lib/types/client/presentation/source-code-model.d.ts +17 -0
- package/lib/types/client/presentation/source-code.d.ts +6 -0
- package/lib/types/client/presentation/source-dialog.d.ts +12 -0
- package/lib/types/client/presentation/source-facts.d.ts +20 -0
- package/lib/types/client/presentation/source-format.d.ts +5 -0
- package/lib/types/client/presentation/source-highlight.d.ts +7 -0
- package/lib/types/client/presentation/sources.d.ts +13 -0
- package/lib/types/client/presentation/styles.d.ts +1 -0
- package/lib/types/client/presentation/table.d.ts +16 -0
- package/lib/types/client/presentation/use-element-width.d.ts +5 -0
- package/lib/types/client/python-tool/card.d.ts +2 -0
- package/lib/types/client/python-tool/execution-error.d.ts +3 -0
- package/lib/types/client/python-tool/install.d.ts +2 -0
- package/lib/types/client/python-tool/model.d.ts +20 -0
- package/lib/types/client/right-tabs/install.d.ts +18 -0
- package/lib/types/client/right-tabs/live-delivery.d.ts +8 -0
- package/lib/types/client/right-tabs/navigation.d.ts +17 -0
- package/lib/types/client/right-tabs/page.d.ts +36 -0
- package/lib/types/client/right-tabs/styles.d.ts +2 -0
- package/lib/types/client/rpc.d.ts +3 -0
- package/lib/types/client/semantic-reference.d.ts +4 -0
- package/lib/types/client/workspace-header-action.d.ts +7 -0
- package/lib/types/client/workspace-shortcuts.d.ts +15 -0
- package/lib/types/client.d.ts +9 -124
- package/lib/types/compatibility.d.ts +3 -3
- package/lib/types/datasource/authoring.d.ts +35 -0
- package/lib/types/datasource/bridge.d.ts +43 -4
- package/lib/types/datasource/changes-contract.d.ts +18 -0
- package/lib/types/datasource/changes.d.ts +15 -0
- package/lib/types/datasource/configure.d.ts +7 -0
- package/lib/types/datasource/defaults.d.ts +7 -0
- package/lib/types/datasource/index.d.ts +8 -5
- package/lib/types/datasource/python-options.d.ts +9 -0
- package/lib/types/datasource/python.d.ts +19 -0
- package/lib/types/datasource/resolver-program.d.ts +6 -0
- package/lib/types/datasource/rpc.d.ts +5 -0
- package/lib/types/datasource/service.d.ts +155 -0
- package/lib/types/datasource/shell-env.d.ts +0 -29
- package/lib/types/datasource/test.d.ts +6 -29
- package/lib/types/disclosure/activation.d.ts +2 -1
- package/lib/types/disclosure/execution-guidance.d.ts +6 -0
- package/lib/types/disclosure/help.d.ts +1 -1
- package/lib/types/environment/binding.d.ts +5 -1
- package/lib/types/environment/errors.d.ts +1 -1
- package/lib/types/environment/runtime.d.ts +5 -5
- package/lib/types/environment/types.d.ts +15 -3
- package/lib/types/environment/workspace.d.ts +1 -0
- package/lib/types/index.d.ts +1 -1
- package/lib/types/lifecycle.d.ts +8 -0
- package/lib/types/plugin-agents.d.ts +27 -0
- package/lib/types/plugin.d.ts +14 -24
- package/lib/types/presentation/asset.d.ts +6 -0
- package/lib/types/presentation/build/index.d.ts +16 -0
- package/lib/types/presentation/commit.d.ts +4 -0
- package/lib/types/presentation/contracts/asset-path.d.ts +2 -0
- package/lib/types/presentation/contracts/catalog.d.ts +27 -0
- package/lib/types/presentation/contracts/charts.d.ts +11 -0
- package/lib/types/presentation/contracts/editing.d.ts +8 -0
- package/lib/types/presentation/contracts/index.d.ts +11 -0
- package/lib/types/presentation/contracts/interaction.d.ts +8 -0
- package/lib/types/presentation/contracts/types.d.ts +255 -0
- package/lib/types/presentation/contracts/values.d.ts +9 -0
- package/lib/types/presentation/delivery.d.ts +3 -0
- package/lib/types/presentation/export-html.d.ts +13 -0
- package/lib/types/presentation/files.d.ts +7 -0
- package/lib/types/presentation/index.d.ts +6 -0
- package/lib/types/presentation/lint.d.ts +13 -0
- package/lib/types/presentation/projection/files.d.ts +2 -0
- package/lib/types/presentation/projection/index.d.ts +20 -0
- package/lib/types/presentation/projection/program.d.ts +2 -0
- package/lib/types/presentation/projection/sql-code.d.ts +2 -0
- package/lib/types/presentation/receipt.d.ts +14 -0
- package/lib/types/presentation/reports.d.ts +10 -0
- package/lib/types/presentation/rpc.d.ts +25 -0
- package/lib/types/presentation/tool.d.ts +12 -0
- package/lib/types/python-execution-contracts.d.ts +10 -0
- package/lib/types/python-execution.d.ts +10 -0
- package/lib/types/report-publishing/adapters.d.ts +6 -0
- package/lib/types/report-publishing/config.d.ts +22 -0
- package/lib/types/report-publishing/service.d.ts +68 -0
- package/lib/types/rpc.d.ts +3 -0
- package/lib/types/semantic-browser/contracts.d.ts +47 -0
- package/lib/types/semantic-browser/definition.d.ts +20 -0
- package/lib/types/semantic-browser/program.d.ts +2 -0
- package/lib/types/semantic-browser/service.d.ts +26 -0
- package/lib/types/semantic-reference/bridge.d.ts +12 -0
- package/lib/types/semantic-reference/contracts.d.ts +54 -0
- package/lib/types/semantic-reference/environment.d.ts +22 -0
- package/lib/types/semantic-reference/index.d.ts +3 -0
- package/lib/types/semantic-reference/labels.d.ts +16 -0
- package/lib/types/semantic-reference/rpc.d.ts +15 -0
- package/lib/types/semantic-reference/search.d.ts +10 -0
- package/lib/types/semantic-reference/usage.d.ts +43 -0
- package/lib/types/tool-lifecycle.d.ts +4 -0
- package/lib/types/workspace-identity.d.ts +6 -0
- package/lib/workspace-identity.js +9 -0
- package/package.json +99 -53
- package/python/presentation-kit/dist/dsh_data_analysis_presentation_kit-1.1.0-py3-none-any.whl +0 -0
- package/skills/dsh-data-analysis-files/SKILL.md +32 -0
- package/skills/dsh-data-analysis-files/references/examples.md +93 -0
- package/skills/dsh-data-analysis-presentation/SKILL.md +28 -0
- package/skills/dsh-data-analysis-presentation/references/chart-examples.md +18 -0
- package/skills/dsh-data-analysis-presentation/references/charts.md +55 -0
- package/skills/dsh-data-analysis-presentation/references/examples/artifact.draft.json +36 -0
- package/skills/dsh-data-analysis-presentation/references/examples/charts-bins.dataset.json +33 -0
- package/skills/dsh-data-analysis-presentation/references/examples/charts-boxes.dataset.json +45 -0
- package/skills/dsh-data-analysis-presentation/references/examples/charts-composition.dataset.json +33 -0
- package/skills/dsh-data-analysis-presentation/references/examples/charts-funnel.dataset.json +27 -0
- package/skills/dsh-data-analysis-presentation/references/examples/charts-precision.dataset.json +21 -0
- package/skills/dsh-data-analysis-presentation/references/examples/charts-series.dataset.json +58 -0
- package/skills/dsh-data-analysis-presentation/references/examples/charts-shares.dataset.json +33 -0
- package/skills/dsh-data-analysis-presentation/references/examples/charts-waterfall.dataset.json +44 -0
- package/skills/dsh-data-analysis-presentation/references/examples/charts.draft.json +393 -0
- package/skills/dsh-data-analysis-presentation/references/examples/comparison-selected.dataset.json +24 -0
- package/skills/dsh-data-analysis-presentation/references/examples/comparison-summary.dataset.json +22 -0
- package/skills/dsh-data-analysis-presentation/references/examples/comparison.dataset.json +18 -0
- package/skills/dsh-data-analysis-presentation/references/examples/comparison.draft.json +80 -0
- package/skills/dsh-data-analysis-presentation/references/examples/computed-sources.draft.json +42 -0
- package/skills/dsh-data-analysis-presentation/references/examples/computed.dataset.json +12 -0
- package/skills/dsh-data-analysis-presentation/references/examples/computed.draft.json +58 -0
- package/skills/dsh-data-analysis-presentation/references/examples/interaction-detail.dataset.json +30 -0
- package/skills/dsh-data-analysis-presentation/references/examples/interaction-summary.dataset.json +22 -0
- package/skills/dsh-data-analysis-presentation/references/examples/interaction.draft.json +279 -0
- package/skills/dsh-data-analysis-presentation/references/examples/source-only.draft.json +27 -0
- package/skills/dsh-data-analysis-presentation/references/examples/write-charts.py +73 -0
- package/skills/dsh-data-analysis-presentation/references/examples/write-comparison.py +42 -0
- package/skills/dsh-data-analysis-presentation/references/examples/write-computed.py +21 -0
- package/skills/dsh-data-analysis-presentation/references/examples/write-interaction.py +33 -0
- package/skills/dsh-data-analysis-presentation/references/examples.md +44 -0
- package/skills/dsh-data-analysis-presentation/references/export-html.md +7 -0
- package/skills/dsh-data-analysis-presentation/references/interaction.md +56 -0
- package/skills/dsh-data-analysis-presentation/references/narrative.md +37 -0
- package/skills/dsh-data-analysis-presentation/references/publishing.md +3 -0
- package/skills/dsh-data-analysis-presentation/references/schema.md +162 -0
- package/lib/datasource/access.js +0 -60
- package/lib/datasource/credentials.js +0 -24
- package/lib/evidence/bridge-program.js +0 -153
- package/lib/evidence/bridge.js +0 -166
- package/lib/evidence/index.js +0 -2
- package/lib/evidence/sources.js +0 -263
- package/lib/types/datasource/access.d.ts +0 -27
- package/lib/types/evidence/bridge.d.ts +0 -40
- package/lib/types/evidence/index.d.ts +0 -4
- package/lib/types/evidence/sources.d.ts +0 -57
- package/python/report-kit/dist/dsh_data_analysis_report_kit-3.0.0-py3-none-any.whl +0 -0
- package/skills/dsh-data-analysis-report/SKILL.md +0 -57
- package/skills/dsh-data-analysis-report/assets/marivo-artifact.js +0 -113
- package/skills/dsh-data-analysis-report/assets/marivo-session-dag.js +0 -1201
- package/skills/dsh-data-analysis-report/assets/report-data.js +0 -449
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// src/presentation/build/index.ts
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import {
|
|
4
|
+
PRESENTATION_BUDGETS,
|
|
5
|
+
PresentationContractError,
|
|
6
|
+
parsePresentationDocument
|
|
7
|
+
} from "../contracts/index.js";
|
|
8
|
+
var escapeHtml = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
9
|
+
var inlineJson = (value) => JSON.stringify(value).replaceAll("<", "\\u003c").replaceAll("\u2028", "\\u2028").replaceAll("\u2029", "\\u2029");
|
|
10
|
+
async function buildPresentation(value, options = { html: true }) {
|
|
11
|
+
parsePresentationDocument(value);
|
|
12
|
+
const documentBytes = Buffer.from(JSON.stringify(value), "utf8");
|
|
13
|
+
const document = parsePresentationDocument(JSON.parse(documentBytes.toString("utf8")));
|
|
14
|
+
if (!options.html) return { document, documentBytes };
|
|
15
|
+
const [portable, staticAssets] = await Promise.all([
|
|
16
|
+
import(new URL("../../../lib/presentation/assets/portable.js", import.meta.url).href),
|
|
17
|
+
import(new URL("../../../lib/presentation/assets/static.js", import.meta.url).href)
|
|
18
|
+
]);
|
|
19
|
+
const fallback = staticAssets.renderFallback(document);
|
|
20
|
+
const script = portable.script.replace(/<\/script/gi, "<\\/script");
|
|
21
|
+
const scriptHash = createHash("sha256").update(script).digest("base64");
|
|
22
|
+
const csp = [
|
|
23
|
+
"default-src 'none'",
|
|
24
|
+
`script-src 'sha256-${scriptHash}' 'wasm-unsafe-eval'`,
|
|
25
|
+
"script-src-attr 'none'",
|
|
26
|
+
"style-src 'unsafe-inline'",
|
|
27
|
+
"connect-src 'none'",
|
|
28
|
+
"img-src 'none'",
|
|
29
|
+
"font-src 'none'",
|
|
30
|
+
"media-src 'none'",
|
|
31
|
+
"frame-src 'none'",
|
|
32
|
+
"worker-src 'none'",
|
|
33
|
+
"object-src 'none'",
|
|
34
|
+
"base-uri 'none'",
|
|
35
|
+
"form-action 'none'"
|
|
36
|
+
].join("; ");
|
|
37
|
+
const htmlBytes = Buffer.from(
|
|
38
|
+
`<!doctype html>
|
|
39
|
+
<html lang="${escapeHtml(document.locale)}">
|
|
40
|
+
<head>
|
|
41
|
+
<meta charset="utf-8">
|
|
42
|
+
<meta http-equiv="Content-Security-Policy" content="${escapeHtml(csp)}">
|
|
43
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
44
|
+
<meta name="color-scheme" content="light dark">
|
|
45
|
+
<meta name="referrer" content="no-referrer">
|
|
46
|
+
<title>${escapeHtml(document.title)}</title>
|
|
47
|
+
<style>${portable.styles.replace(/<\/style/gi, "<\\/style")}</style>
|
|
48
|
+
</head>
|
|
49
|
+
<body data-presentation-portable="true">
|
|
50
|
+
<div id="presentation-fallback">${fallback}</div>
|
|
51
|
+
<div id="reader"></div>
|
|
52
|
+
<script id="presentation-data" type="application/json">${inlineJson(document)}</script>
|
|
53
|
+
<script id="third-party-notices" type="application/json">${inlineJson(portable.notices)}</script>
|
|
54
|
+
<script id="presentation-runtime">${script}</script>
|
|
55
|
+
</body>
|
|
56
|
+
</html>
|
|
57
|
+
`,
|
|
58
|
+
"utf8"
|
|
59
|
+
);
|
|
60
|
+
if (htmlBytes.length > PRESENTATION_BUDGETS.htmlBytes) {
|
|
61
|
+
throw new PresentationContractError(
|
|
62
|
+
"budget",
|
|
63
|
+
"/html",
|
|
64
|
+
`HTML exceeds the ${PRESENTATION_BUDGETS.htmlBytes} byte budget.`,
|
|
65
|
+
"Reduce repeated blocks, displayed rows, or text before building again."
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
return { document, documentBytes, htmlBytes };
|
|
69
|
+
}
|
|
70
|
+
export {
|
|
71
|
+
buildPresentation
|
|
72
|
+
};
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { constants } from 'node:fs';
|
|
2
|
+
import { lstat, mkdir, mkdtemp, open, realpath, rename, rm } from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { PRESENTATION_BUDGETS, parsePresentationDocument, parsePresentationReceipt, } from "./contracts/index.js";
|
|
5
|
+
import { presentationAssetPath, presentationSha256, readPresentationAsset } from "./files.js";
|
|
6
|
+
async function directory(filename) {
|
|
7
|
+
const stat = await lstat(filename);
|
|
8
|
+
if (!stat.isDirectory() || stat.isSymbolicLink() || (await realpath(filename)) !== filename)
|
|
9
|
+
throw new Error('presentation-directory-mismatch');
|
|
10
|
+
return stat;
|
|
11
|
+
}
|
|
12
|
+
async function syncDirectory(filename) {
|
|
13
|
+
const handle = await open(filename, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
14
|
+
try {
|
|
15
|
+
await handle.sync();
|
|
16
|
+
}
|
|
17
|
+
finally {
|
|
18
|
+
await handle.close();
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
async function sameDirectory(filename, expected) {
|
|
22
|
+
const actual = await directory(filename);
|
|
23
|
+
if (actual.dev !== expected.dev || actual.ino !== expected.ino)
|
|
24
|
+
throw new Error('presentation-directory-changed');
|
|
25
|
+
}
|
|
26
|
+
/** Owns only one newly-created build. A single directory rename publishes all requested complete files. */
|
|
27
|
+
export async function commitPresentation(workspaceRoot, input, assertOwner, signal) {
|
|
28
|
+
signal?.throwIfAborted();
|
|
29
|
+
const document = parsePresentationDocument(structuredClone(input.document));
|
|
30
|
+
const built = {
|
|
31
|
+
document,
|
|
32
|
+
documentBytes: Buffer.from(input.documentBytes),
|
|
33
|
+
htmlBytes: input.htmlBytes && Buffer.from(input.htmlBytes),
|
|
34
|
+
};
|
|
35
|
+
if (built.documentBytes.length > PRESENTATION_BUDGETS.documentBytes ||
|
|
36
|
+
(built.htmlBytes !== undefined &&
|
|
37
|
+
(built.htmlBytes.length === 0 || built.htmlBytes.length > PRESENTATION_BUDGETS.htmlBytes)))
|
|
38
|
+
throw new Error('presentation-build-budget');
|
|
39
|
+
if (!built.documentBytes.equals(Buffer.from(JSON.stringify(document))))
|
|
40
|
+
throw new Error('presentation-build-document-mismatch');
|
|
41
|
+
const root = await realpath(workspaceRoot);
|
|
42
|
+
if (root !== path.resolve(workspaceRoot))
|
|
43
|
+
throw new Error('presentation-workspace-mismatch');
|
|
44
|
+
const paths = [
|
|
45
|
+
root,
|
|
46
|
+
path.join(root, '.dsh-data-analysis'),
|
|
47
|
+
path.join(root, '.dsh-data-analysis', 'presentations'),
|
|
48
|
+
path.join(root, '.dsh-data-analysis', 'presentations', document.reportId),
|
|
49
|
+
path.join(root, '.dsh-data-analysis', 'presentations', document.reportId, 'builds'),
|
|
50
|
+
];
|
|
51
|
+
const identities = [];
|
|
52
|
+
for (const [index, filename] of paths.entries()) {
|
|
53
|
+
if (index > 0) {
|
|
54
|
+
await sameDirectory(paths[index - 1], identities[index - 1]);
|
|
55
|
+
try {
|
|
56
|
+
await mkdir(filename, { mode: 0o700 });
|
|
57
|
+
await syncDirectory(paths[index - 1]);
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
if (error.code !== 'EEXIST')
|
|
61
|
+
throw error;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
identities.push(await directory(filename));
|
|
65
|
+
}
|
|
66
|
+
const check = async () => {
|
|
67
|
+
signal?.throwIfAborted();
|
|
68
|
+
await assertOwner();
|
|
69
|
+
for (const [index, filename] of paths.entries())
|
|
70
|
+
await sameDirectory(filename, identities[index]);
|
|
71
|
+
signal?.throwIfAborted();
|
|
72
|
+
};
|
|
73
|
+
await check();
|
|
74
|
+
const parent = paths.at(-1);
|
|
75
|
+
const temporary = await mkdtemp(path.join(parent, '.pending-'));
|
|
76
|
+
const ownedIdentity = await directory(temporary);
|
|
77
|
+
const final = path.dirname(presentationAssetPath(root, built.document.reportId, built.document.buildId, 'presentation.json'));
|
|
78
|
+
let owned = temporary;
|
|
79
|
+
try {
|
|
80
|
+
await check();
|
|
81
|
+
for (const [asset, bytes] of [
|
|
82
|
+
['presentation.json', built.documentBytes],
|
|
83
|
+
['index.html', built.htmlBytes],
|
|
84
|
+
]) {
|
|
85
|
+
if (!bytes)
|
|
86
|
+
continue;
|
|
87
|
+
await sameDirectory(temporary, ownedIdentity);
|
|
88
|
+
signal?.throwIfAborted();
|
|
89
|
+
const file = await open(path.join(temporary, asset), constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, 0o600);
|
|
90
|
+
try {
|
|
91
|
+
await file.writeFile(bytes, { signal });
|
|
92
|
+
await file.sync();
|
|
93
|
+
}
|
|
94
|
+
finally {
|
|
95
|
+
await file.close();
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
await check();
|
|
99
|
+
await sameDirectory(temporary, ownedIdentity);
|
|
100
|
+
await syncDirectory(temporary);
|
|
101
|
+
// Never overwrite a completed build (the caller creates an unpredictable build UUID).
|
|
102
|
+
try {
|
|
103
|
+
await lstat(final);
|
|
104
|
+
throw new Error('presentation-build-exists');
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
if (error.code !== 'ENOENT')
|
|
108
|
+
throw error;
|
|
109
|
+
}
|
|
110
|
+
await rename(temporary, final);
|
|
111
|
+
owned = final;
|
|
112
|
+
for (const filename of [...paths].reverse())
|
|
113
|
+
await syncDirectory(filename);
|
|
114
|
+
await check();
|
|
115
|
+
await sameDirectory(final, ownedIdentity);
|
|
116
|
+
const document = await readPresentationAsset(root, built.document.reportId, built.document.buildId, 'presentation.json', signal);
|
|
117
|
+
const html = built.htmlBytes &&
|
|
118
|
+
(await readPresentationAsset(root, built.document.reportId, built.document.buildId, 'index.html', signal));
|
|
119
|
+
if (!document.equals(built.documentBytes) || (html && !html.equals(built.htmlBytes)))
|
|
120
|
+
throw new Error('presentation-build-changed');
|
|
121
|
+
await check();
|
|
122
|
+
return parsePresentationReceipt({
|
|
123
|
+
schemaVersion: 2,
|
|
124
|
+
reportId: built.document.reportId,
|
|
125
|
+
kind: 'marivo.presentation',
|
|
126
|
+
workspaceId: built.document.workspaceId,
|
|
127
|
+
buildId: built.document.buildId,
|
|
128
|
+
title: built.document.title,
|
|
129
|
+
summary: `${built.document.blocks.length} 个区块 · ${built.document.datasets.length} 份数据集 · ${built.document.sources.length} 个声明来源 · ${built.document.diagnostics.length} 条诊断`,
|
|
130
|
+
files: {
|
|
131
|
+
document: {
|
|
132
|
+
asset: 'presentation.json',
|
|
133
|
+
path: path.join(final, 'presentation.json'),
|
|
134
|
+
sha256: presentationSha256(document),
|
|
135
|
+
bytes: document.length,
|
|
136
|
+
},
|
|
137
|
+
...(html
|
|
138
|
+
? {
|
|
139
|
+
html: {
|
|
140
|
+
asset: 'index.html',
|
|
141
|
+
path: path.join(final, 'index.html'),
|
|
142
|
+
sha256: presentationSha256(html),
|
|
143
|
+
bytes: html.length,
|
|
144
|
+
},
|
|
145
|
+
}
|
|
146
|
+
: {}),
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
catch (error) {
|
|
151
|
+
// Cleanup only this invocation's unchanged directory; never follow a replaced parent.
|
|
152
|
+
try {
|
|
153
|
+
for (const [index, filename] of paths.entries())
|
|
154
|
+
await sameDirectory(filename, identities[index]);
|
|
155
|
+
await sameDirectory(owned, ownedIdentity);
|
|
156
|
+
await rm(owned, { recursive: true, force: true });
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
/* Failure cleanup must not hide the original cancellation/write error. */
|
|
160
|
+
}
|
|
161
|
+
throw error;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Fixed Build locator shared by the browser and Workspace storage. */
|
|
2
|
+
import { parsePresentationBuildId } from "./index.js";
|
|
3
|
+
export function presentationAssetRelativePath(reportId, buildId, asset) {
|
|
4
|
+
parsePresentationBuildId(reportId, '/reportId');
|
|
5
|
+
parsePresentationBuildId(buildId);
|
|
6
|
+
if (asset !== 'presentation.json' && asset !== 'index.html')
|
|
7
|
+
throw new Error('invalid-asset');
|
|
8
|
+
return `.dsh-data-analysis/presentations/${reportId}/builds/${buildId}/${asset}`;
|
|
9
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { parsePresentationReceipt } from "./index.js";
|
|
2
|
+
export const REPORT_HISTORY_BYTES = 16 * 1024 * 1024;
|
|
3
|
+
export const REPORT_HISTORY_LIMIT = 4096;
|
|
4
|
+
export function parseReportVersion(value) {
|
|
5
|
+
const entry = value;
|
|
6
|
+
if (!entry ||
|
|
7
|
+
Object.keys(entry).sort().join(',') !== 'publishedAt,receipt,source' ||
|
|
8
|
+
!(entry.publishedAt === null ||
|
|
9
|
+
(typeof entry.publishedAt === 'string' &&
|
|
10
|
+
Number.isFinite(Date.parse(entry.publishedAt)) &&
|
|
11
|
+
new Date(entry.publishedAt).toISOString() === entry.publishedAt)) ||
|
|
12
|
+
!(entry.source === null ||
|
|
13
|
+
(entry.source &&
|
|
14
|
+
Object.keys(entry.source).sort().join(',') === 'kind,sessionId' &&
|
|
15
|
+
['agent', 'reader'].includes(entry.source.kind) &&
|
|
16
|
+
(entry.source.sessionId === null ||
|
|
17
|
+
(typeof entry.source.sessionId === 'string' &&
|
|
18
|
+
!!entry.source.sessionId.trim() &&
|
|
19
|
+
entry.source.sessionId.length <= 512)))))
|
|
20
|
+
throw new Error('invalid-report-history');
|
|
21
|
+
parsePresentationReceipt(entry.receipt);
|
|
22
|
+
return entry;
|
|
23
|
+
}
|
|
24
|
+
export function parseReportHistory(value) {
|
|
25
|
+
const history = value;
|
|
26
|
+
if (!history ||
|
|
27
|
+
!Array.isArray(history.versions) ||
|
|
28
|
+
!history.versions.length ||
|
|
29
|
+
history.versions.length > REPORT_HISTORY_LIMIT ||
|
|
30
|
+
typeof history.legacyHistoryUnavailable !== 'boolean')
|
|
31
|
+
throw new Error('invalid-report-history');
|
|
32
|
+
const ids = new Set();
|
|
33
|
+
for (const entry of history.versions) {
|
|
34
|
+
const { receipt } = parseReportVersion(entry);
|
|
35
|
+
if (receipt.workspaceId !== history.workspaceId ||
|
|
36
|
+
receipt.reportId !== history.reportId ||
|
|
37
|
+
ids.has(receipt.buildId))
|
|
38
|
+
throw new Error('invalid-report-history');
|
|
39
|
+
ids.add(receipt.buildId);
|
|
40
|
+
}
|
|
41
|
+
if (history.currentBuildId !== history.versions[0].receipt.buildId)
|
|
42
|
+
throw new Error('invalid-report-history');
|
|
43
|
+
return history;
|
|
44
|
+
}
|
|
45
|
+
export function parseReportCatalog(value) {
|
|
46
|
+
const catalog = value;
|
|
47
|
+
if (!catalog ||
|
|
48
|
+
typeof catalog.workspaceId !== 'string' ||
|
|
49
|
+
!Array.isArray(catalog.reports) ||
|
|
50
|
+
catalog.reports.length > 4096 ||
|
|
51
|
+
!Number.isInteger(catalog.unavailable) ||
|
|
52
|
+
catalog.unavailable < 0)
|
|
53
|
+
throw new Error('invalid-report-catalog');
|
|
54
|
+
const ids = new Set();
|
|
55
|
+
for (const item of catalog.reports) {
|
|
56
|
+
const { receipt } = parseReportVersion(item);
|
|
57
|
+
if (receipt.workspaceId !== catalog.workspaceId || ids.has(receipt.reportId))
|
|
58
|
+
throw new Error('invalid-report-catalog');
|
|
59
|
+
ids.add(receipt.reportId);
|
|
60
|
+
}
|
|
61
|
+
return catalog;
|
|
62
|
+
}
|