@gloom-sh/terminal 0.19.1 → 0.19.3
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 +7 -1
- package/package.json +2 -1
- package/patches/electrobun@1.18.1.patch +108 -1
- package/src/account/upgrade-dialog.tsx +4 -23
- package/src/api-client/coalesce.ts +118 -0
- package/src/api-client/credit-boards.ts +13 -0
- package/src/api-client/crypto-etf-flows.ts +6 -0
- package/src/api-client/crypto-markets.ts +40 -0
- package/src/api-client/data.ts +201 -14
- package/src/api-client/equity-screener.ts +10 -0
- package/src/api-client/errors.ts +58 -0
- package/src/api-client/gpu.ts +29 -0
- package/src/api-client/index.ts +5 -2
- package/src/api-client/plan-access.ts +10 -1
- package/src/api-client/plan-rules.ts +35 -0
- package/src/api-client/research-activity.ts +1 -32
- package/src/api-client/types.ts +21 -1
- package/src/app/attention-notice.tsx +94 -0
- package/src/app/runtime/startup.ts +3 -1
- package/src/app.tsx +23 -1
- package/src/architecture/core-builtin-allowlist.json +2 -1
- package/src/capabilities/chart-series.ts +2 -1
- package/src/capabilities/factories.ts +1 -0
- package/src/cli/ascii-output.ts +42 -0
- package/src/cli/base-converter.ts +10 -2
- package/src/cli/command-options.ts +16 -1
- package/src/cli/commands/crypto-hints.ts +23 -4
- package/src/cli/commands/market.ts +101 -27
- package/src/cli/commands/news-search.ts +24 -5
- package/src/cli/commands/overview.ts +14 -10
- package/src/cli/commands/search.ts +84 -25
- package/src/cli/commands/system.ts +22 -30
- package/src/cli/commands/ticker.ts +49 -9
- package/src/cli/commands/version.ts +33 -0
- package/src/cli/context.ts +6 -9
- package/src/cli/data-dir.ts +7 -0
- package/src/cli/desktop-pane-shot-api-policy.ts +85 -0
- package/src/cli/desktop-pane-shot-browser.ts +320 -0
- package/src/cli/desktop-pane-shot-routes.ts +26 -1
- package/src/cli/desktop-pane-shot.ts +196 -256
- package/src/cli/early-command.ts +42 -0
- package/src/cli/earnings-calendar.ts +9 -0
- package/src/cli/entry.ts +19 -13
- package/src/cli/errors.ts +12 -2
- package/src/cli/glossary.ts +2 -1
- package/src/cli/help.ts +1 -1
- package/src/cli/history-export.ts +22 -2
- package/src/cli/history-rows.ts +189 -11
- package/src/cli/index.ts +18 -1
- package/src/cli/known-gaps.ts +51 -5
- package/src/cli/listing-arg.ts +63 -10
- package/src/cli/options.ts +1 -1
- package/src/cli/output-file.ts +60 -13
- package/src/cli/pane-functions/catalog.ts +14 -3
- package/src/cli/pane-functions/data.ts +1 -1
- package/src/cli/pane-functions/dom.ts +45 -0
- package/src/cli/pane-functions/freshness.ts +46 -9
- package/src/cli/pane-functions/function-help.ts +17 -9
- package/src/cli/pane-functions/headless.ts +22 -14
- package/src/cli/pane-functions/index.ts +6 -2
- package/src/cli/pane-functions/options.ts +6 -2
- package/src/cli/pane-functions/resolver.ts +27 -5
- package/src/cli/pane-functions/screenshot-assessment.ts +33 -0
- package/src/cli/pane-functions/screenshot-bridge.ts +94 -13
- package/src/cli/pane-functions/screenshot-http.ts +70 -0
- package/src/cli/pane-functions/screenshot-render.ts +19 -5
- package/src/cli/quote-answer.ts +49 -6
- package/src/cli/registry.ts +41 -13
- package/src/cli/result.ts +12 -6
- package/src/cli/ticker-resolution.ts +2 -1
- package/src/cli/types.ts +2 -0
- package/src/components/chart/composite/format.ts +3 -0
- package/src/components/chart-table/header.tsx +1 -1
- package/src/components/chart-table/layout.ts +28 -11
- package/src/components/command-bar/action-runtime.ts +4 -0
- package/src/components/command-bar/commands/direct/index.ts +18 -33
- package/src/components/command-bar/commands/direct/runtime.ts +5 -0
- package/src/components/command-bar/commands/registry.ts +10 -1
- package/src/components/command-bar/routes/root/command-items.ts +9 -4
- package/src/components/command-bar/routes/root/runtime.ts +51 -6
- package/src/components/command-bar/selection-runtime.ts +12 -29
- package/src/components/command-bar/surface/index.tsx +9 -3
- package/src/components/company-logo.tsx +2 -1
- package/src/components/layout/detached-pane-shell.tsx +2 -2
- package/src/components/layout/header.tsx +10 -5
- package/src/components/layout/market-summary.ts +25 -7
- package/src/components/layout/pane/footer/index.tsx +8 -2
- package/src/components/layout/pane/footer/model.ts +73 -6
- package/src/components/layout/pane/header.tsx +2 -2
- package/src/components/layout/pane/status-footer.ts +2 -1
- package/src/components/market-board/index.tsx +3 -2
- package/src/components/onboarding/desks-step.tsx +12 -4
- package/src/components/onboarding/first-run-workspace.ts +81 -4
- package/src/components/onboarding/onboarding-frame.tsx +4 -2
- package/src/components/onboarding/onboarding-keyboard.ts +8 -5
- package/src/components/onboarding/onboarding-wizard.tsx +60 -26
- package/src/components/onboarding/portfolio-step/positions-panel.tsx +7 -2
- package/src/components/onboarding/run-setup-again.ts +24 -0
- package/src/components/onboarding/use-onboarding-progress.ts +33 -15
- package/src/components/onboarding/use-onboarding-upgrade.ts +2 -6
- package/src/components/onboarding/wizard-model.ts +57 -3
- package/src/components/onboarding/wizard-positions.ts +1 -1
- package/src/components/ui/delayed-mark.tsx +56 -0
- package/src/components/ui/display.tsx +16 -5
- package/src/components/ui/external-link.tsx +3 -6
- package/src/components/ui/icon.tsx +16 -3
- package/src/components/ui/logo-mark.tsx +8 -5
- package/src/components/ui/stat-grid.tsx +263 -19
- package/src/core/app-services.ts +11 -2
- package/src/data/app-persistence.ts +9 -7
- package/src/data/config/store/node.ts +11 -5
- package/src/data/config/store/normalize.ts +16 -3
- package/src/data/plugin-cache.ts +15 -0
- package/src/data/plugin-state-store.ts +81 -24
- package/src/data/profile-permissions.ts +47 -0
- package/src/data/resource-store.ts +165 -90
- package/src/data/session-store.ts +31 -8
- package/src/data/sqlite/access.ts +324 -0
- package/src/data/sqlite/checkpointer.ts +285 -0
- package/src/data/sqlite/database.ts +80 -4
- package/src/data/sqlite/retry.ts +50 -9
- package/src/data/ticker-store.ts +57 -23
- package/src/docs/snapshot.json +191 -78
- package/src/i18n/es.ts +18 -2
- package/src/i18n/ja.ts +18 -2
- package/src/i18n/ko.ts +18 -2
- package/src/i18n/zh-cn.ts +18 -2
- package/src/i18n/zh-tw.ts +18 -2
- package/src/layout/desk-keys.ts +4 -1
- package/src/layout/desks.ts +38 -5
- package/src/layout-marketplace/gallery-desktop.tsx +45 -16
- package/src/market-data/batched-reads.ts +16 -0
- package/src/market-data/coordinator/auxiliary.ts +4 -2
- package/src/market-data/coordinator/index.ts +11 -2
- package/src/market-data/futures-board.ts +1 -1
- package/src/market-data/futures-contract.ts +3 -1
- package/src/market-data/market/index-level.ts +6 -4
- package/src/market-data/published-apac-sessions.ts +6 -0
- package/src/market-data/realized-volatility.ts +4 -2
- package/src/news/aggregator.ts +7 -4
- package/src/news/news-model.ts +11 -0
- package/src/news/story-id.ts +22 -0
- package/src/news/ticker-symbols.ts +17 -1
- package/src/notifications/app-notifier.ts +35 -14
- package/src/plugins/auto-update-node.ts +10 -6
- package/src/plugins/builtin/account-management/form-components.tsx +1 -1
- package/src/plugins/builtin/account-management/index.tsx +2 -3
- package/src/plugins/builtin/account-management/model.ts +1 -1
- package/src/plugins/builtin/alerts/cli-forms.ts +15 -1
- package/src/plugins/builtin/alerts/events-pane.tsx +7 -5
- package/src/plugins/builtin/alerts/events.ts +10 -2
- package/src/plugins/builtin/alerts/index.tsx +5 -5
- package/src/plugins/builtin/alerts/research-builder.ts +5 -1
- package/src/plugins/builtin/alerts/research-rules.ts +18 -0
- package/src/plugins/builtin/alerts/targets.ts +4 -1
- package/src/plugins/builtin/attention/headless.ts +1 -1
- package/src/plugins/builtin/attention/index.tsx +2 -4
- package/src/plugins/builtin/attention/listing.ts +1 -1
- package/src/plugins/builtin/attention/series.ts +1 -1
- package/src/plugins/builtin/awards/index.tsx +1 -2
- package/src/plugins/builtin/backtest/client.ts +1 -0
- package/src/plugins/builtin/backtest/index.tsx +1 -2
- package/src/plugins/builtin/backtest/rules.ts +3 -1
- package/src/plugins/builtin/backtest/window.ts +8 -7
- package/src/plugins/builtin/broker-manager/detail.tsx +16 -3
- package/src/plugins/builtin/broker-manager/index.tsx +1 -1
- package/src/plugins/builtin/buildout/cells.tsx +5 -0
- package/src/plugins/builtin/buildout/company/flow.tsx +367 -0
- package/src/plugins/builtin/buildout/company/id.ts +2 -0
- package/src/plugins/builtin/buildout/company/model.ts +247 -0
- package/src/plugins/builtin/buildout/company/pane.tsx +317 -0
- package/src/plugins/builtin/buildout/data-runtime.ts +31 -10
- package/src/plugins/builtin/buildout/facilities.ts +309 -0
- package/src/plugins/builtin/buildout/format.ts +9 -0
- package/src/plugins/builtin/buildout/index.tsx +51 -5
- package/src/plugins/builtin/buildout/model/index.ts +62 -10
- package/src/plugins/builtin/buildout/model/normalizers.ts +5 -0
- package/src/plugins/builtin/buildout/model/types.ts +11 -2
- package/src/plugins/builtin/buildout/pane/index.tsx +91 -20
- package/src/plugins/builtin/buildout/pane/status.tsx +1 -1
- package/src/plugins/builtin/buildout/pane/themes.tsx +110 -0
- package/src/plugins/builtin/buildout/table-model.ts +26 -2
- package/src/plugins/builtin/builtin-plugin-meta.ts +16 -0
- package/src/plugins/builtin/cds/headless.ts +10 -2
- package/src/plugins/builtin/cds/model.ts +7 -3
- package/src/plugins/builtin/central-bank-rates/index.tsx +1 -2
- package/src/plugins/builtin/central-bank-rates/pane.tsx +2 -1
- package/src/plugins/builtin/central-bank-rates/series.ts +2 -3
- package/src/plugins/builtin/changelog/index.tsx +3 -2
- package/src/plugins/builtin/chart-composer/cli-options.ts +16 -0
- package/src/plugins/builtin/chart-composer/headless-schema.ts +10 -0
- package/src/plugins/builtin/chart-composer/headless.ts +88 -21
- package/src/plugins/builtin/chart-composer/index.tsx +2 -3
- package/src/plugins/builtin/chart-composer/pane.tsx +30 -2
- package/src/plugins/builtin/chart-composer/series-expression.ts +3 -2
- package/src/plugins/builtin/chart-composer/session-range.ts +5 -2
- package/src/plugins/builtin/chart-composer/studies.ts +1 -1
- package/src/plugins/builtin/chat/controller/index.ts +1 -1
- package/src/plugins/builtin/chat/controller/messages.ts +15 -4
- package/src/plugins/builtin/chat/ghost-user.ts +8 -0
- package/src/plugins/builtin/chat/index.tsx +5 -5
- package/src/plugins/builtin/clinical-trials/index.tsx +1 -2
- package/src/plugins/builtin/cloud/company-picker.tsx +1 -1
- package/src/plugins/builtin/cloud/index.tsx +1 -2
- package/src/plugins/builtin/cloud/plugin.tsx +6 -4
- package/src/plugins/builtin/cloud/team/module.tsx +1 -1
- package/src/plugins/builtin/cloud/thesis/module.tsx +1 -1
- package/src/plugins/builtin/cloud/trial-offer-status-widget.tsx +24 -1
- package/src/plugins/builtin/cloud/upgrade-status-widget.tsx +39 -15
- package/src/plugins/builtin/comment-letters/index.tsx +1 -2
- package/src/plugins/builtin/commodity-balances/client.ts +4 -1
- package/src/plugins/builtin/commodity-balances/index.tsx +3 -3
- package/src/plugins/builtin/commodity-balances/pane.tsx +2 -2
- package/src/plugins/builtin/congress-trades/aggregates.ts +13 -2
- package/src/plugins/builtin/congress-trades/detail.tsx +20 -4
- package/src/plugins/builtin/congress-trades/headless.ts +2 -0
- package/src/plugins/builtin/congress-trades/model.ts +92 -64
- package/src/plugins/builtin/congress-trades/table.tsx +14 -2
- package/src/plugins/builtin/connections/index.ts +2 -3
- package/src/plugins/builtin/cot/client.ts +28 -2
- package/src/plugins/builtin/cot/headless.ts +4 -1
- package/src/plugins/builtin/cot/index.tsx +5 -4
- package/src/plugins/builtin/cot/model.ts +58 -7
- package/src/plugins/builtin/cot/pane.tsx +6 -2
- package/src/plugins/builtin/cpi/client.ts +6 -5
- package/src/plugins/builtin/cpi/index.tsx +1 -2
- package/src/plugins/builtin/credit-boards/cdx-pane.tsx +2 -1
- package/src/plugins/builtin/credit-boards/client.ts +7 -2
- package/src/plugins/builtin/credit-boards/headless.ts +25 -12
- package/src/plugins/builtin/credit-boards/model.ts +29 -0
- package/src/plugins/builtin/credit-boards/series.ts +3 -4
- package/src/plugins/builtin/credit-boards/sovr-pane.tsx +9 -4
- package/src/plugins/builtin/credit-conditions/client.ts +22 -14
- package/src/plugins/builtin/crypto-board/bitcoin-network.ts +143 -0
- package/src/plugins/builtin/crypto-board/client.ts +5 -5
- package/src/plugins/builtin/crypto-board/headless.ts +4 -3
- package/src/plugins/builtin/crypto-board/index.tsx +4 -2
- package/src/plugins/builtin/crypto-board/series.ts +6 -4
- package/src/plugins/builtin/crypto-etf-flows/client.ts +9 -2
- package/src/plugins/builtin/crypto-etf-flows/headless.ts +61 -6
- package/src/plugins/builtin/crypto-etf-flows/index.tsx +1 -2
- package/src/plugins/builtin/crypto-etf-flows/model.ts +14 -1
- package/src/plugins/builtin/crypto-etf-flows/pane.tsx +8 -5
- package/src/plugins/builtin/debt-maturities/headless.ts +4 -3
- package/src/plugins/builtin/debt-maturities/model.ts +17 -1
- package/src/plugins/builtin/debt-maturities/pane.tsx +18 -4
- package/src/plugins/builtin/debt-maturities/series.ts +3 -3
- package/src/plugins/builtin/debug/index.tsx +1 -1
- package/src/plugins/builtin/dividend-yield/client.ts +32 -2
- package/src/plugins/builtin/dividend-yield/headless.ts +58 -33
- package/src/plugins/builtin/dividend-yield/index.tsx +37 -2
- package/src/plugins/builtin/dividend-yield/pane.tsx +32 -13
- package/src/plugins/builtin/dividend-yield/ranking-reads.ts +73 -0
- package/src/plugins/builtin/dividend-yield/ranking-view.tsx +163 -0
- package/src/plugins/builtin/dividend-yield/ranking.ts +1 -1
- package/src/plugins/builtin/dividend-yield/view.ts +21 -0
- package/src/plugins/builtin/docs/index.tsx +2 -3
- package/src/plugins/builtin/doe/client.ts +6 -5
- package/src/plugins/builtin/doe/headless.ts +16 -3
- package/src/plugins/builtin/doe/index.tsx +5 -4
- package/src/plugins/builtin/doe/prices.ts +2 -3
- package/src/plugins/builtin/doe/series.ts +4 -4
- package/src/plugins/builtin/earnings/board.tsx +2 -1
- package/src/plugins/builtin/earnings/history.tsx +2 -1
- package/src/plugins/builtin/earnings-calls/calendar.tsx +10 -2
- package/src/plugins/builtin/earnings-calls/data.ts +19 -11
- package/src/plugins/builtin/earnings-calls/pane.tsx +27 -14
- package/src/plugins/builtin/earnings-ripple/client.ts +32 -7
- package/src/plugins/builtin/earnings-ripple/index.tsx +1 -2
- package/src/plugins/builtin/econ/calendar-model.ts +29 -6
- package/src/plugins/builtin/econ/detail-view.tsx +16 -2
- package/src/plugins/builtin/econ-statistics/index.tsx +1 -2
- package/src/plugins/builtin/equity-screener/client.ts +1 -1
- package/src/plugins/builtin/equity-screener/headless.ts +1 -0
- package/src/plugins/builtin/equity-screener/ideas.ts +30 -1
- package/src/plugins/builtin/equity-screener/index.tsx +1 -2
- package/src/plugins/builtin/equity-screener/model.ts +28 -4
- package/src/plugins/builtin/equity-screener/pane.tsx +5 -0
- package/src/plugins/builtin/equity-screener/screen-check.ts +23 -1
- package/src/plugins/builtin/estimate-revisions/index.tsx +1 -2
- package/src/plugins/builtin/executives/data.ts +6 -1
- package/src/plugins/builtin/executives/pane.tsx +9 -21
- package/src/plugins/builtin/exposure/holdings.ts +35 -13
- package/src/plugins/builtin/exposure/index.tsx +1 -2
- package/src/plugins/builtin/fear-greed/cache.ts +4 -0
- package/src/plugins/builtin/fear-greed/cli.ts +44 -0
- package/src/plugins/builtin/fear-greed/index.tsx +5 -53
- package/src/plugins/builtin/fear-greed/pane.tsx +2 -1
- package/src/plugins/builtin/fear-greed/series.ts +6 -4
- package/src/plugins/builtin/fixed-income/index.tsx +1 -2
- package/src/plugins/builtin/futures/board-reads.ts +25 -0
- package/src/plugins/builtin/futures/coalesced-bump.ts +44 -0
- package/src/plugins/builtin/futures/index.tsx +15 -0
- package/src/plugins/builtin/futures/model.ts +14 -0
- package/src/plugins/builtin/futures/use-board-terms.ts +22 -15
- package/src/plugins/builtin/futures/use-front-returns.ts +20 -14
- package/src/plugins/builtin/futures-curve/headless.ts +6 -1
- package/src/plugins/builtin/futures-curve/index.tsx +1 -2
- package/src/plugins/builtin/fx-matrix/index.tsx +2 -2
- package/src/plugins/builtin/gpu/board.tsx +51 -12
- package/src/plugins/builtin/gpu/client.ts +19 -3
- package/src/plugins/builtin/gpu/equities.tsx +5 -2
- package/src/plugins/builtin/gpu/headless.ts +14 -2
- package/src/plugins/builtin/gpu/index.tsx +1 -2
- package/src/plugins/builtin/gpu/model.ts +20 -2
- package/src/plugins/builtin/gpu/pane.tsx +1 -1
- package/src/plugins/builtin/gpu/series.ts +2 -3
- package/src/plugins/builtin/help/index.tsx +1 -1
- package/src/plugins/builtin/holders/beneficial-view.tsx +1 -2
- package/src/plugins/builtin/holders/index-holders.ts +24 -12
- package/src/plugins/builtin/holders/index.tsx +2 -3
- package/src/plugins/builtin/ipo-calendar/index.tsx +1 -2
- package/src/plugins/builtin/iv-history/index.tsx +1 -2
- package/src/plugins/builtin/jodi/client.ts +4 -1
- package/src/plugins/builtin/jodi/index.tsx +1 -2
- package/src/plugins/builtin/kelly-sizer/index.tsx +1 -2
- package/src/plugins/builtin/layout-manager/index.tsx +9 -8
- package/src/plugins/builtin/macro-day/pane.tsx +4 -2
- package/src/plugins/builtin/market-halts/index.tsx +1 -2
- package/src/plugins/builtin/market-heatmap/index.tsx +11 -2
- package/src/plugins/builtin/market-movers/client.ts +1 -0
- package/src/plugins/builtin/market-movers/index.tsx +16 -5
- package/src/plugins/builtin/market-movers/screener.ts +23 -6
- package/src/plugins/builtin/market-movers/session-body.tsx +6 -2
- package/src/plugins/builtin/market-valuation/index.tsx +1 -2
- package/src/plugins/builtin/members/index.tsx +1 -2
- package/src/plugins/builtin/money-markets/index.tsx +1 -2
- package/src/plugins/builtin/money-markets/pane.tsx +2 -1
- package/src/plugins/builtin/money-markets/series.ts +3 -5
- package/src/plugins/builtin/news/headless.ts +7 -0
- package/src/plugins/builtin/news/wire/index.tsx +1 -1
- package/src/plugins/builtin/news/wire/news/pop-out.tsx +9 -3
- package/src/plugins/builtin/notes/index.tsx +3 -6
- package/src/plugins/builtin/openfda/index.tsx +1 -2
- package/src/plugins/builtin/options/view.tsx +6 -5
- package/src/plugins/builtin/options-calculator/index.tsx +1 -2
- package/src/plugins/builtin/options-positioning/index.tsx +1 -2
- package/src/plugins/builtin/options-scenario/index.tsx +2 -4
- package/src/plugins/builtin/perps/index.tsx +1 -2
- package/src/plugins/builtin/plugin-marketplace/index.tsx +2 -3
- package/src/plugins/builtin/plugin-marketplace/pane.tsx +2 -1
- package/src/plugins/builtin/plugin-module.ts +16 -2
- package/src/plugins/builtin/portfolio-list/cli/render.ts +53 -3
- package/src/plugins/builtin/portfolio-list/index.tsx +1 -2
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +11 -0
- package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +2 -2
- package/src/plugins/builtin/power/gas-client.ts +2 -1
- package/src/plugins/builtin/power/index.tsx +1 -2
- package/src/plugins/builtin/power/prices-client.ts +3 -2
- package/src/plugins/builtin/rate-path/index.tsx +1 -2
- package/src/plugins/builtin/realized-vol/headless.ts +2 -0
- package/src/plugins/builtin/realized-vol/index.tsx +1 -2
- package/src/plugins/builtin/realized-vol/model.ts +16 -2
- package/src/plugins/builtin/realized-vol/pane.tsx +3 -3
- package/src/plugins/builtin/relative-rotation/index.tsx +1 -2
- package/src/plugins/builtin/research/analyst-headless.ts +33 -6
- package/src/plugins/builtin/research/analyst-model.ts +37 -2
- package/src/plugins/builtin/research/analyst-pane.tsx +25 -5
- package/src/plugins/builtin/research/index.tsx +5 -8
- package/src/plugins/builtin/research-search/index.tsx +1 -2
- package/src/plugins/builtin/revenue-breakdown/headless.ts +22 -8
- package/src/plugins/builtin/revenue-breakdown/model.ts +23 -0
- package/src/plugins/builtin/reverse-dcf/pane.tsx +4 -2
- package/src/plugins/builtin/risk-factors/data.ts +8 -4
- package/src/plugins/builtin/risk-factors/index.tsx +3 -1
- package/src/plugins/builtin/risk-factors/pane.tsx +5 -8
- package/src/plugins/builtin/roadmap/index.tsx +2 -3
- package/src/plugins/builtin/roadmap/pane.tsx +49 -2
- package/src/plugins/builtin/scanner/hilo-model.ts +18 -1
- package/src/plugins/builtin/scanner/hilo-pane.tsx +12 -2
- package/src/plugins/builtin/scanner/live-pane.tsx +7 -4
- package/src/plugins/builtin/scanner/lock-note.ts +26 -3
- package/src/plugins/builtin/sec/filing-content.ts +38 -6
- package/src/plugins/builtin/sec/headless.ts +5 -1
- package/src/plugins/builtin/sec/index.tsx +4 -2
- package/src/plugins/builtin/sec/model.ts +2 -0
- package/src/plugins/builtin/sectors/index.tsx +11 -5
- package/src/plugins/builtin/series-inventory.ts +2 -0
- package/src/plugins/builtin/shared/cloud-resource.ts +73 -8
- package/src/plugins/builtin/shared/cloud-upgrade.ts +24 -1
- package/src/plugins/builtin/shared/forming-bars.ts +29 -0
- package/src/plugins/builtin/shared/fred-points.ts +6 -1
- package/src/plugins/builtin/shared/fx-rate-freshness.ts +17 -0
- package/src/plugins/builtin/shared/pro-lock-note.ts +23 -8
- package/src/plugins/builtin/shared/series-detail-chart.tsx +4 -2
- package/src/plugins/builtin/shared/series-warning.ts +13 -4
- package/src/plugins/builtin/shared/use-quote-board.ts +5 -0
- package/src/plugins/builtin/short-interest/publications-pane.tsx +2 -1
- package/src/plugins/builtin/short-volume/pane.tsx +2 -1
- package/src/plugins/builtin/short-volume/series.ts +2 -3
- package/src/plugins/builtin/social-mentions/client.ts +15 -10
- package/src/plugins/builtin/social-mentions/index.tsx +1 -2
- package/src/plugins/builtin/social-mentions/pane.tsx +2 -1
- package/src/plugins/builtin/social-mentions/series.ts +2 -3
- package/src/plugins/builtin/supply-chain/client.ts +22 -1
- package/src/plugins/builtin/supply-chain/graph-client.ts +22 -1
- package/src/plugins/builtin/supply-chain/index.tsx +1 -2
- package/src/plugins/builtin/thirteenf/api.ts +127 -29
- package/src/plugins/builtin/thirteenf/data.ts +165 -35
- package/src/plugins/builtin/thirteenf/index.tsx +2 -4
- package/src/plugins/builtin/thirteenf/model.ts +2 -0
- package/src/plugins/builtin/thirteenf/overlap-pane.tsx +5 -1
- package/src/plugins/builtin/thirteenf/pane.tsx +41 -16
- package/src/plugins/builtin/thirteenf/types.ts +14 -0
- package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +22 -6
- package/src/plugins/builtin/ticker-detail/data-panes/returns-history.ts +81 -11
- package/src/plugins/builtin/ticker-detail/financials/model.ts +35 -2
- package/src/plugins/builtin/ticker-detail/financials/ratios.ts +53 -18
- package/src/plugins/builtin/ticker-detail/headless.ts +70 -11
- package/src/plugins/builtin/ticker-detail/index.tsx +5 -10
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +16 -6
- package/src/plugins/builtin/time-sales/index.tsx +1 -2
- package/src/plugins/builtin/time-sales/pane.tsx +3 -1
- package/src/plugins/builtin/trace-bonds/headless.ts +11 -2
- package/src/plugins/builtin/trace-bonds/model.ts +44 -10
- package/src/plugins/builtin/trace-bonds/pane.tsx +12 -7
- package/src/plugins/builtin/treasury-auctions/cache.ts +20 -8
- package/src/plugins/builtin/treasury-auctions/client.ts +3 -3
- package/src/plugins/builtin/treasury-auctions/headless.ts +3 -1
- package/src/plugins/builtin/treasury-auctions/pane.tsx +10 -4
- package/src/plugins/builtin/vol-surface/client.ts +18 -3
- package/src/plugins/builtin/vol-surface/index.tsx +1 -2
- package/src/plugins/builtin/volatility/client.ts +43 -16
- package/src/plugins/builtin/volatility/headless.ts +5 -2
- package/src/plugins/builtin/volatility/index.tsx +1 -2
- package/src/plugins/builtin/volatility/model.ts +17 -6
- package/src/plugins/builtin/volatility/pane.tsx +6 -2
- package/src/plugins/builtin/volatility/series.ts +2 -1
- package/src/plugins/builtin/world-indices/footer.ts +6 -1
- package/src/plugins/builtin/world-venue-map/client.ts +10 -1
- package/src/plugins/builtin/world-venue-map/index.ts +6 -2
- package/src/plugins/builtin/world-venue-map/layered-pane.tsx +48 -7
- package/src/plugins/builtin/world-venue-map/layers.ts +17 -0
- package/src/plugins/builtin/world-venue-map/local-layers.ts +141 -0
- package/src/plugins/builtin/world-venue-map/map-symbols.ts +18 -0
- package/src/plugins/builtin/world-venue-map/map.tsx +4 -2
- package/src/plugins/builtin/world-venue-map/scope.ts +63 -0
- package/src/plugins/builtin/yield-curve/client.ts +4 -6
- package/src/plugins/builtin/yield-curve/curves.ts +16 -2
- package/src/plugins/builtin/yield-curve/headless.ts +3 -3
- package/src/plugins/bundle.ts +52 -2
- package/src/plugins/catalog-browser.ts +2 -2
- package/src/plugins/catalog-cli.ts +20 -0
- package/src/plugins/host-link.ts +13 -1
- package/src/plugins/installer.ts +64 -42
- package/src/plugins/loader.ts +43 -11
- package/src/plugins/peer-imports.ts +201 -0
- package/src/plugins/peer-retry.ts +26 -29
- package/src/plugins/plugin-process.ts +128 -0
- package/src/plugins/redirection-guard.ts +45 -0
- package/src/remote/endpoint.ts +16 -3
- package/src/renderers/browser/main.tsx +3 -2
- package/src/renderers/cloudflare/http-proxy.ts +7 -123
- package/src/renderers/dom/desktop/icons.tsx +5 -2
- package/src/renderers/dom/host/image-surface.tsx +46 -21
- package/src/renderers/dom/styles.css +7 -0
- package/src/renderers/dom/toast-host.tsx +2 -0
- package/src/renderers/electrobun/bun/desktop/backend.ts +7 -1
- package/src/renderers/electrobun/bun/desktop/detached-windows.ts +23 -1
- package/src/renderers/electrobun/bun/desktop/initialization.ts +12 -3
- package/src/renderers/electrobun/bun/desktop/rpc-timing.ts +88 -0
- package/src/renderers/electrobun/bun/desktop/update.ts +20 -4
- package/src/renderers/electrobun/bun/desktop/view-ids.ts +8 -4
- package/src/renderers/electrobun/bun/external-plugins.ts +4 -0
- package/src/renderers/electrobun/bun/window/attention.ts +54 -0
- package/src/renderers/electrobun/bun/window/create.ts +6 -2
- package/src/renderers/electrobun/bun/window/native-timing.ts +48 -0
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +82 -4
- package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +13 -5
- package/src/renderers/electrobun/view/desktop/window/snapshot.ts +3 -0
- package/src/renderers/electrobun/view/main.tsx +7 -2
- package/src/renderers/electrobun/view/remote/asset-data-client.ts +1 -0
- package/src/renderers/electrobun/view/ui-host.tsx +9 -2
- package/src/renderers/opentui/browser-open.ts +10 -4
- package/src/renderers/opentui/image/surface.tsx +1 -1
- package/src/renderers/opentui/start.tsx +12 -6
- package/src/renderers/opentui/toast-host.tsx +2 -1
- package/src/series/registry.ts +18 -0
- package/src/series/resolve.ts +2 -1
- package/src/series/standalone.ts +10 -1
- package/src/series/types.ts +12 -0
- package/src/series/validate.ts +11 -2
- package/src/sources/gloomberb-cloud/fred-series.ts +5 -3
- package/src/sources/gloomberb-cloud/index.ts +28 -5
- package/src/sources/gloomberb-cloud/news.ts +24 -3
- package/src/sources/gloomberb-cloud/normalizers.ts +17 -2
- package/src/sources/provider-errors.ts +26 -3
- package/src/sources/provider-router/history.ts +5 -0
- package/src/sources/provider-router/index.ts +13 -0
- package/src/sync/core-contributors.ts +32 -0
- package/src/telemetry/attention-counts.ts +64 -6
- package/src/telemetry/attention-setting.ts +33 -0
- package/src/telemetry/crash-reports-node.ts +3 -1
- package/src/telemetry/stall-clocks.ts +93 -0
- package/src/telemetry/stall-reports-node.ts +25 -9
- package/src/telemetry/stall-reports.ts +218 -13
- package/src/test-support/outage.ts +8 -0
- package/src/test-support/preload.ts +9 -0
- package/src/tickers/company-name-listing.ts +59 -29
- package/src/tickers/company-name.ts +84 -1
- package/src/tickers/listing-fate.ts +14 -3
- package/src/tickers/saved-instrument.ts +17 -0
- package/src/tickers/search/home-listing.ts +104 -8
- package/src/tickers/search/index.ts +5 -2
- package/src/tickers/search/types.ts +6 -1
- package/src/time-series/bar-aggregation.ts +7 -1
- package/src/time-series/base-series.ts +3 -3
- package/src/time-series/chart-bounds.ts +1 -1
- package/src/time-series/chart-data.ts +5 -2
- package/src/time-series/chart-seed.ts +1 -1
- package/src/time-series/coin-session-close.ts +211 -0
- package/src/time-series/live-bars.ts +13 -2
- package/src/time-series/live-quotes.ts +1 -1
- package/src/time-series/market.ts +5 -3
- package/src/time-series/official-close.ts +159 -0
- package/src/time-series/price-comparison.ts +75 -8
- package/src/time-series/resolve.ts +32 -4
- package/src/time-series/series-presentation.ts +1 -1
- package/src/time-series/studies/formula.ts +205 -0
- package/src/time-series/studies/indicators.ts +299 -0
- package/src/time-series/studies/leverage.ts +75 -0
- package/src/time-series/studies/pair.ts +138 -0
- package/src/time-series/studies/resolve.ts +333 -0
- package/src/time-series/studies/shared.ts +141 -0
- package/src/time-series/studies/units.ts +243 -0
- package/src/time-series/studies/vwap.ts +274 -0
- package/src/time-series/types.ts +7 -0
- package/src/types/config.ts +18 -2
- package/src/types/data-provider.ts +23 -0
- package/src/types/financials.ts +40 -3
- package/src/types/headless.ts +42 -3
- package/src/types/plugin.ts +32 -3
- package/src/ui/host.tsx +17 -1
- package/src/ui/toast.tsx +26 -1
- package/src/utils/ascii-text.ts +140 -0
- package/src/utils/cli-output.ts +91 -18
- package/src/utils/concurrency.ts +24 -0
- package/src/utils/exchanges.ts +6 -0
- package/src/utils/external-url.ts +20 -7
- package/src/utils/financial-statements.ts +73 -16
- package/src/utils/fundamentals.ts +17 -5
- package/src/utils/futures-generic.ts +4 -4
- package/src/utils/http-proxy-upstream.ts +124 -0
- package/src/utils/lease-liabilities.ts +54 -0
- package/src/utils/main-thread-stall.ts +122 -5
- package/src/utils/perf-marks.ts +58 -12
- package/src/utils/price-history-integrity.ts +47 -1
- package/src/utils/private-files.ts +141 -0
- package/src/utils/public-address.ts +99 -0
- package/src/account/upgrade-personalization.ts +0 -57
- package/src/account/upgrade-tickers.ts +0 -124
- package/src/api-client/upgrade-experiment.ts +0 -91
- package/src/plugins/builtin/cloud/browser.tsx +0 -3
- package/src/time-series/studies.ts +0 -1628
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { useMemo } from "react";
|
|
2
|
+
import { useAppSelector, usePaneInstance } from "../../../state/app/context";
|
|
3
|
+
import { usePaneTickerIdentity } from "../../../state/hooks/pane-ticker";
|
|
4
|
+
import { usePluginPaneState } from "../../../public/react";
|
|
5
|
+
import type { PaneInstanceConfig } from "../../../types/config";
|
|
6
|
+
import { parseLayerTokens, LAYERS_SETTING_KEY } from "./layers";
|
|
7
|
+
import { isLocalGeoLayer, type GeoScope } from "./local-layers";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Pane state a ticker source may publish beside `cursorSymbol`: the group the
|
|
11
|
+
* selected company belongs to, which a linked map can widen to. TheBuildout
|
|
12
|
+
* publishes the theme its table lists.
|
|
13
|
+
*/
|
|
14
|
+
export const GEO_SCOPE_STATE_KEY = "geoScope";
|
|
15
|
+
|
|
16
|
+
type GeoScopeMode = "company" | "theme";
|
|
17
|
+
|
|
18
|
+
function publishedScope(value: unknown): GeoScope | null {
|
|
19
|
+
if (!value || typeof value !== "object") return null;
|
|
20
|
+
const { key, label } = value as Partial<GeoScope>;
|
|
21
|
+
return typeof key === "string" && key && typeof label === "string" ? { key, label } : null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** A map with one of the app's own layers on can be linked to a list's or a scanner's selection. */
|
|
25
|
+
export function mapFollowsTicker(pane: PaneInstanceConfig): boolean {
|
|
26
|
+
return parseLayerTokens(pane.settings?.[LAYERS_SETTING_KEY]).some(isLocalGeoLayer);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface LinkedGeoScope {
|
|
30
|
+
/** What the map's own layers show; null shows everything. */
|
|
31
|
+
scope: GeoScope | null;
|
|
32
|
+
/** The company the link resolves to. */
|
|
33
|
+
company: GeoScope | null;
|
|
34
|
+
/** The group its source publishes for that company. */
|
|
35
|
+
theme: GeoScope | null;
|
|
36
|
+
mode: GeoScopeMode;
|
|
37
|
+
setMode: (mode: GeoScopeMode) => void;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* What a linked map narrows its own layers to: the company the link resolves
|
|
42
|
+
* to, or the theme its source publishes once the pane is switched to it (and
|
|
43
|
+
* whenever there is no company). An unlinked map has neither and shows
|
|
44
|
+
* everything.
|
|
45
|
+
*/
|
|
46
|
+
export function useLinkedGeoScope(enabled: boolean): LinkedGeoScope {
|
|
47
|
+
const instance = usePaneInstance();
|
|
48
|
+
const linked = enabled && (instance?.binding?.kind === "follow" || instance?.binding?.kind === "fixed");
|
|
49
|
+
const sourceId = linked && instance?.binding?.kind === "follow" ? instance.binding.sourceInstanceId : null;
|
|
50
|
+
const published = useAppSelector((state) => (sourceId ? state.paneState[sourceId]?.[GEO_SCOPE_STATE_KEY] ?? null : null));
|
|
51
|
+
const { symbol } = usePaneTickerIdentity();
|
|
52
|
+
const [mode, setMode] = usePluginPaneState<GeoScopeMode>("geoScopeMode", "company");
|
|
53
|
+
const theme = linked ? publishedScope(published) : null;
|
|
54
|
+
const companySymbol = linked ? symbol : null;
|
|
55
|
+
const themeKey = theme?.key ?? null;
|
|
56
|
+
const themeLabel = theme?.label ?? "";
|
|
57
|
+
return useMemo(() => {
|
|
58
|
+
const company = companySymbol ? { key: `company:${companySymbol}`, label: companySymbol } : null;
|
|
59
|
+
const group = themeKey ? { key: themeKey, label: themeLabel } : null;
|
|
60
|
+
const scope = mode === "theme" ? group ?? company : company ?? group;
|
|
61
|
+
return { scope, company, theme: group, mode, setMode };
|
|
62
|
+
}, [companySymbol, mode, setMode, themeKey, themeLabel]);
|
|
63
|
+
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { apiClient } from "../../../api-client";
|
|
2
2
|
import type { SwapCurvePayload, SwapTenor } from "../../../api-client/swap-curve";
|
|
3
|
-
import {
|
|
4
|
-
import { createPluginCache, type PluginCacheResult } from "../../../data/plugin-cache";
|
|
3
|
+
import { createPluginCache, savedCopyForTransientFailure, type PluginCacheResult } from "../../../data/plugin-cache";
|
|
5
4
|
import { cachedCloudResource, loadCloudResource, unavailableOnServer } from "../shared/cloud-resource";
|
|
6
5
|
import { validateSwapCurve } from "./swaps-model";
|
|
7
6
|
import { loadCurveData, type CurveData, type CurveId } from "./curves";
|
|
@@ -52,12 +51,11 @@ export interface SavedCopyAnswer<T> {
|
|
|
52
51
|
const curveKey = (curve: CurveId, requestedDate: string) => `${curve}:${requestedDate || "latest"}`;
|
|
53
52
|
/**
|
|
54
53
|
* A copy is the saved one by where it came from, whatever its failure said. A
|
|
55
|
-
* refusal
|
|
56
|
-
* said rather than covered by the copy, as `loadCloudResource` does.
|
|
54
|
+
* refusal (access denied, an unknown curve) or a reply that fails validation
|
|
55
|
+
* is said rather than covered by the copy, as `loadCloudResource` does.
|
|
57
56
|
*/
|
|
58
57
|
function answer<T>(result: PluginCacheResult<T>): SavedCopyAnswer<T> {
|
|
59
|
-
const saved = result.source === "stale-fallback";
|
|
60
|
-
if (saved && isPermanentClientError(result.error)) throw result.error;
|
|
58
|
+
const saved = savedCopyForTransientFailure(result).source === "stale-fallback";
|
|
61
59
|
return { data: result.data, savedAt: saved ? result.fetchedAt : null };
|
|
62
60
|
}
|
|
63
61
|
|
|
@@ -44,7 +44,12 @@ export function curveOption(id: CurveId): CurveOption {
|
|
|
44
44
|
return CURVE_OPTIONS.find((option) => option.id === id) ?? CURVE_OPTIONS[0]!;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
/**
|
|
47
|
+
/**
|
|
48
|
+
* `GC`, `GC 2026-01-02`, `GC JGB`, `GC TIPS 2024-01-02`: a curve word and a
|
|
49
|
+
* date, either order. Words with no digit are no date: one that names no
|
|
50
|
+
* curve (`GC world`, `GC "South Africa"`) lists the curves and points at the
|
|
51
|
+
* world tab, which has the other countries' government yields.
|
|
52
|
+
*/
|
|
48
53
|
export function parseCurveArgument(value: unknown, now = new Date()): { curve: CurveId | null; date: string } {
|
|
49
54
|
const words = typeof value === "string" ? value.trim().split(/\s+/).filter(Boolean) : [];
|
|
50
55
|
let curve: CurveId | null = null;
|
|
@@ -54,7 +59,16 @@ export function parseCurveArgument(value: unknown, now = new Date()): { curve: C
|
|
|
54
59
|
if (match && !curve) curve = match.id;
|
|
55
60
|
else rest.push(word);
|
|
56
61
|
}
|
|
57
|
-
|
|
62
|
+
const text = rest.join(" ");
|
|
63
|
+
if (text && !/\d/.test(text) && text.toLowerCase() !== "latest") throw new Error(unknownCurveMessage(text));
|
|
64
|
+
return { curve, date: yieldCurveDate(text || null, now) };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** `Unknown curve "world"`, with the curves there are and where the rest of the world's yields are. */
|
|
68
|
+
export function unknownCurveMessage(word: string): string {
|
|
69
|
+
const names = CURVE_OPTIONS.map((option) => option.aliases[0]);
|
|
70
|
+
return `Unknown curve "${word}"; the curves are ${names.slice(0, -1).join(", ")} and ${names.at(-1)}. `
|
|
71
|
+
+ "Other countries' government bond yields are on the world tab: gloom fn GC --tab world.";
|
|
58
72
|
}
|
|
59
73
|
|
|
60
74
|
/**
|
|
@@ -3,8 +3,8 @@ import type {
|
|
|
3
3
|
HeadlessPaneDefinition,
|
|
4
4
|
} from "../../../types/plugin";
|
|
5
5
|
import {
|
|
6
|
-
compareDate, curveCutoff,
|
|
7
|
-
parseCompareInput, parseCurveArgument, type CurveData, type CurveId,
|
|
6
|
+
compareDate, curveCutoff, curveIdOf, curveOption, curveSpreadFigures, curveTenorMove, loadComparePoints,
|
|
7
|
+
parseCompareInput, parseCurveArgument, unknownCurveMessage, type CurveData, type CurveId,
|
|
8
8
|
} from "./curves";
|
|
9
9
|
import { yieldTenorRows, type YieldLookbackCurves } from "./chart";
|
|
10
10
|
import { curveAsOf, type YieldPoint, yieldCurveErrors } from "./treasury-data";
|
|
@@ -194,7 +194,7 @@ export function createYieldCurveHeadless(
|
|
|
194
194
|
const argument = parseCurveArgument(args.argument);
|
|
195
195
|
const optionCurve = args.options.curve == null || args.options.curve === "" ? null : curveIdOf(args.options.curve);
|
|
196
196
|
if (args.options.curve && !optionCurve) {
|
|
197
|
-
throw new Error(
|
|
197
|
+
throw new Error(unknownCurveMessage(String(args.options.curve)));
|
|
198
198
|
}
|
|
199
199
|
const curve = optionCurve ?? argument.curve ?? "us";
|
|
200
200
|
const requestedDate = args.options.date != null ? yieldCurveDate(args.options.date, new Date(), "--date") : argument.date;
|
package/src/plugins/bundle.ts
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import { mkdir } from "fs/promises";
|
|
2
|
-
import { join } from "path";
|
|
2
|
+
import { basename, join } from "path";
|
|
3
3
|
|
|
4
4
|
import { explainPluginLoadError } from "./compat";
|
|
5
5
|
import { hostPublicModules } from "./host-link";
|
|
6
6
|
import { resolvePluginBrowserEntry, resolvePluginEntry } from "./loader";
|
|
7
|
+
import { siblingImportsBundlerPlugin } from "./peer-imports";
|
|
7
8
|
import { PLUGIN_HOST_GLOBAL, SHARED_SPECIFIERS, importPluginHostModule } from "./host-modules";
|
|
9
|
+
import {
|
|
10
|
+
PLUGIN_PROCESS_IDENTIFIER,
|
|
11
|
+
pluginProcessDeclaration,
|
|
12
|
+
pluginProcessModuleSource,
|
|
13
|
+
} from "./plugin-process";
|
|
8
14
|
|
|
9
15
|
/**
|
|
10
16
|
* Compiles an external plugin for a renderer that cannot read the filesystem.
|
|
@@ -90,6 +96,7 @@ export interface BundlePluginResult {
|
|
|
90
96
|
function createSharedModuleResolver(
|
|
91
97
|
exportNamesFor: (specifier: string) => Promise<readonly string[]>,
|
|
92
98
|
onShared?: (specifier: string) => void,
|
|
99
|
+
options: { providesProcess?: boolean } = {},
|
|
93
100
|
): import("bun").BunPlugin {
|
|
94
101
|
const namespace = "gloom-host";
|
|
95
102
|
const shared = new Set<string>(SHARED_SPECIFIERS);
|
|
@@ -118,6 +125,19 @@ function createSharedModuleResolver(
|
|
|
118
125
|
contents: buildSharedModuleSource(args.path, await exportNamesFor(args.path)),
|
|
119
126
|
loader: "js",
|
|
120
127
|
}));
|
|
128
|
+
|
|
129
|
+
if (!options.providesProcess) return;
|
|
130
|
+
// `import process from "node:process"` reads the same shim as the bare
|
|
131
|
+
// global. Bun's own browser stand-in has no default export, so without
|
|
132
|
+
// this such a plugin did not compile for the desktop at all.
|
|
133
|
+
build.onResolve({ filter: /^(?:node:)?process$/ }, () => ({
|
|
134
|
+
path: "process",
|
|
135
|
+
namespace: "gloom-process",
|
|
136
|
+
}));
|
|
137
|
+
build.onLoad({ filter: /.*/, namespace: "gloom-process" }, () => ({
|
|
138
|
+
contents: pluginProcessModuleSource(),
|
|
139
|
+
loader: "js",
|
|
140
|
+
}));
|
|
121
141
|
},
|
|
122
142
|
};
|
|
123
143
|
}
|
|
@@ -144,6 +164,11 @@ export async function bundleExternalPlugin(
|
|
|
144
164
|
*/
|
|
145
165
|
minify?: boolean;
|
|
146
166
|
define?: Record<string, string>;
|
|
167
|
+
/**
|
|
168
|
+
* Named in the errors the browser's `process` stand-in throws. Defaults to
|
|
169
|
+
* the plugin's folder name.
|
|
170
|
+
*/
|
|
171
|
+
pluginName?: string;
|
|
147
172
|
/**
|
|
148
173
|
* `bun` compiles the native entry the terminal imports instead. Only the
|
|
149
174
|
* registry check uses it, to cover the native half of a plugin that has
|
|
@@ -161,7 +186,16 @@ export async function bundleExternalPlugin(
|
|
|
161
186
|
|
|
162
187
|
await mkdir(outDir, { recursive: true });
|
|
163
188
|
|
|
189
|
+
// The browser has no `process`, which the terminal gives every plugin. Each
|
|
190
|
+
// bundle gets its own stand-in (see plugin-process.ts); a bundle compiled
|
|
191
|
+
// for Bun runs where the real one exists.
|
|
192
|
+
const providesProcess = (options.target ?? "browser") === "browser";
|
|
193
|
+
const pluginName = options.pluginName ?? basename(pluginDir);
|
|
194
|
+
|
|
164
195
|
const shared = new Set<string>();
|
|
196
|
+
// A sibling plugin it imports is compiled from the sibling's folder, as
|
|
197
|
+
// the loader imports it, not through the plugin's link (peer-imports.ts).
|
|
198
|
+
const siblings = siblingImportsBundlerPlugin(pluginDir);
|
|
165
199
|
const result = await Bun.build({
|
|
166
200
|
entrypoints: [entry],
|
|
167
201
|
outdir: outDir,
|
|
@@ -174,12 +208,19 @@ export async function bundleExternalPlugin(
|
|
|
174
208
|
// `errors`. Returning it lets the check below report what actually broke,
|
|
175
209
|
// such as an import this host version does not export.
|
|
176
210
|
throw: false,
|
|
177
|
-
|
|
211
|
+
// The more specific `process.env.NODE_ENV` a caller defines still wins,
|
|
212
|
+
// so dead branches keyed on it stay dropped.
|
|
213
|
+
define: {
|
|
214
|
+
...(providesProcess ? { process: PLUGIN_PROCESS_IDENTIFIER } : {}),
|
|
215
|
+
...options.define,
|
|
216
|
+
},
|
|
178
217
|
plugins: [
|
|
179
218
|
createSharedModuleResolver(
|
|
180
219
|
options.exportNamesFor ?? hostExportNames,
|
|
181
220
|
(specifier) => shared.add(specifier),
|
|
221
|
+
{ providesProcess },
|
|
182
222
|
),
|
|
223
|
+
...(siblings ? [siblings] : []),
|
|
183
224
|
],
|
|
184
225
|
});
|
|
185
226
|
|
|
@@ -188,6 +229,15 @@ export async function bundleExternalPlugin(
|
|
|
188
229
|
const output = result.outputs.find((entryOutput) => entryOutput.kind === "entry-point");
|
|
189
230
|
if (!output) throw new Error(`Bundling ${pluginDir} produced no entry point`);
|
|
190
231
|
|
|
232
|
+
// Only a bundle that reads `process` carries the stand-in, so the rest of
|
|
233
|
+
// the plugins the web app serves stay as small as they were.
|
|
234
|
+
if (providesProcess) {
|
|
235
|
+
const code = await Bun.file(output.path).text();
|
|
236
|
+
if (code.includes(PLUGIN_PROCESS_IDENTIFIER)) {
|
|
237
|
+
await Bun.write(output.path, pluginProcessDeclaration(pluginName) + code);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
191
241
|
return {
|
|
192
242
|
outputPath: output.path,
|
|
193
243
|
shared: [...shared].sort(),
|
|
@@ -43,7 +43,7 @@ import { ipoCalendarPlugin } from "./builtin/ipo-calendar";
|
|
|
43
43
|
import { clinicalTrialsPlugin } from "./builtin/clinical-trials";
|
|
44
44
|
import { commentLettersPlugin } from "./builtin/comment-letters";
|
|
45
45
|
import { openFdaPlugin } from "./builtin/openfda";
|
|
46
|
-
import {
|
|
46
|
+
import { gloomberbCloudPlugin } from "./builtin/cloud";
|
|
47
47
|
import { changelogModule } from "./builtin/changelog";
|
|
48
48
|
import { roadmapModule } from "./builtin/roadmap";
|
|
49
49
|
import { chartComposerModule } from "./builtin/chart-composer";
|
|
@@ -298,7 +298,7 @@ const browserEarningsPlugin = composeBuiltinPlugin({
|
|
|
298
298
|
* browser-safe public API are absent rather than registered behind stubs.
|
|
299
299
|
*/
|
|
300
300
|
export const browserBuiltinPlugins: readonly GloomPlugin[] = [
|
|
301
|
-
|
|
301
|
+
gloomberbCloudPlugin,
|
|
302
302
|
browserPortfolioPlugin,
|
|
303
303
|
browserTickerCorePlugin,
|
|
304
304
|
browserOptionsVolatilityPlugin,
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { CliCommandDef } from "../types/plugin";
|
|
2
|
+
import { fearGreedPluginMeta, newsPluginMeta, notesPluginMeta, portfolioPluginMeta } from "./builtin/builtin-plugin-meta";
|
|
3
|
+
import { ALERTS_PLUGIN_ID } from "./builtin/alerts/constants";
|
|
4
|
+
import { alertsCliCommand } from "./builtin/alerts/cli";
|
|
5
|
+
import { fearGreedCliCommand } from "./builtin/fear-greed/cli";
|
|
6
|
+
import { rssCliCommand } from "./builtin/news/wire/rss/cli";
|
|
7
|
+
import { notesCliCommand } from "./builtin/notes/cli";
|
|
8
|
+
import { portfolioCliCommand } from "./builtin/portfolio-list/cli/portfolio-command";
|
|
9
|
+
import { watchlistCliCommand } from "./builtin/portfolio-list/cli/watchlist-command";
|
|
10
|
+
|
|
11
|
+
/** Command definitions shared with the plugins, without loading their pane components. */
|
|
12
|
+
export function getBuiltinCliCommands(): { pluginId: string; commands: CliCommandDef[] }[] {
|
|
13
|
+
return [
|
|
14
|
+
{ pluginId: portfolioPluginMeta.id, commands: [portfolioCliCommand, watchlistCliCommand] },
|
|
15
|
+
{ pluginId: newsPluginMeta.id, commands: [rssCliCommand] },
|
|
16
|
+
{ pluginId: notesPluginMeta.id, commands: [notesCliCommand] },
|
|
17
|
+
{ pluginId: fearGreedPluginMeta.id, commands: [fearGreedCliCommand] },
|
|
18
|
+
{ pluginId: ALERTS_PLUGIN_ID, commands: [alertsCliCommand] },
|
|
19
|
+
];
|
|
20
|
+
}
|
package/src/plugins/host-link.ts
CHANGED
|
@@ -225,6 +225,8 @@ export interface PeerLinkState {
|
|
|
225
225
|
target: string | null;
|
|
226
226
|
/** The link resolves to the sibling's folder. */
|
|
227
227
|
linked: boolean;
|
|
228
|
+
/** Why reading through the link failed, when it did: it can be in place and still not lead anywhere. */
|
|
229
|
+
resolveError?: string;
|
|
228
230
|
/** Links this process wrote at that path, and when it last did. */
|
|
229
231
|
writes: number;
|
|
230
232
|
lastWriteAt?: number;
|
|
@@ -272,11 +274,21 @@ export function describePeerLink(pluginDir: string, peer: string, pluginsDir: st
|
|
|
272
274
|
link = stats.isSymbolicLink() ? (process.platform === "win32" ? "junction" : "symlink") : "file";
|
|
273
275
|
}
|
|
274
276
|
const isLinkType = link === "junction" || link === "symlink";
|
|
277
|
+
const linked = directory !== null && alreadyLinked(linkPath, join(pluginsDir, directory));
|
|
278
|
+
let resolveError: string | undefined;
|
|
279
|
+
if (isLinkType && !linked) {
|
|
280
|
+
try {
|
|
281
|
+
statSync(linkPath);
|
|
282
|
+
} catch (error) {
|
|
283
|
+
resolveError = errorCode(error);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
275
286
|
return {
|
|
276
287
|
...base,
|
|
277
288
|
link,
|
|
278
289
|
target: isLinkType ? describeTarget(linkPath, pluginsDir) : null,
|
|
279
|
-
linked
|
|
290
|
+
linked,
|
|
291
|
+
...(resolveError ? { resolveError } : {}),
|
|
280
292
|
};
|
|
281
293
|
}
|
|
282
294
|
|
package/src/plugins/installer.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { findAbsorbedPlugin, type AbsorbedPlugin } from "./absorbed";
|
|
|
7
7
|
import type { PluginPin } from "./marketplace/store";
|
|
8
8
|
import { bunCommand, recordDependencyInstall } from "./dependencies";
|
|
9
9
|
import { linkHostPackages, missingPeerPlugins } from "./host-link";
|
|
10
|
+
import { importSiblingsFromTheirFolders } from "./peer-imports";
|
|
10
11
|
import {
|
|
11
12
|
findAbsorbedCheckout,
|
|
12
13
|
getPluginsDir,
|
|
@@ -42,7 +43,7 @@ function ensurePluginsDir() {
|
|
|
42
43
|
}
|
|
43
44
|
}
|
|
44
45
|
|
|
45
|
-
const GITHUB_SEGMENT_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]
|
|
46
|
+
const GITHUB_SEGMENT_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]*(?![\s\S])/;
|
|
46
47
|
const COMMIT_PATTERN = /^[0-9a-f]{7,40}$/i;
|
|
47
48
|
|
|
48
49
|
export function validatePluginDirectoryName(name: string): string {
|
|
@@ -114,6 +115,13 @@ interface RunResult {
|
|
|
114
115
|
stderr: string;
|
|
115
116
|
}
|
|
116
117
|
|
|
118
|
+
/** Resolves git before a command changes into a plugin checkout. */
|
|
119
|
+
function gitExecutable(): string {
|
|
120
|
+
const executable = Bun.which("git");
|
|
121
|
+
if (!executable) throw Object.assign(new Error(gitMissingMessage()), { code: "ENOENT" });
|
|
122
|
+
return executable;
|
|
123
|
+
}
|
|
124
|
+
|
|
117
125
|
/**
|
|
118
126
|
* Runs a command without blocking the event loop: a fetch or `bun install`
|
|
119
127
|
* takes seconds, and the terminal UI and the desktop's Bun process keep
|
|
@@ -128,7 +136,7 @@ function run(
|
|
|
128
136
|
env?: NodeJS.ProcessEnv,
|
|
129
137
|
): Promise<RunResult> {
|
|
130
138
|
return new Promise((resolveRun, reject) => {
|
|
131
|
-
const child = spawn(command, args, { cwd, env, stdio: ["ignore", output.stdout, output.stderr] });
|
|
139
|
+
const child = spawn(command === "git" ? gitExecutable() : command, args, { cwd, env, stdio: ["ignore", output.stdout, output.stderr] });
|
|
132
140
|
let stdout = "";
|
|
133
141
|
let stderr = "";
|
|
134
142
|
child.stdout?.setEncoding("utf-8").on("data", (chunk: string) => { stdout += chunk; });
|
|
@@ -141,7 +149,13 @@ function run(
|
|
|
141
149
|
/** Whether a `git` command is on the PATH; plugins from GitHub need one to install or update. */
|
|
142
150
|
export function gitInstalled(): boolean {
|
|
143
151
|
if (typeof Bun !== "undefined") return Bun.which("git") !== null;
|
|
144
|
-
|
|
152
|
+
try {
|
|
153
|
+
execFileSync("git", ["--version"], { stdio: "ignore" });
|
|
154
|
+
return true;
|
|
155
|
+
} catch {
|
|
156
|
+
// Intentional ignore: a git that does not start is the answer this asks for.
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
145
159
|
}
|
|
146
160
|
|
|
147
161
|
/** What a plugin install says when there is no git to run: what to install, then to try again. */
|
|
@@ -171,14 +185,9 @@ async function git(args: string[], cwd: string, quiet: boolean, failure: string)
|
|
|
171
185
|
return result.stdout.trim();
|
|
172
186
|
}
|
|
173
187
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
return true;
|
|
178
|
-
} catch {
|
|
179
|
-
// Intentional ignore: Detached HEAD is expected after a pinned install.
|
|
180
|
-
return false;
|
|
181
|
-
}
|
|
188
|
+
/** False on a detached HEAD, which a pinned install leaves. */
|
|
189
|
+
function isOnBranch(dir: string): Promise<boolean> {
|
|
190
|
+
return gitSucceedsAsync(["symbolic-ref", "-q", "HEAD"], dir);
|
|
182
191
|
}
|
|
183
192
|
|
|
184
193
|
function pinMatches(commit: string | null, expected: string | undefined): boolean {
|
|
@@ -208,27 +217,27 @@ async function checkoutCommit(targetDir: string, commit: string, quiet: boolean)
|
|
|
208
217
|
await git(["checkout", "--detach", "--force", commit], targetDir, quiet, `Could not check out ${commit.slice(0, 7)}`);
|
|
209
218
|
}
|
|
210
219
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
}
|
|
220
|
+
|
|
221
|
+
/*
|
|
222
|
+
* The update path runs in the app (the terminal, the desktop's Bun process),
|
|
223
|
+
* where a sync git call holds the thread for the whole process start: tens of
|
|
224
|
+
* milliseconds each on Windows, several per plugin. These never block it.
|
|
225
|
+
*/
|
|
226
|
+
async function gitSucceedsAsync(args: string[], cwd: string): Promise<boolean> {
|
|
227
|
+
const result = await run("git", args, cwd, { stdout: "pipe", stderr: "pipe" })
|
|
228
|
+
.catch(logFailure("plugin-installer", "Could not run git", null));
|
|
229
|
+
return result?.code === 0;
|
|
219
230
|
}
|
|
220
231
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
return null;
|
|
227
|
-
}
|
|
232
|
+
/** Null when git fails: shallow or manifest-free checkouts may lack historical metadata. */
|
|
233
|
+
async function gitOutputAsync(args: string[], cwd: string): Promise<string | null> {
|
|
234
|
+
const result = await run("git", args, cwd, { stdout: "pipe", stderr: "pipe" })
|
|
235
|
+
.catch(logFailure("plugin-installer", "Could not run git", null));
|
|
236
|
+
return result?.code === 0 ? result.stdout.trim() : null;
|
|
228
237
|
}
|
|
229
238
|
|
|
230
|
-
function versionAt(dir: string, commit: string): string | null {
|
|
231
|
-
const text =
|
|
239
|
+
async function versionAt(dir: string, commit: string): Promise<string | null> {
|
|
240
|
+
const text = await gitOutputAsync(["show", `${commit}:package.json`], dir);
|
|
232
241
|
if (!text) return null;
|
|
233
242
|
try {
|
|
234
243
|
const version = JSON.parse(text).version;
|
|
@@ -247,15 +256,15 @@ function versionAt(dir: string, commit: string): string | null {
|
|
|
247
256
|
* it; otherwise the versions the two commits declare decide, then, when
|
|
248
257
|
* those match, their commit dates.
|
|
249
258
|
*/
|
|
250
|
-
export function describeOlderCommit(dir: string, installed: string, candidate: string, label = candidate.slice(0, 7)): string | null {
|
|
251
|
-
if (installed === candidate ||
|
|
252
|
-
const installedVersion = versionAt(dir, installed);
|
|
253
|
-
const byVersion = compareSemver(versionAt(dir, candidate), installedVersion);
|
|
259
|
+
export async function describeOlderCommit(dir: string, installed: string, candidate: string, label = candidate.slice(0, 7)): Promise<string | null> {
|
|
260
|
+
if (installed === candidate || await gitSucceedsAsync(["merge-base", "--is-ancestor", installed, candidate], dir)) return null;
|
|
261
|
+
const installedVersion = await versionAt(dir, installed);
|
|
262
|
+
const byVersion = compareSemver(await versionAt(dir, candidate), installedVersion);
|
|
254
263
|
const older = () => `the registry's ${label} is older than the installed ${byVersion ? installedVersion : installed.slice(0, 7)}`;
|
|
255
|
-
if (
|
|
264
|
+
if (await gitSucceedsAsync(["merge-base", "--is-ancestor", candidate, installed], dir)) return older();
|
|
256
265
|
if (byVersion !== null && byVersion !== 0) return byVersion < 0 ? older() : null;
|
|
257
|
-
const candidateTime = Number(
|
|
258
|
-
const installedTime = Number(
|
|
266
|
+
const candidateTime = Number(await gitOutputAsync(["show", "-s", "--format=%ct", candidate], dir));
|
|
267
|
+
const installedTime = Number(await gitOutputAsync(["show", "-s", "--format=%ct", installed], dir));
|
|
259
268
|
return candidateTime > 0 && installedTime > 0 && candidateTime < installedTime ? older() : null;
|
|
260
269
|
}
|
|
261
270
|
|
|
@@ -296,6 +305,9 @@ async function readPluginExport(targetDir: string): Promise<Pick<GloomPlugin, "i
|
|
|
296
305
|
if (missingPeerPlugins(targetDir).length > 0) return null;
|
|
297
306
|
const entryFile = await resolvePluginEntry(targetDir);
|
|
298
307
|
if (!entryFile) return null;
|
|
308
|
+
// The siblings from their folders, as the loader imports them, so the
|
|
309
|
+
// modules this caches are the ones a load in this process reuses.
|
|
310
|
+
importSiblingsFromTheirFolders(targetDir);
|
|
299
311
|
// Fresh so an update is validated against the new code, not the module Bun
|
|
300
312
|
// cached when the previous version loaded.
|
|
301
313
|
const plugin = pluginFromModule(await import(`${entryFile}?validate=${Date.now()}`));
|
|
@@ -403,19 +415,29 @@ export interface PluginUpdateResult extends PluginDirectoryInfo {
|
|
|
403
415
|
kept?: string;
|
|
404
416
|
}
|
|
405
417
|
|
|
418
|
+
function githubRepoFromUrl(url: string): string | null {
|
|
419
|
+
const match = /github\.com[:/]([^/]+)\/([^/]+?)(?:\.git)?$/i.exec(url.trim());
|
|
420
|
+
return match ? `${match[1]}/${match[2]}` : null;
|
|
421
|
+
}
|
|
422
|
+
|
|
406
423
|
/** `owner/repo` from the checkout's origin remote, or null for anything that is not a GitHub clone. */
|
|
407
424
|
export function readPluginRemote(pluginDir: string): string | null {
|
|
408
425
|
try {
|
|
409
|
-
|
|
410
|
-
.toString("utf-8")
|
|
411
|
-
const match = /github\.com[:/]([^/]+)\/([^/]+?)(?:\.git)?$/i.exec(url);
|
|
412
|
-
return match ? `${match[1]}/${match[2]}` : null;
|
|
426
|
+
return githubRepoFromUrl(execFileSync(gitExecutable(), ["config", "--get", "remote.origin.url"], { cwd: pluginDir, stdio: ["ignore", "pipe", "ignore"] })
|
|
427
|
+
.toString("utf-8"));
|
|
413
428
|
} catch {
|
|
414
429
|
// Intentional ignore: Hand-copied plugins and local repositories may have no origin remote.
|
|
415
430
|
return null;
|
|
416
431
|
}
|
|
417
432
|
}
|
|
418
433
|
|
|
434
|
+
/** `readPluginRemote` without holding the thread, for the app's own update check. */
|
|
435
|
+
export async function readPluginRemoteAsync(pluginDir: string): Promise<string | null> {
|
|
436
|
+
// No origin remote (a hand-copied plugin, a local repository) is a failed git, and no repo.
|
|
437
|
+
const url = await gitOutputAsync(["config", "--get", "remote.origin.url"], pluginDir);
|
|
438
|
+
return url ? githubRepoFromUrl(url) : null;
|
|
439
|
+
}
|
|
440
|
+
|
|
419
441
|
/** The sha from `git ls-remote origin HEAD`; null when the answer is not one. */
|
|
420
442
|
export function parseRemoteHead(output: string): string | null {
|
|
421
443
|
const sha = output.trim().split(/\s+/)[0] ?? "";
|
|
@@ -439,7 +461,7 @@ export async function readPluginRemoteHead(name: string): Promise<string | null>
|
|
|
439
461
|
if (!existsSync(join(dir, ".git"))) return null;
|
|
440
462
|
if (lstatSync(dir, { throwIfNoEntry: false })?.isSymbolicLink()) return null;
|
|
441
463
|
try {
|
|
442
|
-
const { stdout } = await execFile(
|
|
464
|
+
const { stdout } = await execFile(gitExecutable(), ["ls-remote", "origin", "HEAD"], {
|
|
443
465
|
cwd: dir,
|
|
444
466
|
timeout: REMOTE_HEAD_TIMEOUT_MS,
|
|
445
467
|
env: { ...process.env, GIT_TERMINAL_PROMPT: "0", GIT_ASKPASS: "echo" },
|
|
@@ -500,7 +522,7 @@ export async function updatePlugin(name: string, options: PluginInstallOptions =
|
|
|
500
522
|
const commit = await fetchPin(targetDir, pin, quiet);
|
|
501
523
|
// Updates only move forward: a checkout installed from the default
|
|
502
524
|
// branch can be ahead of the tag the registry reviewed.
|
|
503
|
-
const kept = before ? describeOlderCommit(targetDir, before, commit, pin.ref ?? commit.slice(0, 7)) : null;
|
|
525
|
+
const kept = before ? await describeOlderCommit(targetDir, before, commit, pin.ref ?? commit.slice(0, 7)) : null;
|
|
504
526
|
if (kept) {
|
|
505
527
|
if (!quiet) console.log(cliStyles.muted(`Kept ${name}: ${kept}.`));
|
|
506
528
|
return { ...describe(targetDir, name, null), before, changed: false, kept };
|
|
@@ -510,7 +532,7 @@ export async function updatePlugin(name: string, options: PluginInstallOptions =
|
|
|
510
532
|
// Not in the registry, so there is nothing reviewed to land on: follow the
|
|
511
533
|
// remote's default branch, whether the checkout is on a branch or detached
|
|
512
534
|
// from an earlier pin.
|
|
513
|
-
if (isOnBranch(targetDir)) {
|
|
535
|
+
if (await isOnBranch(targetDir)) {
|
|
514
536
|
await git(["pull", "--ff-only"], targetDir, quiet, `Failed to update ${name}`);
|
|
515
537
|
} else {
|
|
516
538
|
await git(["fetch", "--depth", "1", "origin", "HEAD"], targetDir, quiet, `Failed to fetch ${name}`);
|
package/src/plugins/loader.ts
CHANGED
|
@@ -15,8 +15,10 @@ import {
|
|
|
15
15
|
missingPluginDependencies,
|
|
16
16
|
settleDependencyInstalls,
|
|
17
17
|
} from "./dependencies";
|
|
18
|
-
import { declaredPeerPlugins, describePeerLink, isLinkedPeerModule, linkHostPackages, relinkPeerPlugin } from "./host-link";
|
|
18
|
+
import { declaredPeerPlugins, describePeerLink, installedPeerPlugins, isLinkedPeerModule, linkHostPackages, relinkPeerPlugin } from "./host-link";
|
|
19
|
+
import { importSiblingsFromTheirFolders } from "./peer-imports";
|
|
19
20
|
import { refreshResolverFolders, rekeyedEntry, withoutRetryQuery } from "./peer-retry";
|
|
21
|
+
import { redirectionGuard } from "./redirection-guard";
|
|
20
22
|
import { pluginDirectoryNames } from "./plugin-names";
|
|
21
23
|
import { pluginFromModule, pluginSupportsTarget } from "./plugin-export";
|
|
22
24
|
|
|
@@ -258,7 +260,7 @@ function hasStaleModules(pluginDir: string, commit: string | null): boolean {
|
|
|
258
260
|
*/
|
|
259
261
|
const retriedEntries = new Map<string, string>();
|
|
260
262
|
|
|
261
|
-
/** Plugin folders whose recovery this process already reported; once is enough to explain it. */
|
|
263
|
+
/** Plugin folders whose recovery, or link that did not resolve, this process already reported; once is enough to explain it. */
|
|
262
264
|
const reportedRecoveries = new Set<string>();
|
|
263
265
|
|
|
264
266
|
/** Loads running now, which a crash report counts: two at once share the links they rewrite. */
|
|
@@ -299,30 +301,55 @@ function failedPeer(pluginDir: string, specifier: string | undefined): string |
|
|
|
299
301
|
/**
|
|
300
302
|
* The state of the link to `peer` and around it, for the crash report of an
|
|
301
303
|
* import that failed on it: whether the sibling's folder is there, what the
|
|
302
|
-
* link is and where it points,
|
|
303
|
-
* process wrote it before, which `bun
|
|
304
|
-
* many loads ran at once,
|
|
304
|
+
* link is and where it points, why reading through it failed, whether the
|
|
305
|
+
* retry rewrote it, how often this process wrote it before, which `bun
|
|
306
|
+
* install` runs this process made, how many loads ran at once, whether this
|
|
307
|
+
* is the first launch of the version, and on Windows whether this process
|
|
308
|
+
* may follow a junction (redirection-guard.ts). `how` leads: the retry's
|
|
309
|
+
* outcome, or how a plugin that loaded imported the sibling.
|
|
305
310
|
*/
|
|
306
|
-
function
|
|
311
|
+
function describePeerLinkState(pluginDir: string, peer: string, how: string, relinked?: string): string {
|
|
307
312
|
const link = describePeerLink(pluginDir, peer);
|
|
308
313
|
const now = Date.now();
|
|
309
314
|
const installs = describeDependencyInstalls(now);
|
|
315
|
+
const guard = redirectionGuard();
|
|
310
316
|
const fields = [
|
|
311
|
-
|
|
317
|
+
how,
|
|
312
318
|
`peer=${peer}`,
|
|
313
319
|
`dir=${link.directory ?? "missing"}`,
|
|
314
320
|
`link=${link.link}${link.target ? `->${link.target}` : ""}${link.linked ? "" : "(not-resolving)"}`,
|
|
315
|
-
`
|
|
321
|
+
...(link.resolveError ? [`link-stat=${link.resolveError}`] : []),
|
|
322
|
+
...(relinked ? [`relinked=${relinked}`] : []),
|
|
316
323
|
`link-writes=${link.writes}${link.lastWriteAt ? `@${((now - link.lastWriteAt) / 1000).toFixed(1)}s` : ""}`,
|
|
317
324
|
...(link.lastError ? [`link-error=${link.lastError}`] : []),
|
|
318
325
|
`installs=${installs.length > 0 ? installs.slice(0, 4).join(",") : "none"}`,
|
|
319
326
|
`loads=${loadsInFlight}`,
|
|
320
327
|
`first-launch=${firstLaunchOfVersion === null ? "unknown" : firstLaunchOfVersion ? "yes" : "no"}`,
|
|
321
328
|
...(typeof Bun !== "undefined" ? [`bun=${Bun.version}`] : []),
|
|
329
|
+
...(guard ? [`redirection-guard=${guard}`] : []),
|
|
322
330
|
];
|
|
323
331
|
return fields.join(" ");
|
|
324
332
|
}
|
|
325
333
|
|
|
334
|
+
/**
|
|
335
|
+
* Reports, once per plugin and process, a plugin that loaded while its link
|
|
336
|
+
* to a sibling it imports does not resolve. The import itself went to the
|
|
337
|
+
* sibling's folder (peer-imports.ts), so nothing failed; the report says the
|
|
338
|
+
* link would have failed it, and why, which is how the first launch after an
|
|
339
|
+
* update looks when the old failure is gone.
|
|
340
|
+
*/
|
|
341
|
+
function reportUnresolvedSiblingLinks(pluginDir: string, directory: string): void {
|
|
342
|
+
if (reportedRecoveries.has(pluginDir)) return;
|
|
343
|
+
for (const { peer } of installedPeerPlugins(pluginDir)) {
|
|
344
|
+
if (describePeerLink(pluginDir, peer).linked) continue;
|
|
345
|
+
reportedRecoveries.add(pluginDir);
|
|
346
|
+
const detail = describePeerLinkState(pluginDir, peer, "peer-import=folder");
|
|
347
|
+
loaderLog.info(`Loaded ${directory} with ${peer} read from its folder; the link to it does not resolve [${detail}]`);
|
|
348
|
+
reportCrash(new Error(`Loaded ${directory} past a link to ${peer} that does not resolve`), { kind: "plugin", plugin: directory, detail });
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
326
353
|
type PeerRetry =
|
|
327
354
|
| { module: unknown; detail: string }
|
|
328
355
|
| { error: unknown; detail: string };
|
|
@@ -353,14 +380,14 @@ async function retryPeerImport(pluginDir: string, entryFile: string, peer: strin
|
|
|
353
380
|
refreshResolverFolders(pluginDir, [peer]);
|
|
354
381
|
const specifier = rekeyedEntry(entryFile, pluginDir);
|
|
355
382
|
if (!specifier) {
|
|
356
|
-
return { error: new Error("This process cannot import a plugin again."), detail:
|
|
383
|
+
return { error: new Error("This process cannot import a plugin again."), detail: describePeerLinkState(pluginDir, peer, "retry=unavailable", relinked) };
|
|
357
384
|
}
|
|
358
385
|
try {
|
|
359
386
|
const module = await import(specifier);
|
|
360
387
|
retriedEntries.set(pluginDir, specifier);
|
|
361
|
-
return { module, detail:
|
|
388
|
+
return { module, detail: describePeerLinkState(pluginDir, peer, "retry=loaded", relinked) };
|
|
362
389
|
} catch (error) {
|
|
363
|
-
return { error, detail:
|
|
390
|
+
return { error, detail: describePeerLinkState(pluginDir, peer, "retry=failed", relinked) };
|
|
364
391
|
}
|
|
365
392
|
}
|
|
366
393
|
|
|
@@ -475,6 +502,10 @@ async function loadPluginDirectory(
|
|
|
475
502
|
};
|
|
476
503
|
};
|
|
477
504
|
|
|
505
|
+
// A sibling it imports is read from the sibling's folder, not through the
|
|
506
|
+
// link just made: a process can be unable to follow that link at all.
|
|
507
|
+
importSiblingsFromTheirFolders(pluginDir);
|
|
508
|
+
|
|
478
509
|
let module: unknown;
|
|
479
510
|
try {
|
|
480
511
|
const retried = options.fresh ? undefined : retriedEntries.get(pluginDir);
|
|
@@ -496,6 +527,7 @@ async function loadPluginDirectory(
|
|
|
496
527
|
}
|
|
497
528
|
module = retry.module;
|
|
498
529
|
}
|
|
530
|
+
reportUnresolvedSiblingLinks(pluginDir, directory);
|
|
499
531
|
|
|
500
532
|
try {
|
|
501
533
|
const plugin = pluginFromModule(module);
|