@astralyn/sash 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +184 -0
- package/LICENSE +21 -0
- package/README.md +71 -0
- package/THIRD_PARTY_NOTICES.md +54 -0
- package/dist/api.js +103 -0
- package/dist/browser.js +25 -0
- package/dist/cli-errors.js +15 -0
- package/dist/cli.js +122 -0
- package/dist/commands/lifecycle.js +50 -0
- package/dist/commands/logs.js +54 -0
- package/dist/commands/shared.js +8 -0
- package/dist/commands/status.js +39 -0
- package/dist/commands/update.js +5 -0
- package/dist/commands/upgrade.js +43 -0
- package/dist/commands/web.js +33 -0
- package/dist/contracts.js +412 -0
- package/dist/core-config-validation.js +72 -0
- package/dist/core-install-record.js +55 -0
- package/dist/core-install-transaction.js +114 -0
- package/dist/core-update-coordination.js +105 -0
- package/dist/core-update-service.js +245 -0
- package/dist/core-update.js +804 -0
- package/dist/core-version.js +18 -0
- package/dist/core.js +206 -0
- package/dist/daemon/app.js +157 -0
- package/dist/daemon/context.js +40 -0
- package/dist/daemon/entry.js +67 -0
- package/dist/daemon/errors.js +47 -0
- package/dist/daemon/handlers/core.js +17 -0
- package/dist/daemon/handlers/daemon.js +66 -0
- package/dist/daemon/handlers/profiles.js +57 -0
- package/dist/daemon/handlers/proxy.js +12 -0
- package/dist/daemon/handlers/settings.js +48 -0
- package/dist/daemon/router.js +312 -0
- package/dist/daemon/scheduler.js +33 -0
- package/dist/daemon/server.js +121 -0
- package/dist/daemon-auth.js +71 -0
- package/dist/daemon-client.js +55 -0
- package/dist/daemon-entry.js +13 -0
- package/dist/daemon-http.js +93 -0
- package/dist/daemon-lifecycle.js +378 -0
- package/dist/daemon-proxy.js +176 -0
- package/dist/daemon-static.js +106 -0
- package/dist/daemon.js +3 -0
- package/dist/error-utils.js +14 -0
- package/dist/fs-atomic.js +123 -0
- package/dist/github.js +187 -0
- package/dist/http.js +282 -0
- package/dist/json-shape.js +17 -0
- package/dist/log-follow.js +280 -0
- package/dist/log.js +17 -0
- package/dist/managed-state-transaction.js +377 -0
- package/dist/mihomo-config.js +334 -0
- package/dist/node-version-guard.js +8 -0
- package/dist/offline-mutation.js +58 -0
- package/dist/paths.js +57 -0
- package/dist/process.js +572 -0
- package/dist/profile-migration.js +101 -0
- package/dist/profile-service.js +667 -0
- package/dist/profiles.js +182 -0
- package/dist/runtime-lifecycle.js +205 -0
- package/dist/runtime-owner.js +87 -0
- package/dist/runtime-recovery.js +31 -0
- package/dist/sash-client.js +163 -0
- package/dist/settings-service.js +242 -0
- package/dist/settings.js +314 -0
- package/dist/state-lock.js +331 -0
- package/dist/status.js +279 -0
- package/dist/supervisor.js +301 -0
- package/dist/sysproxy/common.js +99 -0
- package/dist/sysproxy/darwin.js +287 -0
- package/dist/sysproxy/factory.js +71 -0
- package/dist/sysproxy/gnome.js +181 -0
- package/dist/sysproxy/legacy.js +58 -0
- package/dist/sysproxy/snapshot.js +284 -0
- package/dist/sysproxy/types.js +24 -0
- package/dist/sysproxy/windows.js +216 -0
- package/dist/sysproxy.js +8 -0
- package/dist/system-proxy-manager.js +460 -0
- package/dist/tun-guidance.js +11 -0
- package/dist/ui/assets/05129d5ad91a6181d272cc3d031363d6-DJkDXMrK.woff2 +0 -0
- package/dist/ui/assets/0526ebf94418638dfd5b6cd9b06c07d4-BzWe4Lji.woff2 +0 -0
- package/dist/ui/assets/053f2647a78f659f6f8a3ae8436bd265-BocXYDl5.woff2 +0 -0
- package/dist/ui/assets/078f666863424d3fd2d98e3ea528704c-DQ_QCvOt.woff2 +0 -0
- package/dist/ui/assets/07f3ff379663ffa0222c12f241421dd9-Cma7oDlr.woff2 +0 -0
- package/dist/ui/assets/08427b8d865853bbd7c439321b26495f-CJmC8fWx.woff2 +0 -0
- package/dist/ui/assets/095224e4db29d40c54098da02b266b0a-DjsP0nQn.woff2 +0 -0
- package/dist/ui/assets/0ada18ca45940b08accde5c61ee871e3-E1BZD3RX.woff2 +0 -0
- package/dist/ui/assets/0be242294f7d791af850c6df38ac78a0-2cL6Ntwf.woff2 +0 -0
- package/dist/ui/assets/0bf18c938d0c27cce06c39f855405fdb-CruLZZZJ.woff2 +0 -0
- package/dist/ui/assets/0cfb3e9f4edf60839c37dc83e6f75ea3-DzAtRHvi.woff2 +0 -0
- package/dist/ui/assets/0fbbd340d0467dfe3101d58846f30cb0-gtFcO250.woff2 +0 -0
- package/dist/ui/assets/1063fa2dc7334d1f383e30824e016eae-B7U-kD-0.woff2 +0 -0
- package/dist/ui/assets/109e26e62ddc85d946e699464afbece7-BLnUjAEV.woff2 +0 -0
- package/dist/ui/assets/12a443b0ae9748a993b5395f8fad412b-Btjff2Lz.woff2 +0 -0
- package/dist/ui/assets/135c4621de7a4d85d852e03d416f13ee-DdSuW36e.woff2 +0 -0
- package/dist/ui/assets/13aa415469ee761c968749b1c0c56ec6-Dwe-Wa_w.woff2 +0 -0
- package/dist/ui/assets/1435b1ad447bd9c0e8bc52035c9dcfa8-C05ldXtw.woff2 +0 -0
- package/dist/ui/assets/175e0915aa874e7446b79b6bc9b00721-Dp0-l77L.woff2 +0 -0
- package/dist/ui/assets/1876e7d02187f006e2b7f769391c3089-CNjpRdap.woff2 +0 -0
- package/dist/ui/assets/1886dc284b3b845e3b5fe04aa2107276-Bh_wHwL1.woff2 +0 -0
- package/dist/ui/assets/19a3dda6611aaf5d787403bd1159fe15-zoEBvdpA.woff2 +0 -0
- package/dist/ui/assets/1afa74e14da193125e45d4783d358a4f-BgjSTENX.woff2 +0 -0
- package/dist/ui/assets/1cd26a6a6d784720b4a43053e9058bcf-TAP3dvNi.woff2 +0 -0
- package/dist/ui/assets/1ce19bddc3beab385a863b1e05685678-BCdXitZw.woff2 +0 -0
- package/dist/ui/assets/1d3459ac6578c80502bdb876a4824a93-CSwdnEGP.woff2 +0 -0
- package/dist/ui/assets/1d479e364dab4af01c539f0a6a7ac036-ZFI-djGt.woff2 +0 -0
- package/dist/ui/assets/1dcea2408d17fcec4197adce50f8b32d-CnK2xXR-.woff2 +0 -0
- package/dist/ui/assets/1e855385699999e24a33f7df8aed0489-DGFHkDVQ.woff2 +0 -0
- package/dist/ui/assets/1f55944b8f7972580e3029694462a6b0-CB7ogVJ9.woff2 +0 -0
- package/dist/ui/assets/1f96eda70929089762dc3c4feb33f9c8-DtSQ6YOf.woff2 +0 -0
- package/dist/ui/assets/1fb275532caadc746455881b31da27c4-BHFTsg74.woff2 +0 -0
- package/dist/ui/assets/20e679bdc00d06f802ea71fd9154b284-D9gRjq_v.woff2 +0 -0
- package/dist/ui/assets/25aff66c4a9f8cb801cbd525f4a923e4-Dgi87hP7.woff2 +0 -0
- package/dist/ui/assets/28e6700380b023b23e77f2d75aee624f-b19D855Q.woff2 +0 -0
- package/dist/ui/assets/2a1c50aa9cd99ce3a0197f5d49948e63-DrztdXCB.woff2 +0 -0
- package/dist/ui/assets/2b4e0ae0bf3af610b97717dfd66a012d-CBoapvha.woff2 +0 -0
- package/dist/ui/assets/2be2b7a672b616bc2f07059a67bfaad8-BUR0aNML.woff2 +0 -0
- package/dist/ui/assets/2e0a358ada78ea98ec8ba86002a27eac-DvgbSr9v.woff2 +0 -0
- package/dist/ui/assets/2e1cf63a2a22a50516c49b1dc521ea8a-CmltIxig.woff2 +0 -0
- package/dist/ui/assets/2e4481801925fcb6573c4337c73f15d7-DYOoCk-m.woff2 +0 -0
- package/dist/ui/assets/2fe5e634c568cd874f94d24b8f02bfd1-DcEHY35G.woff2 +0 -0
- package/dist/ui/assets/309c33f63f29887fb557115aa24d4bb5-CUgqrXIj.woff2 +0 -0
- package/dist/ui/assets/3158f45c959e1b346b0bbd6f3193dbf0-kimcClqp.woff2 +0 -0
- package/dist/ui/assets/316adbc8037a36f8d804637b053bbe5e-CjbZmJf9.woff2 +0 -0
- package/dist/ui/assets/319e9631bc29fc7235b70fc9aac43cd9-IV6yZuHZ.woff2 +0 -0
- package/dist/ui/assets/32054f9bb88d9431b1cdf023f770e614-CYrpCgqK.woff2 +0 -0
- package/dist/ui/assets/3245fa1aac6338c1d9d04befb7c346f8-CzvPhVPA.woff2 +0 -0
- package/dist/ui/assets/326c730f98afa476057957f2743c81e2-GPHJ3Fpu.woff2 +0 -0
- package/dist/ui/assets/33885558dc783bf9bae6c5d4668fef07-D33us0yR.woff2 +0 -0
- package/dist/ui/assets/33cd11f5e61556e4618801c8231f2744-CoR_Tv3X.woff2 +0 -0
- package/dist/ui/assets/34a170749cb7e6c5851acc510882af6b-BCZwsihk.woff2 +0 -0
- package/dist/ui/assets/3538de2d387591258d7e9908aad803b9-CewOkERZ.woff2 +0 -0
- package/dist/ui/assets/38d7b029c036080067a2e94211098171-CettR5Qv.woff2 +0 -0
- package/dist/ui/assets/393f47efeefc9f9e9cca83eb10a82c69-Brhz4z1y.woff2 +0 -0
- package/dist/ui/assets/396a51f2b274d01f6ba606a94f3e5fe2-DgRk-jtI.woff2 +0 -0
- package/dist/ui/assets/3991bf955d4c925e84b4a6542760d4d5-II4HtRkQ.woff2 +0 -0
- package/dist/ui/assets/39b6cb6ae8d1ea0e9832b88d2c21ef99-CbJm-q6b.woff2 +0 -0
- package/dist/ui/assets/3a1133157fbd259f1a51aaa9f8418a26-3FmUjjvi.woff2 +0 -0
- package/dist/ui/assets/3a34cff07dca7a65ed2486be19229290-B7ml2yBN.woff2 +0 -0
- package/dist/ui/assets/3c339dd7c27e6b8a06eea431733e076f-dGQqXaEj.woff2 +0 -0
- package/dist/ui/assets/3d074ea67f60953b816ce7cf5d87a980-B5VkPOmg.woff2 +0 -0
- package/dist/ui/assets/3d38f54dcf4a9f4a50165d5849becf71-CVDtFSZn.woff2 +0 -0
- package/dist/ui/assets/3e3fc9a19a27f94e4ff1b815b55b2671-Ci736GLi.woff2 +0 -0
- package/dist/ui/assets/3e8abb1f606f29e2f0a8a8a6b702d263-C5sOSf5L.woff2 +0 -0
- package/dist/ui/assets/3f0ab4e1cd99bdbfdf9acbc8f4bcb9ce-0DcdTLxV.woff2 +0 -0
- package/dist/ui/assets/3f30c0482650345c981d66920f56ab61-CTD2CsZI.woff2 +0 -0
- package/dist/ui/assets/41a9aa12ad9a60758b304c1a998fb781-BTQUDLf1.woff2 +0 -0
- package/dist/ui/assets/424010b2bc5cee540b92b28da49b61bf-DT8upW2M.woff2 +0 -0
- package/dist/ui/assets/42fbdafff2d44b76c6665d51bb2cee9d-_UrojhXD.woff2 +0 -0
- package/dist/ui/assets/4341db13c311b3cb0eaaf6b4c71b89e5-CzjA7MzG.woff2 +0 -0
- package/dist/ui/assets/434db5a796128131fde78d4389d14b71-AeNdWKua.woff2 +0 -0
- package/dist/ui/assets/43cb51fc70a15274d49e2d956c5ec3b4-BQbufd96.woff2 +0 -0
- package/dist/ui/assets/43e79de1c7815f21efbc966ff96fcbd6-DlCQvOsQ.woff2 +0 -0
- package/dist/ui/assets/43faf6a8a3cde1088bd5f7c830890a89-SvDzF3nA.woff2 +0 -0
- package/dist/ui/assets/44731ae21d7690ce538d0f8242755833-CEmu9Z88.woff2 +0 -0
- package/dist/ui/assets/45324b2a64cc0c0db797d5c09e239d34-BuFudtvM.woff2 +0 -0
- package/dist/ui/assets/4584df05d00aa75ae6e1f850fdc9df1d-GNHt6xwM.woff2 +0 -0
- package/dist/ui/assets/48d539539e98481fb511a761031c59fd-Cd9gdi8B.woff2 +0 -0
- package/dist/ui/assets/49a1fb9fc6a35796b2e0d3539088ede8-CD1cRDOy.woff2 +0 -0
- package/dist/ui/assets/4bec02eb0c2371e59bfb7d41a141ea1e-CmBnNPo0.woff2 +0 -0
- package/dist/ui/assets/4c5a81ef5683f6fbf9935e640de804e7-B4wQxzVs.woff2 +0 -0
- package/dist/ui/assets/4f11c9c75e86214b7f69ddee3a9f8076-DHsQ2xpE.woff2 +0 -0
- package/dist/ui/assets/50434b8ff88e27101d1c67ba953ee873-PH_hIqxu.woff2 +0 -0
- package/dist/ui/assets/5107fd5153d52c6a3f8d43ff214dd6c2-ls3eo5Qd.woff2 +0 -0
- package/dist/ui/assets/520cb7d3683dee99e767650efc0aa5b1-Lkd4z-9S.woff2 +0 -0
- package/dist/ui/assets/5455087df2f091b7bf3d38c5a102a2e7-QNkiOxwe.woff2 +0 -0
- package/dist/ui/assets/556450fdf0978d329cf8277e3b71dcdb-CtypGAPw.woff2 +0 -0
- package/dist/ui/assets/5a1b5c1b71cad50c51a79a7f4825e6e6-DQA407Ec.woff2 +0 -0
- package/dist/ui/assets/5ba02e809d2ef4f7639178e7a7d654c0-Dty0omtl.woff2 +0 -0
- package/dist/ui/assets/5bd654afe48e094f95e5142e4eaadc75-D9pUq71R.woff2 +0 -0
- package/dist/ui/assets/5c4c3ca0a3f4115879a61a96d02e3efc-RcVLBj31.woff2 +0 -0
- package/dist/ui/assets/5c737d740638e8d764f12c1e5c1cd144-D2Npjq-N.woff2 +0 -0
- package/dist/ui/assets/5e78019102102aa5741aa591313fd5b5-V7Azrw-2.woff2 +0 -0
- package/dist/ui/assets/6287cacac64e1849941f7b7d7e6316c5-DzkbPOID.woff2 +0 -0
- package/dist/ui/assets/643df7194160d54edc48d16d76e5ba74-BKmmIRjw.woff2 +0 -0
- package/dist/ui/assets/65d3b214c69ff6786fd1cab06f8394b7-B0cRUJAr.woff2 +0 -0
- package/dist/ui/assets/6698103c11bfe260d44fcf00c6d8b623-DVWYOeyb.woff2 +0 -0
- package/dist/ui/assets/692e07b3a609a8f1a21c15c45631f348-DJW5LcZH.woff2 +0 -0
- package/dist/ui/assets/694e87456af181279c2bfd551aa3c6e3-Cu7kRuJB.woff2 +0 -0
- package/dist/ui/assets/6b88066bdc7a6e8bc823128dbd0f6f3b-BG38UZSq.woff2 +0 -0
- package/dist/ui/assets/6d6f36f15381826d113832bfdd41b021-Bgh7YPiq.woff2 +0 -0
- package/dist/ui/assets/6e22361162bd0a5cd189ca3c98d23d52-DqnptHqF.woff2 +0 -0
- package/dist/ui/assets/6e884dd501b8da24ecd37425a9b3d51d-BVNqra8s.woff2 +0 -0
- package/dist/ui/assets/71200dbc7497fbafa28db5cfeae74acb-B6xMqoC6.woff2 +0 -0
- package/dist/ui/assets/72a85441a9757146440e3985f3b02075-CzTSlCPZ.woff2 +0 -0
- package/dist/ui/assets/7472d7e02045dea3ff288eac850e38eb-CboJEy4X.woff2 +0 -0
- package/dist/ui/assets/795ffa953bea666763ae79ac88aa7e5a-C6UsoDmi.woff2 +0 -0
- package/dist/ui/assets/7aa8647d6c52d249169b73417aa8e444-kJejhQ0-.woff2 +0 -0
- package/dist/ui/assets/7be6b794fc233ccd8b5769496301313a-Ba2iZC0a.woff2 +0 -0
- package/dist/ui/assets/7c0748f04d52086809c9f551c5aaded7-DSzO_ybF.woff2 +0 -0
- package/dist/ui/assets/7c858bb53a7652227fab8cf9753d3e1f-Bi9fbvIF.woff2 +0 -0
- package/dist/ui/assets/7e7026091b4e6061b05963bdb3a7f944-CrmRDJpc.woff2 +0 -0
- package/dist/ui/assets/7f80369a7479d568ab8bf492000282bd-CqcI5ZOp.woff2 +0 -0
- package/dist/ui/assets/834ce2f2571aaa26e47a618c4ad55e58-DZfWJrxo.woff2 +0 -0
- package/dist/ui/assets/834faab602d9c795341bc7dfd6578fe5-B1VeBVWh.woff2 +0 -0
- package/dist/ui/assets/8513a8eea35b460a047a16a87c36f8bb-H259w_6b.woff2 +0 -0
- package/dist/ui/assets/8699e8a36465bf46671dc28ce24e188c-YlkPpQ6h.woff2 +0 -0
- package/dist/ui/assets/86ae8881eae9eac7e9c0182c8ce6c627-Ch2r3LNs.woff2 +0 -0
- package/dist/ui/assets/86b3ae1be58d76f7147793f09264451b-CpgTfcn-.woff2 +0 -0
- package/dist/ui/assets/8769f8d7d45adcfcbd3f80460d22720d-DV_yqcTo.woff2 +0 -0
- package/dist/ui/assets/87bb0ceea9f5515490c832bdf6ebf641-CJpxd8e6.woff2 +0 -0
- package/dist/ui/assets/8928b9a50b5efbaaafa039674a3c7301-C-42WRD6.woff2 +0 -0
- package/dist/ui/assets/899f2c2a36cbeac473a7caeeaabc46b1-xrIcXJXU.woff2 +0 -0
- package/dist/ui/assets/8a075d4043e6bf66740808b492186438-Dq0346Qm.woff2 +0 -0
- package/dist/ui/assets/8b86676936792154c53205ab5d90b797-BGH_CGsg.woff2 +0 -0
- package/dist/ui/assets/8c63ca479653200a0e12f428242e1171-CsBTcPwY.woff2 +0 -0
- package/dist/ui/assets/8c9c7ff8342f5fed60e73c4a7ad67325-CGSYpt52.woff2 +0 -0
- package/dist/ui/assets/8ca655fcc5d9cf33d49815e231f3f356-b8PyEeZg.woff2 +0 -0
- package/dist/ui/assets/8e96f0f9385ef826f900587d0fc18b3d-D7GIG6Uq.woff2 +0 -0
- package/dist/ui/assets/8ea917252cf2c1e664f06ee5badffeea-CDZ1AFr0.woff2 +0 -0
- package/dist/ui/assets/8f42f890fc37e292017446af85af2745-Qx6oGI0z.woff2 +0 -0
- package/dist/ui/assets/8f9589e71c53bb753749a9c412f15c01-BVR5mPJl.woff2 +0 -0
- package/dist/ui/assets/90e70b52a3cc2491bbb2b4c0caf0ae7e-BdwPu67j.woff2 +0 -0
- package/dist/ui/assets/92ac4ca42fe050c1e680c585551abd9c-CXLyuj7l.woff2 +0 -0
- package/dist/ui/assets/92c12bb794c491930fd06f3f97299807-u_0ognJ0.woff2 +0 -0
- package/dist/ui/assets/92f0b4d2bb274a801aac10fc34c1e0cc-DFHB6dyu.woff2 +0 -0
- package/dist/ui/assets/93247aebd628367f5518eafdb2770ce9-o1N4zvTO.woff2 +0 -0
- package/dist/ui/assets/940446ea160995af66a9ee223cc3d4d1-C-_7aPYO.woff2 +0 -0
- package/dist/ui/assets/94be430de618453b6ae27bd366827ba2-1YYf1n5N.woff2 +0 -0
- package/dist/ui/assets/98cd7d62095e466e55885b34f63c86f1-z33YCk0X.woff2 +0 -0
- package/dist/ui/assets/99049fd5cec5894cafe2a1e5e13d1e03-DNrg8nIk.woff2 +0 -0
- package/dist/ui/assets/99e2752b7351aaa7681b4d91db80e89a-b3hi7miY.woff2 +0 -0
- package/dist/ui/assets/9d1b670cbfd64716ac685dea26237d95-DAZ9U6es.woff2 +0 -0
- package/dist/ui/assets/CodeEditorModal-6m0TJWyF.css +1 -0
- package/dist/ui/assets/CodeEditorModal-CFEnWsyh.js +14 -0
- package/dist/ui/assets/ConnectionsView-DHo6yvU6.css +1 -0
- package/dist/ui/assets/ConnectionsView-DNGmZBSU.js +1 -0
- package/dist/ui/assets/LogsView-BoeE92VF.css +1 -0
- package/dist/ui/assets/LogsView-fSiaxQ13.js +1 -0
- package/dist/ui/assets/PaginationFooter-B3kHzRfB.js +1 -0
- package/dist/ui/assets/PaginationFooter-BNkpcNoB.css +1 -0
- package/dist/ui/assets/ProfileEditorDialog-BydoZthX.js +1 -0
- package/dist/ui/assets/ProfilesView-Btc1DOxE.js +2 -0
- package/dist/ui/assets/ProfilesView-ByqdFNHN.css +1 -0
- package/dist/ui/assets/RulesView-CHyynVqA.css +1 -0
- package/dist/ui/assets/RulesView-D9vZBiJ1.js +1 -0
- package/dist/ui/assets/SettingsFileDialog-CNFEAVs4.js +1 -0
- package/dist/ui/assets/SettingsView-BlDhZkXQ.js +2 -0
- package/dist/ui/assets/SettingsView-CngS3vBM.css +1 -0
- package/dist/ui/assets/a0f2d41645a113d821e7ff0891daad1e-XK0rDAci.woff2 +0 -0
- package/dist/ui/assets/a1d1345d8407dd6169f39c28ff6b1708-Bn7hMmv8.woff2 +0 -0
- package/dist/ui/assets/a29f51b4ebb6776e43e7492a036be28e-CrAKKz8u.woff2 +0 -0
- package/dist/ui/assets/a3252ac86b2c4e88bfb9baecabcad817-qnA_3LNE.woff2 +0 -0
- package/dist/ui/assets/a3d63a61e7eb2bb497dbc2da65bd0873-BRMJAm0m.woff2 +0 -0
- package/dist/ui/assets/a45eea933252dbe3529f30a0323c2661-D4kBOrXR.woff2 +0 -0
- package/dist/ui/assets/a4886c4ebed8c8b1f8a87b5b491a39aa-CBQdRqpF.woff2 +0 -0
- package/dist/ui/assets/a50b4ab745c7676d9d3ceb3c0a023a3d-CW990pgI.woff2 +0 -0
- package/dist/ui/assets/a5851ae4d7e58996d8bd681f80d4727c-BBytdb0t.woff2 +0 -0
- package/dist/ui/assets/a5cdaa96bf7d1db6c671432f9ab7bccb-slKJjgfX.woff2 +0 -0
- package/dist/ui/assets/a640dd2e962cf72c7fcf76234c973303-CUCWUDBY.woff2 +0 -0
- package/dist/ui/assets/a67e457bbd366aef5104ce743a2cf6d4-DkE8fLlE.woff2 +0 -0
- package/dist/ui/assets/a6c3f2257315d45aa212bd2e86a02852-CxDbXnZV.woff2 +0 -0
- package/dist/ui/assets/a73399a40a5f81fb7314b8b9a10cd221-DRAvJPKI.woff2 +0 -0
- package/dist/ui/assets/a73a64b0941803d9d807b9ec082df641-BbPpPyy5.woff2 +0 -0
- package/dist/ui/assets/a73d1a0a1dfd4f4234f7a0649ece4da4-BHeqktY9.woff2 +0 -0
- package/dist/ui/assets/a740044e77a400a450e71fcdea55523f-Co5hpBNK.woff2 +0 -0
- package/dist/ui/assets/a7669b29f404662c179c71b87e67c8d2-CVnuBgHy.woff2 +0 -0
- package/dist/ui/assets/a964a4def15811e91b05a91740a1cf37-CzcDfCRn.woff2 +0 -0
- package/dist/ui/assets/aac3f69132da164921fda75d2d90bfb7-B6rUZQ4_.woff2 +0 -0
- package/dist/ui/assets/aacd263c3bb4ba163a4d5079c159680a-DtrDLFlk.woff2 +0 -0
- package/dist/ui/assets/ac62ec881ded2cdb450e542b396e24d0-CRlVJvPa.woff2 +0 -0
- package/dist/ui/assets/aebcb9b7d4b174ab5a4b911206f24036-BuleSpgP.woff2 +0 -0
- package/dist/ui/assets/af70712dadc05c7b18d248996a5ab0a9-gCZSZGzb.woff2 +0 -0
- package/dist/ui/assets/afbd3a8a4cfaa4561b7848c28bc6c4f8-DgnqceEw.woff2 +0 -0
- package/dist/ui/assets/b02d3867e6e2ea921d2845b44c4a5a31-C9v4FrqD.woff2 +0 -0
- package/dist/ui/assets/b1b22825b930688403337d4fd7bbba62-vmMs2pJf.woff2 +0 -0
- package/dist/ui/assets/b3206c979bfb28d12d7ad6df58e74b9f-X1PqLaCR.woff2 +0 -0
- package/dist/ui/assets/b6280e42d4f11dcab70594f9d6774936-CtezImLG.woff2 +0 -0
- package/dist/ui/assets/b71b8cd1a70405c3dbc0e7a44055ca9a-Cd6cIOEr.woff2 +0 -0
- package/dist/ui/assets/b9198c4f220a4ca075f8daa4db529e50-Bgc7zlEt.woff2 +0 -0
- package/dist/ui/assets/baad304a58d7d42d0af29235c5e9490b-BQZYwARB.woff2 +0 -0
- package/dist/ui/assets/bb99e34f8784cc44f0eeadbe735dd238-UHFYI827.woff2 +0 -0
- package/dist/ui/assets/bc1cb0f523c8a2ff566a047aeddf90fd-BSlXQn97.woff2 +0 -0
- package/dist/ui/assets/bd3171e975d62612e50148bf38b2158f-CtZKzCzQ.woff2 +0 -0
- package/dist/ui/assets/be10640c403263dd5917ad65c4d453a6-B-rbuuja.woff2 +0 -0
- package/dist/ui/assets/be764e101e78b50b749aa1414d4810a7-yIPaQQih.woff2 +0 -0
- package/dist/ui/assets/bfa682a7f5f12945b41230c35eb859f6-CzagcI5f.woff2 +0 -0
- package/dist/ui/assets/bfa9753ed26f2e1f81ab7020167bd1a0-C6ZNQQ2p.woff2 +0 -0
- package/dist/ui/assets/branding/sash-cat.png +0 -0
- package/dist/ui/assets/c0e78c6ad709b19ce437677c91033489-BtwtlApg.woff2 +0 -0
- package/dist/ui/assets/c5a59409c610cc8c265d1691eb3f71eb-DmI_ky2X.woff2 +0 -0
- package/dist/ui/assets/c5ff66a1a57f2a29424e8de5d4ef3756-C9pOsaV1.woff2 +0 -0
- package/dist/ui/assets/c607bdee84a0e5b69721367ccc0c9c31-eoHbdXeg.woff2 +0 -0
- package/dist/ui/assets/c8095033941d18db730496eb4c863757-DoexTYks.woff2 +0 -0
- package/dist/ui/assets/c8295519e16f4cf3a350e9729f07653a-C8bAtIO_.woff2 +0 -0
- package/dist/ui/assets/c91f9c9f4db81afeba37c8e73afb0bfd-BGIl2iKW.woff2 +0 -0
- package/dist/ui/assets/c9ad9caa4afdd40766add91d321b0350-HfuiRq1k.woff2 +0 -0
- package/dist/ui/assets/cc4f271c90f9ea5157db9c0a4edc976b-MAjuDvHE.woff2 +0 -0
- package/dist/ui/assets/cdf7c3d96ba172214d7e1e9fe5e6ba8b-CUx4fWQE.woff2 +0 -0
- package/dist/ui/assets/cf70d5821ea7c3ca34653169ac763606-CE0T0Svc.woff2 +0 -0
- package/dist/ui/assets/d0d5efe92496b1ceea38e180b0ac97d3-7G71Z6v3.woff2 +0 -0
- package/dist/ui/assets/d1977142eceb878b06440375e64c4372-mr-7LWm-.woff2 +0 -0
- package/dist/ui/assets/d42feb13392b4dd9214c61b61a6e5ae8-DjnKADf_.woff2 +0 -0
- package/dist/ui/assets/d46a696ec6a077c6873fc39043c3c082-C62E-13k.woff2 +0 -0
- package/dist/ui/assets/d4c0bbf1605a8a9c36fff5a2ead81bb5-C2F6tzOj.woff2 +0 -0
- package/dist/ui/assets/d918d4e47697b7c752ea223e0d0d4c81-twovU2ty.woff2 +0 -0
- package/dist/ui/assets/d988c05da0b5624267dd825fedd133d1-Bgbc9SVH.woff2 +0 -0
- package/dist/ui/assets/da3a071df79531f4f033b7c8499651d0-0tgZJL_N.woff2 +0 -0
- package/dist/ui/assets/da8eba4acbf93048645043006de1c5f0-Ce0dmHn_.woff2 +0 -0
- package/dist/ui/assets/dc3f16892e20a85a8fcad2d3097e622b-8PRrFPJt.woff2 +0 -0
- package/dist/ui/assets/dd5f808ec691f911483a2eb60b5049bd-BhVupSbT.woff2 +0 -0
- package/dist/ui/assets/e2b04534cc697a76afd70e885119cb8c-DO303iCb.woff2 +0 -0
- package/dist/ui/assets/e307280eb2fb7809adee14ff0d247d32-DHMLXvSs.woff2 +0 -0
- package/dist/ui/assets/e3b484bec850c669094f713570870b3a-BI_9kRw8.woff2 +0 -0
- package/dist/ui/assets/e7b1d196949b7530cbda0ad956fbb2cc-i2m2Id0q.woff2 +0 -0
- package/dist/ui/assets/e7c2e5fb387880508811cadc2926e13a-BfROXX4U.woff2 +0 -0
- package/dist/ui/assets/e8a5fe283c8b9e173097bee0b5757ab6-BvfjDjYP.woff2 +0 -0
- package/dist/ui/assets/e8e3465d48435612e9c3509be3984188-BeUPU5-9.woff2 +0 -0
- package/dist/ui/assets/e93903f4604b007fc1b7134a74f5c534-B1M4h46T.woff2 +0 -0
- package/dist/ui/assets/eb0994906efd969102d522b6d8b3aff2-i9SHU5CJ.woff2 +0 -0
- package/dist/ui/assets/eb32eee23ceadd3fc4eb5763d3573f57-B3h9kgf3.woff2 +0 -0
- package/dist/ui/assets/ebadbc005027fb3e2b1c2d15be38c66b-qVQSBwBu.woff2 +0 -0
- package/dist/ui/assets/ecbbb464ff6934b5012cf33c80c7a907-CBEPqZfS.woff2 +0 -0
- package/dist/ui/assets/ed79ac25c014b6434f22fe1e2549bc26-mWhz7hkU.woff2 +0 -0
- package/dist/ui/assets/eed64f40443554cb9f65edaca176abb0-Cqw29XTm.woff2 +0 -0
- package/dist/ui/assets/efc749471476cce09e12e7038c757950-BhSWPf1Q.woff2 +0 -0
- package/dist/ui/assets/f2a7c2cb375662aa93d8f31116d39c20-D1zsMYCW.woff2 +0 -0
- package/dist/ui/assets/f5ba0fb4332ccceadc481e47134023d3-CAKMPiu5.woff2 +0 -0
- package/dist/ui/assets/f6326eb5031e18478aa4588598f96965-Dj3uu55n.woff2 +0 -0
- package/dist/ui/assets/f8678cbaab9caef95e1986731dcbd92f-CnIuR3EN.woff2 +0 -0
- package/dist/ui/assets/f8d9d55e2a811e9839c9af5f8f2d62ac-DIUQRBsH.woff2 +0 -0
- package/dist/ui/assets/f98b117e363eb51f4d7c7d1056da7688-O9MtqjIW.woff2 +0 -0
- package/dist/ui/assets/f9ab5d9163148ceb211e5b4f15a3d8e0-Dtqse-AT.woff2 +0 -0
- package/dist/ui/assets/fa69ce224ff0a6c0ff97b10a829779c2-pX45dCNY.woff2 +0 -0
- package/dist/ui/assets/fc38d1da3930d39dd8e286f708105765-DmbOHwTI.woff2 +0 -0
- package/dist/ui/assets/fe03c1dfaffc1cad3521fe55f1541d6b-BW8aEqCj.woff2 +0 -0
- package/dist/ui/assets/fe0b2421eb4d1f4f8949c602871a317c-BsBLV6jH.woff2 +0 -0
- package/dist/ui/assets/fe9c75d0cb3d808023412d26dfc9d388-eXjcgUPI.woff2 +0 -0
- package/dist/ui/assets/index-B61V60w_.js +19 -0
- package/dist/ui/assets/index-bkyxJG8J.css +1 -0
- package/dist/ui/assets/theme-BNq4FkXS.js +1 -0
- package/dist/ui/index.html +52 -0
- package/dist/webui.js +21 -0
- package/docs/backend.md +260 -0
- package/docs/frontend.md +125 -0
- package/docs/remix-icon-license.txt +186 -0
- package/docs/usage.md +227 -0
- package/package.json +82 -0
package/dist/http.js
ADDED
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { Transform } from "node:stream";
|
|
4
|
+
import { pipeline } from "node:stream/promises";
|
|
5
|
+
import { Agent, EnvHttpProxyAgent, interceptors, request } from "undici";
|
|
6
|
+
export const USER_AGENT = "sash-cli (https://github.com/ming-kang/Sash)";
|
|
7
|
+
export const ERROR_BODY_LIMIT = 32 * 1024;
|
|
8
|
+
let baseProxyDispatcher;
|
|
9
|
+
let redirectProxyDispatcher;
|
|
10
|
+
let baseDirectDispatcher;
|
|
11
|
+
let redirectDirectDispatcher;
|
|
12
|
+
function getBaseProxyDispatcher() {
|
|
13
|
+
if (!baseProxyDispatcher) {
|
|
14
|
+
const allProxyRaw = process.env.ALL_PROXY ?? process.env.all_proxy;
|
|
15
|
+
const allProxy = allProxyRaw && /^https?:\/\//i.test(allProxyRaw) ? allProxyRaw : undefined;
|
|
16
|
+
const httpProxy = process.env.HTTP_PROXY ?? process.env.http_proxy ?? allProxy;
|
|
17
|
+
const httpsProxy = process.env.HTTPS_PROXY ?? process.env.https_proxy ?? allProxy;
|
|
18
|
+
baseProxyDispatcher = new EnvHttpProxyAgent({
|
|
19
|
+
...(httpProxy ? { httpProxy } : {}),
|
|
20
|
+
...(httpsProxy ? { httpsProxy } : {}),
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
return baseProxyDispatcher;
|
|
24
|
+
}
|
|
25
|
+
function getBaseDirectDispatcher() {
|
|
26
|
+
if (!baseDirectDispatcher)
|
|
27
|
+
baseDirectDispatcher = new Agent();
|
|
28
|
+
return baseDirectDispatcher;
|
|
29
|
+
}
|
|
30
|
+
export function proxyAwareDispatcher() {
|
|
31
|
+
return getBaseProxyDispatcher();
|
|
32
|
+
}
|
|
33
|
+
export function directDispatcherForLoopback() {
|
|
34
|
+
return getBaseDirectDispatcher();
|
|
35
|
+
}
|
|
36
|
+
function pickDispatcher(opts) {
|
|
37
|
+
const base = opts.direct ? getBaseDirectDispatcher() : getBaseProxyDispatcher();
|
|
38
|
+
if (opts.manualRedirect)
|
|
39
|
+
return base;
|
|
40
|
+
if (opts.direct) {
|
|
41
|
+
if (!redirectDirectDispatcher) {
|
|
42
|
+
redirectDirectDispatcher = base.compose(interceptors.redirect({ maxRedirections: 5 }));
|
|
43
|
+
}
|
|
44
|
+
return redirectDirectDispatcher;
|
|
45
|
+
}
|
|
46
|
+
if (!redirectProxyDispatcher) {
|
|
47
|
+
redirectProxyDispatcher = base.compose(interceptors.redirect({ maxRedirections: 5 }));
|
|
48
|
+
}
|
|
49
|
+
return redirectProxyDispatcher;
|
|
50
|
+
}
|
|
51
|
+
const RETRYABLE_STATUS = new Set([408, 425, 429, 500, 502, 503, 504]);
|
|
52
|
+
const RETRYABLE_METHODS = new Set(["GET", "HEAD"]);
|
|
53
|
+
function sleep(ms, signal) {
|
|
54
|
+
if (signal.aborted) {
|
|
55
|
+
return Promise.reject(signal.reason instanceof Error ? signal.reason : new Error("HTTP request aborted"));
|
|
56
|
+
}
|
|
57
|
+
return new Promise((resolve, reject) => {
|
|
58
|
+
const timer = setTimeout(() => {
|
|
59
|
+
signal.removeEventListener("abort", onAbort);
|
|
60
|
+
resolve();
|
|
61
|
+
}, ms);
|
|
62
|
+
const onAbort = () => {
|
|
63
|
+
clearTimeout(timer);
|
|
64
|
+
signal.removeEventListener("abort", onAbort);
|
|
65
|
+
reject(signal.reason instanceof Error ? signal.reason : new Error("HTTP request aborted"));
|
|
66
|
+
};
|
|
67
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
function positiveTimeout(value, fallback, name) {
|
|
71
|
+
const resolved = value ?? fallback;
|
|
72
|
+
if (!Number.isSafeInteger(resolved) || resolved <= 0) {
|
|
73
|
+
throw new Error(`${name} must be a positive integer`);
|
|
74
|
+
}
|
|
75
|
+
return resolved;
|
|
76
|
+
}
|
|
77
|
+
function defaultAttempts(method) {
|
|
78
|
+
return RETRYABLE_METHODS.has(method.toUpperCase()) ? 4 : 1;
|
|
79
|
+
}
|
|
80
|
+
export async function fetchWithRetry(url, opts = {}) {
|
|
81
|
+
const method = (opts.method ?? "GET").toUpperCase();
|
|
82
|
+
const attempts = opts.attempts ?? defaultAttempts(method);
|
|
83
|
+
if (!Number.isSafeInteger(attempts) || attempts < 1) {
|
|
84
|
+
throw new Error("attempts must be a positive integer");
|
|
85
|
+
}
|
|
86
|
+
const headersTimeoutMs = positiveTimeout(opts.headersTimeoutMs, 30_000, "headersTimeoutMs");
|
|
87
|
+
const bodyInactivityTimeoutMs = positiveTimeout(opts.bodyInactivityTimeoutMs, 30_000, "bodyInactivityTimeoutMs");
|
|
88
|
+
const deadlineMs = positiveTimeout(opts.deadlineMs, 60_000, "deadlineMs");
|
|
89
|
+
const deadline = new AbortController();
|
|
90
|
+
const deadlineTimer = setTimeout(() => {
|
|
91
|
+
deadline.abort(new Error(`HTTP request deadline exceeded after ${deadlineMs}ms`));
|
|
92
|
+
}, deadlineMs);
|
|
93
|
+
let settled = false;
|
|
94
|
+
let responseReturned = false;
|
|
95
|
+
const clearDeadline = () => {
|
|
96
|
+
if (!settled) {
|
|
97
|
+
settled = true;
|
|
98
|
+
clearTimeout(deadlineTimer);
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
let lastErr;
|
|
102
|
+
try {
|
|
103
|
+
for (let attempt = 1; attempt <= attempts; attempt++) {
|
|
104
|
+
try {
|
|
105
|
+
const res = await request(url, {
|
|
106
|
+
method: method,
|
|
107
|
+
headers: { "user-agent": USER_AGENT, ...opts.headers },
|
|
108
|
+
body: opts.body,
|
|
109
|
+
headersTimeout: headersTimeoutMs,
|
|
110
|
+
bodyTimeout: bodyInactivityTimeoutMs,
|
|
111
|
+
signal: deadline.signal,
|
|
112
|
+
dispatcher: pickDispatcher(opts),
|
|
113
|
+
});
|
|
114
|
+
if (RETRYABLE_STATUS.has(res.statusCode) && attempt < attempts) {
|
|
115
|
+
await res.body.dump();
|
|
116
|
+
throw new Error(`HTTP ${res.statusCode}`);
|
|
117
|
+
}
|
|
118
|
+
const body = res.body;
|
|
119
|
+
let claimed = false;
|
|
120
|
+
const claimBody = () => {
|
|
121
|
+
if (claimed)
|
|
122
|
+
throw new Error("Response body has already been consumed or discarded");
|
|
123
|
+
claimed = true;
|
|
124
|
+
};
|
|
125
|
+
const consume = async (maxBytes) => {
|
|
126
|
+
claimBody();
|
|
127
|
+
if (!Number.isSafeInteger(maxBytes) || maxBytes < 0) {
|
|
128
|
+
body.destroy();
|
|
129
|
+
clearDeadline();
|
|
130
|
+
throw new Error("maxBytes must be a non-negative safe integer");
|
|
131
|
+
}
|
|
132
|
+
const chunks = [];
|
|
133
|
+
let total = 0;
|
|
134
|
+
try {
|
|
135
|
+
for await (const chunk of body) {
|
|
136
|
+
const data = Buffer.from(chunk);
|
|
137
|
+
total += data.length;
|
|
138
|
+
if (total > maxBytes) {
|
|
139
|
+
body.destroy();
|
|
140
|
+
throw new Error(`Response body exceeds ${maxBytes} byte limit`);
|
|
141
|
+
}
|
|
142
|
+
chunks.push(data);
|
|
143
|
+
}
|
|
144
|
+
return Buffer.concat(chunks, total);
|
|
145
|
+
}
|
|
146
|
+
finally {
|
|
147
|
+
clearDeadline();
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
responseReturned = true;
|
|
151
|
+
return {
|
|
152
|
+
statusCode: res.statusCode,
|
|
153
|
+
headers: res.headers,
|
|
154
|
+
text: async (maxBytes) => (await consume(maxBytes)).toString("utf8"),
|
|
155
|
+
buffer: consume,
|
|
156
|
+
discard: async () => {
|
|
157
|
+
claimBody();
|
|
158
|
+
try {
|
|
159
|
+
await body.dump();
|
|
160
|
+
}
|
|
161
|
+
finally {
|
|
162
|
+
clearDeadline();
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
catch (err) {
|
|
168
|
+
lastErr = err;
|
|
169
|
+
if (deadline.signal.aborted || attempt === attempts)
|
|
170
|
+
break;
|
|
171
|
+
await sleep(Math.min(4_000, 300 * 2 ** (attempt - 1)) + Math.random() * 200, deadline.signal);
|
|
172
|
+
if (deadline.signal.aborted)
|
|
173
|
+
break;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
throw lastErr instanceof Error ? lastErr : new Error(String(lastErr));
|
|
177
|
+
}
|
|
178
|
+
finally {
|
|
179
|
+
if (!responseReturned)
|
|
180
|
+
clearDeadline();
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
function abortResponseBody(body) {
|
|
184
|
+
body.on("error", () => undefined);
|
|
185
|
+
body.destroy();
|
|
186
|
+
}
|
|
187
|
+
function validateRedirectTarget(location, currentUrl, allowedHosts, requireHttps = false) {
|
|
188
|
+
const target = new URL(location, currentUrl);
|
|
189
|
+
if (target.protocol !== "http:" && target.protocol !== "https:") {
|
|
190
|
+
throw new Error(`Refusing redirect to non-http(s) URL: ${target.href}`);
|
|
191
|
+
}
|
|
192
|
+
if (requireHttps && target.protocol !== "https:") {
|
|
193
|
+
throw new Error(`Refusing non-HTTPS download URL: ${target.href}`);
|
|
194
|
+
}
|
|
195
|
+
if (!allowedHosts.has(target.hostname.toLowerCase())) {
|
|
196
|
+
throw new Error(`Refusing redirect to untrusted host: ${target.hostname}`);
|
|
197
|
+
}
|
|
198
|
+
return target.href;
|
|
199
|
+
}
|
|
200
|
+
export async function downloadToFile(url, dest, opts) {
|
|
201
|
+
const stallMs = positiveTimeout(opts.stallMs, 60_000, "stallMs");
|
|
202
|
+
const deadlineMs = positiveTimeout(opts.deadlineMs, 15 * 60_000, "deadlineMs");
|
|
203
|
+
const maxRedirects = 5;
|
|
204
|
+
const deadline = new AbortController();
|
|
205
|
+
const deadlineTimer = setTimeout(() => {
|
|
206
|
+
deadline.abort(new Error(`Download deadline exceeded after ${deadlineMs}ms`));
|
|
207
|
+
}, deadlineMs);
|
|
208
|
+
const dispatcher = pickDispatcher({ direct: false, manualRedirect: true });
|
|
209
|
+
let outputStarted = false;
|
|
210
|
+
let res;
|
|
211
|
+
try {
|
|
212
|
+
let currentUrl = validateRedirectTarget(url, url, opts.allowedHosts, opts.requireHttps);
|
|
213
|
+
let hops = 0;
|
|
214
|
+
for (;;) {
|
|
215
|
+
res = await request(currentUrl, {
|
|
216
|
+
method: "GET",
|
|
217
|
+
headers: { "user-agent": USER_AGENT, ...opts.headers },
|
|
218
|
+
headersTimeout: 30_000,
|
|
219
|
+
bodyTimeout: stallMs,
|
|
220
|
+
signal: deadline.signal,
|
|
221
|
+
dispatcher,
|
|
222
|
+
});
|
|
223
|
+
const isRedirect = res.statusCode >= 300 && res.statusCode < 400 && Boolean(res.headers.location);
|
|
224
|
+
if (!isRedirect)
|
|
225
|
+
break;
|
|
226
|
+
const locationHeader = res.headers.location;
|
|
227
|
+
const location = Array.isArray(locationHeader) ? locationHeader[0] : locationHeader;
|
|
228
|
+
await res.body.dump();
|
|
229
|
+
if (!location)
|
|
230
|
+
throw new Error(`Redirect without Location header from ${currentUrl}`);
|
|
231
|
+
hops += 1;
|
|
232
|
+
if (hops > maxRedirects) {
|
|
233
|
+
throw new Error(`Too many redirects (>${maxRedirects}) downloading ${url}`);
|
|
234
|
+
}
|
|
235
|
+
currentUrl = validateRedirectTarget(location, currentUrl, opts.allowedHosts, opts.requireHttps);
|
|
236
|
+
}
|
|
237
|
+
if (res.statusCode < 200 || res.statusCode >= 300) {
|
|
238
|
+
await res.body.dump();
|
|
239
|
+
throw new Error(`HTTP ${res.statusCode} for ${currentUrl}`);
|
|
240
|
+
}
|
|
241
|
+
const totalHeader = res.headers["content-length"];
|
|
242
|
+
const parsedTotal = typeof totalHeader === "string" ? Number.parseInt(totalHeader, 10) : Number.NaN;
|
|
243
|
+
const total = Number.isFinite(parsedTotal) && parsedTotal >= 0 ? parsedTotal : undefined;
|
|
244
|
+
const maxBytes = opts.maxBytes;
|
|
245
|
+
if (maxBytes !== undefined && (!Number.isSafeInteger(maxBytes) || maxBytes <= 0)) {
|
|
246
|
+
abortResponseBody(res.body);
|
|
247
|
+
throw new Error(`Invalid download size limit: ${maxBytes}`);
|
|
248
|
+
}
|
|
249
|
+
if (maxBytes !== undefined && total !== undefined && total > maxBytes) {
|
|
250
|
+
abortResponseBody(res.body);
|
|
251
|
+
throw new Error(`Download exceeds ${maxBytes} byte safety limit: ${currentUrl}`);
|
|
252
|
+
}
|
|
253
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
254
|
+
let downloaded = 0;
|
|
255
|
+
const limiter = new Transform({
|
|
256
|
+
transform(chunk, _encoding, callback) {
|
|
257
|
+
downloaded += chunk.length;
|
|
258
|
+
if (maxBytes !== undefined && downloaded > maxBytes) {
|
|
259
|
+
callback(new Error(`Download exceeds ${maxBytes} byte safety limit: ${currentUrl}`));
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
opts.onProgress?.(downloaded, total);
|
|
263
|
+
callback(null, chunk);
|
|
264
|
+
},
|
|
265
|
+
});
|
|
266
|
+
outputStarted = true;
|
|
267
|
+
await pipeline(res.body, limiter, fs.createWriteStream(dest, { mode: 0o755 }));
|
|
268
|
+
if (downloaded === 0)
|
|
269
|
+
throw new Error(`Empty download from ${currentUrl}`);
|
|
270
|
+
return downloaded;
|
|
271
|
+
}
|
|
272
|
+
catch (err) {
|
|
273
|
+
if (res)
|
|
274
|
+
abortResponseBody(res.body);
|
|
275
|
+
if (outputStarted)
|
|
276
|
+
fs.rmSync(dest, { force: true });
|
|
277
|
+
throw err;
|
|
278
|
+
}
|
|
279
|
+
finally {
|
|
280
|
+
clearTimeout(deadlineTimer);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export function isPlainObject(value) {
|
|
2
|
+
return (typeof value === "object" &&
|
|
3
|
+
value !== null &&
|
|
4
|
+
!Array.isArray(value) &&
|
|
5
|
+
Object.getPrototypeOf(value) === Object.prototype);
|
|
6
|
+
}
|
|
7
|
+
export function hasExactOwnKeys(value, expectedKeys) {
|
|
8
|
+
const actualKeys = Object.keys(value);
|
|
9
|
+
return (actualKeys.length === expectedKeys.length &&
|
|
10
|
+
expectedKeys.every((key) => Object.hasOwn(value, key)));
|
|
11
|
+
}
|
|
12
|
+
export function isCanonicalIsoTimestamp(value) {
|
|
13
|
+
if (typeof value !== "string")
|
|
14
|
+
return false;
|
|
15
|
+
const milliseconds = Date.parse(value);
|
|
16
|
+
return Number.isFinite(milliseconds) && new Date(milliseconds).toISOString() === value;
|
|
17
|
+
}
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
export const LOG_FOLLOW_CHUNK_BYTES = 64 * 1024;
|
|
4
|
+
export function normalizeLines(input, fallback = 50) {
|
|
5
|
+
return typeof input === "number" && Number.isSafeInteger(input) && input > 0 ? input : fallback;
|
|
6
|
+
}
|
|
7
|
+
export function parseLogLineCount(value) {
|
|
8
|
+
if (!/^[1-9]\d*$/.test(value))
|
|
9
|
+
throw new Error("must be a positive integer");
|
|
10
|
+
const parsed = Number(value);
|
|
11
|
+
if (!Number.isSafeInteger(parsed))
|
|
12
|
+
throw new Error("must be a positive safe integer");
|
|
13
|
+
return parsed;
|
|
14
|
+
}
|
|
15
|
+
function fileIdentity(stat) {
|
|
16
|
+
return `${stat.dev}:${stat.ino}:${stat.birthtimeMs}`;
|
|
17
|
+
}
|
|
18
|
+
function validateChunkBytes(chunkBytes) {
|
|
19
|
+
if (!Number.isSafeInteger(chunkBytes) || chunkBytes <= 0) {
|
|
20
|
+
throw new Error("chunkBytes must be a positive safe integer");
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function missingGrowth() {
|
|
24
|
+
return {
|
|
25
|
+
chunks: [],
|
|
26
|
+
cursor: { identity: null, offset: 0 },
|
|
27
|
+
hasMore: false,
|
|
28
|
+
missing: true,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
export function logCursorAtEnd(file) {
|
|
32
|
+
let fd;
|
|
33
|
+
try {
|
|
34
|
+
fd = fs.openSync(file, "r");
|
|
35
|
+
const stat = fs.fstatSync(fd);
|
|
36
|
+
if (!stat.isFile())
|
|
37
|
+
throw new Error(`Log path is not a regular file: ${file}`);
|
|
38
|
+
if (!Number.isSafeInteger(stat.size) || stat.size < 0) {
|
|
39
|
+
throw new Error(`Log file size is not safely readable: ${file}`);
|
|
40
|
+
}
|
|
41
|
+
return { identity: fileIdentity(stat), offset: stat.size };
|
|
42
|
+
}
|
|
43
|
+
catch (err) {
|
|
44
|
+
if (err.code === "ENOENT") {
|
|
45
|
+
return { identity: null, offset: 0 };
|
|
46
|
+
}
|
|
47
|
+
throw err;
|
|
48
|
+
}
|
|
49
|
+
finally {
|
|
50
|
+
if (fd !== undefined)
|
|
51
|
+
fs.closeSync(fd);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
export function readLogGrowth(file, cursor, chunkBytes = LOG_FOLLOW_CHUNK_BYTES) {
|
|
55
|
+
validateChunkBytes(chunkBytes);
|
|
56
|
+
const safeOffset = Number.isSafeInteger(cursor.offset) && cursor.offset >= 0 ? cursor.offset : 0;
|
|
57
|
+
let fd;
|
|
58
|
+
try {
|
|
59
|
+
fd = fs.openSync(file, "r");
|
|
60
|
+
const stat = fs.fstatSync(fd);
|
|
61
|
+
if (!stat.isFile())
|
|
62
|
+
throw new Error(`Log path is not a regular file: ${file}`);
|
|
63
|
+
if (!Number.isSafeInteger(stat.size) || stat.size < 0) {
|
|
64
|
+
throw new Error(`Log file size is not safely readable: ${file}`);
|
|
65
|
+
}
|
|
66
|
+
const identity = fileIdentity(stat);
|
|
67
|
+
const position = cursor.identity === identity && stat.size >= safeOffset ? safeOffset : 0;
|
|
68
|
+
if (position >= stat.size) {
|
|
69
|
+
return {
|
|
70
|
+
chunks: [],
|
|
71
|
+
cursor: { identity, offset: position },
|
|
72
|
+
hasMore: false,
|
|
73
|
+
missing: false,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
const length = Math.min(chunkBytes, stat.size - position);
|
|
77
|
+
const chunk = Buffer.allocUnsafe(length);
|
|
78
|
+
const bytesRead = fs.readSync(fd, chunk, 0, length, position);
|
|
79
|
+
const nextOffset = position + Math.max(0, bytesRead);
|
|
80
|
+
return {
|
|
81
|
+
chunks: bytesRead > 0 ? [chunk.subarray(0, bytesRead)] : [],
|
|
82
|
+
cursor: { identity, offset: nextOffset },
|
|
83
|
+
hasMore: nextOffset < stat.size,
|
|
84
|
+
missing: false,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
catch (err) {
|
|
88
|
+
if (err.code === "ENOENT")
|
|
89
|
+
return missingGrowth();
|
|
90
|
+
throw err;
|
|
91
|
+
}
|
|
92
|
+
finally {
|
|
93
|
+
if (fd !== undefined)
|
|
94
|
+
fs.closeSync(fd);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
export function logTailCursor(file) {
|
|
98
|
+
try {
|
|
99
|
+
return logCursorAtEnd(file);
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
return { identity: "unreadable", offset: 0 };
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
export function boundedLogTailSince(file, cursor, options = {}) {
|
|
106
|
+
const maxBytes = options.maxBytes ?? LOG_FOLLOW_CHUNK_BYTES;
|
|
107
|
+
const maxLines = options.maxLines ?? 20;
|
|
108
|
+
if (!Number.isSafeInteger(maxBytes) ||
|
|
109
|
+
maxBytes <= 0 ||
|
|
110
|
+
!Number.isSafeInteger(maxLines) ||
|
|
111
|
+
maxLines <= 0) {
|
|
112
|
+
return "";
|
|
113
|
+
}
|
|
114
|
+
let fd;
|
|
115
|
+
try {
|
|
116
|
+
fd = fs.openSync(file, "r");
|
|
117
|
+
const stat = fs.fstatSync(fd);
|
|
118
|
+
if (!stat.isFile() || !Number.isSafeInteger(stat.size) || stat.size < 0)
|
|
119
|
+
return "";
|
|
120
|
+
const identity = fileIdentity(stat);
|
|
121
|
+
const offset = Number.isSafeInteger(cursor.offset) && cursor.offset >= 0 ? cursor.offset : 0;
|
|
122
|
+
if (cursor.identity !== null && cursor.identity !== identity)
|
|
123
|
+
return "";
|
|
124
|
+
if (stat.size < offset || stat.size === offset)
|
|
125
|
+
return "";
|
|
126
|
+
const position = Math.max(offset, stat.size - maxBytes);
|
|
127
|
+
const length = stat.size - position;
|
|
128
|
+
const buffer = Buffer.allocUnsafe(length);
|
|
129
|
+
const bytesRead = fs.readSync(fd, buffer, 0, length, position);
|
|
130
|
+
let start = 0;
|
|
131
|
+
while (start < bytesRead && ((buffer[start] ?? 0) & 0xc0) === 0x80)
|
|
132
|
+
start++;
|
|
133
|
+
const lines = buffer
|
|
134
|
+
.subarray(start, bytesRead)
|
|
135
|
+
.toString("utf8")
|
|
136
|
+
.split(/\r?\n/)
|
|
137
|
+
.filter((line) => line.trim().length > 0);
|
|
138
|
+
return lines.slice(-maxLines).join("\n");
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
return "";
|
|
142
|
+
}
|
|
143
|
+
finally {
|
|
144
|
+
if (fd !== undefined) {
|
|
145
|
+
try {
|
|
146
|
+
fs.closeSync(fd);
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
export function followLogFile(file, options) {
|
|
154
|
+
const chunkBytes = options.chunkBytes ?? LOG_FOLLOW_CHUNK_BYTES;
|
|
155
|
+
const pollMs = options.pollMs ?? 500;
|
|
156
|
+
validateChunkBytes(chunkBytes);
|
|
157
|
+
if (!Number.isSafeInteger(pollMs) || pollMs <= 0) {
|
|
158
|
+
throw new Error("pollMs must be a positive safe integer");
|
|
159
|
+
}
|
|
160
|
+
return new Promise((resolve, reject) => {
|
|
161
|
+
let cursor = options.cursor ?? { identity: null, offset: 0 };
|
|
162
|
+
let watcher;
|
|
163
|
+
let timer;
|
|
164
|
+
let draining = false;
|
|
165
|
+
let queued = false;
|
|
166
|
+
let stopped = false;
|
|
167
|
+
const closeWatcher = () => {
|
|
168
|
+
try {
|
|
169
|
+
watcher?.close();
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
}
|
|
173
|
+
watcher = undefined;
|
|
174
|
+
};
|
|
175
|
+
const cleanup = () => {
|
|
176
|
+
if (timer)
|
|
177
|
+
clearInterval(timer);
|
|
178
|
+
timer = undefined;
|
|
179
|
+
closeWatcher();
|
|
180
|
+
options.signal.removeEventListener("abort", onAbort);
|
|
181
|
+
};
|
|
182
|
+
const finish = (err) => {
|
|
183
|
+
if (stopped)
|
|
184
|
+
return;
|
|
185
|
+
stopped = true;
|
|
186
|
+
cleanup();
|
|
187
|
+
if (err === undefined)
|
|
188
|
+
resolve();
|
|
189
|
+
else
|
|
190
|
+
reject(err);
|
|
191
|
+
};
|
|
192
|
+
const installWatcher = () => {
|
|
193
|
+
if (watcher || stopped)
|
|
194
|
+
return;
|
|
195
|
+
try {
|
|
196
|
+
const watched = fs.watch(path.dirname(file), (_event, filename) => {
|
|
197
|
+
if (filename === null || filename.toString() === path.basename(file))
|
|
198
|
+
scheduleDrain();
|
|
199
|
+
});
|
|
200
|
+
watched.on("error", () => {
|
|
201
|
+
if (watcher === watched)
|
|
202
|
+
watcher = undefined;
|
|
203
|
+
try {
|
|
204
|
+
watched.close();
|
|
205
|
+
}
|
|
206
|
+
catch {
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
watched.on("close", () => {
|
|
210
|
+
if (watcher === watched)
|
|
211
|
+
watcher = undefined;
|
|
212
|
+
});
|
|
213
|
+
watcher = watched;
|
|
214
|
+
}
|
|
215
|
+
catch {
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
const drain = async () => {
|
|
219
|
+
if (stopped || draining) {
|
|
220
|
+
queued = !stopped;
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
draining = true;
|
|
224
|
+
try {
|
|
225
|
+
let chunksThisTurn = 0;
|
|
226
|
+
do {
|
|
227
|
+
queued = false;
|
|
228
|
+
let growth;
|
|
229
|
+
try {
|
|
230
|
+
growth = readLogGrowth(file, cursor, chunkBytes);
|
|
231
|
+
}
|
|
232
|
+
catch {
|
|
233
|
+
break;
|
|
234
|
+
}
|
|
235
|
+
cursor = growth.cursor;
|
|
236
|
+
if (stopped)
|
|
237
|
+
break;
|
|
238
|
+
for (const chunk of growth.chunks) {
|
|
239
|
+
await options.onChunk(chunk);
|
|
240
|
+
if (stopped)
|
|
241
|
+
break;
|
|
242
|
+
}
|
|
243
|
+
queued = !stopped && (queued || growth.hasMore);
|
|
244
|
+
chunksThisTurn += growth.chunks.length;
|
|
245
|
+
if (queued && chunksThisTurn >= 16)
|
|
246
|
+
break;
|
|
247
|
+
} while (queued && !stopped);
|
|
248
|
+
}
|
|
249
|
+
catch (err) {
|
|
250
|
+
finish(err);
|
|
251
|
+
}
|
|
252
|
+
finally {
|
|
253
|
+
draining = false;
|
|
254
|
+
if (queued && !stopped)
|
|
255
|
+
setImmediate(scheduleDrain);
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
const scheduleDrain = () => {
|
|
259
|
+
if (stopped)
|
|
260
|
+
return;
|
|
261
|
+
if (draining) {
|
|
262
|
+
queued = true;
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
void drain();
|
|
266
|
+
};
|
|
267
|
+
const onAbort = () => finish();
|
|
268
|
+
if (options.signal.aborted) {
|
|
269
|
+
finish();
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
options.signal.addEventListener("abort", onAbort, { once: true });
|
|
273
|
+
installWatcher();
|
|
274
|
+
timer = setInterval(() => {
|
|
275
|
+
installWatcher();
|
|
276
|
+
scheduleDrain();
|
|
277
|
+
}, pollMs);
|
|
278
|
+
scheduleDrain();
|
|
279
|
+
});
|
|
280
|
+
}
|
package/dist/log.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export const log = {
|
|
2
|
+
info(msg) {
|
|
3
|
+
console.log(`[sash] ${msg}`);
|
|
4
|
+
},
|
|
5
|
+
ok(msg) {
|
|
6
|
+
console.log(`[sash] ✓ ${msg}`);
|
|
7
|
+
},
|
|
8
|
+
warn(msg) {
|
|
9
|
+
console.warn(`[sash] ! ${msg}`);
|
|
10
|
+
},
|
|
11
|
+
error(msg) {
|
|
12
|
+
console.error(`[sash] ✗ ${msg}`);
|
|
13
|
+
},
|
|
14
|
+
kv(key, value) {
|
|
15
|
+
console.log(` ${key.padEnd(16)}${value}`);
|
|
16
|
+
},
|
|
17
|
+
};
|