srcly 0.1.32__tar.gz → 0.1.34__tar.gz
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.
- {srcly-0.1.32 → srcly-0.1.34}/PKG-INFO +2 -2
- {srcly-0.1.32 → srcly-0.1.34}/app/config.py +3 -0
- {srcly-0.1.32 → srcly-0.1.34}/app/main.py +52 -1
- {srcly-0.1.32 → srcly-0.1.34}/app/models.py +0 -56
- {srcly-0.1.32 → srcly-0.1.34}/app/routers/analysis.py +17 -84
- srcly-0.1.34/app/routers/scans.py +121 -0
- {srcly-0.1.32 → srcly-0.1.34}/app/run.py +17 -1
- {srcly-0.1.32 → srcly-0.1.34}/app/services/analysis.py +149 -164
- srcly-0.1.34/app/services/scan_jobs.py +217 -0
- srcly-0.1.34/app/services/scan_progress.py +39 -0
- srcly-0.1.34/app/services/scan_workers.py +306 -0
- srcly-0.1.34/app/static/assets/CodeModal-BBkwbpez.js +29 -0
- srcly-0.1.34/app/static/assets/DependencyGraph-9gIcsNut.js +1 -0
- srcly-0.1.32/app/static/assets/shellscript-Yzrsuije.js → srcly-0.1.34/app/static/assets/bash-DgJyDXW6.js +1 -1
- srcly-0.1.32/app/static/assets/css-DPfMkruS.js → srcly-0.1.34/app/static/assets/css-PZBBrkI0.js +1 -1
- srcly-0.1.34/app/static/assets/dependencyForce.worker-WVMkyynK.js +1 -0
- srcly-0.1.34/app/static/assets/elk-worker.min-DGCccKuH.js +6386 -0
- srcly-0.1.34/app/static/assets/github-light-Rcb2q3L5.js +1 -0
- srcly-0.1.34/app/static/assets/highlight.worker-C64dOQ1-.js +14 -0
- srcly-0.1.32/app/static/assets/html-GMplVEZG.js → srcly-0.1.34/app/static/assets/html-DOf0XnR2.js +1 -1
- srcly-0.1.34/app/static/assets/ibm-plex-mono-cyrillic-400-normal-BSMlKf0J.woff2 +0 -0
- srcly-0.1.34/app/static/assets/ibm-plex-mono-cyrillic-400-normal-CEL4l2ZJ.woff +0 -0
- srcly-0.1.34/app/static/assets/ibm-plex-mono-cyrillic-500-normal-Ael50iVv.woff +0 -0
- srcly-0.1.34/app/static/assets/ibm-plex-mono-cyrillic-500-normal-Bq9vWWag.woff2 +0 -0
- srcly-0.1.34/app/static/assets/ibm-plex-mono-cyrillic-600-normal-CTOM6hUh.woff2 +0 -0
- srcly-0.1.34/app/static/assets/ibm-plex-mono-cyrillic-600-normal-fLZuRloM.woff +0 -0
- srcly-0.1.34/app/static/assets/ibm-plex-mono-cyrillic-ext-400-normal-DMdlQ8Kv.woff +0 -0
- srcly-0.1.34/app/static/assets/ibm-plex-mono-cyrillic-ext-400-normal-xuaO2J-f.woff2 +0 -0
- srcly-0.1.34/app/static/assets/ibm-plex-mono-cyrillic-ext-500-normal-BIfNGwUT.woff +0 -0
- srcly-0.1.34/app/static/assets/ibm-plex-mono-cyrillic-ext-500-normal-BqneJy0T.woff2 +0 -0
- srcly-0.1.34/app/static/assets/ibm-plex-mono-cyrillic-ext-600-normal-9HEixskS.woff +0 -0
- srcly-0.1.34/app/static/assets/ibm-plex-mono-cyrillic-ext-600-normal-V-xxqcpd.woff2 +0 -0
- srcly-0.1.34/app/static/assets/ibm-plex-mono-latin-400-normal-CvHOgSBP.woff +0 -0
- srcly-0.1.34/app/static/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2 +0 -0
- srcly-0.1.34/app/static/assets/ibm-plex-mono-latin-500-normal-CB9ihrfo.woff +0 -0
- srcly-0.1.34/app/static/assets/ibm-plex-mono-latin-500-normal-DSY6xOcd.woff2 +0 -0
- srcly-0.1.34/app/static/assets/ibm-plex-mono-latin-600-normal-BgSNZQsw.woff2 +0 -0
- srcly-0.1.34/app/static/assets/ibm-plex-mono-latin-600-normal-DWFSQ4vo.woff +0 -0
- srcly-0.1.34/app/static/assets/ibm-plex-mono-latin-ext-400-normal-BmRBH3aV.woff2 +0 -0
- srcly-0.1.34/app/static/assets/ibm-plex-mono-latin-ext-400-normal-D3D2R8hC.woff +0 -0
- srcly-0.1.34/app/static/assets/ibm-plex-mono-latin-ext-500-normal-CAhNIIs5.woff2 +0 -0
- srcly-0.1.34/app/static/assets/ibm-plex-mono-latin-ext-500-normal-CZ70TYgx.woff +0 -0
- srcly-0.1.34/app/static/assets/ibm-plex-mono-latin-ext-600-normal-D38SheWl.woff2 +0 -0
- srcly-0.1.34/app/static/assets/ibm-plex-mono-latin-ext-600-normal-DmB0ttJJ.woff +0 -0
- srcly-0.1.34/app/static/assets/ibm-plex-mono-vietnamese-400-normal-BulugwFq.woff2 +0 -0
- srcly-0.1.34/app/static/assets/ibm-plex-mono-vietnamese-400-normal-DDuiU_S-.woff +0 -0
- srcly-0.1.34/app/static/assets/ibm-plex-mono-vietnamese-500-normal-C8zxqsMH.woff +0 -0
- srcly-0.1.34/app/static/assets/ibm-plex-mono-vietnamese-500-normal-DZ4AoWbu.woff2 +0 -0
- srcly-0.1.34/app/static/assets/ibm-plex-mono-vietnamese-600-normal-D2EvbN8M.woff2 +0 -0
- srcly-0.1.34/app/static/assets/ibm-plex-mono-vietnamese-600-normal-iLQfcSjf.woff +0 -0
- srcly-0.1.34/app/static/assets/index-64jBfyHS.css +1 -0
- srcly-0.1.34/app/static/assets/index-CPpALji7.js +2 -0
- srcly-0.1.34/app/static/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
- srcly-0.1.34/app/static/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
- srcly-0.1.34/app/static/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
- srcly-0.1.34/app/static/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
- srcly-0.1.34/app/static/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
- srcly-0.1.34/app/static/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
- srcly-0.1.34/app/static/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
- srcly-0.1.32/app/static/assets/javascript-wDzz0qaB.js → srcly-0.1.34/app/static/assets/javascript-WyGyM1wV.js +1 -1
- srcly-0.1.32/app/static/assets/json-Cp-IABpG.js → srcly-0.1.34/app/static/assets/json-B8JryHcM.js +1 -1
- srcly-0.1.32/app/static/assets/jsx-g9-lgVsj.js → srcly-0.1.34/app/static/assets/jsx-8cLv2Six.js +1 -1
- srcly-0.1.32/app/static/assets/markdown-Cvjx9yec.js → srcly-0.1.34/app/static/assets/markdown-DX-TNHnc.js +1 -1
- srcly-0.1.32/app/static/assets/python-B6aJPvgy.js → srcly-0.1.34/app/static/assets/python-B6Ihcp-E.js +1 -1
- srcly-0.1.32/app/static/assets/toml-vGWfd6FD.js → srcly-0.1.34/app/static/assets/toml-Ms459DBw.js +1 -1
- srcly-0.1.32/app/static/assets/tsx-COt5Ahok.js → srcly-0.1.34/app/static/assets/tsx-D6116Ycy.js +1 -1
- srcly-0.1.32/app/static/assets/typescript-BPQ3VLAy.js → srcly-0.1.34/app/static/assets/typescript-BqvgUolh.js +1 -1
- srcly-0.1.32/app/static/assets/yaml-Buea-lGh.js → srcly-0.1.34/app/static/assets/yaml-Bopit2L3.js +1 -1
- srcly-0.1.34/app/static/favicon.svg +9 -0
- srcly-0.1.34/app/static/index.html +18 -0
- {srcly-0.1.32 → srcly-0.1.34}/pyproject.toml +1 -1
- srcly-0.1.32/app/services/data_flow_analysis.py +0 -875
- srcly-0.1.32/app/services/focus_overlay.py +0 -1239
- srcly-0.1.32/app/static/assets/abap-BdImnpbu.js +0 -1
- srcly-0.1.32/app/static/assets/actionscript-3-CfeIJUat.js +0 -1
- srcly-0.1.32/app/static/assets/ada-bCR0ucgS.js +0 -1
- srcly-0.1.32/app/static/assets/andromeeda-C-Jbm3Hp.js +0 -1
- srcly-0.1.32/app/static/assets/angular-html-CU67Zn6k.js +0 -1
- srcly-0.1.32/app/static/assets/angular-ts-BwZT4LLn.js +0 -1
- srcly-0.1.32/app/static/assets/apache-Pmp26Uib.js +0 -1
- srcly-0.1.32/app/static/assets/apex-DhZLUxFE.js +0 -1
- srcly-0.1.32/app/static/assets/apl-dKokRX4l.js +0 -1
- srcly-0.1.32/app/static/assets/applescript-Co6uUVPk.js +0 -1
- srcly-0.1.32/app/static/assets/ara-BRHolxvo.js +0 -1
- srcly-0.1.32/app/static/assets/asciidoc-Dv7Oe6Be.js +0 -1
- srcly-0.1.32/app/static/assets/asm-D_Q5rh1f.js +0 -1
- srcly-0.1.32/app/static/assets/astro-CbQHKStN.js +0 -1
- srcly-0.1.32/app/static/assets/aurora-x-D-2ljcwZ.js +0 -1
- srcly-0.1.32/app/static/assets/awk-DMzUqQB5.js +0 -1
- srcly-0.1.32/app/static/assets/ayu-dark-Cv9koXgw.js +0 -1
- srcly-0.1.32/app/static/assets/ballerina-BFfxhgS-.js +0 -1
- srcly-0.1.32/app/static/assets/bat-BkioyH1T.js +0 -1
- srcly-0.1.32/app/static/assets/beancount-k_qm7-4y.js +0 -1
- srcly-0.1.32/app/static/assets/berry-D08WgyRC.js +0 -1
- srcly-0.1.32/app/static/assets/bibtex-CHM0blh-.js +0 -1
- srcly-0.1.32/app/static/assets/bicep-Bmn6On1c.js +0 -1
- srcly-0.1.32/app/static/assets/blade-DVc8C-J4.js +0 -1
- srcly-0.1.32/app/static/assets/bsl-BO_Y6i37.js +0 -1
- srcly-0.1.32/app/static/assets/c-BIGW1oBm.js +0 -1
- srcly-0.1.32/app/static/assets/cadence-Bv_4Rxtq.js +0 -1
- srcly-0.1.32/app/static/assets/cairo-KRGpt6FW.js +0 -1
- srcly-0.1.32/app/static/assets/catppuccin-frappe-DFWUc33u.js +0 -1
- srcly-0.1.32/app/static/assets/catppuccin-latte-C9dUb6Cb.js +0 -1
- srcly-0.1.32/app/static/assets/catppuccin-macchiato-DQyhUUbL.js +0 -1
- srcly-0.1.32/app/static/assets/catppuccin-mocha-D87Tk5Gz.js +0 -1
- srcly-0.1.32/app/static/assets/clarity-D53aC0YG.js +0 -1
- srcly-0.1.32/app/static/assets/clojure-P80f7IUj.js +0 -1
- srcly-0.1.32/app/static/assets/cmake-D1j8_8rp.js +0 -1
- srcly-0.1.32/app/static/assets/cobol-nwyudZeR.js +0 -1
- srcly-0.1.32/app/static/assets/codeowners-Bp6g37R7.js +0 -1
- srcly-0.1.32/app/static/assets/codeql-DsOJ9woJ.js +0 -1
- srcly-0.1.32/app/static/assets/coffee-Ch7k5sss.js +0 -1
- srcly-0.1.32/app/static/assets/common-lisp-Cg-RD9OK.js +0 -1
- srcly-0.1.32/app/static/assets/coq-DkFqJrB1.js +0 -1
- srcly-0.1.32/app/static/assets/cpp-CofmeUqb.js +0 -1
- srcly-0.1.32/app/static/assets/crystal-tKQVLTB8.js +0 -1
- srcly-0.1.32/app/static/assets/csharp-CX12Zw3r.js +0 -1
- srcly-0.1.32/app/static/assets/csv-fuZLfV_i.js +0 -1
- srcly-0.1.32/app/static/assets/cue-D82EKSYY.js +0 -1
- srcly-0.1.32/app/static/assets/cypher-COkxafJQ.js +0 -1
- srcly-0.1.32/app/static/assets/d-85-TOEBH.js +0 -1
- srcly-0.1.32/app/static/assets/dark-plus-eOWES_5F.js +0 -1
- srcly-0.1.32/app/static/assets/dart-CF10PKvl.js +0 -1
- srcly-0.1.32/app/static/assets/dax-CEL-wOlO.js +0 -1
- srcly-0.1.32/app/static/assets/desktop-BmXAJ9_W.js +0 -1
- srcly-0.1.32/app/static/assets/diff-D97Zzqfu.js +0 -1
- srcly-0.1.32/app/static/assets/docker-BcOcwvcX.js +0 -1
- srcly-0.1.32/app/static/assets/dotenv-Da5cRb03.js +0 -1
- srcly-0.1.32/app/static/assets/dracula-BzJJZx-M.js +0 -1
- srcly-0.1.32/app/static/assets/dracula-soft-BXkSAIEj.js +0 -1
- srcly-0.1.32/app/static/assets/dream-maker-BtqSS_iP.js +0 -1
- srcly-0.1.32/app/static/assets/edge-BkV0erSs.js +0 -1
- srcly-0.1.32/app/static/assets/elixir-CDX3lj18.js +0 -1
- srcly-0.1.32/app/static/assets/elm-DbKCFpqz.js +0 -1
- srcly-0.1.32/app/static/assets/emacs-lisp-C9XAeP06.js +0 -1
- srcly-0.1.32/app/static/assets/erb-BOJIQeun.js +0 -1
- srcly-0.1.32/app/static/assets/erlang-DsQrWhSR.js +0 -1
- srcly-0.1.32/app/static/assets/everforest-dark-BgDCqdQA.js +0 -1
- srcly-0.1.32/app/static/assets/everforest-light-C8M2exoo.js +0 -1
- srcly-0.1.32/app/static/assets/fennel-BYunw83y.js +0 -1
- srcly-0.1.32/app/static/assets/fish-BvzEVeQv.js +0 -1
- srcly-0.1.32/app/static/assets/fluent-C4IJs8-o.js +0 -1
- srcly-0.1.32/app/static/assets/fortran-fixed-form-BZjJHVRy.js +0 -1
- srcly-0.1.32/app/static/assets/fortran-free-form-D22FLkUw.js +0 -1
- srcly-0.1.32/app/static/assets/fsharp-CXgrBDvD.js +0 -1
- srcly-0.1.32/app/static/assets/gdresource-B7Tvp0Sc.js +0 -1
- srcly-0.1.32/app/static/assets/gdscript-DTMYz4Jt.js +0 -1
- srcly-0.1.32/app/static/assets/gdshader-DkwncUOv.js +0 -1
- srcly-0.1.32/app/static/assets/genie-D0YGMca9.js +0 -1
- srcly-0.1.32/app/static/assets/gherkin-DyxjwDmM.js +0 -1
- srcly-0.1.32/app/static/assets/git-commit-F4YmCXRG.js +0 -1
- srcly-0.1.32/app/static/assets/git-rebase-r7XF79zn.js +0 -1
- srcly-0.1.32/app/static/assets/github-dark-DHJKELXO.js +0 -1
- srcly-0.1.32/app/static/assets/github-dark-default-Cuk6v7N8.js +0 -1
- srcly-0.1.32/app/static/assets/github-dark-dimmed-DH5Ifo-i.js +0 -1
- srcly-0.1.32/app/static/assets/github-dark-high-contrast-E3gJ1_iC.js +0 -1
- srcly-0.1.32/app/static/assets/github-light-DAi9KRSo.js +0 -1
- srcly-0.1.32/app/static/assets/github-light-default-D7oLnXFd.js +0 -1
- srcly-0.1.32/app/static/assets/github-light-high-contrast-BfjtVDDH.js +0 -1
- srcly-0.1.32/app/static/assets/gleam-BspZqrRM.js +0 -1
- srcly-0.1.32/app/static/assets/glimmer-js-Rg0-pVw9.js +0 -1
- srcly-0.1.32/app/static/assets/glimmer-ts-U6CK756n.js +0 -1
- srcly-0.1.32/app/static/assets/glsl-DplSGwfg.js +0 -1
- srcly-0.1.32/app/static/assets/gnuplot-DdkO51Og.js +0 -1
- srcly-0.1.32/app/static/assets/go-Dn2_MT6a.js +0 -1
- srcly-0.1.32/app/static/assets/graphql-ChdNCCLP.js +0 -1
- srcly-0.1.32/app/static/assets/groovy-gcz8RCvz.js +0 -1
- srcly-0.1.32/app/static/assets/gruvbox-dark-hard-CFHQjOhq.js +0 -1
- srcly-0.1.32/app/static/assets/gruvbox-dark-medium-GsRaNv29.js +0 -1
- srcly-0.1.32/app/static/assets/gruvbox-dark-soft-CVdnzihN.js +0 -1
- srcly-0.1.32/app/static/assets/gruvbox-light-hard-CH1njM8p.js +0 -1
- srcly-0.1.32/app/static/assets/gruvbox-light-medium-DRw_LuNl.js +0 -1
- srcly-0.1.32/app/static/assets/gruvbox-light-soft-hJgmCMqR.js +0 -1
- srcly-0.1.32/app/static/assets/hack-CaT9iCJl.js +0 -1
- srcly-0.1.32/app/static/assets/haml-B8DHNrY2.js +0 -1
- srcly-0.1.32/app/static/assets/handlebars-BL8al0AC.js +0 -1
- srcly-0.1.32/app/static/assets/haskell-Df6bDoY_.js +0 -1
- srcly-0.1.32/app/static/assets/haxe-CzTSHFRz.js +0 -1
- srcly-0.1.32/app/static/assets/hcl-BWvSN4gD.js +0 -1
- srcly-0.1.32/app/static/assets/hjson-D5-asLiD.js +0 -1
- srcly-0.1.32/app/static/assets/hlsl-D3lLCCz7.js +0 -1
- srcly-0.1.32/app/static/assets/houston-DnULxvSX.js +0 -1
- srcly-0.1.32/app/static/assets/html-derivative-BFtXZ54Q.js +0 -1
- srcly-0.1.32/app/static/assets/http-jrhK8wxY.js +0 -1
- srcly-0.1.32/app/static/assets/hurl-irOxFIW8.js +0 -1
- srcly-0.1.32/app/static/assets/hxml-Bvhsp5Yf.js +0 -1
- srcly-0.1.32/app/static/assets/hy-DFXneXwc.js +0 -1
- srcly-0.1.32/app/static/assets/imba-DGztddWO.js +0 -1
- srcly-0.1.32/app/static/assets/index--Q3ju03c.js +0 -66
- srcly-0.1.32/app/static/assets/index-DOSG0br7.css +0 -1
- srcly-0.1.32/app/static/assets/ini-BEwlwnbL.js +0 -1
- srcly-0.1.32/app/static/assets/java-CylS5w8V.js +0 -1
- srcly-0.1.32/app/static/assets/jinja-4LBKfQ-Z.js +0 -1
- srcly-0.1.32/app/static/assets/jison-wvAkD_A8.js +0 -1
- srcly-0.1.32/app/static/assets/json5-C9tS-k6U.js +0 -1
- srcly-0.1.32/app/static/assets/jsonc-Des-eS-w.js +0 -1
- srcly-0.1.32/app/static/assets/jsonl-DcaNXYhu.js +0 -1
- srcly-0.1.32/app/static/assets/jsonnet-DFQXde-d.js +0 -1
- srcly-0.1.32/app/static/assets/jssm-C2t-YnRu.js +0 -1
- srcly-0.1.32/app/static/assets/julia-C8NyazO9.js +0 -1
- srcly-0.1.32/app/static/assets/kanagawa-dragon-CkXjmgJE.js +0 -1
- srcly-0.1.32/app/static/assets/kanagawa-lotus-CfQXZHmo.js +0 -1
- srcly-0.1.32/app/static/assets/kanagawa-wave-DWedfzmr.js +0 -1
- srcly-0.1.32/app/static/assets/kdl-DV7GczEv.js +0 -1
- srcly-0.1.32/app/static/assets/kotlin-BdnUsdx6.js +0 -1
- srcly-0.1.32/app/static/assets/kusto-BvAqAH-y.js +0 -1
- srcly-0.1.32/app/static/assets/laserwave-DUszq2jm.js +0 -1
- srcly-0.1.32/app/static/assets/latex-BUKiar2Z.js +0 -1
- srcly-0.1.32/app/static/assets/lean-DP1Csr6i.js +0 -1
- srcly-0.1.32/app/static/assets/less-B1dDrJ26.js +0 -1
- srcly-0.1.32/app/static/assets/light-plus-B7mTdjB0.js +0 -1
- srcly-0.1.32/app/static/assets/liquid-DYVedYrR.js +0 -1
- srcly-0.1.32/app/static/assets/llvm-BtvRca6l.js +0 -1
- srcly-0.1.32/app/static/assets/log-2UxHyX5q.js +0 -1
- srcly-0.1.32/app/static/assets/logo-BtOb2qkB.js +0 -1
- srcly-0.1.32/app/static/assets/lua-BbnMAYS6.js +0 -1
- srcly-0.1.32/app/static/assets/luau-CXu1NL6O.js +0 -1
- srcly-0.1.32/app/static/assets/make-CHLpvVh8.js +0 -1
- srcly-0.1.32/app/static/assets/marko-CPi9NSCl.js +0 -1
- srcly-0.1.32/app/static/assets/material-theme-D5KoaKCx.js +0 -1
- srcly-0.1.32/app/static/assets/material-theme-darker-BfHTSMKl.js +0 -1
- srcly-0.1.32/app/static/assets/material-theme-lighter-B0m2ddpp.js +0 -1
- srcly-0.1.32/app/static/assets/material-theme-ocean-CyktbL80.js +0 -1
- srcly-0.1.32/app/static/assets/material-theme-palenight-Csfq5Kiy.js +0 -1
- srcly-0.1.32/app/static/assets/matlab-D7o27uSR.js +0 -1
- srcly-0.1.32/app/static/assets/mdc-DUICxH0z.js +0 -1
- srcly-0.1.32/app/static/assets/mdx-Cmh6b_Ma.js +0 -1
- srcly-0.1.32/app/static/assets/mermaid-DKYwYmdq.js +0 -1
- srcly-0.1.32/app/static/assets/min-dark-CafNBF8u.js +0 -1
- srcly-0.1.32/app/static/assets/min-light-CTRr51gU.js +0 -1
- srcly-0.1.32/app/static/assets/mipsasm-CKIfxQSi.js +0 -1
- srcly-0.1.32/app/static/assets/mojo-1DNp92w6.js +0 -1
- srcly-0.1.32/app/static/assets/monokai-D4h5O-jR.js +0 -1
- srcly-0.1.32/app/static/assets/move-Bu9oaDYs.js +0 -1
- srcly-0.1.32/app/static/assets/narrat-DRg8JJMk.js +0 -1
- srcly-0.1.32/app/static/assets/nextflow-CUEJCptM.js +0 -1
- srcly-0.1.32/app/static/assets/nginx-DknmC5AR.js +0 -1
- srcly-0.1.32/app/static/assets/night-owl-C39BiMTA.js +0 -1
- srcly-0.1.32/app/static/assets/nim-CVrawwO9.js +0 -1
- srcly-0.1.32/app/static/assets/nix-BbRYJGeE.js +0 -1
- srcly-0.1.32/app/static/assets/nord-Ddv68eIx.js +0 -1
- srcly-0.1.32/app/static/assets/nushell-C-sUppwS.js +0 -1
- srcly-0.1.32/app/static/assets/objective-c-DXmwc3jG.js +0 -1
- srcly-0.1.32/app/static/assets/objective-cpp-CLxacb5B.js +0 -1
- srcly-0.1.32/app/static/assets/ocaml-C0hk2d4L.js +0 -1
- srcly-0.1.32/app/static/assets/one-dark-pro-DVMEJ2y_.js +0 -1
- srcly-0.1.32/app/static/assets/one-light-PoHY5YXO.js +0 -1
- srcly-0.1.32/app/static/assets/pascal-D93ZcfNL.js +0 -1
- srcly-0.1.32/app/static/assets/perl-C0TMdlhV.js +0 -1
- srcly-0.1.32/app/static/assets/php-CDn_0X-4.js +0 -1
- srcly-0.1.32/app/static/assets/pkl-u5AG7uiY.js +0 -1
- srcly-0.1.32/app/static/assets/plastic-3e1v2bzS.js +0 -1
- srcly-0.1.32/app/static/assets/plsql-ChMvpjG-.js +0 -1
- srcly-0.1.32/app/static/assets/po-BTJTHyun.js +0 -1
- srcly-0.1.32/app/static/assets/poimandres-CS3Unz2-.js +0 -1
- srcly-0.1.32/app/static/assets/polar-C0HS_06l.js +0 -1
- srcly-0.1.32/app/static/assets/postcss-CXtECtnM.js +0 -1
- srcly-0.1.32/app/static/assets/powerquery-CEu0bR-o.js +0 -1
- srcly-0.1.32/app/static/assets/powershell-Dpen1YoG.js +0 -1
- srcly-0.1.32/app/static/assets/prisma-Dd19v3D-.js +0 -1
- srcly-0.1.32/app/static/assets/prolog-CbFg5uaA.js +0 -1
- srcly-0.1.32/app/static/assets/proto-DyJlTyXw.js +0 -1
- srcly-0.1.32/app/static/assets/pug-CGlum2m_.js +0 -1
- srcly-0.1.32/app/static/assets/puppet-BMWR74SV.js +0 -1
- srcly-0.1.32/app/static/assets/purescript-CklMAg4u.js +0 -1
- srcly-0.1.32/app/static/assets/qml-3beO22l8.js +0 -1
- srcly-0.1.32/app/static/assets/qmldir-C8lEn-DE.js +0 -1
- srcly-0.1.32/app/static/assets/qss-IeuSbFQv.js +0 -1
- srcly-0.1.32/app/static/assets/r-DiinP2Uv.js +0 -1
- srcly-0.1.32/app/static/assets/racket-BqYA7rlc.js +0 -1
- srcly-0.1.32/app/static/assets/raku-DXvB9xmW.js +0 -1
- srcly-0.1.32/app/static/assets/razor-WgofotgN.js +0 -1
- srcly-0.1.32/app/static/assets/red-bN70gL4F.js +0 -1
- srcly-0.1.32/app/static/assets/reg-C-SQnVFl.js +0 -1
- srcly-0.1.32/app/static/assets/regexp-CDVJQ6XC.js +0 -1
- srcly-0.1.32/app/static/assets/rel-C3B-1QV4.js +0 -1
- srcly-0.1.32/app/static/assets/riscv-BM1_JUlF.js +0 -1
- srcly-0.1.32/app/static/assets/rose-pine-BHrmToEH.js +0 -1
- srcly-0.1.32/app/static/assets/rose-pine-dawn-CnK8MTSM.js +0 -1
- srcly-0.1.32/app/static/assets/rose-pine-moon-NleAzG8P.js +0 -1
- srcly-0.1.32/app/static/assets/rosmsg-BJDFO7_C.js +0 -1
- srcly-0.1.32/app/static/assets/rst-B0xPkSld.js +0 -1
- srcly-0.1.32/app/static/assets/ruby-BvKwtOVI.js +0 -1
- srcly-0.1.32/app/static/assets/rust-B1yitclQ.js +0 -1
- srcly-0.1.32/app/static/assets/sas-cz2c8ADy.js +0 -1
- srcly-0.1.32/app/static/assets/sass-Cj5Yp3dK.js +0 -1
- srcly-0.1.32/app/static/assets/scala-C151Ov-r.js +0 -1
- srcly-0.1.32/app/static/assets/scheme-C98Dy4si.js +0 -1
- srcly-0.1.32/app/static/assets/scss-OYdSNvt2.js +0 -1
- srcly-0.1.32/app/static/assets/sdbl-DVxCFoDh.js +0 -1
- srcly-0.1.32/app/static/assets/shaderlab-Dg9Lc6iA.js +0 -1
- srcly-0.1.32/app/static/assets/shellsession-BADoaaVG.js +0 -1
- srcly-0.1.32/app/static/assets/slack-dark-BthQWCQV.js +0 -1
- srcly-0.1.32/app/static/assets/slack-ochin-DqwNpetd.js +0 -1
- srcly-0.1.32/app/static/assets/smalltalk-BERRCDM3.js +0 -1
- srcly-0.1.32/app/static/assets/snazzy-light-Bw305WKR.js +0 -1
- srcly-0.1.32/app/static/assets/solarized-dark-DXbdFlpD.js +0 -1
- srcly-0.1.32/app/static/assets/solarized-light-L9t79GZl.js +0 -1
- srcly-0.1.32/app/static/assets/solidity-BbcW6ACK.js +0 -1
- srcly-0.1.32/app/static/assets/soy-Brmx7dQM.js +0 -1
- srcly-0.1.32/app/static/assets/sparql-rVzFXLq3.js +0 -1
- srcly-0.1.32/app/static/assets/splunk-BtCnVYZw.js +0 -1
- srcly-0.1.32/app/static/assets/sql-BLtJtn59.js +0 -1
- srcly-0.1.32/app/static/assets/ssh-config-_ykCGR6B.js +0 -1
- srcly-0.1.32/app/static/assets/stata-BH5u7GGu.js +0 -1
- srcly-0.1.32/app/static/assets/stylus-BEDo0Tqx.js +0 -1
- srcly-0.1.32/app/static/assets/svelte-3Dk4HxPD.js +0 -1
- srcly-0.1.32/app/static/assets/swift-Dg5xB15N.js +0 -1
- srcly-0.1.32/app/static/assets/synthwave-84-CbfX1IO0.js +0 -1
- srcly-0.1.32/app/static/assets/system-verilog-CnnmHF94.js +0 -1
- srcly-0.1.32/app/static/assets/systemd-4A_iFExJ.js +0 -1
- srcly-0.1.32/app/static/assets/talonscript-CkByrt1z.js +0 -1
- srcly-0.1.32/app/static/assets/tasl-QIJgUcNo.js +0 -1
- srcly-0.1.32/app/static/assets/tcl-dwOrl1Do.js +0 -1
- srcly-0.1.32/app/static/assets/templ-W15q3VgB.js +0 -1
- srcly-0.1.32/app/static/assets/terraform-BETggiCN.js +0 -1
- srcly-0.1.32/app/static/assets/tex-Cppo0RY3.js +0 -1
- srcly-0.1.32/app/static/assets/tokyo-night-hegEt444.js +0 -1
- srcly-0.1.32/app/static/assets/ts-tags-zn1MmPIZ.js +0 -1
- srcly-0.1.32/app/static/assets/tsv-B_m7g4N7.js +0 -1
- srcly-0.1.32/app/static/assets/turtle-BsS91CYL.js +0 -1
- srcly-0.1.32/app/static/assets/twig-CO9l9SDP.js +0 -1
- srcly-0.1.32/app/static/assets/typespec-Df68jz8_.js +0 -1
- srcly-0.1.32/app/static/assets/typst-DHCkPAjA.js +0 -1
- srcly-0.1.32/app/static/assets/v-BcVCzyr7.js +0 -1
- srcly-0.1.32/app/static/assets/vala-CsfeWuGM.js +0 -1
- srcly-0.1.32/app/static/assets/vb-D17OF-Vu.js +0 -1
- srcly-0.1.32/app/static/assets/verilog-BQ8w6xss.js +0 -1
- srcly-0.1.32/app/static/assets/vesper-DU1UobuO.js +0 -1
- srcly-0.1.32/app/static/assets/vhdl-CeAyd5Ju.js +0 -1
- srcly-0.1.32/app/static/assets/viml-CJc9bBzg.js +0 -1
- srcly-0.1.32/app/static/assets/vitesse-black-Bkuqu6BP.js +0 -1
- srcly-0.1.32/app/static/assets/vitesse-dark-D0r3Knsf.js +0 -1
- srcly-0.1.32/app/static/assets/vitesse-light-CVO1_9PV.js +0 -1
- srcly-0.1.32/app/static/assets/vue-CCoi5OLL.js +0 -1
- srcly-0.1.32/app/static/assets/vue-html-DAAvJJDi.js +0 -1
- srcly-0.1.32/app/static/assets/vue-vine-_Ih-lPRR.js +0 -1
- srcly-0.1.32/app/static/assets/vyper-CDx5xZoG.js +0 -1
- srcly-0.1.32/app/static/assets/wasm-MzD3tlZU.js +0 -1
- srcly-0.1.32/app/static/assets/wenyan-BV7otONQ.js +0 -1
- srcly-0.1.32/app/static/assets/wgsl-Dx-B1_4e.js +0 -1
- srcly-0.1.32/app/static/assets/wikitext-BhOHFoWU.js +0 -1
- srcly-0.1.32/app/static/assets/wit-5i3qLPDT.js +0 -1
- srcly-0.1.32/app/static/assets/wolfram-lXgVvXCa.js +0 -1
- srcly-0.1.32/app/static/assets/xml-sdJ4AIDG.js +0 -1
- srcly-0.1.32/app/static/assets/xsl-CtQFsRM5.js +0 -1
- srcly-0.1.32/app/static/assets/zenscript-DVFEvuxE.js +0 -1
- srcly-0.1.32/app/static/assets/zig-VOosw3JB.js +0 -1
- srcly-0.1.32/app/static/index.html +0 -17
- srcly-0.1.32/app/static/vite.svg +0 -1
- {srcly-0.1.32 → srcly-0.1.34}/.gitignore +0 -0
- {srcly-0.1.32 → srcly-0.1.34}/README.md +0 -0
- {srcly-0.1.32 → srcly-0.1.34}/app/.gitignore +0 -0
- {srcly-0.1.32 → srcly-0.1.34}/app/__init__.py +0 -0
- {srcly-0.1.32 → srcly-0.1.34}/app/routers/files.py +0 -0
- {srcly-0.1.32 → srcly-0.1.34}/app/services/analysis_types.py +0 -0
- {srcly-0.1.32 → srcly-0.1.34}/app/services/cache.py +0 -0
- {srcly-0.1.32 → srcly-0.1.34}/app/services/css/css_analysis.py +0 -0
- {srcly-0.1.32 → srcly-0.1.34}/app/services/ipynb/ipynb_analysis.py +0 -0
- {srcly-0.1.32 → srcly-0.1.34}/app/services/markdown/markdown_analysis.py +0 -0
- {srcly-0.1.32 → srcly-0.1.34}/app/services/python/python_analysis.py +0 -0
- {srcly-0.1.32 → srcly-0.1.34}/app/services/reporting.py +0 -0
- {srcly-0.1.32 → srcly-0.1.34}/app/services/typescript/typescript_analysis.py +0 -0
- {srcly-0.1.32 → srcly-0.1.34}/app/static/assets/wasm-CG6Dc4jp.js +0 -0
|
@@ -1,16 +1,66 @@
|
|
|
1
|
+
from contextlib import asynccontextmanager
|
|
1
2
|
from fastapi import FastAPI
|
|
2
3
|
from fastapi.middleware.cors import CORSMiddleware
|
|
3
4
|
from fastapi.staticfiles import StaticFiles
|
|
4
5
|
from importlib import resources
|
|
5
6
|
from pathlib import Path
|
|
6
7
|
import os
|
|
8
|
+
import signal
|
|
9
|
+
import threading
|
|
10
|
+
|
|
11
|
+
from app.routers import analysis, files, scans
|
|
12
|
+
from app.services.scan_workers import cancel_all_scans
|
|
13
|
+
|
|
14
|
+
_SHUTDOWN_SIGNALS = (signal.SIGINT, signal.SIGTERM)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _install_scan_cancelling_signal_handlers() -> dict:
|
|
18
|
+
"""
|
|
19
|
+
Chain a "cancel running scans" step in front of the server's own
|
|
20
|
+
SIGINT/SIGTERM handlers.
|
|
21
|
+
|
|
22
|
+
Uvicorn handles Ctrl+C by waiting for in-flight requests to finish. A scan
|
|
23
|
+
can run for minutes, so without this the first Ctrl+C appears to do
|
|
24
|
+
nothing. Cancelling scans first lets their requests end promptly and the
|
|
25
|
+
normal graceful shutdown proceed. Works for `srcly` and plain `uvicorn`.
|
|
26
|
+
"""
|
|
27
|
+
if threading.current_thread() is not threading.main_thread():
|
|
28
|
+
return {}
|
|
29
|
+
|
|
30
|
+
previous: dict = {}
|
|
31
|
+
for sig in _SHUTDOWN_SIGNALS:
|
|
32
|
+
prior = signal.getsignal(sig)
|
|
33
|
+
|
|
34
|
+
def handler(signum, frame, _prior=prior):
|
|
35
|
+
cancel_all_scans()
|
|
36
|
+
if callable(_prior):
|
|
37
|
+
_prior(signum, frame)
|
|
38
|
+
else:
|
|
39
|
+
# SIG_DFL / SIG_IGN: restore it and re-deliver the signal.
|
|
40
|
+
signal.signal(signum, _prior)
|
|
41
|
+
signal.raise_signal(signum)
|
|
42
|
+
|
|
43
|
+
previous[sig] = prior
|
|
44
|
+
signal.signal(sig, handler)
|
|
45
|
+
return previous
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@asynccontextmanager
|
|
49
|
+
async def lifespan(_app: FastAPI):
|
|
50
|
+
previous = _install_scan_cancelling_signal_handlers()
|
|
51
|
+
try:
|
|
52
|
+
yield
|
|
53
|
+
finally:
|
|
54
|
+
cancel_all_scans()
|
|
55
|
+
for sig, prior in previous.items():
|
|
56
|
+
signal.signal(sig, prior)
|
|
7
57
|
|
|
8
|
-
from app.routers import analysis, files
|
|
9
58
|
|
|
10
59
|
app = FastAPI(
|
|
11
60
|
title="Srcly Server",
|
|
12
61
|
description="API for static code analysis and file serving.",
|
|
13
62
|
version="1.0.0",
|
|
63
|
+
lifespan=lifespan,
|
|
14
64
|
)
|
|
15
65
|
|
|
16
66
|
# Configure CORS
|
|
@@ -25,6 +75,7 @@ app.add_middleware(
|
|
|
25
75
|
# Include Routers
|
|
26
76
|
app.include_router(analysis.router)
|
|
27
77
|
app.include_router(files.router)
|
|
78
|
+
app.include_router(scans.router)
|
|
28
79
|
|
|
29
80
|
|
|
30
81
|
def _find_static_dir() -> str | None:
|
|
@@ -74,59 +74,3 @@ class DependencyGraph(BaseModel):
|
|
|
74
74
|
nodes: List[DependencyNode]
|
|
75
75
|
edges: List[DependencyEdge]
|
|
76
76
|
|
|
77
|
-
|
|
78
|
-
class FocusOverlayRequest(BaseModel):
|
|
79
|
-
path: str
|
|
80
|
-
sliceStartLine: int
|
|
81
|
-
sliceEndLine: int
|
|
82
|
-
focusStartLine: int | None = None
|
|
83
|
-
focusEndLine: int | None = None
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
class OverlayToken(BaseModel):
|
|
87
|
-
fileLine: int
|
|
88
|
-
startCol: int
|
|
89
|
-
endCol: int
|
|
90
|
-
category: str
|
|
91
|
-
symbolId: str
|
|
92
|
-
tooltip: str
|
|
93
|
-
definitionSnippet: str | None = None
|
|
94
|
-
definitionLine: int | None = None
|
|
95
|
-
scopeSnippet: str | None = None
|
|
96
|
-
scopeLine: int | None = None
|
|
97
|
-
scopeEndLine: int | None = None
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
class FocusOverlayResponse(BaseModel):
|
|
101
|
-
tokens: List[OverlayToken] = Field(default_factory=list)
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
# --- Scope Graph Models ---
|
|
105
|
-
|
|
106
|
-
class ScopeGraphRequest(BaseModel):
|
|
107
|
-
path: str
|
|
108
|
-
focusStartLine: int
|
|
109
|
-
focusEndLine: int
|
|
110
|
-
|
|
111
|
-
class SymbolNode(BaseModel):
|
|
112
|
-
id: str # e.g. "var:expanded:L12:C5" or just a unique ID
|
|
113
|
-
name: str
|
|
114
|
-
kind: str # "var", "func", "param", "class", "jsx_comp", etc.
|
|
115
|
-
declLine: int
|
|
116
|
-
isCaptured: bool = False
|
|
117
|
-
isDeclaredHere: bool = False
|
|
118
|
-
|
|
119
|
-
class ScopeNode(BaseModel):
|
|
120
|
-
id: str
|
|
121
|
-
kind: str # "function", "jsx", "block", "root"
|
|
122
|
-
name: str | None = None
|
|
123
|
-
startLine: int
|
|
124
|
-
endLine: int
|
|
125
|
-
children: List["ScopeNode"] = Field(default_factory=list)
|
|
126
|
-
declared: List[SymbolNode] = Field(default_factory=list)
|
|
127
|
-
captured: List[SymbolNode] = Field(default_factory=list)
|
|
128
|
-
|
|
129
|
-
class ScopeGraph(BaseModel):
|
|
130
|
-
root: ScopeNode
|
|
131
|
-
# We might want edges later, but for MVP, nested scopes + lists are enough.
|
|
132
|
-
|
|
@@ -6,9 +6,9 @@ from typing import Dict, List, Optional, Tuple
|
|
|
6
6
|
|
|
7
7
|
from app.models import Node, DependencyGraph, DependencyNode, DependencyEdge
|
|
8
8
|
from app.services import analysis, cache
|
|
9
|
+
from app.services.scan_workers import ScanCancelled
|
|
9
10
|
from app.services.typescript import typescript_analysis
|
|
10
11
|
from app.config import IGNORE_DIRS
|
|
11
|
-
from app.models import FocusOverlayRequest, FocusOverlayResponse, ScopeGraphRequest, ScopeGraph
|
|
12
12
|
|
|
13
13
|
router = APIRouter(prefix="/api/analysis", tags=["analysis"])
|
|
14
14
|
|
|
@@ -303,8 +303,18 @@ def _resolve_internal_file(spec_path: Path, file_to_id: Dict[Path, str]) -> Opti
|
|
|
303
303
|
|
|
304
304
|
return None
|
|
305
305
|
|
|
306
|
+
def _scan_or_503(target_path: Path) -> Node:
|
|
307
|
+
try:
|
|
308
|
+
return analysis.scan_codebase(target_path)
|
|
309
|
+
except ScanCancelled:
|
|
310
|
+
raise HTTPException(status_code=503, detail="Scan cancelled (server shutting down)")
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
# Scan endpoints are plain `def` on purpose: FastAPI runs them in a worker
|
|
314
|
+
# thread, so a long scan never blocks the event loop (which would also stop
|
|
315
|
+
# uvicorn from reacting to Ctrl+C).
|
|
306
316
|
@router.get("", response_model=Node)
|
|
307
|
-
|
|
317
|
+
def get_analysis(path: str = None):
|
|
308
318
|
"""
|
|
309
319
|
Get the static analysis of the codebase.
|
|
310
320
|
Returns cached result if available, otherwise triggers a scan.
|
|
@@ -321,13 +331,12 @@ async def get_analysis(path: str = None):
|
|
|
321
331
|
if cached_tree:
|
|
322
332
|
return cached_tree
|
|
323
333
|
|
|
324
|
-
|
|
325
|
-
tree = analysis.scan_codebase(target_path)
|
|
334
|
+
tree = _scan_or_503(target_path)
|
|
326
335
|
cache.save_analysis(target_path, tree)
|
|
327
336
|
return tree
|
|
328
337
|
|
|
329
338
|
@router.get("/dependencies", response_model=DependencyGraph)
|
|
330
|
-
|
|
339
|
+
def get_dependencies(path: str = None):
|
|
331
340
|
"""
|
|
332
341
|
Build a dependency graph for the specified path.
|
|
333
342
|
"""
|
|
@@ -527,11 +536,11 @@ async def get_dependencies(path: str = None):
|
|
|
527
536
|
return DependencyGraph(nodes=nodes, edges=edges)
|
|
528
537
|
|
|
529
538
|
@router.post("/refresh", response_model=Node)
|
|
530
|
-
|
|
539
|
+
def refresh_analysis():
|
|
531
540
|
"""
|
|
532
541
|
Force a re-scan of the codebase.
|
|
533
542
|
"""
|
|
534
|
-
tree =
|
|
543
|
+
tree = _scan_or_503(ROOT_PATH)
|
|
535
544
|
cache.save_analysis(ROOT_PATH, tree)
|
|
536
545
|
return tree
|
|
537
546
|
|
|
@@ -564,7 +573,7 @@ def _estimate_counts(root: Path) -> tuple[int, int]:
|
|
|
564
573
|
|
|
565
574
|
|
|
566
575
|
@router.get("/context")
|
|
567
|
-
|
|
576
|
+
def get_analysis_context():
|
|
568
577
|
"""
|
|
569
578
|
Return basic information about the current analysis root directory.
|
|
570
579
|
|
|
@@ -596,79 +605,3 @@ async def get_analysis_context():
|
|
|
596
605
|
"repo_file_count": repo_file_count,
|
|
597
606
|
"repo_folder_count": repo_folder_count,
|
|
598
607
|
}
|
|
599
|
-
|
|
600
|
-
@router.get("/data-flow")
|
|
601
|
-
async def get_data_flow(path: str):
|
|
602
|
-
"""
|
|
603
|
-
Analyze data flow for a specific file.
|
|
604
|
-
"""
|
|
605
|
-
target_path = Path(path)
|
|
606
|
-
if not target_path.exists() or not target_path.is_file():
|
|
607
|
-
raise HTTPException(status_code=404, detail="File not found")
|
|
608
|
-
|
|
609
|
-
from app.services.data_flow_analysis import DataFlowAnalyzer
|
|
610
|
-
analyzer = DataFlowAnalyzer()
|
|
611
|
-
|
|
612
|
-
try:
|
|
613
|
-
graph = analyzer.analyze_file(str(target_path))
|
|
614
|
-
return graph
|
|
615
|
-
except Exception as e:
|
|
616
|
-
print(f"Error analyzing data flow for {path}: {e}")
|
|
617
|
-
raise HTTPException(status_code=500, detail=str(e))
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
@router.post("/focus/overlay", response_model=FocusOverlayResponse)
|
|
621
|
-
async def get_focus_overlay(req: FocusOverlayRequest):
|
|
622
|
-
"""
|
|
623
|
-
Return a minimal overlay model for a single file and a focus range.
|
|
624
|
-
|
|
625
|
-
The client uses this to decorate Shiki-rendered HTML with subtle identifier
|
|
626
|
-
background highlights + provenance tooltips.
|
|
627
|
-
"""
|
|
628
|
-
target_path = Path(req.path)
|
|
629
|
-
if not target_path.exists() or not target_path.is_file():
|
|
630
|
-
raise HTTPException(status_code=404, detail="File not found")
|
|
631
|
-
|
|
632
|
-
# If no focus is provided, return an empty overlay.
|
|
633
|
-
if req.focusStartLine is None or req.focusEndLine is None:
|
|
634
|
-
return FocusOverlayResponse(tokens=[])
|
|
635
|
-
|
|
636
|
-
from app.services.focus_overlay import compute_focus_overlay
|
|
637
|
-
|
|
638
|
-
try:
|
|
639
|
-
return compute_focus_overlay(
|
|
640
|
-
file_path=str(target_path),
|
|
641
|
-
slice_start_line=req.sliceStartLine,
|
|
642
|
-
slice_end_line=req.sliceEndLine,
|
|
643
|
-
focus_start_line=req.focusStartLine,
|
|
644
|
-
focus_end_line=req.focusEndLine,
|
|
645
|
-
)
|
|
646
|
-
except HTTPException:
|
|
647
|
-
raise
|
|
648
|
-
except Exception as e:
|
|
649
|
-
print(f"Error computing focus overlay for {req.path}: {e}")
|
|
650
|
-
raise HTTPException(status_code=500, detail=str(e))
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
@router.post("/focus/scope-graph", response_model=ScopeGraph)
|
|
654
|
-
async def get_scope_graph(req: ScopeGraphRequest):
|
|
655
|
-
"""
|
|
656
|
-
Return the nested scope graph for a focused region.
|
|
657
|
-
"""
|
|
658
|
-
target_path = Path(req.path)
|
|
659
|
-
if not target_path.exists() or not target_path.is_file():
|
|
660
|
-
raise HTTPException(status_code=404, detail="File not found")
|
|
661
|
-
|
|
662
|
-
from app.services.focus_overlay import compute_scope_graph
|
|
663
|
-
|
|
664
|
-
try:
|
|
665
|
-
return compute_scope_graph(
|
|
666
|
-
file_path=str(target_path),
|
|
667
|
-
focus_start_line=req.focusStartLine,
|
|
668
|
-
focus_end_line=req.focusEndLine,
|
|
669
|
-
)
|
|
670
|
-
except HTTPException:
|
|
671
|
-
raise
|
|
672
|
-
except Exception as e:
|
|
673
|
-
print(f"Error computing scope graph for {req.path}: {e}")
|
|
674
|
-
raise HTTPException(status_code=500, detail=str(e))
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Scan jobs with live progress over Server-Sent Events.
|
|
3
|
+
|
|
4
|
+
POST /api/scans start (or join) a scan -> snapshot
|
|
5
|
+
GET /api/scans/{id} current snapshot (polling fallback)
|
|
6
|
+
GET /api/scans/{id}/events SSE stream of snapshots
|
|
7
|
+
GET /api/scans/{id}/result analysis tree once complete
|
|
8
|
+
DELETE /api/scans/{id} cancel -> snapshot
|
|
9
|
+
|
|
10
|
+
SSE rather than WebSockets: progress only flows server -> client, cancel is
|
|
11
|
+
a plain HTTP call, and EventSource gives reconnects for free. Each stream
|
|
12
|
+
sends the full snapshot (not deltas), so a reconnecting client is always
|
|
13
|
+
consistent without replay. Snapshots are checked every ``STREAM_TICK_SECONDS``
|
|
14
|
+
and sent when the job's version changed (which throttles bursts), or every
|
|
15
|
+
``REFRESH_SECONDS`` regardless so elapsed and in-flight timers keep moving
|
|
16
|
+
while a slow file holds things up. The refresh doubles as a keep-alive.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import asyncio
|
|
22
|
+
import json
|
|
23
|
+
import time
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
from typing import Optional
|
|
26
|
+
|
|
27
|
+
from fastapi import APIRouter, HTTPException, Request
|
|
28
|
+
from fastapi.responses import StreamingResponse
|
|
29
|
+
from pydantic import BaseModel
|
|
30
|
+
|
|
31
|
+
from app.models import Node
|
|
32
|
+
from app.routers import analysis as analysis_router
|
|
33
|
+
from app.services.scan_jobs import TERMINAL_PHASES, ScanJob, manager
|
|
34
|
+
|
|
35
|
+
router = APIRouter(prefix="/api/scans", tags=["scans"])
|
|
36
|
+
|
|
37
|
+
STREAM_TICK_SECONDS = 0.1
|
|
38
|
+
REFRESH_SECONDS = 1.0
|
|
39
|
+
CLIENT_RETRY_MS = 2000
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class StartScanRequest(BaseModel):
|
|
43
|
+
path: Optional[str] = None
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _job_or_404(job_id: str) -> ScanJob:
|
|
47
|
+
job = manager.get(job_id)
|
|
48
|
+
if job is None:
|
|
49
|
+
raise HTTPException(status_code=404, detail="Scan not found")
|
|
50
|
+
return job
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _sse(event: str, data: dict, event_id: Optional[int] = None) -> str:
|
|
54
|
+
lines = [f"event: {event}"]
|
|
55
|
+
if event_id is not None:
|
|
56
|
+
lines.append(f"id: {event_id}")
|
|
57
|
+
lines.append(f"data: {json.dumps(data, separators=(',', ':'))}")
|
|
58
|
+
return "\n".join(lines) + "\n\n"
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@router.post("", status_code=202)
|
|
62
|
+
def start_scan(req: StartScanRequest) -> dict:
|
|
63
|
+
target = Path(req.path) if req.path else analysis_router.ROOT_PATH
|
|
64
|
+
if not target.exists():
|
|
65
|
+
raise HTTPException(status_code=404, detail=f"Path not found: {target}")
|
|
66
|
+
return manager.start(target).snapshot()
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@router.get("/{job_id}")
|
|
70
|
+
def get_scan(job_id: str) -> dict:
|
|
71
|
+
return _job_or_404(job_id).snapshot()
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@router.delete("/{job_id}")
|
|
75
|
+
def cancel_scan(job_id: str) -> dict:
|
|
76
|
+
job = _job_or_404(job_id)
|
|
77
|
+
job.cancel()
|
|
78
|
+
return job.snapshot()
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@router.get("/{job_id}/result", response_model=Node)
|
|
82
|
+
def get_scan_result(job_id: str):
|
|
83
|
+
job = _job_or_404(job_id)
|
|
84
|
+
if job.phase != "complete" or job.result is None:
|
|
85
|
+
raise HTTPException(status_code=409, detail=f"Scan is {job.phase}, not complete")
|
|
86
|
+
return job.result
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@router.get("/{job_id}/events")
|
|
90
|
+
async def stream_scan(job_id: str, request: Request) -> StreamingResponse:
|
|
91
|
+
job = _job_or_404(job_id)
|
|
92
|
+
|
|
93
|
+
async def events():
|
|
94
|
+
yield f"retry: {CLIENT_RETRY_MS}\n\n"
|
|
95
|
+
sent_version = -1
|
|
96
|
+
last_write = time.monotonic()
|
|
97
|
+
while True:
|
|
98
|
+
snapshot = job.snapshot()
|
|
99
|
+
stale = time.monotonic() - last_write >= REFRESH_SECONDS
|
|
100
|
+
if snapshot["version"] != sent_version or stale:
|
|
101
|
+
sent_version = snapshot["version"]
|
|
102
|
+
last_write = time.monotonic()
|
|
103
|
+
if snapshot["phase"] in TERMINAL_PHASES:
|
|
104
|
+
yield _sse(snapshot["phase"], snapshot, sent_version)
|
|
105
|
+
return
|
|
106
|
+
yield _sse("progress", snapshot, sent_version)
|
|
107
|
+
|
|
108
|
+
if await request.is_disconnected():
|
|
109
|
+
return
|
|
110
|
+
await asyncio.sleep(STREAM_TICK_SECONDS)
|
|
111
|
+
|
|
112
|
+
return StreamingResponse(
|
|
113
|
+
events(),
|
|
114
|
+
media_type="text/event-stream",
|
|
115
|
+
headers={
|
|
116
|
+
"Cache-Control": "no-cache",
|
|
117
|
+
"Connection": "keep-alive",
|
|
118
|
+
# Disable proxy buffering (nginx and friends) so events flush immediately.
|
|
119
|
+
"X-Accel-Buffering": "no",
|
|
120
|
+
},
|
|
121
|
+
)
|
|
@@ -10,6 +10,12 @@ from pathlib import Path
|
|
|
10
10
|
import uvicorn
|
|
11
11
|
|
|
12
12
|
from app.services import reporting
|
|
13
|
+
from app.services.scan_workers import ScanCancelled
|
|
14
|
+
|
|
15
|
+
# Seconds uvicorn waits for open connections after Ctrl+C before closing them.
|
|
16
|
+
GRACEFUL_SHUTDOWN_SECONDS = 3
|
|
17
|
+
# Conventional exit status for a process stopped by SIGINT.
|
|
18
|
+
EXIT_INTERRUPTED = 130
|
|
13
19
|
|
|
14
20
|
|
|
15
21
|
def _open_browser_later(url: str, delay: float = 1.0) -> None:
|
|
@@ -54,8 +60,17 @@ def _find_repo_root(start_path: str) -> str:
|
|
|
54
60
|
|
|
55
61
|
|
|
56
62
|
def main(argv: list[str] | None = None) -> None:
|
|
63
|
+
"""CLI entry point. Ctrl+C exits promptly with status 130 and no traceback."""
|
|
64
|
+
try:
|
|
65
|
+
_main(argv)
|
|
66
|
+
except (KeyboardInterrupt, ScanCancelled):
|
|
67
|
+
print("\nStopped.", file=sys.stderr, flush=True)
|
|
68
|
+
raise SystemExit(EXIT_INTERRUPTED)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _main(argv: list[str] | None = None) -> None:
|
|
57
72
|
"""
|
|
58
|
-
|
|
73
|
+
Parse arguments and dispatch.
|
|
59
74
|
|
|
60
75
|
- With no path argument, uses the enclosing Git repo root as the codebase root.
|
|
61
76
|
- If a path is provided (including "."), uses that as the codebase root.
|
|
@@ -127,6 +142,7 @@ def main(argv: list[str] | None = None) -> None:
|
|
|
127
142
|
host=args.host,
|
|
128
143
|
port=port,
|
|
129
144
|
reload=False,
|
|
145
|
+
timeout_graceful_shutdown=GRACEFUL_SHUTDOWN_SECONDS,
|
|
130
146
|
)
|
|
131
147
|
|
|
132
148
|
|