synabun 2.0.0 → 2.0.7
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/CHANGELOG.md +97 -0
- package/NOTICE +5 -0
- package/README.md +2 -1
- package/THIRD-PARTY-LICENSES.md +59 -0
- package/benchmarks/fixtures/browser/README.md +1 -1
- package/benchmarks/fixtures/desktop/README.md +1 -1
- package/docs/whatsapp.md +153 -18
- package/hooks/claude-code/pre-compact.mjs +11 -0
- package/hooks/claude-code/prompt-submit.mjs +60 -184
- package/hooks/claude-code/session-start.mjs +73 -16
- package/hooks/claude-code/shared.mjs +26 -3
- package/hooks/claude-code/stop.mjs +5 -5
- package/lib/child-path.js +303 -0
- package/lib/claude-hooks.js +2 -1
- package/lib/data-home-migration.js +2 -0
- package/lib/desktop-app.js +265 -0
- package/lib/external-tools.js +7 -3
- package/lib/log-redact.js +102 -0
- package/lib/packaged-runtime.js +138 -11
- package/lib/start-launcher.js +88 -1
- package/mcp-server/dist/http.d.ts +3 -1
- package/mcp-server/dist/http.d.ts.map +1 -1
- package/mcp-server/dist/http.js +15 -4
- package/mcp-server/dist/http.js.map +1 -1
- package/mcp-server/dist/index.d.ts.map +1 -1
- package/mcp-server/dist/index.js +9 -6
- package/mcp-server/dist/index.js.map +1 -1
- package/mcp-server/dist/services/budget-judgments.d.ts +108 -0
- package/mcp-server/dist/services/budget-judgments.d.ts.map +1 -0
- package/mcp-server/dist/services/budget-judgments.js +153 -0
- package/mcp-server/dist/services/budget-judgments.js.map +1 -0
- package/mcp-server/dist/services/codex-config-heal.d.ts +5 -1
- package/mcp-server/dist/services/codex-config-heal.d.ts.map +1 -1
- package/mcp-server/dist/services/codex-config-heal.js +41 -4
- package/mcp-server/dist/services/codex-config-heal.js.map +1 -1
- package/mcp-server/dist/services/identity.d.ts +11 -0
- package/mcp-server/dist/services/identity.d.ts.map +1 -1
- package/mcp-server/dist/services/identity.js +24 -0
- package/mcp-server/dist/services/identity.js.map +1 -1
- package/mcp-server/dist/services/neural-interface.d.ts +13 -0
- package/mcp-server/dist/services/neural-interface.d.ts.map +1 -1
- package/mcp-server/dist/services/neural-interface.js +34 -2
- package/mcp-server/dist/services/neural-interface.js.map +1 -1
- package/mcp-server/dist/services/typesafe-config.d.ts +1 -1
- package/mcp-server/dist/services/typesafe-config.d.ts.map +1 -1
- package/mcp-server/dist/services/typesafe-config.js +4 -1
- package/mcp-server/dist/services/typesafe-config.js.map +1 -1
- package/mcp-server/dist/tools/agents.d.ts +10 -7
- package/mcp-server/dist/tools/agents.d.ts.map +1 -1
- package/mcp-server/dist/tools/agents.js +34 -13
- package/mcp-server/dist/tools/agents.js.map +1 -1
- package/mcp-server/dist/tools/whatsapp-send-file.d.ts +17 -0
- package/mcp-server/dist/tools/whatsapp-send-file.d.ts.map +1 -0
- package/mcp-server/dist/tools/whatsapp-send-file.js +44 -0
- package/mcp-server/dist/tools/whatsapp-send-file.js.map +1 -0
- package/mcp-server/dist/tui.js +2 -1
- package/mcp-server/dist/tui.js.map +1 -1
- package/mcp-server/dist/version.d.ts +5 -0
- package/mcp-server/dist/version.d.ts.map +1 -0
- package/mcp-server/dist/version.js +21 -0
- package/mcp-server/dist/version.js.map +1 -0
- package/mcp-server/package-lock.json +2 -2
- package/mcp-server/package.json +1 -3
- package/mcp-server/run.mjs +15 -9
- package/mcp-server/src/http.ts +18 -3
- package/mcp-server/src/index.ts +9 -6
- package/mcp-server/src/services/budget-judgments.ts +183 -0
- package/mcp-server/src/services/codex-config-heal.ts +35 -4
- package/mcp-server/src/services/identity.ts +27 -0
- package/mcp-server/src/services/neural-interface.ts +34 -2
- package/mcp-server/src/services/typesafe-config.ts +4 -1
- package/mcp-server/src/tools/agents.ts +33 -14
- package/mcp-server/src/tools/whatsapp-send-file.ts +51 -0
- package/mcp-server/src/tui.ts +2 -1
- package/mcp-server/src/version.ts +22 -0
- package/neural-interface/i18n/en.json +2126 -209
- package/neural-interface/i18n/pt-BR.json +2123 -207
- package/neural-interface/lib/account-usage.js +163 -0
- package/neural-interface/lib/assistant-api.js +316 -57
- package/neural-interface/lib/assistant-brains/claude.js +19 -2
- package/neural-interface/lib/assistant-brains/codex.js +3 -0
- package/neural-interface/lib/assistant-brains/opencode.js +17 -4
- package/neural-interface/lib/assistant-budget-planner.js +390 -0
- package/neural-interface/lib/assistant-budget-split.js +408 -0
- package/neural-interface/lib/assistant-budget.js +94 -95
- package/neural-interface/lib/assistant-catalog.js +179 -27
- package/neural-interface/lib/assistant-config.js +91 -14
- package/neural-interface/lib/assistant-dispatch.js +494 -138
- package/neural-interface/lib/assistant-hidden-models.js +393 -66
- package/neural-interface/lib/assistant-jev.js +26 -3
- package/neural-interface/lib/assistant-memory.js +24 -10
- package/neural-interface/lib/assistant-persona.js +73 -12
- package/neural-interface/lib/assistant-plan-permissions.js +8 -1
- package/neural-interface/lib/assistant-pricing.js +21 -6
- package/neural-interface/lib/assistant-route-accounts.js +14 -0
- package/neural-interface/lib/assistant-route-gate.js +19 -3
- package/neural-interface/lib/assistant-router.js +373 -42
- package/neural-interface/lib/assistant-runtime.js +488 -59
- package/neural-interface/lib/assistant-task-prompt.js +13 -3
- package/neural-interface/lib/assistant-test-density.js +142 -0
- package/neural-interface/lib/assistant-user-context-budgets.js +123 -0
- package/neural-interface/lib/assistant-user-context.js +908 -0
- package/neural-interface/lib/assistant-user-knowledge.js +328 -0
- package/neural-interface/lib/assistant-user-learn.js +404 -0
- package/neural-interface/lib/assistant-user-threads.js +226 -0
- package/neural-interface/lib/assistant-user-writer.js +183 -0
- package/neural-interface/lib/augmented-path.js +317 -53
- package/neural-interface/lib/browser-background-tab.js +134 -0
- package/neural-interface/lib/browser-screenshot.js +678 -61
- package/neural-interface/lib/claude-accounts.js +342 -31
- package/neural-interface/lib/claude-agent-bridge.js +123 -9
- package/neural-interface/lib/claude-executable.js +6 -1
- package/neural-interface/lib/claude-model-catalog.js +33 -5
- package/neural-interface/lib/claude-panel-auth.js +221 -0
- package/neural-interface/lib/claude-profile-api-key.js +62 -0
- package/neural-interface/lib/claude-temporary.js +46 -7
- package/neural-interface/lib/cli-install-api.js +137 -0
- package/neural-interface/lib/cli-install.js +797 -0
- package/neural-interface/lib/cli-path.js +130 -0
- package/neural-interface/lib/cli-resolver.js +744 -0
- package/neural-interface/lib/cli-update-planner.js +143 -7
- package/neural-interface/lib/cloudflared/install.js +533 -0
- package/neural-interface/lib/cloudflared/manager.js +545 -0
- package/neural-interface/lib/cloudflared/pins.js +115 -0
- package/neural-interface/lib/cloudflared/pins.json +63 -0
- package/neural-interface/lib/cloudflared/tunnel.js +265 -0
- package/neural-interface/lib/codex-account-auth.js +178 -0
- package/neural-interface/lib/codex-accounts.js +489 -0
- package/neural-interface/lib/codex-capabilities.js +2 -1
- package/neural-interface/lib/codex-compat-rpc.js +8 -2
- package/neural-interface/lib/codex-mcp-approvals.js +5 -1
- package/neural-interface/lib/codex-panel-auth.js +62 -0
- package/neural-interface/lib/codex-session-settings.js +1 -1
- package/neural-interface/lib/codex-settings-schema.js +120 -0
- package/neural-interface/lib/desktop/config.js +3 -0
- package/neural-interface/lib/editor-completion-endpoint.js +264 -0
- package/neural-interface/lib/editor-completion-opencode.js +436 -0
- package/neural-interface/lib/editor-completion.js +714 -0
- package/neural-interface/lib/editor-config.js +149 -0
- package/neural-interface/lib/editor-files-api.js +268 -0
- package/neural-interface/lib/editor-git-api.js +134 -0
- package/neural-interface/lib/file-explorer-api.js +1039 -0
- package/neural-interface/lib/file-explorer-system.js +355 -0
- package/neural-interface/lib/file-explorer-trash.ps1 +85 -0
- package/neural-interface/lib/file-explorer-watch.js +239 -0
- package/neural-interface/lib/git-bash/bzip2.js +339 -0
- package/neural-interface/lib/git-bash/claude-settings.js +73 -0
- package/neural-interface/lib/git-bash/extract-worker.js +34 -0
- package/neural-interface/lib/git-bash/install.js +529 -0
- package/neural-interface/lib/git-bash/licenses/COPYING +360 -0
- package/neural-interface/lib/git-bash/licenses/LGPL-2.1 +511 -0
- package/neural-interface/lib/git-bash/manager.js +268 -0
- package/neural-interface/lib/git-bash/pins.js +206 -0
- package/neural-interface/lib/git-bash/pins.json +34 -0
- package/neural-interface/lib/git-bash/resolver.js +194 -0
- package/neural-interface/lib/git-diff-api.js +103 -0
- package/neural-interface/lib/git-read.js +234 -0
- package/neural-interface/lib/greeting/api.js +89 -0
- package/neural-interface/lib/greeting/budgets.js +90 -0
- package/neural-interface/lib/greeting/engine.js +461 -0
- package/neural-interface/lib/greeting/facts.js +320 -0
- package/neural-interface/lib/greeting/sources.js +252 -0
- package/neural-interface/lib/greeting/writer.js +139 -0
- package/neural-interface/lib/http-guards.js +189 -13
- package/neural-interface/lib/install-details.js +145 -0
- package/neural-interface/lib/machine-names.js +88 -0
- package/neural-interface/lib/mcp-installer.js +14 -2
- package/neural-interface/lib/memory-api.js +31 -7
- package/neural-interface/lib/native-loop-providers.js +26 -8
- package/neural-interface/lib/native-loop-runtime.js +9 -4
- package/neural-interface/lib/os-path.js +319 -0
- package/neural-interface/lib/plugin-installer.js +4 -2
- package/neural-interface/lib/project-storage.js +5 -7
- package/neural-interface/lib/provider-panel-api-key-auth.js +92 -0
- package/neural-interface/lib/provider-panel-terminal-auth.js +95 -0
- package/neural-interface/lib/pty-host/core.js +2 -1
- package/neural-interface/lib/pty-host/manager.js +27 -2
- package/neural-interface/lib/remote-policy.js +20 -11
- package/neural-interface/lib/request-gate.js +589 -0
- package/neural-interface/lib/session-title-generator.js +4 -3
- package/neural-interface/lib/system-about.js +340 -0
- package/neural-interface/lib/system-activity.js +66 -0
- package/neural-interface/lib/terminal-env.js +11 -3
- package/neural-interface/lib/terminal-path.js +55 -0
- package/neural-interface/lib/ui-blob-api.js +108 -0
- package/neural-interface/lib/ui-blob-store.js +68 -0
- package/neural-interface/lib/virtual-socket.js +12 -1
- package/neural-interface/lib/whatsapp/baileys-adapter.js +165 -45
- package/neural-interface/lib/whatsapp/bridge.js +78 -34
- package/neural-interface/lib/whatsapp/commands.js +13 -14
- package/neural-interface/lib/whatsapp/config.js +134 -20
- package/neural-interface/lib/whatsapp/fake-baileys.js +99 -14
- package/neural-interface/lib/whatsapp/host-core.js +168 -35
- package/neural-interface/lib/whatsapp/inbound.js +5 -3
- package/neural-interface/lib/whatsapp/local-file.js +524 -0
- package/neural-interface/lib/whatsapp/manager.js +92 -32
- package/neural-interface/lib/whatsapp/paths.js +11 -1
- package/neural-interface/lib/whatsapp/protocol.js +65 -5
- package/neural-interface/lib/whatsapp/redact.js +63 -0
- package/neural-interface/lib/whatsapp/service.js +343 -91
- package/neural-interface/lib/whatsapp/transcription.js +463 -0
- package/neural-interface/package-lock.json +701 -2
- package/neural-interface/package.json +13 -2
- package/neural-interface/public/index.html +2 -0
- package/neural-interface/public/index2d.html +2 -0
- package/neural-interface/public/install-app.html +9 -6
- package/neural-interface/public/offline.html +19 -3
- package/neural-interface/public/onboarding.html +107 -2
- package/neural-interface/public/shared/account-i18n.js +7 -0
- package/neural-interface/public/shared/account-menu.js +539 -0
- package/neural-interface/public/shared/account-usage.js +19 -0
- package/neural-interface/public/shared/account-view.js +335 -0
- package/neural-interface/public/shared/agent-runtime-options.js +50 -14
- package/neural-interface/public/shared/assistant/asst-accounts.js +182 -184
- package/neural-interface/public/shared/assistant/asst-api.js +32 -2
- package/neural-interface/public/shared/assistant/asst-automation.js +286 -0
- package/neural-interface/public/shared/assistant/asst-brain-picker.js +430 -81
- package/neural-interface/public/shared/assistant/asst-budget.js +259 -303
- package/neural-interface/public/shared/assistant/asst-composer.js +11 -1
- package/neural-interface/public/shared/assistant/asst-mascot.js +140 -61
- package/neural-interface/public/shared/assistant/asst-menu.js +27 -11
- package/neural-interface/public/shared/assistant/asst-panel.js +198 -42
- package/neural-interface/public/shared/assistant/asst-render.js +197 -23
- package/neural-interface/public/shared/assistant/asst-route-models.js +108 -0
- package/neural-interface/public/shared/assistant/asst-route.js +95 -343
- package/neural-interface/public/shared/assistant/asst-routes-editor.js +534 -0
- package/neural-interface/public/shared/assistant/asst-sanitize.js +8 -1
- package/neural-interface/public/shared/assistant/asst-sidepanel.js +31 -9
- package/neural-interface/public/shared/assistant/asst-slash.js +1 -1
- package/neural-interface/public/shared/assistant/asst-state.js +186 -2
- package/neural-interface/public/shared/assistant/asst-styles.js +247 -11
- package/neural-interface/public/shared/blob-store.js +111 -22
- package/neural-interface/public/shared/cdx/cdx-commands.js +4 -1
- package/neural-interface/public/shared/cdx/cdx-features.js +22 -14
- package/neural-interface/public/shared/cdx/cdx-icons.js +2 -0
- package/neural-interface/public/shared/cdx/cdx-panel.js +80 -118
- package/neural-interface/public/shared/cdx/cdx-render.js +25 -89
- package/neural-interface/public/shared/cdx/cdx-settings.js +231 -0
- package/neural-interface/public/shared/cdx/cdx-styles.js +164 -107
- package/neural-interface/public/shared/cdx/cdx-tabs.js +348 -668
- package/neural-interface/public/shared/cdx/cdx-ws.js +2 -0
- package/neural-interface/public/shared/claude-account-view.js +3 -0
- package/neural-interface/public/shared/cli-install-view.js +392 -0
- package/neural-interface/public/shared/cli-status.js +282 -22
- package/neural-interface/public/shared/constants.js +6 -1
- package/neural-interface/public/shared/cp/cp-events.js +2 -1
- package/neural-interface/public/shared/cp/cp-rehydrate.js +2 -1
- package/neural-interface/public/shared/cp/cp-styles.js +61 -0
- package/neural-interface/public/shared/desktop-bridge.js +39 -0
- package/neural-interface/public/shared/desktop-titlebar-page.js +59 -0
- package/neural-interface/public/shared/desktop-titlebar-view.js +122 -0
- package/neural-interface/public/shared/desktop-titlebar.js +153 -0
- package/neural-interface/public/shared/desktop-update-view.js +462 -0
- package/neural-interface/public/shared/editor/editor-ai-logic.js +409 -0
- package/neural-interface/public/shared/editor/editor-ai.js +619 -0
- package/neural-interface/public/shared/editor/editor-commands.js +212 -0
- package/neural-interface/public/shared/editor/editor-compare.js +103 -0
- package/neural-interface/public/shared/editor/editor-completion-list.js +172 -0
- package/neural-interface/public/shared/editor/editor-doc.js +257 -0
- package/neural-interface/public/shared/editor/editor-git-logic.js +131 -0
- package/neural-interface/public/shared/editor/editor-git.js +95 -0
- package/neural-interface/public/shared/editor/editor-languages.js +156 -0
- package/neural-interface/public/shared/editor/editor-lint.js +229 -0
- package/neural-interface/public/shared/editor/editor-loader.js +51 -0
- package/neural-interface/public/shared/editor/editor-markdown.js +90 -0
- package/neural-interface/public/shared/editor/editor-menu.js +233 -0
- package/neural-interface/public/shared/editor/editor-minimap.js +371 -0
- package/neural-interface/public/shared/editor/editor-palette-logic.js +192 -0
- package/neural-interface/public/shared/editor/editor-palette.js +125 -0
- package/neural-interface/public/shared/editor/editor-panel-logic.js +288 -0
- package/neural-interface/public/shared/editor/editor-panel.js +2099 -0
- package/neural-interface/public/shared/editor/editor-prefs.js +224 -0
- package/neural-interface/public/shared/editor/editor-safety.js +500 -0
- package/neural-interface/public/shared/editor/editor-search.js +352 -0
- package/neural-interface/public/shared/editor/editor-statusbar.js +144 -0
- package/neural-interface/public/shared/editor/editor-tabs-logic.js +331 -0
- package/neural-interface/public/shared/editor/editor-tabs.js +233 -0
- package/neural-interface/public/shared/editor/editor-theme.js +244 -0
- package/neural-interface/public/shared/editor/editor-view.js +332 -0
- package/neural-interface/public/shared/explorer/fx-api.js +216 -0
- package/neural-interface/public/shared/explorer/fx-links.js +204 -0
- package/neural-interface/public/shared/explorer/fx-logic.js +537 -0
- package/neural-interface/public/shared/explorer/fx-main.js +865 -0
- package/neural-interface/public/shared/explorer/fx-names.js +87 -0
- package/neural-interface/public/shared/explorer/fx-ops.js +370 -0
- package/neural-interface/public/shared/explorer/fx-popups.js +490 -0
- package/neural-interface/public/shared/explorer/fx-tree.js +790 -0
- package/neural-interface/public/shared/file-links.js +339 -0
- package/neural-interface/public/shared/git-bash-view.js +145 -0
- package/neural-interface/public/shared/greeting/greeting-card.js +178 -0
- package/neural-interface/public/shared/greeting/greeting-view.js +148 -0
- package/neural-interface/public/shared/html-shell.js +161 -165
- package/neural-interface/public/shared/i18n.js +19 -1
- package/neural-interface/public/shared/install-details-view.js +86 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-childpanel.js +4 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-greeting.js +47 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-panel.js +131 -17
- package/neural-interface/public/shared/ocp-v2/ocp-v2-render.js +7 -1
- package/neural-interface/public/shared/ocp-v2/ocp-v2-send.js +22 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-styles.js +158 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-ws.js +3 -2
- package/neural-interface/public/shared/pwa-install-page.js +32 -2
- package/neural-interface/public/shared/pwa-install.css +3 -3
- package/neural-interface/public/shared/pwa-install.js +100 -1
- package/neural-interface/public/shared/settings/settings-about-pane.js +271 -0
- package/neural-interface/public/shared/settings/settings-about.js +254 -0
- package/neural-interface/public/shared/settings/settings-code-editor-pane.js +412 -0
- package/neural-interface/public/shared/settings/settings-code-editor.js +328 -0
- package/neural-interface/public/shared/settings/settings-greeting.js +133 -0
- package/neural-interface/public/shared/settings/settings-ia.js +33 -4
- package/neural-interface/public/shared/settings/settings-user-context.js +154 -0
- package/neural-interface/public/shared/showcase-privacy.js +489 -0
- package/neural-interface/public/shared/start-bridge.js +238 -19
- package/neural-interface/public/shared/storage.js +30 -3
- package/neural-interface/public/shared/styles.css +1710 -770
- package/neural-interface/public/shared/synabun-mascot.js +68 -58
- package/neural-interface/public/shared/tunnel-view.js +167 -0
- package/neural-interface/public/shared/ui-claude-panel.js +369 -229
- package/neural-interface/public/shared/ui-cli-banner.js +355 -0
- package/neural-interface/public/shared/ui-detail.js +15 -0
- package/neural-interface/public/shared/ui-explorer.js +22 -0
- package/neural-interface/public/shared/ui-file-explorer.js +481 -2496
- package/neural-interface/public/shared/ui-file-links.js +103 -0
- package/neural-interface/public/shared/ui-git-bash.js +124 -0
- package/neural-interface/public/shared/ui-help.js +26 -0
- package/neural-interface/public/shared/ui-invite.js +126 -116
- package/neural-interface/public/shared/ui-judgments.js +10 -1
- package/neural-interface/public/shared/ui-keybinds.js +4 -0
- package/neural-interface/public/shared/ui-loading.js +187 -19
- package/neural-interface/public/shared/ui-menubar.js +25 -33
- package/neural-interface/public/shared/ui-native-loop-router-state.js +11 -0
- package/neural-interface/public/shared/ui-native-loop-router.js +15 -2
- package/neural-interface/public/shared/ui-navbar.js +48 -22
- package/neural-interface/public/shared/ui-provider-auth.js +104 -0
- package/neural-interface/public/shared/ui-provider-terminal-auth.js +223 -0
- package/neural-interface/public/shared/ui-sessions.js +9 -3
- package/neural-interface/public/shared/ui-settings.js +509 -490
- package/neural-interface/public/shared/ui-sidepanel-runtime.js +2 -1
- package/neural-interface/public/shared/ui-sidepanel-sessions.js +3 -1
- package/neural-interface/public/shared/ui-sync.js +22 -0
- package/neural-interface/public/shared/ui-terminal.js +40 -5
- package/neural-interface/public/shared/ui-tool-updates.js +5 -0
- package/neural-interface/public/shared/ui-tunnel.js +130 -0
- package/neural-interface/public/shared/ui-tutorial-steps.js +6 -2
- package/neural-interface/public/shared/ui-update.js +594 -5
- package/neural-interface/public/shared/ui-whatsapp.js +21 -6
- package/neural-interface/public/shared/ui-whiteboard.js +12 -0
- package/neural-interface/public/shared/ui-worker-end.js +245 -0
- package/neural-interface/public/shared/ui-workspaces.js +41 -27
- package/neural-interface/public/shared/whatsapp/wa-view.js +51 -8
- package/neural-interface/public/shared/worker-end.js +253 -0
- package/neural-interface/public/sw.js +2 -2
- package/neural-interface/public/variant/2d/main.js +10 -1
- package/neural-interface/public/variant/3d/main.js +10 -1
- package/neural-interface/public/vendor/codemirror/chunks/apl-CSDKT72Z.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/asciiarmor-JYYPU5BM.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/asn1-NRWCZDA6.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/asterisk-5OU6D4ZQ.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/brainfuck-ID4N4SYC.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/chunk-4RZWGCS2.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/chunk-ACTJM3JR.js +11 -0
- package/neural-interface/public/vendor/codemirror/chunks/chunk-S6BMAXLK.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/chunk-TO2RDOHQ.js +3 -0
- package/neural-interface/public/vendor/codemirror/chunks/chunk-US2GMZZQ.js +13 -0
- package/neural-interface/public/vendor/codemirror/chunks/chunk-WYYO5ZAJ.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/chunk-XALZTDJC.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/chunk-YRNUTRDV.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/chunk-Z3QOKH62.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/clike-OWQBSOGL.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/clojure-KV75UQWC.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/cmake-GTEP5Y3C.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/cobol-WCZ7QEGJ.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/coffeescript-TGLBOT6B.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/commonlisp-WVVI432V.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/crystal-S6R6HLKQ.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/css-PDA2RINP.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/cypher-HL5QPL4C.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/d-HHJH2HAJ.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/diff-MN3A35BY.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-25SQLBVS.js +3 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-27ZGGJJS.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-4E52OJIF.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-6FT73XDC.js +2 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-BE6YGRMW.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-CJSGKW2O.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-CRPH2WLI.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-CTG7JEQN.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-I3FEVELX.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-IM5BBSWZ.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-KERUKTIF.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-LXTMWZAM.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-MABASSET.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-MJVBUZLL.js +3 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-MNB3WZVU.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-QYWXOE3U.js +7 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-TEBOFWLZ.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-UNXFROM3.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-XL2SLQO6.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-XO42Y3AP.js +6 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-Y2WWDWJK.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-YFEZKWMU.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-ZY7SWQO6.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dockerfile-3Y4R4NCH.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dtd-5343GJ4P.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dylan-FI2CXSW2.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/ebnf-GNQ77YWA.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/ecl-MRPUAE2L.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/eiffel-DZROES27.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/elm-4KICRFKQ.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/erlang-3IHYC263.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/factor-C6D3E5UK.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/fcl-VYTWUJMI.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/forth-ZBO2EH5M.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/fortran-5NDCRWIK.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/gas-SKRMOXFP.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/gherkin-L45YXZLW.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/groovy-GLPTLN44.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/haskell-E7NF7P4Y.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/haxe-K2NVDOB6.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/http-Q6DRQIJB.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/idl-MZYNJVLN.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/javascript-6YRYK2HR.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/julia-HXEL2T5G.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/livescript-MV7CI2JZ.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/lua-E36NHOOD.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/mathematica-3XOZZCMC.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/mbox-ODYGFKS7.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/mirc-WB43AWF5.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/mllike-ZT6ET7ZH.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/modelica-LLMZPFGA.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/mscgen-2OPNNYFH.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/mumps-6P3HWJH6.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/nginx-TCGKQTRW.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/nsis-ATIGIXTJ.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/ntriples-5PLBI5OV.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/octave-QF2S3NL6.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/oz-JFIVFFH2.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/pascal-JG6SW76V.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/perl-4B3IWVVO.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/pig-ABEBH4MI.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/powershell-H32XE7TL.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/properties-NN644MMA.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/protobuf-YKWQK2J3.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/pug-RIYXEKFW.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/puppet-JZQ5WPEV.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/python-EBCGGHAC.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/q-5UMH2YQY.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/r-7XMGG453.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/rpm-IEV7TSLR.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/ruby-T4SPFPNM.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/sas-GEDX67ET.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/scheme-FTLOWKPC.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/shell-HUFRKFSJ.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/sieve-P5QUDVYO.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/smalltalk-2SORJNQI.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/solr-M22TGAR5.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/sparql-4M3KL752.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/spreadsheet-3K23LA4N.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/sql-VHRFMUBI.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/stex-QZDAQUY2.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/stylus-7ZDKAKBD.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/swift-WCLCLGMR.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/tcl-JLMU6WOK.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/textile-4CHDKZ4T.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/tiddlywiki-DKMCZZQ4.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/tiki-F4JIG36Q.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/toml-BHMHTIM2.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/troff-YQ2KV5NT.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/ttcn-ZHRCVFJY.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/ttcn-cfg-KJOVTY4X.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/turtle-535P5N6I.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/vb-76UMQHOT.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/vbscript-CGNDTK6Z.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/velocity-VSJDO5T5.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/verilog-4T6ETXIP.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/vhdl-T7FNAXUN.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/webidl-P6MJSJ6J.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/xquery-UOVILRGN.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/yacas-CV2APROL.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/z80-3KFNVM4M.js +1 -0
- package/neural-interface/public/vendor/codemirror/codemirror-6.43.14-75f05e67.min.js +2 -0
- package/neural-interface/public/vendor/codemirror/manifest.json +60 -0
- package/neural-interface/scripts/build-vendor.mjs +78 -2
- package/neural-interface/scripts/vendor-codemirror-entry.js +121 -0
- package/neural-interface/server.js +2085 -1629
- package/neural-interface/templates/rulesets/README.md +1 -1
- package/package.json +5 -8
- package/setup.js +53 -15
- package/benchmarks/browser-live-results.json +0 -79
- package/benchmarks/browser-v2-results.json +0 -258
- package/neural-interface/lib/codex-capabilities.d.ts +0 -41
- package/neural-interface/public/category-logos/claude.svg +0 -1
- package/neural-interface/public/category-logos/gemini-color.svg +0 -1
- package/neural-interface/public/category-logos/openai.svg +0 -1
- package/neural-interface/public/category-logos/synabun.png +0 -0
- package/neural-interface/public/claude.svg +0 -1
- package/neural-interface/public/gallery.svg +0 -6
- package/neural-interface/public/leonardoai.svg +0 -1
- package/neural-interface/public/mcp.svg +0 -4
- package/neural-interface/public/opencode-logo-dark-square.svg +0 -1
- package/neural-interface/public/pin-svgrepo-com.svg +0 -25
- package/neural-interface/public/sessionicon.svg +0 -8
- package/neural-interface/public/toolbar-album.svg +0 -8
- package/neural-interface/public/toolbar-question.svg +0 -6
- package/neural-interface/public/toolbar-sessions.svg +0 -6
- package/neural-interface/scripts/codex-protocol-fixture.mjs +0 -83
- package/skills/synabun/synabunicon.png +0 -0
|
@@ -0,0 +1,529 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════
|
|
2
|
+
// SynaBun — Git for Windows: fetch, check, unpack, put in place
|
|
3
|
+
// ═══════════════════════════════════════════
|
|
4
|
+
//
|
|
5
|
+
// How the pinned Git for Windows (pins.js) gets onto a disk. One sequence, used
|
|
6
|
+
// by the running application on a Windows npm install (manager.js, into the
|
|
7
|
+
// data home) and, from the unpacking on, by an application build
|
|
8
|
+
// (packaging/lib/git-bash.mjs, into the Windows payload):
|
|
9
|
+
//
|
|
10
|
+
// download the release file, over HTTPS and nothing else (every
|
|
11
|
+
// redirect too), stopped at the pinned size, compared with
|
|
12
|
+
// the pinned SHA-256 (cloudflared/install.js downloadPinned).
|
|
13
|
+
// A file that does not match is removed: nothing of it is
|
|
14
|
+
// ever unpacked
|
|
15
|
+
// extractArchive the .tar.bz2 read by this application itself (bzip2.js,
|
|
16
|
+
// then the tar reader below), never by a tar program: what a
|
|
17
|
+
// Windows 10 tar.exe can read differs from build to build.
|
|
18
|
+
// Every name is checked before it is used as a path; links
|
|
19
|
+
// other than the pinned `skip` ones are refused
|
|
20
|
+
// treeDigest what was unpacked, compared with the pinned tree: its
|
|
21
|
+
// number of files, their bytes, and one SHA-256 over every
|
|
22
|
+
// file's path, size and SHA-256
|
|
23
|
+
// installGitBash all of it in a work folder of its own beside the
|
|
24
|
+
// destination, the license and source files and the marker
|
|
25
|
+
// added, then one rename: the destination holds the whole
|
|
26
|
+
// checked tree or nothing
|
|
27
|
+
//
|
|
28
|
+
// A work folder an earlier attempt left (a download stopped, a server that was
|
|
29
|
+
// closed while it unpacked) is removed by the next attempt (sweepStaleWork):
|
|
30
|
+
// every attempt can be repeated. The unpacking and the digest run in a worker
|
|
31
|
+
// thread (extract-worker.js) when the server does it, so its event loop never
|
|
32
|
+
// waits for them.
|
|
33
|
+
//
|
|
34
|
+
// Only node: modules and this application's own files here (see pins.js).
|
|
35
|
+
|
|
36
|
+
import { createHash, randomBytes } from 'node:crypto';
|
|
37
|
+
import {
|
|
38
|
+
closeSync, constants, copyFileSync, existsSync, linkSync, lstatSync, mkdirSync, openSync, readdirSync, readFileSync,
|
|
39
|
+
renameSync, rmSync, statSync, writeFileSync, writeSync,
|
|
40
|
+
} from 'node:fs';
|
|
41
|
+
import { dirname, join } from 'node:path';
|
|
42
|
+
import { Worker } from 'node:worker_threads';
|
|
43
|
+
import { downloadPinned, ownFolder, sweepStaleWork } from '../cloudflared/install.js';
|
|
44
|
+
import { bunzip2Blocks } from './bzip2.js';
|
|
45
|
+
import { ADDED_FILES, inTree, isTreePath, licenseText, MARKER, sourceText } from './pins.js';
|
|
46
|
+
|
|
47
|
+
/** What can go wrong, by the name the page shows a sentence for. */
|
|
48
|
+
export const GIT_BASH_ERROR_CODES = Object.freeze([
|
|
49
|
+
'unsupported', // there is no Git for Windows for this system (not Windows, or not pinned for its processor)
|
|
50
|
+
'offline', // the download server was not reached
|
|
51
|
+
'http', // it answered, but not with the file
|
|
52
|
+
'interrupted', // the transfer ended before the file did
|
|
53
|
+
'timeout', // nothing arrived for too long
|
|
54
|
+
'checksum', // what arrived, or what was unpacked, is not the pinned file
|
|
55
|
+
'unpack', // the archive could not be read, or holds something it must not
|
|
56
|
+
'disk', // the data home could not be written (no room, no permission)
|
|
57
|
+
'cancelled', // stopped on request
|
|
58
|
+
]);
|
|
59
|
+
|
|
60
|
+
export class GitBashError extends Error {
|
|
61
|
+
/** @param {string} code one of GIT_BASH_ERROR_CODES */
|
|
62
|
+
constructor(code, message, { cause, detail } = {}) {
|
|
63
|
+
super(message, cause ? { cause } : undefined);
|
|
64
|
+
this.name = 'GitBashError';
|
|
65
|
+
this.code = GIT_BASH_ERROR_CODES.includes(code) ? code : 'disk';
|
|
66
|
+
if (detail) this.detail = detail;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const WORK_PREFIX = '.work-';
|
|
71
|
+
const DISK_CODES = new Set(['ENOSPC', 'EACCES', 'EPERM', 'EROFS', 'EDQUOT', 'EMFILE', 'ENFILE', 'ENOTDIR', 'EISDIR', 'EBUSY', 'ENAMETOOLONG']);
|
|
72
|
+
const codeOf = (error) => error?.code || error?.cause?.code || '';
|
|
73
|
+
|
|
74
|
+
/** Any failure as a GitBashError: what it says it is (a download's, the reader's), or `fallback`. */
|
|
75
|
+
export function asGitBashError(error, fallback = 'disk', message = 'Git for Windows could not be put in place.') {
|
|
76
|
+
if (error instanceof GitBashError) return error;
|
|
77
|
+
if (error?.name === 'CloudflaredError' && GIT_BASH_ERROR_CODES.includes(error.code)) return new GitBashError(error.code, error.message, { cause: error, detail: error.detail });
|
|
78
|
+
if (error?.name === 'Bzip2Error') return new GitBashError('unpack', error.message, { cause: error });
|
|
79
|
+
const code = codeOf(error);
|
|
80
|
+
if (DISK_CODES.has(code)) return new GitBashError('disk', `The data folder could not be written (${code}).`, { cause: error, detail: code });
|
|
81
|
+
return new GitBashError(fallback, message, { cause: error, detail: code || error?.message || String(error) });
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const cancelled = () => new GitBashError('cancelled', 'Stopped.');
|
|
85
|
+
|
|
86
|
+
// ── The tar reader ──────────────────────────────────────────────────────────
|
|
87
|
+
|
|
88
|
+
const BLOCK = 512;
|
|
89
|
+
const text = (block, from, to) => {
|
|
90
|
+
const end = block.indexOf(0, from);
|
|
91
|
+
return block.toString('utf8', from, end === -1 || end > to ? to : end);
|
|
92
|
+
};
|
|
93
|
+
const octal = (block, from, to) => {
|
|
94
|
+
if (block[from] & 0x80) { // base-256, for sizes octal cannot hold
|
|
95
|
+
let value = 0;
|
|
96
|
+
for (let i = from + 1; i < to; i++) value = value * 256 + block[i];
|
|
97
|
+
return value;
|
|
98
|
+
}
|
|
99
|
+
const field = text(block, from, to).trim();
|
|
100
|
+
return field ? parseInt(field, 8) : 0;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
/** One header block: its name, size, type, link and mode, checksum checked. */
|
|
104
|
+
function readHeader(block) {
|
|
105
|
+
let sum = 0;
|
|
106
|
+
for (let i = 0; i < BLOCK; i++) sum += i >= 148 && i < 156 ? 32 : block[i];
|
|
107
|
+
if (octal(block, 148, 156) !== sum) throw new GitBashError('unpack', 'The archive is not a tar archive (a header checksum does not match).');
|
|
108
|
+
const size = octal(block, 124, 136);
|
|
109
|
+
if (!Number.isSafeInteger(size) || size < 0) throw new GitBashError('unpack', 'The archive names an entry without a size.');
|
|
110
|
+
// POSIX ustar keeps a prefix of the name at 345; the GNU format keeps other fields there.
|
|
111
|
+
const posixMagic = block.toString('latin1', 257, 263) === 'ustar\0';
|
|
112
|
+
const prefix = posixMagic ? text(block, 345, 500) : '';
|
|
113
|
+
return {
|
|
114
|
+
name: `${prefix ? `${prefix}/` : ''}${text(block, 0, 100)}`,
|
|
115
|
+
size,
|
|
116
|
+
type: String.fromCharCode(block[156] || 0x30),
|
|
117
|
+
link: text(block, 157, 257),
|
|
118
|
+
mode: octal(block, 100, 108),
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** The records of a PAX extended header: `path`, `linkpath`, `size` are the ones that change an entry. */
|
|
123
|
+
function paxRecords(data) {
|
|
124
|
+
const out = {};
|
|
125
|
+
let at = 0;
|
|
126
|
+
while (at < data.length) {
|
|
127
|
+
const space = data.indexOf(0x20, at);
|
|
128
|
+
if (space === -1) break;
|
|
129
|
+
const length = parseInt(data.toString('latin1', at, space), 10);
|
|
130
|
+
if (!Number.isInteger(length) || length <= 0 || at + length > data.length) throw new GitBashError('unpack', 'The archive has an extended header it cannot read.');
|
|
131
|
+
const record = data.toString('utf8', space + 1, at + length - 1);
|
|
132
|
+
const equals = record.indexOf('=');
|
|
133
|
+
if (equals > 0) out[record.slice(0, equals)] = record.slice(equals + 1);
|
|
134
|
+
at += length;
|
|
135
|
+
}
|
|
136
|
+
return out;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const RESERVED = /^(con|prn|aux|nul|com[0-9]|lpt[0-9])(\.|$)/i;
|
|
140
|
+
|
|
141
|
+
/** A name of the archive as a path inside the tree, or a refusal: nothing outside it, nothing Windows reads otherwise. */
|
|
142
|
+
function treePathOf(name) {
|
|
143
|
+
const clean = String(name).replace(/^(\.\/)+/, '').replace(/\/+$/, '');
|
|
144
|
+
if (!isTreePath(clean) || clean.split('/').some((part) => RESERVED.test(part))) {
|
|
145
|
+
throw new GitBashError('unpack', `The archive names a path that has no place in the tree (${JSON.stringify(String(name).slice(0, 120))}).`);
|
|
146
|
+
}
|
|
147
|
+
return clean;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Unpack the .tar.bz2 at `archive` into `destination` (a new, empty folder of the caller's). Regular files,
|
|
152
|
+
* folders and hard links are written; an entry whose path is in `skip` is left out, and any other link or
|
|
153
|
+
* special file stops the read. Synchronous: the server runs it in a worker thread (extract-worker.js).
|
|
154
|
+
*
|
|
155
|
+
* @param {object} o
|
|
156
|
+
* @param {string} o.archive
|
|
157
|
+
* @param {string} o.destination
|
|
158
|
+
* @param {string[]} [o.skip] paths of the tree to leave out (the pin's)
|
|
159
|
+
* @param {() => boolean} [o.stopped] asked between blocks: true stops the read (`cancelled`)
|
|
160
|
+
* @param {(progress: { consumed: number, total: number }) => void} [o.onProgress]
|
|
161
|
+
* @param {string} [o.platform] the separators to write in (process.platform)
|
|
162
|
+
* @returns {{ files: number, folders: number, links: number, skipped: string[] }}
|
|
163
|
+
*/
|
|
164
|
+
export function extractArchive({ archive, destination, skip = [], stopped = () => false, onProgress = () => {}, platform = process.platform }) {
|
|
165
|
+
const leaveOut = new Set(skip);
|
|
166
|
+
const written = new Set(); // tree paths of the regular files written (a hard link names one of them)
|
|
167
|
+
const made = new Set(['']);
|
|
168
|
+
const result = { files: 0, folders: 0, links: 0, skipped: [] };
|
|
169
|
+
const folder = (rel) => {
|
|
170
|
+
if (made.has(rel)) return;
|
|
171
|
+
mkdirSync(inTree(destination, rel, platform), { recursive: true });
|
|
172
|
+
for (let at = rel; at && !made.has(at); at = at.includes('/') ? at.slice(0, at.lastIndexOf('/')) : '') made.add(at);
|
|
173
|
+
};
|
|
174
|
+
const parentOf = (rel) => (rel.includes('/') ? rel.slice(0, rel.lastIndexOf('/')) : '');
|
|
175
|
+
|
|
176
|
+
let input;
|
|
177
|
+
try { input = readFileSync(archive); } catch (error) { throw asGitBashError(error, 'unpack', 'The archive could not be read.'); }
|
|
178
|
+
|
|
179
|
+
let pending = Buffer.alloc(0);
|
|
180
|
+
let mode = 'header'; // header | data | skip | meta | end
|
|
181
|
+
let left = 0; // bytes of the current entry's data still to come
|
|
182
|
+
let padding = 0;
|
|
183
|
+
let fd = null;
|
|
184
|
+
let meta = null; // { kind: 'L' | 'K' | 'x', parts: Buffer[] } while a GNU long name or a PAX header is read
|
|
185
|
+
let longName = null;
|
|
186
|
+
let longLink = null;
|
|
187
|
+
let pax = null;
|
|
188
|
+
let current = null; // the tree path of the file being written
|
|
189
|
+
const closeFile = () => { if (fd !== null) { closeSync(fd); fd = null; } };
|
|
190
|
+
|
|
191
|
+
const startEntry = (header) => {
|
|
192
|
+
if (header.type === 'L' || header.type === 'K' || header.type === 'x' || header.type === 'g') {
|
|
193
|
+
if (header.size > 1024 * 1024) throw new GitBashError('unpack', 'The archive has an extended header that is too large.');
|
|
194
|
+
meta = { kind: header.type, parts: [] };
|
|
195
|
+
left = header.size;
|
|
196
|
+
padding = (BLOCK - (header.size % BLOCK)) % BLOCK;
|
|
197
|
+
mode = header.size ? 'meta' : 'header';
|
|
198
|
+
if (!header.size) finishMeta();
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
const name = pax?.path ?? longName ?? header.name;
|
|
202
|
+
const link = pax?.linkpath ?? longLink ?? header.link;
|
|
203
|
+
const size = pax?.size !== undefined ? Number(pax.size) : header.size;
|
|
204
|
+
longName = null; longLink = null; pax = null;
|
|
205
|
+
if (!Number.isSafeInteger(size) || size < 0) throw new GitBashError('unpack', 'The archive names an entry without a size.');
|
|
206
|
+
padding = (BLOCK - (size % BLOCK)) % BLOCK;
|
|
207
|
+
left = size;
|
|
208
|
+
const rel = treePathOf(name);
|
|
209
|
+
if (leaveOut.has(rel)) {
|
|
210
|
+
result.skipped.push(rel);
|
|
211
|
+
mode = size + padding ? 'skip' : 'header';
|
|
212
|
+
left = size + padding;
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
const type = header.type;
|
|
216
|
+
if (type === '5' || ((type === '0' || type === '\0') && String(name).endsWith('/'))) {
|
|
217
|
+
folder(rel);
|
|
218
|
+
result.folders++;
|
|
219
|
+
mode = size + padding ? 'skip' : 'header';
|
|
220
|
+
left = size + padding;
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
if (type === '0' || type === '\0' || type === '7') {
|
|
224
|
+
if (written.has(rel)) throw new GitBashError('unpack', `The archive holds ${rel} twice.`);
|
|
225
|
+
folder(parentOf(rel));
|
|
226
|
+
const path = inTree(destination, rel, platform);
|
|
227
|
+
fd = openSync(path, 'wx', platform === 'win32' ? undefined : ((header.mode & 0o111) ? 0o755 : 0o644));
|
|
228
|
+
current = rel;
|
|
229
|
+
written.add(rel);
|
|
230
|
+
result.files++;
|
|
231
|
+
if (!size) { closeFile(); mode = padding ? 'skip' : 'header'; left = padding; } else mode = 'data';
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
if (type === '1') {
|
|
235
|
+
const target = treePathOf(link);
|
|
236
|
+
if (!written.has(target)) throw new GitBashError('unpack', `The archive links ${rel} to ${target}, which it does not hold.`);
|
|
237
|
+
if (written.has(rel)) throw new GitBashError('unpack', `The archive holds ${rel} twice.`);
|
|
238
|
+
folder(parentOf(rel));
|
|
239
|
+
const from = inTree(destination, target, platform);
|
|
240
|
+
const to = inTree(destination, rel, platform);
|
|
241
|
+
// A file system without hard links (FAT, exFAT) gets a copy: the same bytes either way.
|
|
242
|
+
try { linkSync(from, to); } catch { copyFileSync(from, to, constants.COPYFILE_EXCL); }
|
|
243
|
+
written.add(rel);
|
|
244
|
+
result.links++;
|
|
245
|
+
mode = size + padding ? 'skip' : 'header';
|
|
246
|
+
left = size + padding;
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
if (type === '2') throw new GitBashError('unpack', `The archive has a link ${rel} the pins do not leave out.`);
|
|
250
|
+
throw new GitBashError('unpack', `The archive has an entry of a kind that has no place in the tree (${rel}, type ${JSON.stringify(type)}).`);
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
function finishMeta() {
|
|
254
|
+
const data = Buffer.concat(meta.parts);
|
|
255
|
+
if (meta.kind === 'L') longName = text(data, 0, data.length);
|
|
256
|
+
else if (meta.kind === 'K') longLink = text(data, 0, data.length);
|
|
257
|
+
else if (meta.kind === 'x') pax = paxRecords(data);
|
|
258
|
+
meta = null;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
try {
|
|
262
|
+
for (const chunk of bunzip2Blocks(input, { progress: (consumed, total) => onProgress({ consumed, total }) })) {
|
|
263
|
+
if (stopped()) throw cancelled();
|
|
264
|
+
pending = pending.length ? Buffer.concat([pending, chunk]) : chunk;
|
|
265
|
+
let at = 0;
|
|
266
|
+
while (at < pending.length && mode !== 'end') {
|
|
267
|
+
if (mode === 'header') {
|
|
268
|
+
if (pending.length - at < BLOCK) break;
|
|
269
|
+
const block = pending.subarray(at, at + BLOCK);
|
|
270
|
+
at += BLOCK;
|
|
271
|
+
if (block.every((byte) => byte === 0)) { mode = 'end'; break; }
|
|
272
|
+
startEntry(readHeader(block));
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
const take = Math.min(left, pending.length - at);
|
|
276
|
+
if (mode === 'data') {
|
|
277
|
+
let off = at;
|
|
278
|
+
while (off < at + take) off += writeSync(fd, pending, off, at + take - off);
|
|
279
|
+
left -= take;
|
|
280
|
+
at += take;
|
|
281
|
+
if (left === 0) { closeFile(); current = null; mode = padding ? 'skip' : 'header'; left = padding; }
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
if (mode === 'meta') {
|
|
285
|
+
meta.parts.push(Buffer.from(pending.subarray(at, at + take)));
|
|
286
|
+
left -= take;
|
|
287
|
+
at += take;
|
|
288
|
+
if (left === 0) { finishMeta(); mode = padding ? 'skip' : 'header'; left = padding; }
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
// skip: what is left of an entry that is not written, or a padding
|
|
292
|
+
left -= take;
|
|
293
|
+
at += take;
|
|
294
|
+
if (left === 0) mode = 'header';
|
|
295
|
+
}
|
|
296
|
+
pending = at < pending.length ? Buffer.from(pending.subarray(at)) : Buffer.alloc(0);
|
|
297
|
+
if (mode === 'end') break;
|
|
298
|
+
}
|
|
299
|
+
if (mode !== 'end') throw new GitBashError('unpack', current ? `The archive ends inside ${current}.` : 'The archive ends before its last entry.');
|
|
300
|
+
return result;
|
|
301
|
+
} catch (error) {
|
|
302
|
+
throw asGitBashError(error, 'unpack', 'The archive could not be unpacked.');
|
|
303
|
+
} finally {
|
|
304
|
+
closeFile();
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// ── What was unpacked ───────────────────────────────────────────────────────
|
|
309
|
+
|
|
310
|
+
/** SHA-256 of a file, read whole (the tree's files are at most a few tens of MB). */
|
|
311
|
+
const sha256Of = (path) => createHash('sha256').update(readFileSync(path)).digest('hex');
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* What a tree is: every file under `root` (forward-slash paths, JavaScript string order), its count, its bytes,
|
|
315
|
+
* and the SHA-256 over one line `path NUL size NUL sha256 LF` per file. The names of `exclude` at the top are
|
|
316
|
+
* left out (what SynaBun adds). A link, or anything that is neither a file nor a folder, is a refusal: the
|
|
317
|
+
* pinned tree has none. Synchronous (a worker thread runs it for the server).
|
|
318
|
+
* @returns {{ files: number, bytes: number, sha256: string }}
|
|
319
|
+
*/
|
|
320
|
+
export function treeDigest(root, { exclude = ADDED_FILES, stopped = () => false } = {}) {
|
|
321
|
+
const files = [];
|
|
322
|
+
const walk = (rel) => {
|
|
323
|
+
for (const entry of readdirSync(rel ? join(root, ...rel.split('/')) : root, { withFileTypes: true })) {
|
|
324
|
+
const path = rel ? `${rel}/${entry.name}` : entry.name;
|
|
325
|
+
if (!rel && exclude.includes(entry.name)) continue;
|
|
326
|
+
if (entry.isSymbolicLink()) throw new GitBashError('checksum', `The tree holds a link (${path}); the pinned one has none.`);
|
|
327
|
+
if (entry.isDirectory()) walk(path);
|
|
328
|
+
else if (entry.isFile()) files.push(path);
|
|
329
|
+
else throw new GitBashError('checksum', `The tree holds something that is not a file (${path}).`);
|
|
330
|
+
}
|
|
331
|
+
};
|
|
332
|
+
walk('');
|
|
333
|
+
files.sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
|
|
334
|
+
const hash = createHash('sha256');
|
|
335
|
+
let bytes = 0;
|
|
336
|
+
for (const rel of files) {
|
|
337
|
+
if (stopped()) throw cancelled();
|
|
338
|
+
const data = readFileSync(join(root, ...rel.split('/')));
|
|
339
|
+
bytes += data.length;
|
|
340
|
+
hash.update(`${rel}\0${data.length}\0${createHash('sha256').update(data).digest('hex')}\n`);
|
|
341
|
+
}
|
|
342
|
+
return { files: files.length, bytes, sha256: hash.digest('hex') };
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/** Is a digest the pinned tree? null when it is, otherwise what differs, as a sentence. */
|
|
346
|
+
export function treeMismatch(digest, pin) {
|
|
347
|
+
if (digest.files !== pin.tree.files) return `${digest.files} files, the pinned tree has ${pin.tree.files}`;
|
|
348
|
+
if (digest.bytes !== pin.tree.bytes) return `${digest.bytes} bytes, the pinned tree has ${pin.tree.bytes}`;
|
|
349
|
+
if (digest.sha256 !== pin.tree.sha256) return 'files with other contents than the pinned tree';
|
|
350
|
+
return null;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/** The files SynaBun starts (the pin's `key`), each its pinned size and SHA-256? null when so, else the first that is not. */
|
|
354
|
+
export function keyFilesMismatch(root, pin, { platform = process.platform } = {}) {
|
|
355
|
+
for (const [rel, want] of Object.entries(pin.keyFiles)) {
|
|
356
|
+
const path = inTree(root, rel, platform);
|
|
357
|
+
let info;
|
|
358
|
+
try { info = lstatSync(path); } catch { return `${rel} is missing`; }
|
|
359
|
+
if (info.isSymbolicLink() || !info.isFile()) return `${rel} is not a file`;
|
|
360
|
+
if (info.size !== want.size) return `${rel} has ${info.size} bytes, the pinned one ${want.size}`;
|
|
361
|
+
if (sha256Of(path) !== want.sha256) return `${rel} is not the pinned file`;
|
|
362
|
+
}
|
|
363
|
+
return null;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/** The marker of a copy, read; null when there is none or it cannot be read. */
|
|
367
|
+
export function readMarker(root) {
|
|
368
|
+
try {
|
|
369
|
+
const marker = JSON.parse(readFileSync(join(root, MARKER), 'utf8'));
|
|
370
|
+
return marker && typeof marker === 'object' ? marker : null;
|
|
371
|
+
} catch { return null; }
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Is the copy at `root` the pinned one, as far as a look that costs nothing can tell? Its marker names this very
|
|
376
|
+
* release file and tree (the marker is written only after the whole tree passed its digest), and the files
|
|
377
|
+
* SynaBun starts are there, each the pinned file. `{ ok, reason }`, `reason` one of missing, marker, key.
|
|
378
|
+
*/
|
|
379
|
+
export function checkInstalled(root, pin, { platform = process.platform } = {}) {
|
|
380
|
+
try {
|
|
381
|
+
if (!statSync(root).isDirectory()) return { ok: false, reason: 'missing' };
|
|
382
|
+
} catch { return { ok: false, reason: 'missing' }; }
|
|
383
|
+
const marker = readMarker(root);
|
|
384
|
+
if (!marker || marker.synabunGit !== 1 || marker.version !== pin.version || marker.sha256 !== pin.sha256 || marker.tree?.sha256 !== pin.tree.sha256) {
|
|
385
|
+
return { ok: false, reason: 'marker' };
|
|
386
|
+
}
|
|
387
|
+
const key = keyFilesMismatch(root, pin, { platform });
|
|
388
|
+
return key ? { ok: false, reason: 'key', detail: key } : { ok: true, reason: null };
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/** The license, source and marker files SynaBun adds at the top of its copy (after the tree passed its digest). */
|
|
392
|
+
export function writeAddedFiles(root, { pins, pin, source, digest, now = () => new Date().toISOString(), licensesDir } = {}) {
|
|
393
|
+
for (const name of ADDED_FILES) {
|
|
394
|
+
if (existsSync(join(root, name))) throw new GitBashError('unpack', `The archive holds ${name}, a name SynaBun adds itself: the pins need a look.`);
|
|
395
|
+
}
|
|
396
|
+
writeFileSync(join(root, 'LICENSE.txt'), licenseText(licensesDir));
|
|
397
|
+
writeFileSync(join(root, 'SOURCE.txt'), sourceText(pins, pin));
|
|
398
|
+
writeFileSync(join(root, MARKER), `${JSON.stringify({
|
|
399
|
+
synabunGit: 1,
|
|
400
|
+
name: pins.name,
|
|
401
|
+
version: pin.version,
|
|
402
|
+
tag: pin.tag,
|
|
403
|
+
file: pin.file,
|
|
404
|
+
sha256: pin.sha256,
|
|
405
|
+
tree: digest,
|
|
406
|
+
source,
|
|
407
|
+
placedAt: now(),
|
|
408
|
+
}, null, 2)}\n`);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Unpack and check, in one go: the tree of `archive` in `destination`, compared with the pin, the added files
|
|
413
|
+
* written. Synchronous: what the worker thread runs, and what a build runs as it is.
|
|
414
|
+
* @returns {{ files: number, folders: number, links: number, skipped: string[], digest: object }}
|
|
415
|
+
*/
|
|
416
|
+
export function unpackAndCheck({ archive, destination, pins, pin, source, stopped, onProgress, onStep = () => {}, platform = process.platform, licensesDir }) {
|
|
417
|
+
onStep('extract');
|
|
418
|
+
const unpacked = extractArchive({ archive, destination, skip: pin.skip, stopped, onProgress, platform });
|
|
419
|
+
// The tree's empty folders (MSYS2's /dev/shm, /dev/mqueue): there whatever the archive held of them.
|
|
420
|
+
for (const rel of pin.folders || []) mkdirSync(inTree(destination, rel, platform), { recursive: true });
|
|
421
|
+
onStep('verify');
|
|
422
|
+
const digest = treeDigest(destination, { stopped });
|
|
423
|
+
const wrong = treeMismatch(digest, pin);
|
|
424
|
+
if (wrong) throw new GitBashError('checksum', `What the archive unpacked to is not the pinned tree (${wrong}). It was removed.`, { detail: wrong });
|
|
425
|
+
const key = keyFilesMismatch(destination, pin, { platform });
|
|
426
|
+
if (key) throw new GitBashError('checksum', `What the archive unpacked to is not the pinned tree (${key}). It was removed.`, { detail: key });
|
|
427
|
+
writeAddedFiles(destination, { pins, pin, source, digest, licensesDir });
|
|
428
|
+
return { ...unpacked, digest };
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* unpackAndCheck in a worker thread: the server's event loop is not held for the minute it takes. `data` is what
|
|
433
|
+
* unpackAndCheck takes, without its functions (it is copied into the thread); a stop on `signal` is seen by the
|
|
434
|
+
* worker between two blocks, which then closes what it has open and answers `cancelled`.
|
|
435
|
+
*/
|
|
436
|
+
export function unpackInWorker(data, { signal, onProgress = () => {}, onStep = () => {} } = {}) {
|
|
437
|
+
return new Promise((resolve, reject) => {
|
|
438
|
+
const flag = new Int32Array(new SharedArrayBuffer(4));
|
|
439
|
+
let worker;
|
|
440
|
+
try {
|
|
441
|
+
worker = new Worker(new URL('./extract-worker.js', import.meta.url), { workerData: { ...data, flag } });
|
|
442
|
+
} catch (error) {
|
|
443
|
+
reject(asGitBashError(error, 'unpack', 'The archive could not be unpacked (no worker).'));
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
const onAbort = () => Atomics.store(flag, 0, 1);
|
|
447
|
+
if (signal?.aborted) onAbort(); else signal?.addEventListener('abort', onAbort, { once: true });
|
|
448
|
+
let settled = false;
|
|
449
|
+
const finish = (fn, value) => {
|
|
450
|
+
if (settled) return;
|
|
451
|
+
settled = true;
|
|
452
|
+
signal?.removeEventListener('abort', onAbort);
|
|
453
|
+
fn(value);
|
|
454
|
+
};
|
|
455
|
+
worker.on('message', (message) => {
|
|
456
|
+
if (message?.type === 'progress') onProgress(message.progress);
|
|
457
|
+
else if (message?.type === 'step') onStep(message.step);
|
|
458
|
+
else if (message?.type === 'done') finish(resolve, message.result);
|
|
459
|
+
else if (message?.type === 'error') finish(reject, new GitBashError(message.code, message.message, { detail: message.detail }));
|
|
460
|
+
});
|
|
461
|
+
worker.on('error', (error) => finish(reject, asGitBashError(error, 'unpack', 'The archive could not be unpacked.')));
|
|
462
|
+
worker.on('exit', (code) => finish(reject, new GitBashError('unpack', `The unpacking stopped (${code}).`)));
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/** Renaming a folder on Windows can meet a file an antivirus scanner holds open for a moment: try again, briefly. */
|
|
467
|
+
async function renameWithRetry(from, to, { tries = 6, wait = (ms) => new Promise((done) => setTimeout(done, ms)) } = {}) {
|
|
468
|
+
for (let attempt = 1; ; attempt++) {
|
|
469
|
+
try { renameSync(from, to); return; } catch (error) {
|
|
470
|
+
if (attempt >= tries || !['EPERM', 'EACCES', 'EBUSY'].includes(error?.code)) throw error;
|
|
471
|
+
await wait(150 * attempt);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Download the pinned Git for Windows and put it at `<root>/<version>/`. Everything happens in a work folder of
|
|
478
|
+
* this attempt inside `root`, removed whatever the outcome; the tree is renamed into place only after the release
|
|
479
|
+
* file matched its SHA-256 and the unpacked tree its digest, so the place holds the whole checked tree or
|
|
480
|
+
* nothing. A copy already there that checks out (another process won) is the result; one that does not is
|
|
481
|
+
* replaced. `root` is SynaBun's own folder (0700 where the system has modes). One attempt at a time per process
|
|
482
|
+
* is the caller's business (manager.js).
|
|
483
|
+
*
|
|
484
|
+
* @param {object} o
|
|
485
|
+
* @param {object} o.pins pins.js readGitBashPins()
|
|
486
|
+
* @param {object} o.pin gitBashPinFor()
|
|
487
|
+
* @param {string} o.root DATA_HOME/runtime/git
|
|
488
|
+
* @param {(step: 'download' | 'extract' | 'verify' | 'place') => void} [o.onStep]
|
|
489
|
+
* @param {(p: { phase: string, received?: number, total: number, consumed?: number }) => void} [o.onProgress]
|
|
490
|
+
* @param {boolean} [o.worker] unpack in a worker thread (true for the server; tests and builds run it inline)
|
|
491
|
+
* @param {Function} [o.unpack] what unpacks (data, { signal, onProgress, onStep }): unpackInWorker or unpackAndCheck
|
|
492
|
+
* @returns {Promise<{ path: string, version: string }>}
|
|
493
|
+
*/
|
|
494
|
+
export async function installGitBash({ pins, pin, root, fetchImpl, signal, onStep = () => {}, onProgress = () => {}, stallMs, worker = true, unpack = null, platform = process.platform, licensesDir }) {
|
|
495
|
+
const directory = join(root, pin.version);
|
|
496
|
+
let work = null;
|
|
497
|
+
try {
|
|
498
|
+
mkdirSync(dirname(root), { recursive: true });
|
|
499
|
+
ownFolder(root);
|
|
500
|
+
sweepStaleWork(root);
|
|
501
|
+
work = join(root, `${WORK_PREFIX}${process.pid}-${randomBytes(6).toString('hex')}`);
|
|
502
|
+
mkdirSync(work, { mode: 0o700 });
|
|
503
|
+
const releaseFile = join(work, 'release.part');
|
|
504
|
+
onStep('download');
|
|
505
|
+
await downloadPinned({ pin, destination: releaseFile, fetchImpl, signal, stallMs, onProgress: ({ received, total }) => onProgress({ phase: 'download', received, total }) });
|
|
506
|
+
if (signal?.aborted) throw cancelled();
|
|
507
|
+
const tree = join(work, 'tree');
|
|
508
|
+
mkdirSync(tree);
|
|
509
|
+
const options = { archive: releaseFile, destination: tree, pins, pin, source: 'downloaded', platform, licensesDir };
|
|
510
|
+
const report = ({ consumed, total }) => onProgress({ phase: 'extract', consumed, total });
|
|
511
|
+
const run = unpack || (worker
|
|
512
|
+
? unpackInWorker
|
|
513
|
+
: (data, o) => unpackAndCheck({ ...data, stopped: () => !!o.signal?.aborted, onProgress: o.onProgress, onStep: o.onStep }));
|
|
514
|
+
await run(options, { signal, onProgress: report, onStep });
|
|
515
|
+
if (signal?.aborted) throw cancelled();
|
|
516
|
+
rmSync(releaseFile, { force: true });
|
|
517
|
+
onStep('place');
|
|
518
|
+
if (existsSync(directory)) {
|
|
519
|
+
if (checkInstalled(directory, pin, { platform }).ok) return { path: directory, version: pin.version };
|
|
520
|
+
rmSync(directory, { recursive: true, force: true, maxRetries: 3 });
|
|
521
|
+
}
|
|
522
|
+
await renameWithRetry(tree, directory);
|
|
523
|
+
return { path: directory, version: pin.version };
|
|
524
|
+
} catch (error) {
|
|
525
|
+
throw asGitBashError(error);
|
|
526
|
+
} finally {
|
|
527
|
+
if (work) { try { rmSync(work, { recursive: true, force: true, maxRetries: 3 }); } catch { /* the next attempt sweeps it */ } }
|
|
528
|
+
}
|
|
529
|
+
}
|