@maycombhq/atticus 0.0.0-stage → 2.0.0-beta.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/LICENSE +177 -0
- package/README.md +17 -2
- package/THIRD-PARTY-LICENSES.txt +2607 -0
- package/dist/bin.js +47 -0
- package/dist/chunk-23W7VWKR.js +2609 -0
- package/dist/chunk-2C5B464C.js +106 -0
- package/dist/chunk-2LLFKP7C.js +53 -0
- package/dist/chunk-2MD7TXSG.js +166 -0
- package/dist/chunk-2UIQBDJO.js +131 -0
- package/dist/chunk-32MPK524.js +378 -0
- package/dist/chunk-3ALD2W2A.js +95 -0
- package/dist/chunk-3G4YI4ZU.js +16 -0
- package/dist/chunk-4AK7IBVP.js +915 -0
- package/dist/chunk-4BMDGTED.js +69 -0
- package/dist/chunk-4NDAAFQT.js +103 -0
- package/dist/chunk-4Q3NN7VF.js +32 -0
- package/dist/chunk-4RS5KZQD.js +25 -0
- package/dist/chunk-4SKZDI62.js +22 -0
- package/dist/chunk-55YODD3N.js +385 -0
- package/dist/chunk-56YQ6ROQ.js +3479 -0
- package/dist/chunk-5A5L3T2U.js +583 -0
- package/dist/chunk-5FEOD73Z.js +374 -0
- package/dist/chunk-5GDRCDQB.js +32 -0
- package/dist/chunk-5WS2YOGD.js +95 -0
- package/dist/chunk-65NYPGRC.js +766 -0
- package/dist/chunk-6BVONWJ7.js +50 -0
- package/dist/chunk-6D4FRTFF.js +53 -0
- package/dist/chunk-6GPJOFV4.js +149 -0
- package/dist/chunk-6JCPI55B.js +92 -0
- package/dist/chunk-6JUBZBMW.js +56 -0
- package/dist/chunk-6SCVQZX5.js +2522 -0
- package/dist/chunk-6TACQN3N.js +41 -0
- package/dist/chunk-72TXNLOC.js +496 -0
- package/dist/chunk-76WKMA4J.js +14 -0
- package/dist/chunk-7EFILTCI.js +67 -0
- package/dist/chunk-7EKXJZ2Y.js +2902 -0
- package/dist/chunk-7EOBHUMC.js +133 -0
- package/dist/chunk-7JUKDRSZ.js +1726 -0
- package/dist/chunk-7NN5VWDC.js +43 -0
- package/dist/chunk-7UFQZXZA.js +5949 -0
- package/dist/chunk-A3KPZJ5M.js +131 -0
- package/dist/chunk-A6AC72A5.js +48 -0
- package/dist/chunk-A7KZKIZX.js +166 -0
- package/dist/chunk-ABWHYWNI.js +101 -0
- package/dist/chunk-AFFIK6JI.js +46 -0
- package/dist/chunk-AHOQZ4OK.js +37 -0
- package/dist/chunk-AVJDSJ2F.js +18 -0
- package/dist/chunk-AXXCPAP6.js +203 -0
- package/dist/chunk-AY3QXYMB.js +41 -0
- package/dist/chunk-B2OKWJIU.js +143 -0
- package/dist/chunk-B632FH3I.js +251 -0
- package/dist/chunk-BLW4SXCU.js +38 -0
- package/dist/chunk-BVS2K73A.js +33 -0
- package/dist/chunk-BWZ77RQT.js +25 -0
- package/dist/chunk-BYBUDX4T.js +163 -0
- package/dist/chunk-BYZCY6SP.js +230 -0
- package/dist/chunk-C6EKFH7J.js +169 -0
- package/dist/chunk-C6MZFGN7.js +271 -0
- package/dist/chunk-CAQZUNOH.js +1150 -0
- package/dist/chunk-CD4XHYGY.js +856 -0
- package/dist/chunk-CEZQOLEO.js +1217 -0
- package/dist/chunk-CHWTETW7.js +128 -0
- package/dist/chunk-CMV7TGU6.js +31 -0
- package/dist/chunk-CNUKCS2T.js +2741 -0
- package/dist/chunk-CP3L3FH7.js +549 -0
- package/dist/chunk-CPGCA6GZ.js +41 -0
- package/dist/chunk-CWM6KM5G.js +315 -0
- package/dist/chunk-CXDSTRUJ.js +873 -0
- package/dist/chunk-D3PO4DOZ.js +261 -0
- package/dist/chunk-DFBCSHVT.js +69 -0
- package/dist/chunk-DHPACDG7.js +104 -0
- package/dist/chunk-DPC5Y5DL.js +3898 -0
- package/dist/chunk-DPUYDDQG.js +164 -0
- package/dist/chunk-DQN7TF5D.js +325 -0
- package/dist/chunk-EADP3PEN.js +450 -0
- package/dist/chunk-ED2LWWIP.js +878 -0
- package/dist/chunk-EHVTK3UE.js +116 -0
- package/dist/chunk-EPTBZ3GP.js +101 -0
- package/dist/chunk-EXIMEJ2X.js +545 -0
- package/dist/chunk-EYWIDJFU.js +130 -0
- package/dist/chunk-F44OMYIN.js +54 -0
- package/dist/chunk-F7HYOUFF.js +287 -0
- package/dist/chunk-FALXWAMD.js +182 -0
- package/dist/chunk-FBNPE7HS.js +49 -0
- package/dist/chunk-FSWUD5QB.js +108 -0
- package/dist/chunk-FVQISWPW.js +296 -0
- package/dist/chunk-G2B4ZQGH.js +240 -0
- package/dist/chunk-GK6XK5SP.js +1227 -0
- package/dist/chunk-GLEBK5L3.js +34 -0
- package/dist/chunk-GNB5QDDI.js +25 -0
- package/dist/chunk-GQ4N4WKF.js +1142 -0
- package/dist/chunk-GRMR5TRQ.js +119 -0
- package/dist/chunk-HC6XKREB.js +334 -0
- package/dist/chunk-HFQCFSJR.js +60 -0
- package/dist/chunk-HJDICQA4.js +102 -0
- package/dist/chunk-HLJAXMM4.js +233 -0
- package/dist/chunk-HVIMMDVB.js +162 -0
- package/dist/chunk-HZVLAPN5.js +32 -0
- package/dist/chunk-I3E7CX4H.js +155 -0
- package/dist/chunk-IQFTZ37C.js +2919 -0
- package/dist/chunk-ITY3VJRX.js +285 -0
- package/dist/chunk-JBTK5OP2.js +55 -0
- package/dist/chunk-JPGCERE2.js +26 -0
- package/dist/chunk-JQDNHIBC.js +38 -0
- package/dist/chunk-JRBCWLOR.js +307 -0
- package/dist/chunk-JRZQA2JM.js +212 -0
- package/dist/chunk-KCBJCA66.js +45 -0
- package/dist/chunk-KJPMEAI4.js +705 -0
- package/dist/chunk-KQDZXGNR.js +14 -0
- package/dist/chunk-KUUQR4HA.js +264 -0
- package/dist/chunk-L24C55VN.js +7923 -0
- package/dist/chunk-L3BX5ZIB.js +400 -0
- package/dist/chunk-L6SGQIR3.js +471 -0
- package/dist/chunk-LBENYT3H.js +66 -0
- package/dist/chunk-LNAJW6FB.js +47 -0
- package/dist/chunk-MC3H7736.js +19910 -0
- package/dist/chunk-MMOSZPWJ.js +273 -0
- package/dist/chunk-MXCHWULV.js +285 -0
- package/dist/chunk-N3E56YPN.js +344 -0
- package/dist/chunk-NHFQMOVK.js +180 -0
- package/dist/chunk-NQU6KNQF.js +27 -0
- package/dist/chunk-OIK5YPTN.js +391 -0
- package/dist/chunk-OLWFG75W.js +4330 -0
- package/dist/chunk-ONPUIE7Y.js +29 -0
- package/dist/chunk-OW7D2PJL.js +376 -0
- package/dist/chunk-P63VL4HE.js +2357 -0
- package/dist/chunk-PA4YNUMU.js +45 -0
- package/dist/chunk-PAMPPUR6.js +4500 -0
- package/dist/chunk-PBS3OFNN.js +113 -0
- package/dist/chunk-PFT3VTS4.js +214 -0
- package/dist/chunk-PFTA6BZF.js +49 -0
- package/dist/chunk-PNN5KLAY.js +119 -0
- package/dist/chunk-PRPPGYZG.js +104 -0
- package/dist/chunk-PUKEZ37F.js +666 -0
- package/dist/chunk-Q3HOFXZJ.js +76 -0
- package/dist/chunk-Q5AMFEWD.js +119 -0
- package/dist/chunk-QKRNMBJO.js +80 -0
- package/dist/chunk-QSU47IQ5.js +36 -0
- package/dist/chunk-QSXK2RGH.js +688 -0
- package/dist/chunk-QXNWYUYI.js +52 -0
- package/dist/chunk-R6RY4Q5T.js +51 -0
- package/dist/chunk-RCU3MPY2.js +1496 -0
- package/dist/chunk-RLIB4JUN.js +174 -0
- package/dist/chunk-RYSCLMU3.js +1991 -0
- package/dist/chunk-RZUZ4RJJ.js +415 -0
- package/dist/chunk-S2A7YC3Q.js +41433 -0
- package/dist/chunk-S2FBBX7D.js +540 -0
- package/dist/chunk-S34B4QPI.js +430 -0
- package/dist/chunk-SETWVXOJ.js +2078 -0
- package/dist/chunk-SMA4N4AC.js +211 -0
- package/dist/chunk-SP2I63OO.js +252 -0
- package/dist/chunk-STHSY74Q.js +242 -0
- package/dist/chunk-STUPFGXY.js +3206 -0
- package/dist/chunk-T5I4S2W3.js +34 -0
- package/dist/chunk-T6AIP7H5.js +3851 -0
- package/dist/chunk-TANFV3MZ.js +531 -0
- package/dist/chunk-TAVDMUV3.js +189 -0
- package/dist/chunk-TBSQJ6IV.js +324 -0
- package/dist/chunk-TIZHP7KE.js +408 -0
- package/dist/chunk-TV45EUGM.js +51 -0
- package/dist/chunk-TW2KPPUC.js +4087 -0
- package/dist/chunk-V2YTAIQO.js +1075 -0
- package/dist/chunk-V5PKHHQQ.js +1963 -0
- package/dist/chunk-VF4IOZZO.js +184 -0
- package/dist/chunk-VJTINHZI.js +48 -0
- package/dist/chunk-VKLILA2D.js +205 -0
- package/dist/chunk-VOGCSYOI.js +16 -0
- package/dist/chunk-VPTTCYOC.js +16101 -0
- package/dist/chunk-WY6PP6AA.js +53 -0
- package/dist/chunk-WYFIXP54.js +174 -0
- package/dist/chunk-WZ2BXIGG.js +34 -0
- package/dist/chunk-XJNDSYGO.js +147 -0
- package/dist/chunk-XPCM366L.js +70 -0
- package/dist/chunk-XSQKGVYA.js +69 -0
- package/dist/chunk-XUPVB7TL.js +207 -0
- package/dist/chunk-XZRON6IV.js +131 -0
- package/dist/chunk-Y4NG2FP5.js +94 -0
- package/dist/chunk-YEDDOYSC.js +27809 -0
- package/dist/chunk-YGMCXNTY.js +41 -0
- package/dist/chunk-YJKPTX6J.js +228 -0
- package/dist/chunk-YMZ3TNYB.js +18 -0
- package/dist/chunk-Z6LF7HQN.js +22 -0
- package/dist/chunk-Z7SG32ZY.js +55 -0
- package/dist/chunk-ZDPAS56H.js +76 -0
- package/dist/chunk-ZJSP24X4.js +1247 -0
- package/dist/chunk-ZMAINWBW.js +18 -0
- package/dist/chunk-ZMFPW66A.js +22 -0
- package/dist/chunk-ZUIAOKXI.js +1611 -0
- package/dist/chunk-ZVM6AKKM.js +47 -0
- package/dist/files.json +1888 -0
- package/dist/fixtures/forge/README.md +19 -0
- package/dist/fixtures/forge/expected/discrepancies.yaml +54 -0
- package/dist/fixtures/forge/expected/inputs.yaml +38 -0
- package/dist/fixtures/forge/expected/scan.yaml +342 -0
- package/dist/fixtures/forge/inputs/diagrams/forge-context.drawio +23 -0
- package/dist/fixtures/forge/inputs/partner-specs/northwind-pay-openapi.yaml +11 -0
- package/dist/fixtures/forge/inputs/tickets/ado-stories.json +4 -0
- package/dist/fixtures/forge/inputs/tickets/closed-service-requests.json +5 -0
- package/dist/fixtures/forge/inputs/wiki/integration-patterns.md +11 -0
- package/dist/fixtures/forge/inputs/wiki/ledger-solution-spec.md +9 -0
- package/dist/fixtures/forge/inputs/wiki/snow-firewall-request.md +4 -0
- package/dist/fixtures/forge/metarepo/judgments/reaches/84af5b6af24f7ca8c23013f3.yaml +17 -0
- package/dist/fixtures/forge/metarepo/judgments/reaches/c3917555edebddce8cecb647.yaml +17 -0
- package/dist/fixtures/forge/metarepo/judgments/reaches/c5fafd6601f8ca9e34e0bbd0.yaml +17 -0
- package/dist/fixtures/forge/metarepo/org-pack/agents/architecture-critic.yaml +9 -0
- package/dist/fixtures/forge/metarepo/org-pack/agents/story-writer.yaml +10 -0
- package/dist/fixtures/forge/metarepo/org-pack/checklists/architecture.yaml +115 -0
- package/dist/fixtures/forge/metarepo/org-pack/dialects/forge.yaml +111 -0
- package/dist/fixtures/forge/metarepo/org-pack/gates.yaml +22 -0
- package/dist/fixtures/forge/metarepo/org-pack/pack.yaml +11 -0
- package/dist/fixtures/forge/metarepo/org-pack/patterns/A.EB.PUB.yaml +12 -0
- package/dist/fixtures/forge/metarepo/org-pack/patterns/D.JDBC.yaml +12 -0
- package/dist/fixtures/forge/metarepo/org-pack/patterns/S.DIR.BFF.yaml +12 -0
- package/dist/fixtures/forge/metarepo/org-pack/patterns/S.GW.SOR.yaml +13 -0
- package/dist/fixtures/forge/metarepo/org-pack/patterns/S.GW.VND.yaml +14 -0
- package/dist/fixtures/forge/metarepo/org-pack/process/change-templates/feature.md +37 -0
- package/dist/fixtures/forge/metarepo/org-pack/process/skills/add-forge-endpoint/SKILL.md +17 -0
- package/dist/fixtures/forge/metarepo/org-pack/process/snow/firewall-rule.yaml +14 -0
- package/dist/fixtures/forge/metarepo/org-pack/process/snow/gateway-route.yaml +13 -0
- package/dist/fixtures/forge/metarepo/org-pack/profiles/forge/profile.yaml +12 -0
- package/dist/fixtures/forge/metarepo/org-pack/profiles/forge/repos.manifest.yaml +44 -0
- package/dist/fixtures/forge/metarepo/org-pack/roles.yaml +14 -0
- package/dist/fixtures/forge/metarepo/org-pack/runtime.yaml +14 -0
- package/dist/fixtures/forge/metarepo/org-pack/sources.yaml +3 -0
- package/dist/fixtures/forge/repos/forge-events/topics/payments.initiated.yaml +4 -0
- package/dist/fixtures/forge/repos/forge-events/topics/transfers.completed.yaml +4 -0
- package/dist/fixtures/forge/repos/forge-gateway/proxies/ledger-v1/apiproxy/ledger-v1.xml +3 -0
- package/dist/fixtures/forge/repos/forge-gateway/proxies/ledger-v1/apiproxy/policies/SpikeArrest.xml +3 -0
- package/dist/fixtures/forge/repos/forge-gateway/proxies/ledger-v1/apiproxy/policies/VerifyOAuth.xml +4 -0
- package/dist/fixtures/forge/repos/forge-gateway/proxies/ledger-v1/apiproxy/proxies/default.xml +14 -0
- package/dist/fixtures/forge/repos/forge-gateway/proxies/ledger-v1/apiproxy/targets/ledger.xml +5 -0
- package/dist/fixtures/forge/repos/forge-infra/alerts/ledger-api.yaml +5 -0
- package/dist/fixtures/forge/repos/forge-infra/k8s/ledger-api.yaml +15 -0
- package/dist/fixtures/forge/repos/forge-infra/k8s/portal-bff.yaml +7 -0
- package/dist/fixtures/forge/repos/forge-infra/terraform/ledger-db.tf +9 -0
- package/dist/fixtures/forge/repos/forge-ledger/api/ledger-openapi.yaml +31 -0
- package/dist/fixtures/forge/repos/forge-ledger/forge.yaml +4 -0
- package/dist/fixtures/forge/repos/forge-ledger/src/main/java/forge/ledger/LedgerEndpoints.java +13 -0
- package/dist/fixtures/forge/repos/forge-ledger/src/main/java/forge/ledger/LedgerEvents.java +11 -0
- package/dist/fixtures/forge/repos/forge-ledger/src/main/java/forge/ledger/NorthwindClient.java +10 -0
- package/dist/fixtures/forge/repos/forge-ledger/src/main/resources/application.yml +3 -0
- package/dist/fixtures/forge/repos/forge-ledger/src/main/resources/db/migration/V1__create_account.sql +5 -0
- package/dist/fixtures/forge/repos/forge-ledger/src/main/resources/db/migration/V2__create_entry.sql +7 -0
- package/dist/fixtures/forge/repos/forge-portal-bff/pom.xml +1 -0
- package/dist/fixtures/forge/repos/forge-portal-bff/src/main/java/forge/portal/AuditPublisher.java +12 -0
- package/dist/fixtures/forge/repos/forge-portal-bff/src/main/java/forge/portal/BalanceController.java +16 -0
- package/dist/fixtures/forge/repos/forge-portal-bff/src/main/java/forge/portal/LedgerClient.java +13 -0
- package/dist/fixtures/forge/repos/forge-portal-bff/src/main/java/forge/portal/PaymentEvents.java +13 -0
- package/dist/fixtures/forge/repos/forge-portal-bff/src/main/resources/application.yml +8 -0
- package/dist/fixtures/forge/repos/forge-portal-ui/package.json +1 -0
- package/dist/fixtures/forge/repos/forge-portal-ui/src/api/balance.ts +5 -0
- package/dist/fixtures/forge/repos/forge-reports-job/src/main/java/forge/reports/DailyEntriesJob.java +19 -0
- package/dist/fixtures/forge/repos/forge-reports-job/src/main/resources/application.yml +3 -0
- package/dist/fixtures/forge-change/README.md +41 -0
- package/dist/fixtures/forge-change/expected/findings.yaml +301 -0
- package/dist/fixtures/forge-change/expected/stories.yaml +101 -0
- package/dist/fixtures/forge-change/judgments/personal-data/0b5f3dbf5257a22275d8c74b.yaml +18 -0
- package/dist/fixtures/forge-change/judgments/personal-data/2759a66770134c417440bf2d.yaml +18 -0
- package/dist/fixtures/forge-change/judgments/personal-data/31b1ea2f14d0737f1aca79fd.yaml +18 -0
- package/dist/fixtures/forge-change/judgments/personal-data/49afd70c08228e65ad6ca1ed.yaml +18 -0
- package/dist/fixtures/forge-change/judgments/personal-data/761cabcb8a39f5e5866d2dd6.yaml +18 -0
- package/dist/fixtures/forge-change/judgments/personal-data/adae902b90cc8ffa0c765145.yaml +18 -0
- package/dist/fixtures/forge-change/judgments/personal-data/ba11bb933105ec9493e065dd.yaml +18 -0
- package/dist/fixtures/forge-change/judgments/personal-data/bdbcd1cdac3021c68063c3e7.yaml +18 -0
- package/dist/fixtures/forge-change/openspec/changes/add-transfer-limit/brief.md +35 -0
- package/dist/fixtures/forge-change/openspec/changes/add-transfer-limit/change.yaml +120 -0
- package/dist/fixtures/forge-change/openspec/changes/add-transfer-limit/data.md +18 -0
- package/dist/fixtures/forge-change/openspec/changes/add-transfer-limit/design.md +29 -0
- package/dist/fixtures/forge-change/openspec/changes/add-transfer-limit/interfaces.md +17 -0
- package/dist/fixtures/forge-change/openspec/changes/add-transfer-limit/mappings.md +26 -0
- package/dist/fixtures/forge-change/openspec/changes/add-transfer-limit/proposal.md +35 -0
- package/dist/fixtures/forge-change/openspec/changes/add-transfer-limit/snow.md +12 -0
- package/dist/fixtures/forge-change/openspec/changes/add-transfer-limit/specs/transfer-limits/spec.md +19 -0
- package/dist/fixtures/forge-change/openspec/changes/add-transfer-limit/stories/s1-refuse-over-limit.md +17 -0
- package/dist/fixtures/forge-change/openspec/changes/add-transfer-limit/stories/s2-show-remaining.md +16 -0
- package/dist/fixtures/forge-change/openspec/changes/add-transfer-limit/stories/s3-audit-refusals.md +18 -0
- package/dist/fixtures/forge-change/openspec/changes/add-transfer-limit/stories/s4-velocity-check.md +16 -0
- package/dist/fixtures/forge-change/openspec/changes/add-transfer-limit/stories.md +8 -0
- package/dist/fixtures/forge-change/openspec/changes/add-transfer-limit/tasks.md +12 -0
- package/dist/fixtures/forge-change/openspec/changes/add-transfer-limit/ux.md +7 -0
- package/dist/fixtures/forge-change/org-pack/agents/code-and-test-critic.yaml +13 -0
- package/dist/fixtures/forge-change/org-pack/agents/complete-critic.yaml +12 -0
- package/dist/fixtures/forge-change/org-pack/agents/safe-critic.yaml +12 -0
- package/dist/fixtures/forge-change/org-pack/agents/ship-critic.yaml +11 -0
- package/dist/fixtures/forge-change/org-pack/agents/true-critic.yaml +11 -0
- package/dist/fixtures/forge-change/org-pack/checklists/review.yaml +54 -0
- package/dist/fixtures/forge-change/org-pack/gates.yaml +25 -0
- package/dist/fixtures/forge-change/org-pack/teams/team-ledger/checklists/ledger-team.yaml +227 -0
- package/dist/fixtures/forge-change/recordings/complete-critic/2ad84f84bf39c1d5412697e69fe23034d6983ec45a00314bf0975e627589a3c5.json +12 -0
- package/dist/fixtures/forge-change/recordings/complete-critic/417c98d8e54a4387d0fd93441c49f44c2868958739070d855c1dce0c41e24db3.json +12 -0
- package/dist/fixtures/forge-change/recordings/complete-critic/50203ee22e9cb3ef047295edb6538fe7d5695e1e4b139f3c929013dc6ce47021.json +12 -0
- package/dist/fixtures/forge-change/recordings/complete-critic/89f18d67f5d9c325bf35e2a9e020f9ddb6ee13b8523315037f0905cc57c040b1.json +12 -0
- package/dist/fixtures/forge-change/recordings/complete-critic/b4097867d26ae8eadc8b9efa8cdd86897013358035f0b40086c299a59c4ba121.json +12 -0
- package/dist/fixtures/forge-change/recordings/safe-critic/302d06f49664dd67ad2e21ab9cf74c1b0f7d3f750709c6ed8eff2b70983274e2.json +12 -0
- package/dist/fixtures/forge-change/recordings/safe-critic/32f1049c9a5ab4a759543290db4644ec6e221cc45fdfdcf06193837c6a8ce0b9.json +12 -0
- package/dist/fixtures/forge-change/recordings/safe-critic/4e4a84bfc04b9f26ca4250319a879901ff7a1c081c71e46a117675fee05026b0.json +12 -0
- package/dist/fixtures/forge-change/recordings/safe-critic/604af0fcdd6df6b76208fa88305fdc15c37606c2a4d1ae816477e46ac8c2dd04.json +12 -0
- package/dist/fixtures/forge-change/recordings/safe-critic/f0bf50af11f14c50d07904f0dffe1c8c20fb0cd89770645a8824c1d30a443955.json +12 -0
- package/dist/fixtures/forge-change/recordings/ship-critic/350954b58833b2c04880c67db45a1b328b7fd541d9f0d943b63905b8875488a6.json +12 -0
- package/dist/fixtures/forge-change/recordings/ship-critic/4af95c6c96a206777cec9de1f9c2d8c55a36ddc9b8db2f81d6eb83217b835efa.json +12 -0
- package/dist/fixtures/forge-change/recordings/ship-critic/76c34522a49cf7587a4e5689a5030d384603fa780b8aba97d27815cbc829683e.json +12 -0
- package/dist/fixtures/forge-change/recordings/ship-critic/a3b2cd1ace48587e5994333db03b34c3a0f9dec4f1932f1730ddfb687f6918c8.json +12 -0
- package/dist/fixtures/forge-change/recordings/ship-critic/de288a4fa64d0fc20890f9a787080ce6eac5203b7b9b72d884df11e8cf111835.json +12 -0
- package/dist/fixtures/forge-change/recordings/story-writer/1ddb00eac2aaf91a5ec5d9dfd220116be9f75aec6546a4a3a3b050ed89af2864.json +12 -0
- package/dist/packages/assistant-kit/frameworks-kit/README.md +86 -0
- package/dist/packages/assistant-kit/frameworks-kit/checklist.md +11 -0
- package/dist/packages/assistant-kit/frameworks-kit/examples/tide-lantern.yaml +56 -0
- package/dist/packages/assistant-kit/frameworks-kit/examples/tide-platform.yaml +43 -0
- package/dist/packages/assistant-kit/frameworks-kit/prompts/write-framework-model.md +12 -0
- package/dist/packages/assistant-kit/kit/README.md +98 -0
- package/dist/packages/assistant-kit/kit/c2-reference.md +334 -0
- package/dist/packages/assistant-kit/kit/checklist.md +39 -0
- package/dist/packages/assistant-kit/kit/examples/house-http-client.yaml +102 -0
- package/dist/packages/assistant-kit/kit/examples/tide-apigee.yaml +56 -0
- package/dist/packages/assistant-kit/kit/examples/tide-apim.yaml +32 -0
- package/dist/packages/assistant-kit/kit/examples/tide-kong.yaml +44 -0
- package/dist/packages/assistant-kit/kit/prompts/write-dialect.md +34 -0
- package/dist/packages/assistant-kit/kit/worked-example.md +198 -0
- package/dist/packages/conformance/sample/README.md +4 -0
- package/dist/packages/conformance/sample/inputs/notes.md +3 -0
- package/dist/packages/conformance/sample/inputs/systems.csv +3 -0
- package/dist/packages/conformance/sample/repo/api/greeter.proto +6 -0
- package/dist/packages/conformance/sample/repo/api/openapi.yaml +10 -0
- package/dist/packages/conformance/sample/repo/api/schema.graphql +3 -0
- package/dist/packages/conformance/sample/repo/db/V1__create_greeting.sql +4 -0
- package/dist/packages/conformance/sample/repo/package.json +7 -0
- package/dist/packages/conformance/sample/repo/src/main/java/sample/HelloController.java +10 -0
- package/dist/packages/conformance/sample/repo/web/app.ts +4 -0
- package/dist/packages/evals/datasets/critic-lenses.yaml +62 -0
- package/dist/packages/evals/datasets/dialect-writer.yaml +14 -0
- package/dist/packages/evals/datasets/feature-namer.yaml +34 -0
- package/dist/packages/evals/datasets/glossary-definer.yaml +25 -0
- package/dist/packages/evals/datasets/story-writer.yaml +13 -0
- package/dist/packages/evals/datasets/template-writer.yaml +16 -0
- package/dist/packages/evals/datasets/test-writer.yaml +18 -0
- package/dist/packages/executors/reference/reference-executor.mjs +233 -0
- package/dist/packages/extractors/dialects/argocd.yaml +45 -0
- package/dist/packages/extractors/dialects/fixtures/jaxrs/accounts-service/src/main/java/forge/accounts/AccountResource.java +47 -0
- package/dist/packages/extractors/dialects/fixtures/jaxrs/accounts-service/src/main/java/forge/accounts/AuditClient.java +12 -0
- package/dist/packages/extractors/dialects/fixtures/jaxrs/accounts-service/src/main/java/forge/accounts/LedgerClient.java +17 -0
- package/dist/packages/extractors/dialects/fixtures/micronaut/statements-service/src/main/java/forge/statements/LedgerClient.java +14 -0
- package/dist/packages/extractors/dialects/fixtures/micronaut/statements-service/src/main/java/forge/statements/RatesClient.java +11 -0
- package/dist/packages/extractors/dialects/fixtures/micronaut/statements-service/src/main/java/forge/statements/StatementController.java +36 -0
- package/dist/packages/extractors/dialects/fixtures/micronaut/statements-service/src/main/resources/application.yml +7 -0
- package/dist/packages/extractors/dialects/fixtures/quarkus-reactive-messaging/fraud-service/src/main/java/forge/fraud/FraudScorer.java +27 -0
- package/dist/packages/extractors/dialects/fixtures/quarkus-reactive-messaging/fraud-service/src/main/resources/application.properties +4 -0
- package/dist/packages/extractors/dialects/fixtures/terraform-aws/forge-aws-infra/envs/prod/api.tf +38 -0
- package/dist/packages/extractors/dialects/fixtures/terraform-aws/forge-aws-infra/envs/prod/main.tf +95 -0
- package/dist/packages/extractors/dialects/fixtures/terraform-aws/forge-aws-infra/envs/prod/terraform.tfvars +1 -0
- package/dist/packages/extractors/dialects/fixtures/terraform-aws/forge-aws-infra/envs/prod/variables.tf +4 -0
- package/dist/packages/extractors/dialects/fixtures/terraform-azure/forge-azure-infra/main.tf +102 -0
- package/dist/packages/extractors/dialects/fixtures/terraform-gcp/forge-gcp-infra/main.tf +51 -0
- package/dist/packages/extractors/dialects/harness.yaml +101 -0
- package/dist/packages/extractors/dialects/jaxrs.yaml +155 -0
- package/dist/packages/extractors/dialects/micronaut.yaml +207 -0
- package/dist/packages/extractors/dialects/quarkus-reactive-messaging.yaml +158 -0
- package/dist/packages/extractors/dialects/tekton.yaml +71 -0
- package/dist/packages/extractors/dialects/terraform-aws.yaml +267 -0
- package/dist/packages/extractors/dialects/terraform-azure.yaml +212 -0
- package/dist/packages/extractors/dialects/terraform-gcp.yaml +109 -0
- package/dist/packages/extractors/grammars/README.md +11 -0
- package/dist/packages/extractors/grammars/tree-sitter-groovy.LICENSE +21 -0
- package/dist/packages/extractors/grammars/tree-sitter-groovy.wasm +0 -0
- package/dist/packages/extractors/grammars/vendored.json +11 -0
- package/dist/packages/learn/lessons/architect-diagrams-and-code.yaml +69 -0
- package/dist/packages/learn/lessons/business-analyst-story-bar.yaml +94 -0
- package/dist/packages/learn/lessons/delivery-lead-release-readiness.yaml +44 -0
- package/dist/packages/learn/lessons/designer-features-in-user-words.yaml +57 -0
- package/dist/packages/learn/lessons/developer-find-your-way.yaml +60 -0
- package/dist/packages/learn/lessons/everyone-first-hour.yaml +74 -0
- package/dist/packages/learn/lessons/platform-lead-coverage-and-defences.yaml +58 -0
- package/dist/packages/learn/lessons/product-owner-first-change.yaml +167 -0
- package/dist/packages/learn/lessons/qa-tests-and-trace.yaml +53 -0
- package/dist/packages/learn/lessons/team-lead-initiative.yaml +146 -0
- package/dist/packages/naysayer/constitution/constitution.yaml +286 -0
- package/dist/packages/naysayer/starter/review.yaml +256 -0
- package/dist/packages/sdk/npm/index.js +8892 -0
- package/dist/packages/speckit/rules/drift.yaml +78 -0
- package/dist/packages/taskrun/rules/conform.yaml +38 -0
- package/dist/packages/testgen/rules/trace.yaml +49 -0
- package/dist/packages/ui/dist/assets/elk-api-tma0MCuh.js +2 -0
- package/dist/packages/ui/dist/assets/elk-worker.min--dAJroGl.js +6401 -0
- package/dist/packages/ui/dist/assets/elk-worker.min-DdYmS5kP.js +2 -0
- package/dist/packages/ui/dist/assets/ibm-plex-sans-cyrillic-400-normal-BTotfTJu.woff +0 -0
- package/dist/packages/ui/dist/assets/ibm-plex-sans-cyrillic-400-normal-DZqxrq2p.woff2 +0 -0
- package/dist/packages/ui/dist/assets/ibm-plex-sans-cyrillic-500-normal-ByOcLdNv.woff +0 -0
- package/dist/packages/ui/dist/assets/ibm-plex-sans-cyrillic-500-normal-CocWQlwt.woff2 +0 -0
- package/dist/packages/ui/dist/assets/ibm-plex-sans-cyrillic-600-normal-71GNu3SW.woff2 +0 -0
- package/dist/packages/ui/dist/assets/ibm-plex-sans-cyrillic-600-normal-BGq0mW3O.woff +0 -0
- package/dist/packages/ui/dist/assets/ibm-plex-sans-cyrillic-ext-400-normal-Dsrv2Tcn.woff +0 -0
- package/dist/packages/ui/dist/assets/ibm-plex-sans-cyrillic-ext-400-normal-g30qAdWV.woff2 +0 -0
- package/dist/packages/ui/dist/assets/ibm-plex-sans-cyrillic-ext-500-normal-Cs5J6C77.woff2 +0 -0
- package/dist/packages/ui/dist/assets/ibm-plex-sans-cyrillic-ext-500-normal-DB5PtV2g.woff +0 -0
- package/dist/packages/ui/dist/assets/ibm-plex-sans-cyrillic-ext-600-normal-Bz0x94Yp.woff +0 -0
- package/dist/packages/ui/dist/assets/ibm-plex-sans-cyrillic-ext-600-normal-DUMzJB7m.woff2 +0 -0
- package/dist/packages/ui/dist/assets/ibm-plex-sans-greek-400-normal-D9ESIMu3.woff +0 -0
- package/dist/packages/ui/dist/assets/ibm-plex-sans-greek-400-normal-_efipK4i.woff2 +0 -0
- package/dist/packages/ui/dist/assets/ibm-plex-sans-greek-500-normal-CuWXN6rf.woff +0 -0
- package/dist/packages/ui/dist/assets/ibm-plex-sans-greek-500-normal-JMMifIXV.woff2 +0 -0
- package/dist/packages/ui/dist/assets/ibm-plex-sans-greek-600-normal-D-CqTdkO.woff +0 -0
- package/dist/packages/ui/dist/assets/ibm-plex-sans-greek-600-normal-DzTrcv_p.woff2 +0 -0
- package/dist/packages/ui/dist/assets/ibm-plex-sans-latin-400-normal-CDDApCn2.woff2 +0 -0
- package/dist/packages/ui/dist/assets/ibm-plex-sans-latin-400-normal-CYLoc0-x.woff +0 -0
- package/dist/packages/ui/dist/assets/ibm-plex-sans-latin-500-normal-6ng42L7E.woff2 +0 -0
- package/dist/packages/ui/dist/assets/ibm-plex-sans-latin-500-normal-BgVn5rGT.woff +0 -0
- package/dist/packages/ui/dist/assets/ibm-plex-sans-latin-600-normal-Cu4Hd6ag.woff +0 -0
- package/dist/packages/ui/dist/assets/ibm-plex-sans-latin-600-normal-CuJfVYMP.woff2 +0 -0
- package/dist/packages/ui/dist/assets/ibm-plex-sans-latin-ext-400-normal-C5H60-Va.woff2 +0 -0
- package/dist/packages/ui/dist/assets/ibm-plex-sans-latin-ext-400-normal-RBey6euL.woff +0 -0
- package/dist/packages/ui/dist/assets/ibm-plex-sans-latin-ext-500-normal-D0aIdm-b.woff +0 -0
- package/dist/packages/ui/dist/assets/ibm-plex-sans-latin-ext-500-normal-DakdToA3.woff2 +0 -0
- package/dist/packages/ui/dist/assets/ibm-plex-sans-latin-ext-600-normal-DIrixKbi.woff +0 -0
- package/dist/packages/ui/dist/assets/ibm-plex-sans-latin-ext-600-normal-DOrvGEcy.woff2 +0 -0
- package/dist/packages/ui/dist/assets/ibm-plex-sans-vietnamese-400-normal-DG4YqDda.woff2 +0 -0
- package/dist/packages/ui/dist/assets/ibm-plex-sans-vietnamese-400-normal-fK1oJ5dG.woff +0 -0
- package/dist/packages/ui/dist/assets/ibm-plex-sans-vietnamese-500-normal-BEb3_waV.woff +0 -0
- package/dist/packages/ui/dist/assets/ibm-plex-sans-vietnamese-500-normal-e4dixQRQ.woff2 +0 -0
- package/dist/packages/ui/dist/assets/ibm-plex-sans-vietnamese-600-normal-DgdngZtN.woff +0 -0
- package/dist/packages/ui/dist/assets/ibm-plex-sans-vietnamese-600-normal-DpPYBSTl.woff2 +0 -0
- package/dist/packages/ui/dist/assets/index-B54fm9FQ.js +29 -0
- package/dist/packages/ui/dist/assets/index-DD_hufQz.css +1 -0
- package/dist/packages/ui/dist/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2 +0 -0
- package/dist/packages/ui/dist/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff +0 -0
- package/dist/packages/ui/dist/assets/jetbrains-mono-cyrillic-500-normal-DJqRU3vO.woff +0 -0
- package/dist/packages/ui/dist/assets/jetbrains-mono-cyrillic-500-normal-DmUKJPL_.woff2 +0 -0
- package/dist/packages/ui/dist/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff +0 -0
- package/dist/packages/ui/dist/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2 +0 -0
- package/dist/packages/ui/dist/assets/jetbrains-mono-greek-500-normal-D7SFKleX.woff +0 -0
- package/dist/packages/ui/dist/assets/jetbrains-mono-greek-500-normal-JpySY46c.woff2 +0 -0
- package/dist/packages/ui/dist/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
- package/dist/packages/ui/dist/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
- package/dist/packages/ui/dist/assets/jetbrains-mono-latin-500-normal-BWZEU5yA.woff2 +0 -0
- package/dist/packages/ui/dist/assets/jetbrains-mono-latin-500-normal-CJOVTJB7.woff +0 -0
- package/dist/packages/ui/dist/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
- package/dist/packages/ui/dist/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff +0 -0
- package/dist/packages/ui/dist/assets/jetbrains-mono-latin-ext-500-normal-Cut-4mMH.woff2 +0 -0
- package/dist/packages/ui/dist/assets/jetbrains-mono-latin-ext-500-normal-ckzbgY84.woff +0 -0
- package/dist/packages/ui/dist/assets/jetbrains-mono-vietnamese-400-normal-CqNFfHCs.woff +0 -0
- package/dist/packages/ui/dist/assets/jetbrains-mono-vietnamese-500-normal-DNRqzVM1.woff +0 -0
- package/dist/packages/ui/dist/assets/rajdhani-devanagari-600-normal-BtY48IV_.woff +0 -0
- package/dist/packages/ui/dist/assets/rajdhani-devanagari-600-normal-DhS7ScYx.woff2 +0 -0
- package/dist/packages/ui/dist/assets/rajdhani-devanagari-700-normal-BQQOj9BB.woff2 +0 -0
- package/dist/packages/ui/dist/assets/rajdhani-devanagari-700-normal-DBRwWuxA.woff +0 -0
- package/dist/packages/ui/dist/assets/rajdhani-latin-600-normal-CXCVEoA9.woff2 +0 -0
- package/dist/packages/ui/dist/assets/rajdhani-latin-600-normal-Dx02qI2_.woff +0 -0
- package/dist/packages/ui/dist/assets/rajdhani-latin-700-normal-BPDUZo87.woff2 +0 -0
- package/dist/packages/ui/dist/assets/rajdhani-latin-700-normal-e0Uj8ZlN.woff +0 -0
- package/dist/packages/ui/dist/assets/rajdhani-latin-ext-600-normal-BleIgkPp.woff +0 -0
- package/dist/packages/ui/dist/assets/rajdhani-latin-ext-600-normal-C9ZjJQAt.woff2 +0 -0
- package/dist/packages/ui/dist/assets/rajdhani-latin-ext-700-normal-BPaQ7tlD.woff +0 -0
- package/dist/packages/ui/dist/assets/rajdhani-latin-ext-700-normal-S6aYESQK.woff2 +0 -0
- package/dist/packages/ui/dist/favicon.svg +1 -0
- package/dist/packages/ui/dist/index.html +15 -0
- package/dist/third-party.json +575 -0
- package/dist/vendor/@fission-ai/openspec/bin/openspec.js +85986 -0
- package/dist/vendor/@fission-ai/openspec/package.json +82 -0
- package/dist/vendor/@fission-ai/openspec/schemas/spec-driven/schema.yaml +248 -0
- package/dist/vendor/@fission-ai/openspec/schemas/spec-driven/templates/design.md +21 -0
- package/dist/vendor/@fission-ai/openspec/schemas/spec-driven/templates/proposal.md +34 -0
- package/dist/vendor/@fission-ai/openspec/schemas/spec-driven/templates/spec.md +13 -0
- package/dist/vendor/@fission-ai/openspec/schemas/spec-driven/templates/tasks.md +11 -0
- package/dist/vendor/tree-sitter-wasms/out/tree-sitter-c_sharp.wasm +0 -0
- package/dist/vendor/tree-sitter-wasms/out/tree-sitter-go.wasm +0 -0
- package/dist/vendor/tree-sitter-wasms/out/tree-sitter-java.wasm +0 -0
- package/dist/vendor/tree-sitter-wasms/out/tree-sitter-javascript.wasm +0 -0
- package/dist/vendor/tree-sitter-wasms/out/tree-sitter-kotlin.wasm +0 -0
- package/dist/vendor/tree-sitter-wasms/out/tree-sitter-php.wasm +0 -0
- package/dist/vendor/tree-sitter-wasms/out/tree-sitter-python.wasm +0 -0
- package/dist/vendor/tree-sitter-wasms/out/tree-sitter-ruby.wasm +0 -0
- package/dist/vendor/tree-sitter-wasms/out/tree-sitter-rust.wasm +0 -0
- package/dist/vendor/tree-sitter-wasms/out/tree-sitter-scala.wasm +0 -0
- package/dist/vendor/tree-sitter-wasms/out/tree-sitter-tsx.wasm +0 -0
- package/dist/vendor/tree-sitter-wasms/out/tree-sitter-typescript.wasm +0 -0
- package/dist/vendor/web-tree-sitter/tree-sitter.wasm +0 -0
- package/package.json +36 -3
|
@@ -0,0 +1,4500 @@
|
|
|
1
|
+
import { createRequire as __atticusCreateRequire } from "node:module";
|
|
2
|
+
import { fileURLToPath as __atticusFileURLToPath } from "node:url";
|
|
3
|
+
var require = __atticusCreateRequire(import.meta.url);
|
|
4
|
+
var __atticusUrl = (p) => new URL("./" + p, import.meta.url).href;
|
|
5
|
+
var __atticusPath = (p) => __atticusFileURLToPath(__atticusUrl(p));
|
|
6
|
+
import {
|
|
7
|
+
globToRegExp
|
|
8
|
+
} from "./chunk-GQ4N4WKF.js";
|
|
9
|
+
import {
|
|
10
|
+
APIGEE_BUNDLE,
|
|
11
|
+
Fact,
|
|
12
|
+
MOCK_FILE,
|
|
13
|
+
NEVER_CHECKED_STEPS,
|
|
14
|
+
citedFiles,
|
|
15
|
+
claimOnLines,
|
|
16
|
+
isSecretPath,
|
|
17
|
+
lineNamesClaim,
|
|
18
|
+
normalizePath,
|
|
19
|
+
partsBuildWord,
|
|
20
|
+
partsNamePath,
|
|
21
|
+
quoteMatches,
|
|
22
|
+
quoteNotRequired,
|
|
23
|
+
redactSecrets,
|
|
24
|
+
staleCite,
|
|
25
|
+
toCanonicalYaml
|
|
26
|
+
} from "./chunk-S2A7YC3Q.js";
|
|
27
|
+
|
|
28
|
+
// packages/verify/dist/source.js
|
|
29
|
+
import { readdirSync, readFileSync, statSync } from "node:fs";
|
|
30
|
+
import { isAbsolute, join, normalize, relative, sep } from "node:path";
|
|
31
|
+
var SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
32
|
+
".git",
|
|
33
|
+
".hg",
|
|
34
|
+
".svn",
|
|
35
|
+
"node_modules",
|
|
36
|
+
".atticus",
|
|
37
|
+
".idea",
|
|
38
|
+
".vscode",
|
|
39
|
+
".gradle",
|
|
40
|
+
".mvn",
|
|
41
|
+
"target",
|
|
42
|
+
"build",
|
|
43
|
+
"dist",
|
|
44
|
+
"out",
|
|
45
|
+
"bin",
|
|
46
|
+
"obj",
|
|
47
|
+
"vendor",
|
|
48
|
+
"__pycache__",
|
|
49
|
+
".venv",
|
|
50
|
+
"venv",
|
|
51
|
+
".next",
|
|
52
|
+
".nuxt",
|
|
53
|
+
"coverage"
|
|
54
|
+
]);
|
|
55
|
+
function insideRepo(path) {
|
|
56
|
+
if (path === "" || isAbsolute(path) || /^[A-Za-z]:/.test(path))
|
|
57
|
+
return false;
|
|
58
|
+
const n = normalize(path).split(sep).join("/");
|
|
59
|
+
return n !== ".." && !n.startsWith("../");
|
|
60
|
+
}
|
|
61
|
+
function dirSource(root) {
|
|
62
|
+
let cache;
|
|
63
|
+
const walk = (dir, out) => {
|
|
64
|
+
let names;
|
|
65
|
+
try {
|
|
66
|
+
names = readdirSync(dir).sort();
|
|
67
|
+
} catch {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
for (const name of names) {
|
|
71
|
+
const full = join(dir, name);
|
|
72
|
+
let st;
|
|
73
|
+
try {
|
|
74
|
+
st = statSync(full);
|
|
75
|
+
} catch {
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
if (st.isDirectory()) {
|
|
79
|
+
if (!SKIP_DIRS.has(name))
|
|
80
|
+
walk(full, out);
|
|
81
|
+
} else if (st.isFile())
|
|
82
|
+
out.push(relative(root, full).split(sep).join("/"));
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
return {
|
|
86
|
+
list() {
|
|
87
|
+
if (!cache) {
|
|
88
|
+
cache = [];
|
|
89
|
+
walk(root, cache);
|
|
90
|
+
}
|
|
91
|
+
return cache;
|
|
92
|
+
},
|
|
93
|
+
read(path) {
|
|
94
|
+
if (!insideRepo(path))
|
|
95
|
+
return void 0;
|
|
96
|
+
try {
|
|
97
|
+
return readFileSync(join(root, ...path.split("/")), "utf8");
|
|
98
|
+
} catch {
|
|
99
|
+
return void 0;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// packages/verify/dist/frameworks.js
|
|
106
|
+
var FRAMEWORKS = [
|
|
107
|
+
"spring",
|
|
108
|
+
"aspnet",
|
|
109
|
+
"express",
|
|
110
|
+
"nest",
|
|
111
|
+
"angular",
|
|
112
|
+
"react",
|
|
113
|
+
"vue",
|
|
114
|
+
"next",
|
|
115
|
+
"quarkus",
|
|
116
|
+
"django",
|
|
117
|
+
"rails",
|
|
118
|
+
"laravel",
|
|
119
|
+
"house"
|
|
120
|
+
];
|
|
121
|
+
var KNOWN = [
|
|
122
|
+
// Java and Kotlin
|
|
123
|
+
["spring-boot*", { name: "spring-boot", topic: "spring", shape: "rest" }],
|
|
124
|
+
["spring-webmvc", { name: "spring", topic: "spring", shape: "rest" }],
|
|
125
|
+
["spring-webflux", { name: "spring", topic: "spring", shape: "rest" }],
|
|
126
|
+
["spring-web", { name: "spring", topic: "spring", shape: "rest" }],
|
|
127
|
+
["spring-data-rest*", { name: "spring-data-rest", topic: "spring", shape: "rest" }],
|
|
128
|
+
["spring-context", { name: "spring", topic: "spring" }],
|
|
129
|
+
["spring-core", { name: "spring", topic: "spring" }],
|
|
130
|
+
["hibernate-core", { name: "hibernate", topic: "spring", shape: "store" }],
|
|
131
|
+
["quarkus-bom", { name: "quarkus", topic: "quarkus" }],
|
|
132
|
+
["quarkus-rest*", { name: "quarkus", topic: "quarkus", shape: "rest" }],
|
|
133
|
+
["quarkus-resteasy*", { name: "quarkus", topic: "quarkus", shape: "rest" }],
|
|
134
|
+
["quarkus*", { name: "quarkus", topic: "quarkus" }],
|
|
135
|
+
["micronaut-http-server*", { name: "micronaut", shape: "rest" }],
|
|
136
|
+
["micronaut*", { name: "micronaut" }],
|
|
137
|
+
// .NET
|
|
138
|
+
["microsoft.net.sdk.web", { name: "asp.net", topic: "aspnet", shape: "rest" }],
|
|
139
|
+
["microsoft.aspnetcore*", { name: "asp.net", topic: "aspnet", shape: "rest" }],
|
|
140
|
+
["microsoft.entityframeworkcore*", { name: "entity-framework", topic: "aspnet", shape: "store" }],
|
|
141
|
+
// Node
|
|
142
|
+
["express", { name: "express", topic: "express", shape: "rest" }],
|
|
143
|
+
["@nestjs/core", { name: "nest", topic: "nest", shape: "rest" }],
|
|
144
|
+
["nestjs/core", { name: "nest", topic: "nest", shape: "rest" }],
|
|
145
|
+
["@angular/core", { name: "angular", topic: "angular", shape: "screen" }],
|
|
146
|
+
["angular/core", { name: "angular", topic: "angular", shape: "screen" }],
|
|
147
|
+
["react", { name: "react", topic: "react", shape: "screen" }],
|
|
148
|
+
["vue", { name: "vue", topic: "vue", shape: "screen" }],
|
|
149
|
+
["next", { name: "next", topic: "next" }],
|
|
150
|
+
["svelte", { name: "svelte" }],
|
|
151
|
+
["typeorm", { name: "typeorm", topic: "express", shape: "store" }],
|
|
152
|
+
["@prisma/client", { name: "prisma", topic: "express", shape: "store" }],
|
|
153
|
+
["prisma/client", { name: "prisma", topic: "express", shape: "store" }],
|
|
154
|
+
// Python
|
|
155
|
+
["django", { name: "django", topic: "django", shape: "rest" }],
|
|
156
|
+
["djangorestframework", { name: "django-rest-framework", topic: "django", shape: "rest" }],
|
|
157
|
+
["flask", { name: "flask", shape: "rest" }],
|
|
158
|
+
["fastapi", { name: "fastapi", shape: "rest" }],
|
|
159
|
+
["celery", { name: "celery", topic: "django", shape: "batch-job" }],
|
|
160
|
+
// Ruby
|
|
161
|
+
["rails", { name: "rails", topic: "rails", shape: "rest" }],
|
|
162
|
+
["sidekiq", { name: "sidekiq", topic: "rails", shape: "batch-job" }],
|
|
163
|
+
["devise", { name: "devise", topic: "rails" }],
|
|
164
|
+
// PHP
|
|
165
|
+
["laravel/framework", { name: "laravel", topic: "laravel", shape: "rest" }],
|
|
166
|
+
["laravel", { name: "laravel", topic: "laravel", shape: "rest" }]
|
|
167
|
+
];
|
|
168
|
+
function declarationKey(declared) {
|
|
169
|
+
const d = declared.trim().toLowerCase();
|
|
170
|
+
const parts = d.split(":");
|
|
171
|
+
const colon = parts.length > 1 ? parts[1] ?? d : d;
|
|
172
|
+
const name = colon.split(/[=<>~!,\s[]/)[0] ?? colon;
|
|
173
|
+
return name.replace(/^['"]|['"]$/g, "");
|
|
174
|
+
}
|
|
175
|
+
function frameworkOf(declared) {
|
|
176
|
+
const key = declarationKey(declared);
|
|
177
|
+
if (!key)
|
|
178
|
+
return void 0;
|
|
179
|
+
for (const [pattern, known] of KNOWN) {
|
|
180
|
+
const star = pattern.endsWith("*");
|
|
181
|
+
const p = star ? pattern.slice(0, -1) : pattern;
|
|
182
|
+
if (star ? key.startsWith(p) : key === p)
|
|
183
|
+
return known;
|
|
184
|
+
}
|
|
185
|
+
return void 0;
|
|
186
|
+
}
|
|
187
|
+
function frameworkNameProblem(f) {
|
|
188
|
+
const known = frameworkOf(f.cite.value);
|
|
189
|
+
const name = f.name.trim().toLowerCase();
|
|
190
|
+
if (known) {
|
|
191
|
+
return name === known.name ? void 0 : `names the framework "${f.name}", but ${f.cite.file}:${f.cite.line} declares ${f.cite.value}, which is ${known.name}`;
|
|
192
|
+
}
|
|
193
|
+
const key = declarationKey(f.cite.value);
|
|
194
|
+
if (declarationKey(name) === key || name === key)
|
|
195
|
+
return void 0;
|
|
196
|
+
return `names the framework "${f.name}", which ${f.cite.file}:${f.cite.line} doesn't declare: it declares ${f.cite.value}, and Atticus doesn't know that one, so name it as the build file writes it (${key})`;
|
|
197
|
+
}
|
|
198
|
+
var frameworkFacts = (facts) => facts.filter((f) => f.kind === "framework").sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
|
|
199
|
+
function frameworkTopics(facts) {
|
|
200
|
+
const fws = frameworkFacts(facts);
|
|
201
|
+
if (!fws.length)
|
|
202
|
+
return void 0;
|
|
203
|
+
const topics = /* @__PURE__ */ new Set();
|
|
204
|
+
for (const f of fws) {
|
|
205
|
+
const known = frameworkOf(f.cite.value);
|
|
206
|
+
if (!known?.topic)
|
|
207
|
+
topics.add("house");
|
|
208
|
+
else
|
|
209
|
+
topics.add(known.topic);
|
|
210
|
+
}
|
|
211
|
+
return FRAMEWORKS.filter((t) => topics.has(t));
|
|
212
|
+
}
|
|
213
|
+
function frameworkTopicsOfType(type) {
|
|
214
|
+
const t = (type ?? "").trim().toLowerCase();
|
|
215
|
+
const topics = t === "spring-boot" || t === "java" ? ["spring"] : t === "dotnet" ? ["aspnet"] : t === "frontend" ? ["angular", "react", "vue", "next"] : t === "node" ? ["express", "nest", "next", "react"] : t === "python" ? ["django"] : [];
|
|
216
|
+
return FRAMEWORKS.filter((x) => topics.includes(x));
|
|
217
|
+
}
|
|
218
|
+
var SHAPE_WORDS = {
|
|
219
|
+
rest: "no REST entry",
|
|
220
|
+
screen: "no screen entry",
|
|
221
|
+
"batch-job": "no batch-job entry",
|
|
222
|
+
store: "no table it reads or writes"
|
|
223
|
+
};
|
|
224
|
+
var SHAPE_ASK = {
|
|
225
|
+
rest: "which routes it serves",
|
|
226
|
+
screen: "which screens it has",
|
|
227
|
+
"batch-job": "which jobs it runs",
|
|
228
|
+
store: "which tables it reads and writes"
|
|
229
|
+
};
|
|
230
|
+
var holdsShape = (facts, shape2) => facts.some((f) => shape2 === "store" ? f.kind === "store" : f.kind === "entry" && f.entryType === (shape2 === "rest" ? "rest" : shape2));
|
|
231
|
+
function frameworkGaps(facts) {
|
|
232
|
+
const fws = frameworkFacts(facts);
|
|
233
|
+
if (!fws.length)
|
|
234
|
+
return [];
|
|
235
|
+
if (!facts.some((f) => f.kind !== "framework"))
|
|
236
|
+
return [];
|
|
237
|
+
const out = [];
|
|
238
|
+
for (const f of fws) {
|
|
239
|
+
const shape2 = frameworkOf(f.cite.value)?.shape;
|
|
240
|
+
if (!shape2 || holdsShape(facts, shape2))
|
|
241
|
+
continue;
|
|
242
|
+
out.push({
|
|
243
|
+
id: f.id,
|
|
244
|
+
framework: f.name,
|
|
245
|
+
cite: `${f.cite.file}:${f.cite.line}`,
|
|
246
|
+
expected: shape2,
|
|
247
|
+
question: `${f.repo} declares ${f.name} at ${f.cite.file}:${f.cite.line}, and its facts have ${SHAPE_WORDS[shape2]}. Were its entry points missed, or does this repo really have none? Read it again for ${SHAPE_ASK[shape2]}, or say it has none.`
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
return out.sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
|
|
251
|
+
}
|
|
252
|
+
function frameworksToLearn(facts, known = []) {
|
|
253
|
+
const have = new Set(known.map(declarationKey));
|
|
254
|
+
return frameworkFacts(facts).filter((f) => !frameworkOf(f.cite.value) && !have.has(declarationKey(f.name)) && !have.has(declarationKey(f.cite.value))).map((f) => ({
|
|
255
|
+
id: f.id,
|
|
256
|
+
framework: f.name,
|
|
257
|
+
cite: `${f.cite.file}:${f.cite.line}`,
|
|
258
|
+
command: `atticus frameworks learn ${f.name} --from ${f.repo}`,
|
|
259
|
+
why: `${f.repo} is built on ${f.name} (${f.cite.file}:${f.cite.line}), which Atticus can't read: teach it once and every repo of the house that uses it is read with it.`
|
|
260
|
+
}));
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// packages/verify/dist/apigee-policy.js
|
|
264
|
+
var MAKES = {
|
|
265
|
+
OAuthV1: ["oauth2"],
|
|
266
|
+
OAuthV2: ["oauth2"],
|
|
267
|
+
VerifyAccessToken: ["oauth2"],
|
|
268
|
+
VerifyJWT: ["oauth2"],
|
|
269
|
+
VerifyAPIKey: ["api-key"],
|
|
270
|
+
BasicAuthentication: ["basic"]
|
|
271
|
+
};
|
|
272
|
+
var CHECKS = {
|
|
273
|
+
oauth2: "an OAuth token",
|
|
274
|
+
"api-key": "an API key",
|
|
275
|
+
basic: "a user name and password"
|
|
276
|
+
};
|
|
277
|
+
var MAKES_NONE = /* @__PURE__ */ new Set([
|
|
278
|
+
"AccessControl",
|
|
279
|
+
"AccessEntity",
|
|
280
|
+
"AssignMessage",
|
|
281
|
+
"ConcurrentRateLimit",
|
|
282
|
+
"ExtractVariables",
|
|
283
|
+
"GenerateJWT",
|
|
284
|
+
"InvalidateCache",
|
|
285
|
+
"JSONThreatProtection",
|
|
286
|
+
"JSONToXML",
|
|
287
|
+
"KeyValueMapOperations",
|
|
288
|
+
"LookupCache",
|
|
289
|
+
"MessageLogging",
|
|
290
|
+
"PopulateCache",
|
|
291
|
+
"Quota",
|
|
292
|
+
"RaiseFault",
|
|
293
|
+
"RegularExpressionProtection",
|
|
294
|
+
"ResponseCache",
|
|
295
|
+
"SpikeArrest",
|
|
296
|
+
"StatisticsCollector",
|
|
297
|
+
"XMLThreatProtection",
|
|
298
|
+
"XMLToJSON",
|
|
299
|
+
"XSL"
|
|
300
|
+
]);
|
|
301
|
+
function bundleRootOf(file) {
|
|
302
|
+
const m = /^(.*(?:^|\/)(?:apiproxy|sharedflowbundle))\//.exec(file.replace(/\\/g, "/"));
|
|
303
|
+
return m?.[1];
|
|
304
|
+
}
|
|
305
|
+
function policyElement(file, policy, lines) {
|
|
306
|
+
const root = bundleRootOf(file);
|
|
307
|
+
if (!root || !/^[A-Za-z0-9][\w.-]*$/.test(policy))
|
|
308
|
+
return void 0;
|
|
309
|
+
const path = `${root}/policies/${policy}.xml`;
|
|
310
|
+
const ls = lines(path);
|
|
311
|
+
if (!ls)
|
|
312
|
+
return void 0;
|
|
313
|
+
for (const l of ls) {
|
|
314
|
+
const m = /^\s*<([A-Za-z][\w.-]*)[\s/>]/.exec(l);
|
|
315
|
+
if (m)
|
|
316
|
+
return { path, element: m[1] };
|
|
317
|
+
}
|
|
318
|
+
return void 0;
|
|
319
|
+
}
|
|
320
|
+
function authPolicyMismatch(auth, file, policy, lines) {
|
|
321
|
+
const found = policyElement(file, policy, lines);
|
|
322
|
+
if (!found)
|
|
323
|
+
return void 0;
|
|
324
|
+
const makes = MAKES[found.element];
|
|
325
|
+
if (makes)
|
|
326
|
+
return makes.includes(auth) ? void 0 : { ...found, makes };
|
|
327
|
+
return MAKES_NONE.has(found.element) ? found : void 0;
|
|
328
|
+
}
|
|
329
|
+
var authChecks = (auth) => CHECKS[auth] ?? auth;
|
|
330
|
+
|
|
331
|
+
// packages/verify/dist/corrections.js
|
|
332
|
+
function correctionFor(f, corrections) {
|
|
333
|
+
const at = (c) => c.target.kind === f.kind && c.target.file === f.cite.file && c.target.line === f.cite.line;
|
|
334
|
+
const byId = corrections.filter((c) => c.fact === f.id && at(c));
|
|
335
|
+
const byCite = corrections.filter((c) => at(c) && c.target.value === f.cite.value);
|
|
336
|
+
const edited = corrections.filter((c) => c.fact === f.id && c.action === "edit");
|
|
337
|
+
const moved = edited.filter((c) => (c.line ?? c.target.line) === f.cite.line);
|
|
338
|
+
return [...byId, ...byCite, ...moved].sort((a, b) => a.at < b.at ? 1 : a.at > b.at ? -1 : 0)[0];
|
|
339
|
+
}
|
|
340
|
+
function correctedFact(f, c) {
|
|
341
|
+
const raw = JSON.parse(JSON.stringify(f));
|
|
342
|
+
for (const [k, v] of Object.entries(c.set ?? {})) {
|
|
343
|
+
if (k === "value")
|
|
344
|
+
raw.cite.value = v;
|
|
345
|
+
else if (v === "")
|
|
346
|
+
delete raw[k];
|
|
347
|
+
else
|
|
348
|
+
raw[k] = v;
|
|
349
|
+
}
|
|
350
|
+
if (c.line !== void 0 && c.line !== f.cite.line) {
|
|
351
|
+
raw.cite.line = c.line;
|
|
352
|
+
delete raw.cite.quote;
|
|
353
|
+
}
|
|
354
|
+
raw.by = "person";
|
|
355
|
+
raw.verified = "person";
|
|
356
|
+
const r = Fact.safeParse(raw);
|
|
357
|
+
if (!r.success)
|
|
358
|
+
return r.error.issues.map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ");
|
|
359
|
+
return r.data;
|
|
360
|
+
}
|
|
361
|
+
function applyCorrections(facts, corrections) {
|
|
362
|
+
const out = [];
|
|
363
|
+
const applied = [];
|
|
364
|
+
const used = /* @__PURE__ */ new Set();
|
|
365
|
+
const stale = [];
|
|
366
|
+
for (const f of facts) {
|
|
367
|
+
const c = correctionFor(f, corrections);
|
|
368
|
+
if (!c) {
|
|
369
|
+
out.push(f);
|
|
370
|
+
continue;
|
|
371
|
+
}
|
|
372
|
+
used.add(c);
|
|
373
|
+
if (c.action === "wrong") {
|
|
374
|
+
applied.push({ fact: f.id, action: "wrong", by: c.by });
|
|
375
|
+
continue;
|
|
376
|
+
}
|
|
377
|
+
const fixed = correctedFact(f, c);
|
|
378
|
+
if (typeof fixed === "string") {
|
|
379
|
+
stale.push({
|
|
380
|
+
correction: c,
|
|
381
|
+
why: "invalid",
|
|
382
|
+
message: `${c.by}'s correction of ${f.kind} ${f.id} doesn't make a fact (${fixed}); the fact is kept as it was`
|
|
383
|
+
});
|
|
384
|
+
out.push(f);
|
|
385
|
+
continue;
|
|
386
|
+
}
|
|
387
|
+
applied.push({ fact: f.id, action: "edit", by: c.by });
|
|
388
|
+
out.push(fixed);
|
|
389
|
+
}
|
|
390
|
+
for (const c of corrections) {
|
|
391
|
+
if (used.has(c))
|
|
392
|
+
continue;
|
|
393
|
+
const replaced = [...used].some((u) => (u.fact === c.fact || u.target.value === c.target.value) && u.target.kind === c.target.kind && u.target.file === c.target.file && u.target.line === c.target.line);
|
|
394
|
+
if (replaced || c.action === "wrong")
|
|
395
|
+
continue;
|
|
396
|
+
stale.push({
|
|
397
|
+
correction: c,
|
|
398
|
+
why: "none",
|
|
399
|
+
message: `${c.by}'s correction of ${c.target.kind} ${c.fact} (${c.target.file}:${c.target.line}) meets no fact in this read; it is kept in the file and applied when the fact is read again`
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
return { facts: out, applied, stale };
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// packages/verify/dist/derived.js
|
|
406
|
+
function springSnake(name) {
|
|
407
|
+
const s = name.replace(/\./g, "_");
|
|
408
|
+
let out = s[0] ?? "";
|
|
409
|
+
for (let i = 1; i < s.length; i++) {
|
|
410
|
+
const before = s[i - 1];
|
|
411
|
+
const cur = s[i];
|
|
412
|
+
const after = s[i + 1];
|
|
413
|
+
if (after !== void 0 && isLower(before) && isUpper(cur) && isLower(after))
|
|
414
|
+
out += "_";
|
|
415
|
+
out += cur;
|
|
416
|
+
}
|
|
417
|
+
return out.toLowerCase();
|
|
418
|
+
}
|
|
419
|
+
var isLower = (c) => c !== c.toUpperCase() && c === c.toLowerCase();
|
|
420
|
+
var isUpper = (c) => c !== c.toLowerCase() && c === c.toUpperCase();
|
|
421
|
+
var railsUnderscore = (name) => name.replace(/::/g, "/").replace(/([A-Z\d]+)([A-Z][a-z])/g, "$1_$2").replace(/([a-z\d])([A-Z])/g, "$1_$2").replace(/-/g, "_").toLowerCase();
|
|
422
|
+
var laravelSnake = (name) => name.replace(/(.)(?=[A-Z])/g, "$1_").toLowerCase();
|
|
423
|
+
var typeormSnake = (name) => name.replace(/([A-Z])([A-Z])([a-z])/g, "$1_$2$3").replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase();
|
|
424
|
+
var UNCOUNTABLE = /* @__PURE__ */ new Set([
|
|
425
|
+
"equipment",
|
|
426
|
+
"information",
|
|
427
|
+
"rice",
|
|
428
|
+
"money",
|
|
429
|
+
"species",
|
|
430
|
+
"series",
|
|
431
|
+
"fish",
|
|
432
|
+
"sheep",
|
|
433
|
+
"jeans",
|
|
434
|
+
"police"
|
|
435
|
+
]);
|
|
436
|
+
var IRREGULAR = [
|
|
437
|
+
["person", "people"],
|
|
438
|
+
["man", "men"],
|
|
439
|
+
["child", "children"],
|
|
440
|
+
["sex", "sexes"],
|
|
441
|
+
["move", "moves"],
|
|
442
|
+
["zombie", "zombies"]
|
|
443
|
+
];
|
|
444
|
+
var PLURAL = [
|
|
445
|
+
[/$/, "s"],
|
|
446
|
+
[/s$/i, "s"],
|
|
447
|
+
[/^(ax|test)is$/i, "$1es"],
|
|
448
|
+
[/(octop|vir)us$/i, "$1i"],
|
|
449
|
+
[/(octop|vir)i$/i, "$1i"],
|
|
450
|
+
[/(alias|status)$/i, "$1es"],
|
|
451
|
+
[/(bu)s$/i, "$1ses"],
|
|
452
|
+
[/(buffal|tomat)o$/i, "$1oes"],
|
|
453
|
+
[/([ti])um$/i, "$1a"],
|
|
454
|
+
[/([ti])a$/i, "$1a"],
|
|
455
|
+
[/sis$/i, "ses"],
|
|
456
|
+
[/(?:([^f])fe|([lr])f)$/i, "$1$2ves"],
|
|
457
|
+
[/(hive)$/i, "$1s"],
|
|
458
|
+
[/([^aeiouy]|qu)y$/i, "$1ies"],
|
|
459
|
+
[/(x|ch|ss|sh)$/i, "$1es"],
|
|
460
|
+
[/(matr|vert|ind)(?:ix|ex)$/i, "$1ices"],
|
|
461
|
+
[/^(m|l)ouse$/i, "$1ice"],
|
|
462
|
+
[/^(m|l)ice$/i, "$1ice"],
|
|
463
|
+
[/^(ox)$/i, "$1en"],
|
|
464
|
+
[/^(oxen)$/i, "$1"],
|
|
465
|
+
[/(quiz)$/i, "$1zes"]
|
|
466
|
+
].reverse();
|
|
467
|
+
function pluralize(word) {
|
|
468
|
+
if (UNCOUNTABLE_RE.some((re) => re.test(word)))
|
|
469
|
+
return word;
|
|
470
|
+
for (const [one, many] of IRREGULAR) {
|
|
471
|
+
const m = new RegExp(`(${one[0]})${one.slice(1)}$`, "i").exec(word);
|
|
472
|
+
if (m)
|
|
473
|
+
return word.slice(0, m.index) + m[1] + many.slice(1);
|
|
474
|
+
}
|
|
475
|
+
for (const [re, to] of PLURAL)
|
|
476
|
+
if (re.test(word))
|
|
477
|
+
return word.replace(re, to);
|
|
478
|
+
return word;
|
|
479
|
+
}
|
|
480
|
+
var UNCOUNTABLE_RE = [...UNCOUNTABLE].map((w) => new RegExp(`(^|[^A-Za-z0-9_])${w}$`, "i"));
|
|
481
|
+
function singularize(word) {
|
|
482
|
+
for (const [one, many] of IRREGULAR)
|
|
483
|
+
if (word.endsWith(many))
|
|
484
|
+
return word.slice(0, -many.length) + one;
|
|
485
|
+
if (/ies$/.test(word))
|
|
486
|
+
return word.replace(/ies$/, "y");
|
|
487
|
+
if (/(x|ch|ss|sh)es$/.test(word))
|
|
488
|
+
return word.replace(/es$/, "");
|
|
489
|
+
if (/[^s]s$/.test(word))
|
|
490
|
+
return word.slice(0, -1);
|
|
491
|
+
return word;
|
|
492
|
+
}
|
|
493
|
+
var camelize = (s) => s.split("/").map((p) => p.replace(/(?:^|_)([a-z\d])/g, (_, c) => c.toUpperCase())).join("::");
|
|
494
|
+
var CLASS_JVM = /^\s*(?:(?:public|protected|private|abstract|final|static|open|data|internal|sealed)\s+)*class\s+([A-Z]\w*)/;
|
|
495
|
+
var isComment = (l) => /^\s*(\/\/|\/\*|\*|#)/.test(l);
|
|
496
|
+
function annotationsAbove(lines, idx, max = 12) {
|
|
497
|
+
let from = idx;
|
|
498
|
+
for (let i = idx - 1; i >= 0 && idx - i <= max; i--) {
|
|
499
|
+
const l = lines[i];
|
|
500
|
+
const t = l.trim();
|
|
501
|
+
if (t === "" || isComment(l)) {
|
|
502
|
+
from = i;
|
|
503
|
+
continue;
|
|
504
|
+
}
|
|
505
|
+
if (/[;{}]\s*$/.test(t) || /^(import|package)\b/.test(t))
|
|
506
|
+
break;
|
|
507
|
+
from = i;
|
|
508
|
+
}
|
|
509
|
+
return { text: lines.slice(from, idx).join("\n"), from };
|
|
510
|
+
}
|
|
511
|
+
function classLineOf(lines, idx, re = CLASS_JVM) {
|
|
512
|
+
if (re.test(lines[idx] ?? ""))
|
|
513
|
+
return idx;
|
|
514
|
+
if (!/^\s*@/.test(lines[idx] ?? ""))
|
|
515
|
+
return void 0;
|
|
516
|
+
for (let i = idx + 1; i < lines.length && i - idx <= 10; i++) {
|
|
517
|
+
if (re.test(lines[i]))
|
|
518
|
+
return i;
|
|
519
|
+
const t = lines[i].trim();
|
|
520
|
+
if (t !== "" && !t.startsWith("@") && !isComment(lines[i]) && /[;{}]\s*$/.test(t))
|
|
521
|
+
return void 0;
|
|
522
|
+
}
|
|
523
|
+
return void 0;
|
|
524
|
+
}
|
|
525
|
+
function jpaEntityAt(file, lines, idx) {
|
|
526
|
+
if (!/\.(java|kt)$/.test(file))
|
|
527
|
+
return void 0;
|
|
528
|
+
const cl = classLineOf(lines, idx);
|
|
529
|
+
if (cl === void 0)
|
|
530
|
+
return void 0;
|
|
531
|
+
const { text, from } = annotationsAbove(lines, cl);
|
|
532
|
+
if (idx < cl && idx < from)
|
|
533
|
+
return void 0;
|
|
534
|
+
if (!/@(?:(?:javax|jakarta)\.persistence\.)?Entity\b/.test(text))
|
|
535
|
+
return void 0;
|
|
536
|
+
const className = CLASS_JVM.exec(lines[cl])[1];
|
|
537
|
+
const entityName = /@Entity\s*\(\s*(?:name\s*=\s*)?"([^"]+)"/.exec(text)?.[1] ?? className;
|
|
538
|
+
const written = /@Table\s*\([^)]*?\bname\s*=\s*"([^"]+)"/.exec(text)?.[1];
|
|
539
|
+
return {
|
|
540
|
+
className,
|
|
541
|
+
entityName,
|
|
542
|
+
table: written ?? springSnake(entityName),
|
|
543
|
+
tableWritten: written !== void 0,
|
|
544
|
+
file,
|
|
545
|
+
lines,
|
|
546
|
+
classLine: cl
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
function enclosingJpaEntity(file, lines, idx) {
|
|
550
|
+
for (let i = idx; i >= 0; i--)
|
|
551
|
+
if (CLASS_JVM.test(lines[i]))
|
|
552
|
+
return jpaEntityAt(file, lines, i);
|
|
553
|
+
return void 0;
|
|
554
|
+
}
|
|
555
|
+
function jpaIdColumn(e) {
|
|
556
|
+
for (let i = e.classLine + 1; i < e.lines.length; i++) {
|
|
557
|
+
if (!/^\s*@(Id|EmbeddedId)\b/.test(e.lines[i]))
|
|
558
|
+
continue;
|
|
559
|
+
if (/@EmbeddedId/.test(e.lines[i]))
|
|
560
|
+
return void 0;
|
|
561
|
+
for (let j = i; j < e.lines.length && j - i <= 5; j++) {
|
|
562
|
+
const f = jvmField(e.lines[j]);
|
|
563
|
+
if (f) {
|
|
564
|
+
const named = /@Column\s*\([^)]*?\bname\s*=\s*"([^"]+)"/.exec(e.lines.slice(i, j + 1).join("\n"))?.[1];
|
|
565
|
+
return named ?? springSnake(f.name);
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
return void 0;
|
|
570
|
+
}
|
|
571
|
+
function jvmField(line) {
|
|
572
|
+
const java = /^\s*(?:(?:private|protected|public|final|transient)\s+)*([\w.]+)(?:<\s*([\w.]+)\s*(?:,\s*[\w.]+\s*)?>)?\s+(\w+)\s*(?:=[^;]*)?;/.exec(line);
|
|
573
|
+
if (java && !/^(return|new|throw)$/.test(java[1]))
|
|
574
|
+
return { type: java[1], arg: java[2], name: java[3] };
|
|
575
|
+
const kt = /^\s*(?:(?:private|protected|public|internal|lateinit|override)\s+)*va[lr]\s+(\w+)\s*:\s*([\w.]+)(?:<\s*([\w.]+)\s*>)?/.exec(line);
|
|
576
|
+
if (kt)
|
|
577
|
+
return { name: kt[1], type: kt[2], arg: kt[3] };
|
|
578
|
+
return void 0;
|
|
579
|
+
}
|
|
580
|
+
function findClassFile(env, name, near, exts, declares, imports = []) {
|
|
581
|
+
const all2 = env.files().filter((p) => exts.test(p));
|
|
582
|
+
const byFile = all2.filter((p) => new RegExp(`(^|/)${name}\\.[a-z]+$`).test(p));
|
|
583
|
+
const declaring = (ps) => ps.filter((p) => declares(env.lines(p) ?? []));
|
|
584
|
+
const imported = imports.map((i) => /import\s+([\w.]+)\s*;?/.exec(i)?.[1]).filter((i) => !!i && i.endsWith(`.${name}`)).map((i) => i.replace(/\./g, "/"));
|
|
585
|
+
for (const imp of imported) {
|
|
586
|
+
const hit = declaring(byFile.filter((p) => p.replace(/\.[a-z]+$/, "").endsWith(imp)));
|
|
587
|
+
if (hit.length === 1)
|
|
588
|
+
return hit[0];
|
|
589
|
+
}
|
|
590
|
+
const dir = near.split("/").slice(0, -1).join("/");
|
|
591
|
+
const same = declaring(byFile.filter((p) => p.split("/").slice(0, -1).join("/") === dir));
|
|
592
|
+
if (same.length === 1)
|
|
593
|
+
return same[0];
|
|
594
|
+
const candidates = declaring(byFile.length ? byFile : all2);
|
|
595
|
+
const shared = (p) => {
|
|
596
|
+
const a = p.split("/");
|
|
597
|
+
const b = near.split("/");
|
|
598
|
+
let n = 0;
|
|
599
|
+
while (n < a.length && n < b.length && a[n] === b[n])
|
|
600
|
+
n++;
|
|
601
|
+
return n;
|
|
602
|
+
};
|
|
603
|
+
const best = Math.max(-1, ...candidates.map(shared));
|
|
604
|
+
const top = candidates.filter((p) => shared(p) === best);
|
|
605
|
+
return top.length === 1 ? top[0] : void 0;
|
|
606
|
+
}
|
|
607
|
+
function jpaEntityByName(env, name, near, nearLines) {
|
|
608
|
+
const re = new RegExp(`^\\s*(?:[a-z]+\\s+)*class\\s+${name}\\b`);
|
|
609
|
+
const file = findClassFile(env, name, near, /\.(java|kt)$/, (ls2) => ls2.some((l) => re.test(l)), nearLines.filter((l) => /^\s*import\s/.test(l)));
|
|
610
|
+
if (!file)
|
|
611
|
+
return void 0;
|
|
612
|
+
const ls = env.lines(file) ?? [];
|
|
613
|
+
const at = ls.findIndex((l) => re.test(l));
|
|
614
|
+
return at < 0 ? void 0 : jpaEntityAt(file, ls, at);
|
|
615
|
+
}
|
|
616
|
+
function jvmFieldAt(lines, idx) {
|
|
617
|
+
const at = (j) => {
|
|
618
|
+
const field = jvmField(lines[j] ?? "");
|
|
619
|
+
return field ? { field, ann: annotationsAbove(lines, j, 6).text, line: j } : void 0;
|
|
620
|
+
};
|
|
621
|
+
const here = at(idx);
|
|
622
|
+
if (here)
|
|
623
|
+
return here;
|
|
624
|
+
if (!/^\s*@/.test(lines[idx] ?? ""))
|
|
625
|
+
return void 0;
|
|
626
|
+
for (let j = idx + 1; j < lines.length && j - idx <= 4; j++) {
|
|
627
|
+
const f = at(j);
|
|
628
|
+
if (f)
|
|
629
|
+
return f;
|
|
630
|
+
if (/[{}]\s*$/.test(lines[j].trim()))
|
|
631
|
+
return void 0;
|
|
632
|
+
}
|
|
633
|
+
return void 0;
|
|
634
|
+
}
|
|
635
|
+
var jpaEntityTable = (file, lines, idx) => {
|
|
636
|
+
const e = jpaEntityAt(file, lines, idx);
|
|
637
|
+
if (!e || e.tableWritten)
|
|
638
|
+
return [];
|
|
639
|
+
return [{ kind: "word", value: e.table, rule: "jpa-spring-table" }];
|
|
640
|
+
};
|
|
641
|
+
var jpaAssociation = (file, lines, idx, env) => {
|
|
642
|
+
if (!/\.(java|kt)$/.test(file))
|
|
643
|
+
return [];
|
|
644
|
+
const at = jvmFieldAt(lines, idx);
|
|
645
|
+
if (!at)
|
|
646
|
+
return [];
|
|
647
|
+
const { field, ann } = at;
|
|
648
|
+
const owner = enclosingJpaEntity(file, lines, at.line);
|
|
649
|
+
if (!owner)
|
|
650
|
+
return [];
|
|
651
|
+
const out = [];
|
|
652
|
+
const mapped = /\bmappedBy\s*=/.test(ann);
|
|
653
|
+
const joinTableNamed = /@JoinTable\s*\([^)]*?\bname\s*=/.test(ann);
|
|
654
|
+
const targetName2 = /targetEntity\s*=\s*(\w+)(?:::|\.)class/.exec(ann)?.[1] ?? field.arg ?? field.type;
|
|
655
|
+
const target = () => jpaEntityByName(env, targetName2, file, lines);
|
|
656
|
+
if (/@ElementCollection\b/.test(ann) && !/@CollectionTable\s*\([^)]*?\bname\s*=/.test(ann))
|
|
657
|
+
out.push({
|
|
658
|
+
kind: "word",
|
|
659
|
+
value: springSnake(`${owner.entityName}_${field.name}`),
|
|
660
|
+
rule: "jpa-collection-table"
|
|
661
|
+
});
|
|
662
|
+
const toMany = /@ManyToMany\b/.test(ann) || /@OneToMany\b/.test(ann) && !/@JoinColumn\b/.test(ann);
|
|
663
|
+
if (toMany && !mapped && field.arg) {
|
|
664
|
+
const t = target();
|
|
665
|
+
if (!joinTableNamed) {
|
|
666
|
+
out.push({
|
|
667
|
+
kind: "word",
|
|
668
|
+
value: springSnake(`${owner.table}_${field.name}`),
|
|
669
|
+
rule: "jpa-spring-join-table"
|
|
670
|
+
});
|
|
671
|
+
if (t)
|
|
672
|
+
out.push({
|
|
673
|
+
kind: "word",
|
|
674
|
+
value: springSnake(`${owner.table}_${t.table}`),
|
|
675
|
+
rule: "jpa-join-table"
|
|
676
|
+
});
|
|
677
|
+
}
|
|
678
|
+
if (!/@JoinTable\s*\([^)]*?joinColumns/.test(ann)) {
|
|
679
|
+
const ownerId = jpaIdColumn(owner);
|
|
680
|
+
const inverse = t ? (() => {
|
|
681
|
+
for (let i = 0; i < t.lines.length; i++)
|
|
682
|
+
if (new RegExp(`mappedBy\\s*=\\s*"${field.name}"`).test(t.lines[i])) {
|
|
683
|
+
const f = jvmFieldAt(t.lines, i);
|
|
684
|
+
if (f)
|
|
685
|
+
return f.field.name;
|
|
686
|
+
}
|
|
687
|
+
return void 0;
|
|
688
|
+
})() : void 0;
|
|
689
|
+
if (ownerId)
|
|
690
|
+
out.push({
|
|
691
|
+
kind: "field",
|
|
692
|
+
value: springSnake(`${inverse ?? owner.entityName}_${ownerId}`),
|
|
693
|
+
rule: "jpa-join-column-owner"
|
|
694
|
+
});
|
|
695
|
+
const targetId = t && jpaIdColumn(t);
|
|
696
|
+
if (targetId)
|
|
697
|
+
out.push({
|
|
698
|
+
kind: "field",
|
|
699
|
+
value: springSnake(`${field.name}_${targetId}`),
|
|
700
|
+
rule: "jpa-join-column-target"
|
|
701
|
+
});
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
const toOne = /@ManyToOne\b/.test(ann) || /@OneToOne\b/.test(ann) && !mapped;
|
|
705
|
+
if (toOne && !/@JoinColumn\s*\([^)]*?\bname\s*=/.test(ann)) {
|
|
706
|
+
const t = target();
|
|
707
|
+
const id = t && jpaIdColumn(t);
|
|
708
|
+
if (id)
|
|
709
|
+
out.push({
|
|
710
|
+
kind: "field",
|
|
711
|
+
value: springSnake(`${field.name}_${id}`),
|
|
712
|
+
rule: "jpa-foreign-key"
|
|
713
|
+
});
|
|
714
|
+
}
|
|
715
|
+
return out;
|
|
716
|
+
};
|
|
717
|
+
var DJANGO_CLASS = /^class\s+(\w+)\s*\(([^)]*)\)\s*:/;
|
|
718
|
+
var djangoAppDir = (file) => /^(?:(.*)\/)?models\.py$/.exec(file)?.[1] ?? /^(?:(.*)\/)?models\/[^/]+\.py$/.exec(file)?.[1];
|
|
719
|
+
function djangoModelAt(file, lines, idx, env) {
|
|
720
|
+
const appDir = djangoAppDir(file);
|
|
721
|
+
if (appDir === void 0)
|
|
722
|
+
return void 0;
|
|
723
|
+
const m = DJANGO_CLASS.exec(lines[idx] ?? "");
|
|
724
|
+
if (!m)
|
|
725
|
+
return void 0;
|
|
726
|
+
const bases = m[2].split(",").map((b) => b.trim().split(".").pop());
|
|
727
|
+
if (!bases.some((b) => /Model$|^Abstract\w*User$/.test(b)))
|
|
728
|
+
return void 0;
|
|
729
|
+
const body = [];
|
|
730
|
+
for (let i = idx + 1; i < lines.length; i++) {
|
|
731
|
+
if (/^\S/.test(lines[i]))
|
|
732
|
+
break;
|
|
733
|
+
body.push(lines[i]);
|
|
734
|
+
}
|
|
735
|
+
const meta = body.join("\n").split(/^\s+class\s+Meta\b.*$/m)[1] ?? "";
|
|
736
|
+
if (/^\s+(abstract|proxy)\s*=\s*True\b/m.test(meta))
|
|
737
|
+
return void 0;
|
|
738
|
+
const written = /^\s+db_table\s*=\s*["']([^"']+)["']/m.exec(meta)?.[1];
|
|
739
|
+
const dirs = appDir ? appDir.split("/") : [];
|
|
740
|
+
let config2;
|
|
741
|
+
for (let n = dirs.length; n > 0 && !config2; n--) {
|
|
742
|
+
const dir = dirs.slice(0, n).join("/");
|
|
743
|
+
const text = env.lines(`${dir}/apps.py`);
|
|
744
|
+
if (text)
|
|
745
|
+
config2 = { dir, text: text.join("\n") };
|
|
746
|
+
}
|
|
747
|
+
const label = /^\s+app_label\s*=\s*["']([^"']+)["']/m.exec(meta)?.[1] ?? (config2 ? /^\s+label\s*=\s*["']([^"']+)["']/m.exec(config2.text)?.[1] ?? /^\s+name\s*=\s*["']([\w.]+)["']/m.exec(config2.text)?.[1]?.split(".").pop() ?? config2.dir.split("/").pop() : dirs[dirs.length - 1]);
|
|
748
|
+
if (!label)
|
|
749
|
+
return void 0;
|
|
750
|
+
return {
|
|
751
|
+
name: m[1],
|
|
752
|
+
table: written ?? `${label}_${m[1].toLowerCase()}`,
|
|
753
|
+
tableWritten: written !== void 0
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
var djangoModelTable = (file, lines, idx, env) => {
|
|
757
|
+
const m = djangoModelAt(file, lines, idx, env);
|
|
758
|
+
return m && !m.tableWritten ? [{ kind: "word", value: m.table, rule: "django-table" }] : [];
|
|
759
|
+
};
|
|
760
|
+
var djangoField = (file, lines, idx, env) => {
|
|
761
|
+
if (djangoAppDir(file) === void 0)
|
|
762
|
+
return [];
|
|
763
|
+
const f = /^\s+(\w+)\s*=\s*(?:models\.)?(ManyToManyField|ForeignKey|OneToOneField)\s*\(/.exec(lines[idx] ?? "");
|
|
764
|
+
if (!f)
|
|
765
|
+
return [];
|
|
766
|
+
let args = "";
|
|
767
|
+
for (let i = idx, depth = 0; i < lines.length && i - idx < 10; i++) {
|
|
768
|
+
args += `${lines[i]}
|
|
769
|
+
`;
|
|
770
|
+
depth += (lines[i].match(/\(/g) ?? []).length - (lines[i].match(/\)/g) ?? []).length;
|
|
771
|
+
if (depth <= 0)
|
|
772
|
+
break;
|
|
773
|
+
}
|
|
774
|
+
let cls = -1;
|
|
775
|
+
for (let i = idx - 1; i >= 0; i--)
|
|
776
|
+
if (/^\S/.test(lines[i])) {
|
|
777
|
+
cls = DJANGO_CLASS.test(lines[i]) ? i : -1;
|
|
778
|
+
break;
|
|
779
|
+
}
|
|
780
|
+
if (cls < 0)
|
|
781
|
+
return [];
|
|
782
|
+
const model = djangoModelAt(file, lines, cls, env);
|
|
783
|
+
if (!model)
|
|
784
|
+
return [];
|
|
785
|
+
if (f[2] === "ManyToManyField")
|
|
786
|
+
return /\b(through|db_table)\s*=/.test(args) ? [] : [{ kind: "word", value: `${model.table}_${f[1]}`, rule: "django-m2m-table" }];
|
|
787
|
+
return /\bdb_column\s*=/.test(args) ? [] : [{ kind: "field", value: `${f[1]}_id`, rule: "django-fk-column" }];
|
|
788
|
+
};
|
|
789
|
+
var railsModelTable = (file, lines, idx) => {
|
|
790
|
+
if (!/\.rb$/.test(file))
|
|
791
|
+
return [];
|
|
792
|
+
const m = /^class\s+([A-Z]\w*)\s*<\s*(ApplicationRecord|ActiveRecord::Base)\s*$/.exec(lines[idx] ?? "");
|
|
793
|
+
if (!m)
|
|
794
|
+
return [];
|
|
795
|
+
const text = lines.join("\n");
|
|
796
|
+
if (/self\.table_name\s*=|self\.abstract_class\s*=\s*true/.test(text))
|
|
797
|
+
return [];
|
|
798
|
+
return [{ kind: "word", value: pluralize(railsUnderscore(m[1])), rule: "rails-table" }];
|
|
799
|
+
};
|
|
800
|
+
var LARAVEL_CLASS = /^\s*(?:final\s+)?class\s+(\w+)\s+extends\s+\\?(?:Illuminate\\(?:Database\\Eloquent|Foundation\\Auth)\\)?(Model|Authenticatable|User)\b/;
|
|
801
|
+
var laravelModel = (file, lines, idx) => {
|
|
802
|
+
if (!/\.php$/.test(file))
|
|
803
|
+
return [];
|
|
804
|
+
const text = lines.join("\n");
|
|
805
|
+
if (/^\s*abstract\s+class\b/m.test(text))
|
|
806
|
+
return [];
|
|
807
|
+
const own = lines.map((l) => LARAVEL_CLASS.exec(l)).find((m) => m)?.[1];
|
|
808
|
+
if (!own)
|
|
809
|
+
return [];
|
|
810
|
+
const cls = LARAVEL_CLASS.exec(lines[idx] ?? "");
|
|
811
|
+
if (cls) {
|
|
812
|
+
if (/protected\s+\$table\s*=/.test(text))
|
|
813
|
+
return [];
|
|
814
|
+
return [{ kind: "word", value: laravelSnake(pluralize(cls[1])), rule: "laravel-table" }];
|
|
815
|
+
}
|
|
816
|
+
const rel = /->belongsToMany\(\s*(?:[\w\\]*\\)?(\w+)::class\s*\)/.exec(lines[idx] ?? "");
|
|
817
|
+
if (!rel)
|
|
818
|
+
return [];
|
|
819
|
+
const pivot = [laravelSnake(own), laravelSnake(rel[1])].sort().join("_");
|
|
820
|
+
return [{ kind: "word", value: pivot, rule: "laravel-pivot" }];
|
|
821
|
+
};
|
|
822
|
+
var TS_CLASS = /^\s*export\s+(?:default\s+)?(?:abstract\s+)?class\s+(\w+)/;
|
|
823
|
+
function typeormEntityAt(lines, idx) {
|
|
824
|
+
const cl = classLineOf(lines, idx, TS_CLASS);
|
|
825
|
+
if (cl === void 0)
|
|
826
|
+
return void 0;
|
|
827
|
+
const { text } = annotationsAbove(lines, cl);
|
|
828
|
+
const e = /@Entity\s*\(([^)]*)\)/.exec(text);
|
|
829
|
+
if (!e)
|
|
830
|
+
return void 0;
|
|
831
|
+
const name = TS_CLASS.exec(lines[cl])[1];
|
|
832
|
+
const written = /^\s*["'`]([^"'`]+)["'`]/.exec(e[1])?.[1] ?? /\bname\s*:\s*["'`]([^"'`]+)["'`]/.exec(e[1])?.[1];
|
|
833
|
+
return { name, table: written ?? typeormSnake(name), written: written !== void 0 };
|
|
834
|
+
}
|
|
835
|
+
var typeorm = (file, lines, idx, env) => {
|
|
836
|
+
if (!/\.[cm]?[jt]s$/.test(file))
|
|
837
|
+
return [];
|
|
838
|
+
const e = typeormEntityAt(lines, idx);
|
|
839
|
+
if (e)
|
|
840
|
+
return e.written ? [] : [{ kind: "word", value: e.table, rule: "typeorm-table" }];
|
|
841
|
+
let prop = -1;
|
|
842
|
+
for (let j = idx; j < lines.length && j - idx <= 4; j++)
|
|
843
|
+
if (/^\s*(?:readonly\s+)?\w+[!?]?\s*:\s*[\w.]+\[\]/.test(lines[j]) || /^\s*\w+[!?]?\s*:\s*(?:Array|Promise)<[\w.]+(?:\[\])?>/.test(lines[j])) {
|
|
844
|
+
prop = j;
|
|
845
|
+
break;
|
|
846
|
+
} else if (j > idx && !/^\s*@/.test(lines[j]))
|
|
847
|
+
break;
|
|
848
|
+
if (prop < 0)
|
|
849
|
+
return [];
|
|
850
|
+
const ann = annotationsAbove(lines, prop, 6).text;
|
|
851
|
+
const jt = /@JoinTable\s*\(([^)]*)\)/.exec(ann);
|
|
852
|
+
const mm = /@ManyToMany\s*\(\s*\(\)\s*=>\s*(\w+)/.exec(ann);
|
|
853
|
+
if (!jt || !mm || /\bname\s*:/.test(jt[1]))
|
|
854
|
+
return [];
|
|
855
|
+
const propName = /^\s*(?:readonly\s+)?(\w+)/.exec(lines[prop])[1];
|
|
856
|
+
let owner;
|
|
857
|
+
for (let i = prop; i >= 0; i--)
|
|
858
|
+
if (TS_CLASS.test(lines[i])) {
|
|
859
|
+
owner = typeormEntityAt(lines, i);
|
|
860
|
+
break;
|
|
861
|
+
}
|
|
862
|
+
const tre = new RegExp(`^\\s*export\\s+(?:default\\s+)?class\\s+${mm[1]}\\b`);
|
|
863
|
+
const tfile = findClassFile(env, mm[1], file, /\.[cm]?[jt]s$/, (ls) => ls.some((l) => tre.test(l)));
|
|
864
|
+
const tl = tfile ? env.lines(tfile) ?? [] : [];
|
|
865
|
+
const ti = tl.findIndex((l) => tre.test(l));
|
|
866
|
+
const target = ti >= 0 ? typeormEntityAt(tl, ti) : void 0;
|
|
867
|
+
if (!owner || !target)
|
|
868
|
+
return [];
|
|
869
|
+
return [
|
|
870
|
+
{
|
|
871
|
+
kind: "word",
|
|
872
|
+
value: typeormSnake(`${owner.table}_${propName}_${target.table}`),
|
|
873
|
+
rule: "typeorm-join-table"
|
|
874
|
+
}
|
|
875
|
+
];
|
|
876
|
+
};
|
|
877
|
+
var prismaImplicitM2M = (file, lines, idx) => {
|
|
878
|
+
if (!/\.prisma$/.test(file))
|
|
879
|
+
return [];
|
|
880
|
+
const f = /^\s*(\w+)\s+(\w+)\[\](?:\s+@relation\(\s*(?:name:\s*)?"([^"]+)"([^)]*)\))?\s*$/.exec(lines[idx] ?? "");
|
|
881
|
+
if (!f || /fields\s*:/.test(f[4] ?? ""))
|
|
882
|
+
return [];
|
|
883
|
+
let model;
|
|
884
|
+
for (let i = idx; i >= 0; i--) {
|
|
885
|
+
const m = /^\s*model\s+(\w+)\s*\{/.exec(lines[i]);
|
|
886
|
+
if (m) {
|
|
887
|
+
model = m[1];
|
|
888
|
+
break;
|
|
889
|
+
}
|
|
890
|
+
if (/^\s*\}/.test(lines[i]))
|
|
891
|
+
return [];
|
|
892
|
+
}
|
|
893
|
+
const target = f[2];
|
|
894
|
+
if (!model || model === target)
|
|
895
|
+
return [];
|
|
896
|
+
const start = lines.findIndex((l) => new RegExp(`^\\s*model\\s+${target}\\s*\\{`).test(l));
|
|
897
|
+
if (start < 0)
|
|
898
|
+
return [];
|
|
899
|
+
let back = false;
|
|
900
|
+
for (let i = start + 1; i < lines.length && !/^\s*\}/.test(lines[i]); i++) {
|
|
901
|
+
const b = /^\s*\w+\s+(\w+)\[\](?:\s+@relation\(\s*(?:name:\s*)?"([^"]+)"[^)]*\))?\s*$/.exec(lines[i]);
|
|
902
|
+
if (b && b[1] === model && b[2] === f[3])
|
|
903
|
+
back = true;
|
|
904
|
+
}
|
|
905
|
+
if (!back)
|
|
906
|
+
return [];
|
|
907
|
+
const value = f[3] ? `_${f[3]}` : `_${[model, target].sort().join("To")}`;
|
|
908
|
+
return [{ kind: "word", value, rule: "prisma-implicit-m2m" }];
|
|
909
|
+
};
|
|
910
|
+
function railsPrefix(lines, idx) {
|
|
911
|
+
const stack = [];
|
|
912
|
+
for (let i = 0; i < idx; i++) {
|
|
913
|
+
const l = lines[i].replace(/#.*$/, "");
|
|
914
|
+
if (/^\s*end\b/.test(l)) {
|
|
915
|
+
stack.pop();
|
|
916
|
+
continue;
|
|
917
|
+
}
|
|
918
|
+
if (!/\bdo\s*(\|[^|]*\|)?\s*$/.test(l))
|
|
919
|
+
continue;
|
|
920
|
+
const ns = /^\s*namespace\s+:(\w+)(.*)$/.exec(l);
|
|
921
|
+
const scope = /^\s*scope\b(.*)$/.exec(l);
|
|
922
|
+
if (ns) {
|
|
923
|
+
const path = /\bpath:\s*["']([^"']*)["']/.exec(ns[2])?.[1];
|
|
924
|
+
stack.push(path ?? ns[1]);
|
|
925
|
+
} else if (scope) {
|
|
926
|
+
const path = /^\s*\(?\s*["']([^"']*)["']/.exec(scope[1])?.[1] ?? /\bpath:\s*["']([^"']*)["']/.exec(scope[1])?.[1];
|
|
927
|
+
if (path === void 0 && /^\s*\(?\s*:\w+\s*(?:,|\)|do)/.test(scope[1]))
|
|
928
|
+
stack.push(null);
|
|
929
|
+
else if (path !== void 0 && /[()*]/.test(path))
|
|
930
|
+
stack.push(null);
|
|
931
|
+
else
|
|
932
|
+
stack.push(path ?? "");
|
|
933
|
+
} else if (/(\.routes\.draw|^\s*(constraints|controller|defaults|devise_scope|as|authenticated?|concern)\b)/.test(l))
|
|
934
|
+
stack.push("");
|
|
935
|
+
else
|
|
936
|
+
stack.push(null);
|
|
937
|
+
}
|
|
938
|
+
if (stack.includes(null))
|
|
939
|
+
return void 0;
|
|
940
|
+
return stack.flatMap((s) => (s ?? "").split("/")).filter(Boolean).map((s) => `/${s}`).join("");
|
|
941
|
+
}
|
|
942
|
+
var railsRoot = (file, lines, idx) => {
|
|
943
|
+
if (!/(^|\/)config\/routes(\/[^/]+)?\.rb$/.test(file))
|
|
944
|
+
return [];
|
|
945
|
+
if (!/^\s*root\s*(\(|\s)\s*(?:to:\s*|:to\s*=>\s*|controller:\s*)?["':]/.test(lines[idx] ?? ""))
|
|
946
|
+
return [];
|
|
947
|
+
const prefix = railsPrefix(lines, idx);
|
|
948
|
+
return prefix === void 0 ? [] : [{ kind: "route", value: prefix || "/", rule: "rails-root" }];
|
|
949
|
+
};
|
|
950
|
+
var DEVISE = [
|
|
951
|
+
{
|
|
952
|
+
module: "database_authenticatable",
|
|
953
|
+
route: "session",
|
|
954
|
+
paths: (n) => [`/${n("sign_in")}`, `/${n("sign_out")}`]
|
|
955
|
+
},
|
|
956
|
+
{
|
|
957
|
+
module: "registerable",
|
|
958
|
+
route: "registration",
|
|
959
|
+
paths: (n) => {
|
|
960
|
+
const r = n("registration");
|
|
961
|
+
const at = r ? `/${r}` : "";
|
|
962
|
+
return [`${at}/${n("sign_up")}`, at || "/", `${at}/${n("edit")}`, `${at}/${n("cancel")}`];
|
|
963
|
+
}
|
|
964
|
+
},
|
|
965
|
+
{
|
|
966
|
+
module: "recoverable",
|
|
967
|
+
route: "password",
|
|
968
|
+
paths: (n) => [
|
|
969
|
+
`/${n("password")}`,
|
|
970
|
+
`/${n("password")}/${n("new")}`,
|
|
971
|
+
`/${n("password")}/${n("edit")}`
|
|
972
|
+
]
|
|
973
|
+
},
|
|
974
|
+
{
|
|
975
|
+
module: "confirmable",
|
|
976
|
+
route: "confirmation",
|
|
977
|
+
paths: (n) => [`/${n("confirmation")}`, `/${n("confirmation")}/${n("new")}`]
|
|
978
|
+
},
|
|
979
|
+
{
|
|
980
|
+
module: "lockable",
|
|
981
|
+
route: "unlock",
|
|
982
|
+
paths: (n) => [`/${n("unlock")}`, `/${n("unlock")}/${n("new")}`]
|
|
983
|
+
}
|
|
984
|
+
];
|
|
985
|
+
var deviseFor = (file, lines, idx, env) => {
|
|
986
|
+
if (!/(^|\/)config\/routes(\/[^/]+)?\.rb$/.test(file))
|
|
987
|
+
return [];
|
|
988
|
+
const d = /^\s*devise_for\s+:(\w+)(.*)$/.exec(lines[idx] ?? "");
|
|
989
|
+
if (!d)
|
|
990
|
+
return [];
|
|
991
|
+
const prefix = railsPrefix(lines, idx);
|
|
992
|
+
if (prefix === void 0)
|
|
993
|
+
return [];
|
|
994
|
+
let opts = d[2];
|
|
995
|
+
for (let i = idx + 1; /,\s*$/.test(lines[i - 1].replace(/#.*$/, "")) && i < lines.length; i++)
|
|
996
|
+
opts += ` ${lines[i].replace(/#.*$/, "")}`;
|
|
997
|
+
const resource = /\bpath:\s*["']([^"']*)["']/.exec(opts)?.[1] ?? d[1];
|
|
998
|
+
const list2 = (k) => {
|
|
999
|
+
const m = new RegExp(`\\b${k}:\\s*(?:\\[([^\\]]*)\\]|:(\\w+))`).exec(opts);
|
|
1000
|
+
return m ? (m[1] ?? `:${m[2]}`).split(",").map((s) => singularize(s.trim().replace(/^:/, ""))).filter(Boolean) : void 0;
|
|
1001
|
+
};
|
|
1002
|
+
const skip = list2("skip") ?? [];
|
|
1003
|
+
const only = list2("only");
|
|
1004
|
+
const names = { registration: "" };
|
|
1005
|
+
const pn = /\bpath_names:\s*\{([^}]*)\}/.exec(opts)?.[1] ?? "";
|
|
1006
|
+
for (const m of pn.matchAll(/(\w+):\s*["']([^"']*)["']/g))
|
|
1007
|
+
names[m[1]] = m[2];
|
|
1008
|
+
const n = (k) => names[k] ?? k;
|
|
1009
|
+
const cls = /\bclass_name:\s*["']([\w:]+)["']/.exec(opts)?.[1] ?? camelize(singularize(d[1]));
|
|
1010
|
+
const modelFile = `app/models/${railsUnderscore(cls)}.rb`;
|
|
1011
|
+
const ml = env.lines(modelFile) ?? env.lines(file.replace(/config\/routes(\/[^/]+)?\.rb$/, modelFile));
|
|
1012
|
+
if (!ml)
|
|
1013
|
+
return [];
|
|
1014
|
+
const start = ml.findIndex((l) => /^\s*devise\s+:/.test(l));
|
|
1015
|
+
if (start < 0)
|
|
1016
|
+
return [];
|
|
1017
|
+
let call = "";
|
|
1018
|
+
for (let i = start; i < ml.length; i++) {
|
|
1019
|
+
call += ` ${ml[i].replace(/#.*$/, "")}`;
|
|
1020
|
+
if (!/,\s*$/.test(ml[i].replace(/#.*$/, "")))
|
|
1021
|
+
break;
|
|
1022
|
+
}
|
|
1023
|
+
const modules = new Set([...call.replace(/\w+:\s*\[[^\]]*\]|\w+:\s*[^,]+/g, "").matchAll(/:(\w+)/g)].map((m) => m[1]));
|
|
1024
|
+
const allowed = (c) => !skip.includes(c) && !skip.includes("all") && (!only || only.includes(c));
|
|
1025
|
+
const at = `${prefix}${resource ? `/${resource}` : ""}`;
|
|
1026
|
+
const out = [];
|
|
1027
|
+
for (const m of DEVISE)
|
|
1028
|
+
if (modules.has(m.module) && allowed(m.route))
|
|
1029
|
+
for (const p of m.paths(n))
|
|
1030
|
+
out.push({ kind: "route", value: p === "/" ? at || "/" : `${at}${p}`, rule: "devise" });
|
|
1031
|
+
if (modules.has("omniauthable") && allowed("omniauth_callback")) {
|
|
1032
|
+
const providers = new Set([...(/omniauth_providers:\s*\[([^\]]*)\]/.exec(call)?.[1] ?? "").matchAll(/:(\w+)/g)].map((m) => m[1]));
|
|
1033
|
+
for (const l of env.lines("config/initializers/devise.rb") ?? [])
|
|
1034
|
+
for (const m of l.matchAll(/^\s*config\.omniauth\s+:(\w+)/g))
|
|
1035
|
+
providers.add(m[1]);
|
|
1036
|
+
for (const p of providers)
|
|
1037
|
+
out.push({ kind: "route", value: `${at}/auth/${p}`, rule: "devise-omniauth" }, { kind: "route", value: `${at}/auth/${p}/callback`, rule: "devise-omniauth" });
|
|
1038
|
+
}
|
|
1039
|
+
return out;
|
|
1040
|
+
};
|
|
1041
|
+
var springDataRest = (file, lines, idx) => {
|
|
1042
|
+
if (!/\.(java|kt)$/.test(file))
|
|
1043
|
+
return [];
|
|
1044
|
+
const a = /@RepositoryRestResource\b(?:\s*\(([^)]*)\))?/.exec(lines[idx] ?? "");
|
|
1045
|
+
if (!a || /\bexported\s*=\s*false/.test(a[1] ?? ""))
|
|
1046
|
+
return [];
|
|
1047
|
+
let decl = -1;
|
|
1048
|
+
for (let i = idx; i < lines.length && i - idx <= 6; i++)
|
|
1049
|
+
if (/\binterface\s+\w+/.test(lines[i])) {
|
|
1050
|
+
decl = i;
|
|
1051
|
+
break;
|
|
1052
|
+
}
|
|
1053
|
+
if (decl < 0)
|
|
1054
|
+
return [];
|
|
1055
|
+
const domain = /\binterface\s+\w+\s*(?:extends|:)\s*[\w.]+\s*<\s*([\w.]+)\s*,/.exec(lines.slice(decl, decl + 3).join(" "))?.[1];
|
|
1056
|
+
const path = /\bpath\s*=\s*"([^"]+)"/.exec(a[1] ?? "")?.[1] ?? (domain ? (() => {
|
|
1057
|
+
const p = pluralize(domain.split(".").pop());
|
|
1058
|
+
return p[0].toLowerCase() + p.slice(1);
|
|
1059
|
+
})() : void 0);
|
|
1060
|
+
if (!path)
|
|
1061
|
+
return [];
|
|
1062
|
+
const at = `/${path.replace(/^\/+|\/+$/g, "")}`;
|
|
1063
|
+
const out = [
|
|
1064
|
+
{ kind: "route", value: at, rule: "spring-data-rest" },
|
|
1065
|
+
{ kind: "route", value: `${at}/{id}`, rule: "spring-data-rest" }
|
|
1066
|
+
];
|
|
1067
|
+
const methods = [];
|
|
1068
|
+
for (let i = decl + 1, depth = 1; i < lines.length && depth > 0; i++) {
|
|
1069
|
+
const l = lines[i];
|
|
1070
|
+
depth += (l.match(/\{/g) ?? []).length - (l.match(/\}/g) ?? []).length;
|
|
1071
|
+
const m = /^\s*(?:@\w+(?:\([^)]*\))?\s+)*[\w<>[\],.?\s]+?\s+((?:find|read|get|query|search|stream|count|exists)\w*)\s*\(/.exec(l) ?? /^\s*fun\s+((?:find|read|get|query|search|stream|count|exists)\w*)\s*\(/.exec(l);
|
|
1072
|
+
if (!m)
|
|
1073
|
+
continue;
|
|
1074
|
+
const rr = /@RestResource\s*\([^)]*\bpath\s*=\s*"([^"]+)"/.exec(`${lines[i - 1] ?? ""} ${l}`)?.[1];
|
|
1075
|
+
methods.push(rr ?? m[1]);
|
|
1076
|
+
}
|
|
1077
|
+
if (methods.length)
|
|
1078
|
+
out.push({ kind: "route", value: `${at}/search`, rule: "spring-data-rest" });
|
|
1079
|
+
for (const m of methods)
|
|
1080
|
+
out.push({
|
|
1081
|
+
kind: "route",
|
|
1082
|
+
value: `${at}/search/${m.replace(/^\/+/, "")}`,
|
|
1083
|
+
rule: "spring-data-rest"
|
|
1084
|
+
});
|
|
1085
|
+
return out;
|
|
1086
|
+
};
|
|
1087
|
+
var rubyWorker = (file, lines, idx) => {
|
|
1088
|
+
if (!/\.rb$/.test(file))
|
|
1089
|
+
return [];
|
|
1090
|
+
const m = /^(\s*)class\s+((?:\w+::)*\w+)(?:\s*<\s*([\w:]+))?\s*$/.exec(lines[idx] ?? "");
|
|
1091
|
+
if (!m)
|
|
1092
|
+
return [];
|
|
1093
|
+
const activeJob = /^(ApplicationJob|ActiveJob::Base)$/.test(m[3] ?? "") || /(^|\/)app\/jobs\//.test(file) && /Job$/.test(m[3] ?? "");
|
|
1094
|
+
const sidekiq = lines.slice(idx + 1, idx + 6).some((l) => /^\s*include\s+Sidekiq::(Job|Worker)\b/.test(l));
|
|
1095
|
+
if (!activeJob && !sidekiq)
|
|
1096
|
+
return [];
|
|
1097
|
+
const indent = m[1].length;
|
|
1098
|
+
const modules = [];
|
|
1099
|
+
let level = indent;
|
|
1100
|
+
for (let i = idx - 1; i >= 0 && level > 0; i--) {
|
|
1101
|
+
const mm = /^(\s*)(?:module|class)\s+((?:\w+::)*\w+)/.exec(lines[i]);
|
|
1102
|
+
if (mm && mm[1].length < level) {
|
|
1103
|
+
modules.unshift(mm[2]);
|
|
1104
|
+
level = mm[1].length;
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
return [
|
|
1108
|
+
{
|
|
1109
|
+
kind: "word",
|
|
1110
|
+
value: [...modules, m[2]].join("::"),
|
|
1111
|
+
rule: sidekiq ? "sidekiq-worker" : "active-job"
|
|
1112
|
+
}
|
|
1113
|
+
];
|
|
1114
|
+
};
|
|
1115
|
+
var celeryTask = (file, lines, idx, env) => {
|
|
1116
|
+
if (!/\.py$/.test(file))
|
|
1117
|
+
return [];
|
|
1118
|
+
let def = -1;
|
|
1119
|
+
for (let j = idx; j < lines.length && j - idx <= 4; j++) {
|
|
1120
|
+
if (/^\s*(?:async\s+)?def\s+\w+/.test(lines[j])) {
|
|
1121
|
+
def = j;
|
|
1122
|
+
break;
|
|
1123
|
+
}
|
|
1124
|
+
if (!/^\s*@/.test(lines[j]))
|
|
1125
|
+
break;
|
|
1126
|
+
}
|
|
1127
|
+
if (def < 0)
|
|
1128
|
+
return [];
|
|
1129
|
+
const decorators = [];
|
|
1130
|
+
for (let i = def - 1; i >= 0 && /^\s*@/.test(lines[i]); i--)
|
|
1131
|
+
decorators.push(lines[i]);
|
|
1132
|
+
const task = decorators.find((d) => /^\s*@(?:shared_task|[\w.]+\.task)\b/.test(d));
|
|
1133
|
+
if (!task || /\bname\s*=/.test(task))
|
|
1134
|
+
return [];
|
|
1135
|
+
const fn = /def\s+(\w+)/.exec(lines[def])[1];
|
|
1136
|
+
const parts = file.replace(/\.py$/, "").split("/");
|
|
1137
|
+
if (parts[parts.length - 1] === "__init__")
|
|
1138
|
+
parts.pop();
|
|
1139
|
+
let top = parts.length - 1;
|
|
1140
|
+
while (top > 0 && env.lines(`${parts.slice(0, top).join("/")}/__init__.py`) !== void 0)
|
|
1141
|
+
top--;
|
|
1142
|
+
const module = parts.slice(top).join(".");
|
|
1143
|
+
return [{ kind: "word", value: `${module}.${fn}`, rule: "celery-task" }];
|
|
1144
|
+
};
|
|
1145
|
+
var RULES = [
|
|
1146
|
+
jpaEntityTable,
|
|
1147
|
+
jpaAssociation,
|
|
1148
|
+
djangoModelTable,
|
|
1149
|
+
djangoField,
|
|
1150
|
+
railsModelTable,
|
|
1151
|
+
laravelModel,
|
|
1152
|
+
typeorm,
|
|
1153
|
+
prismaImplicitM2M,
|
|
1154
|
+
railsRoot,
|
|
1155
|
+
deviseFor,
|
|
1156
|
+
springDataRest,
|
|
1157
|
+
rubyWorker,
|
|
1158
|
+
celeryTask
|
|
1159
|
+
];
|
|
1160
|
+
var PARAM_SEG = /^(\{[^}]*\}|:[^/]+|<[^>]+>|\$\{[^}]*\})$/;
|
|
1161
|
+
var segsOf = (p) => p.split(/[?#]/, 1)[0].split("/").filter(Boolean).map((s) => PARAM_SEG.test(s) ? "{}" : s.toLowerCase());
|
|
1162
|
+
var fieldKey = (f) => f.split(".").pop().toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
1163
|
+
function derivedHolds(c, derived, partLines, namesPath) {
|
|
1164
|
+
if (c.kind === "word")
|
|
1165
|
+
return derived.some((d) => d.kind === "word" && d.value.toLowerCase() === c.value.trim().toLowerCase());
|
|
1166
|
+
if (c.kind === "field")
|
|
1167
|
+
return derived.some((d) => d.kind === "field" && fieldKey(d.value) === fieldKey(c.value));
|
|
1168
|
+
const claim = segsOf(c.value);
|
|
1169
|
+
return derived.some((d) => {
|
|
1170
|
+
if (d.kind !== "route")
|
|
1171
|
+
return false;
|
|
1172
|
+
const r = segsOf(d.value);
|
|
1173
|
+
if (claim.length === r.length)
|
|
1174
|
+
return claim.every((s, i) => s === r[i]);
|
|
1175
|
+
if (!partLines.length || claim.length < r.length)
|
|
1176
|
+
return false;
|
|
1177
|
+
const head = claim.slice(0, claim.length - r.length);
|
|
1178
|
+
if (!claim.slice(head.length).every((s, i) => s === r[i]))
|
|
1179
|
+
return false;
|
|
1180
|
+
const original = c.value.split(/[?#]/, 1)[0].split("/").filter(Boolean).slice(0, head.length);
|
|
1181
|
+
return original.some((s) => !PARAM_SEG.test(s)) && namesPath(partLines, `/${original.join("/")}`);
|
|
1182
|
+
});
|
|
1183
|
+
}
|
|
1184
|
+
function derivedNamesAt(file, lines, line, env) {
|
|
1185
|
+
const out = [];
|
|
1186
|
+
for (let i = Math.max(0, line - 2); i <= Math.min(lines.length - 1, line); i++) {
|
|
1187
|
+
if (isComment(lines[i]))
|
|
1188
|
+
continue;
|
|
1189
|
+
for (const r of RULES)
|
|
1190
|
+
out.push(...r(file, lines, i, env));
|
|
1191
|
+
}
|
|
1192
|
+
return out;
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
// packages/verify/dist/not-product.js
|
|
1196
|
+
var LOCALE_BUNDLE = /(^|\/)(i18n|l10n|locales?|lang|langs|languages|translations?)\/(.+\/)?[^/]+\.(json|ya?ml|properties)$|(^|\/)res\/values[^/]*\/(strings|plurals|arrays)\.xml$/i;
|
|
1197
|
+
function isLocaleBundle(path) {
|
|
1198
|
+
return LOCALE_BUNDLE.test(path);
|
|
1199
|
+
}
|
|
1200
|
+
var DATA_FILE_LINES = 2e3;
|
|
1201
|
+
var DATA_FILE_CHARS = 2e5;
|
|
1202
|
+
var DATA_EXT = /\.(json|xml)$/i;
|
|
1203
|
+
var KEEP_BIG = /(^|\/)(pom\.xml|package\.json|composer\.json|angular\.json|project\.json|nx\.json|lerna\.json|workspace\.json|tsconfig[^/]*\.json|jsconfig\.json|appsettings[^/]*\.json|launchSettings\.json|[^/]*(openapi|swagger|asyncapi|api-docs)[^/]*\.(json|xml)|[^/]*mapper[^/]*\.xml)$|(^|\/)(config|configuration|migrations?|db|database|changelogs?|liquibase|flyway)\//i;
|
|
1204
|
+
function isDataFile(path, lines) {
|
|
1205
|
+
if (!DATA_EXT.test(path) || KEEP_BIG.test(path))
|
|
1206
|
+
return false;
|
|
1207
|
+
if (lines.length - (lines[lines.length - 1] === "" ? 1 : 0) > DATA_FILE_LINES)
|
|
1208
|
+
return true;
|
|
1209
|
+
let chars = 0;
|
|
1210
|
+
for (const l of lines)
|
|
1211
|
+
if ((chars += l.length + 1) > DATA_FILE_CHARS)
|
|
1212
|
+
return true;
|
|
1213
|
+
return false;
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
// packages/verify/dist/pages.js
|
|
1217
|
+
function pageRouteOf(file, lines, exists) {
|
|
1218
|
+
const first = lines.find((l) => l.trim() !== "")?.trim() ?? "";
|
|
1219
|
+
const razor = /(?:^|\/)(?:Areas\/([^/]+)\/)?Pages\/(.+)\.cshtml$/.exec(file);
|
|
1220
|
+
if (razor && /^@page\b/.test(first)) {
|
|
1221
|
+
const template = /^@page\s+"([^"]*)"/.exec(first)?.[1];
|
|
1222
|
+
if (template?.startsWith("/"))
|
|
1223
|
+
return template;
|
|
1224
|
+
const rel = razor[2].replace(/(^|\/)Index$/, "");
|
|
1225
|
+
const path = `${razor[1] ? `/${razor[1]}` : ""}/${rel}`.replace(/\/+$/, "") || "/";
|
|
1226
|
+
return template ? `${path.replace(/\/$/, "")}/${template}` : path;
|
|
1227
|
+
}
|
|
1228
|
+
if (/\.aspx$/i.test(file) && lines.some((l) => /<%@\s*Page\b/i.test(l))) {
|
|
1229
|
+
const dirs = file.split("/").slice(0, -1);
|
|
1230
|
+
for (let n = dirs.length; n >= 0; n--) {
|
|
1231
|
+
const root = dirs.slice(0, n).join("/");
|
|
1232
|
+
const at = (name) => root ? `${root}/${name}` : name;
|
|
1233
|
+
if (exists(at("Web.config")) || exists(at("web.config")))
|
|
1234
|
+
return `/${file.slice(root ? root.length + 1 : 0)}`;
|
|
1235
|
+
}
|
|
1236
|
+
return void 0;
|
|
1237
|
+
}
|
|
1238
|
+
const param = (s) => s.replace(/^\[\[?(?:\.\.\.)?([^\]]+)\]\]?$/, "{$1}");
|
|
1239
|
+
const app = /(?:^|\/)(?:src\/)?app\/((?:.+\/)?)page\.[jt]sx?$/.exec(file);
|
|
1240
|
+
if (app)
|
|
1241
|
+
return "/" + app[1].split("/").filter((s) => s && !/^\(.*\)$/.test(s) && !s.startsWith("@")).map(param).join("/");
|
|
1242
|
+
const pages = /(?:^|\/)(?:src\/)?pages\/(.+)\.[jt]sx?$/.exec(file);
|
|
1243
|
+
if (pages && !/(^|\/)_(app|document|error)$/.test(pages[1]))
|
|
1244
|
+
return "/" + pages[1].replace(/(^|\/)index$/, "").split("/").filter(Boolean).map(param).join("/");
|
|
1245
|
+
return void 0;
|
|
1246
|
+
}
|
|
1247
|
+
function pageRouteMatches(file, lines, exists, path) {
|
|
1248
|
+
const route = pageRouteOf(file, lines, exists);
|
|
1249
|
+
if (route === void 0)
|
|
1250
|
+
return false;
|
|
1251
|
+
const norm = (p) => normalizePath(p.replace(/\{([^}:?]*)[^}]*\}/g, "{$1}")).toLowerCase() || "/";
|
|
1252
|
+
return norm(route) === norm(path);
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
// packages/verify/dist/reviewed.js
|
|
1256
|
+
var SHAPE = `{"path": "<file or glob>", "why": "..."}`;
|
|
1257
|
+
function parseReviewed(text) {
|
|
1258
|
+
const entries = [];
|
|
1259
|
+
const bad = [];
|
|
1260
|
+
text.split(/\r?\n/).forEach((raw, i) => {
|
|
1261
|
+
if (!raw.trim())
|
|
1262
|
+
return;
|
|
1263
|
+
const line = i + 1;
|
|
1264
|
+
let json2;
|
|
1265
|
+
try {
|
|
1266
|
+
json2 = JSON.parse(raw);
|
|
1267
|
+
} catch (e) {
|
|
1268
|
+
bad.push({ line, raw, message: `line ${line} is not JSON: ${e.message}` });
|
|
1269
|
+
return;
|
|
1270
|
+
}
|
|
1271
|
+
const o = json2;
|
|
1272
|
+
const issues = [];
|
|
1273
|
+
if (!o || typeof o !== "object" || Array.isArray(o))
|
|
1274
|
+
issues.push("not an object");
|
|
1275
|
+
else {
|
|
1276
|
+
if (typeof o.path !== "string" || !o.path.trim())
|
|
1277
|
+
issues.push("path: a file or glob");
|
|
1278
|
+
if (typeof o.why !== "string" || !o.why.trim())
|
|
1279
|
+
issues.push("why: why it holds nothing");
|
|
1280
|
+
const extra = Object.keys(o).filter((k) => k !== "path" && k !== "why");
|
|
1281
|
+
if (extra.length)
|
|
1282
|
+
issues.push(`unknown keys: ${extra.join(", ")}`);
|
|
1283
|
+
}
|
|
1284
|
+
if (issues.length) {
|
|
1285
|
+
bad.push({ line, raw, message: `line ${line} isn't ${SHAPE}: ${issues.join("; ")}` });
|
|
1286
|
+
return;
|
|
1287
|
+
}
|
|
1288
|
+
entries.push({ line, path: o.path.trim(), why: o.why.trim() });
|
|
1289
|
+
});
|
|
1290
|
+
return { entries, bad };
|
|
1291
|
+
}
|
|
1292
|
+
var isGlob = (path) => /[*?]/.test(path);
|
|
1293
|
+
|
|
1294
|
+
// packages/verify/dist/verify.js
|
|
1295
|
+
var FOLLOWED_TYPES = ["event-topic", "mq-queue", "rest", "grpc"];
|
|
1296
|
+
function entriesNoFlowFollows(facts) {
|
|
1297
|
+
const started = new Set(facts.flatMap((f) => f.kind === "flow" ? [f.entry] : []));
|
|
1298
|
+
return facts.flatMap((f) => f.kind === "entry" && FOLLOWED_TYPES.includes(f.entryType) && !started.has(f.id) && // An Apigee route forwards; its forwarding call is its flow (ADR 0124 addendum A-gateway).
|
|
1299
|
+
!APIGEE_BUNDLE.test(f.cite.file) ? [
|
|
1300
|
+
{
|
|
1301
|
+
id: f.id,
|
|
1302
|
+
entryType: f.entryType,
|
|
1303
|
+
...f.method ? { method: f.method } : {},
|
|
1304
|
+
path: f.path,
|
|
1305
|
+
cite: `${f.cite.file}:${f.cite.line}`
|
|
1306
|
+
}
|
|
1307
|
+
] : []).sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
|
|
1308
|
+
}
|
|
1309
|
+
var LINES_HELD = 64;
|
|
1310
|
+
var SOURCE_EXT = /\.(java|kt|kts|scala|groovy|cs|vb|fs|ts|tsx|js|jsx|mjs|cjs|py|rb|go|rs|php|swift|m|mm|c|cc|cpp|h|hpp|sql|graphql|gql|proto|ya?ml|json|xml|properties|toml|ini|conf|tf|tfvars|hcl|bicep|sh|ps1|bat|cmd|gradle|html?|vue|svelte|cshtml|razor|aspx|jsp|ftl|hbs|cob|cbl|cpy|jcl|avsc|wsdl|xsd|csproj|sln|config)$/i;
|
|
1311
|
+
var SOURCE_NAME = /(^|\/)(Dockerfile[^/]*|Makefile|Jenkinsfile|Procfile|Containerfile)$/;
|
|
1312
|
+
var LOCKFILE = /(^|\/)(package-lock\.json|npm-shrinkwrap\.json|yarn\.lock|pnpm-lock\.yaml|composer\.lock|Gemfile\.lock|Cargo\.lock|poetry\.lock|go\.sum|packages\.lock\.json)$/;
|
|
1313
|
+
var NOT_PRODUCT = [
|
|
1314
|
+
/(^|\/)(src\/test|src\/it|test|tests|__tests__|__mocks__|spec|specs|e2e|testdata|test-data|fixtures)\//i,
|
|
1315
|
+
/(\.|_|-)(test|spec|tests|specs)\.[a-z0-9]+$/i,
|
|
1316
|
+
/(Test|Tests|IT|Spec)\.(java|kt|scala|groovy|cs|vb)$/,
|
|
1317
|
+
/(^|\/)test_[^/]+\.py$/,
|
|
1318
|
+
/(^|\/)(mvnw|mvnw\.cmd|gradlew|gradlew\.bat)$/,
|
|
1319
|
+
/(^|\/)package-info\.java$/,
|
|
1320
|
+
/(^|\/)messages(_[A-Za-z_]+)?\.properties$/,
|
|
1321
|
+
/\.(resx|po|pot)$/i
|
|
1322
|
+
];
|
|
1323
|
+
function isSourceFile(path, firstLine = "", lines) {
|
|
1324
|
+
if (isSecretPath(path) || LOCKFILE.test(path))
|
|
1325
|
+
return false;
|
|
1326
|
+
if (NOT_PRODUCT.some((re) => re.test(path)) || isLocaleBundle(path))
|
|
1327
|
+
return false;
|
|
1328
|
+
if (lines && isDataFile(path, lines))
|
|
1329
|
+
return false;
|
|
1330
|
+
if (quoteNotRequired(path, firstLine))
|
|
1331
|
+
return false;
|
|
1332
|
+
return SOURCE_EXT.test(path) || SOURCE_NAME.test(path);
|
|
1333
|
+
}
|
|
1334
|
+
var lastPart = (s) => s.split(".").pop() ?? s;
|
|
1335
|
+
var isCommentLine = (l) => /^\s*(\/\/|\/\*|\*|<!--|-- |#(?!\s*(region|endregion|if|elif|else|endif|define|undef|pragma|nullable|include|import)\b)(?!!))/.test(l);
|
|
1336
|
+
var routeOrWord = (v) => v.includes("/") ? { kind: "route", value: v } : { kind: "word", value: v };
|
|
1337
|
+
function claimsOf(f) {
|
|
1338
|
+
switch (f.kind) {
|
|
1339
|
+
case "entry":
|
|
1340
|
+
case "call":
|
|
1341
|
+
return [routeOrWord(f.path)];
|
|
1342
|
+
case "store":
|
|
1343
|
+
return [
|
|
1344
|
+
{ kind: "word", value: f.name },
|
|
1345
|
+
{ kind: "word", value: lastPart(f.name) }
|
|
1346
|
+
];
|
|
1347
|
+
case "field":
|
|
1348
|
+
return [{ kind: "field", value: f.name }];
|
|
1349
|
+
case "deploy":
|
|
1350
|
+
return [{ kind: "word", value: f.resourceName ?? f.cite.value }];
|
|
1351
|
+
// A-framework: the declaration as the build file writes it (`spring-boot-starter-parent`,
|
|
1352
|
+
// `Microsoft.NET.Sdk.Web`, `express`). The framework's own name follows from it, and is
|
|
1353
|
+
// checked against what Atticus knows that declaration to mean, not against the line.
|
|
1354
|
+
case "framework":
|
|
1355
|
+
return [
|
|
1356
|
+
{ kind: "word", value: f.cite.value },
|
|
1357
|
+
{ kind: "word", value: f.name }
|
|
1358
|
+
];
|
|
1359
|
+
case "config": {
|
|
1360
|
+
const tail = f.key.trim().split(/\s+/).pop().replace(/^-+/, "");
|
|
1361
|
+
const lastOf = (k) => k.split(/[.:]|__/).pop() ?? k;
|
|
1362
|
+
return [
|
|
1363
|
+
{ kind: "word", value: f.key },
|
|
1364
|
+
{ kind: "word", value: lastPart(f.key) },
|
|
1365
|
+
{ kind: "word", value: tail },
|
|
1366
|
+
{ kind: "word", value: lastOf(tail) }
|
|
1367
|
+
];
|
|
1368
|
+
}
|
|
1369
|
+
case "flow":
|
|
1370
|
+
return [routeOrWord(f.cite.value)];
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1373
|
+
var sameCommit = (a, b) => a.startsWith(b) || b.startsWith(a);
|
|
1374
|
+
function changesFor(carry, commit) {
|
|
1375
|
+
if (!carry)
|
|
1376
|
+
return void 0;
|
|
1377
|
+
const key = Object.keys(carry).find((k) => sameCommit(k, commit));
|
|
1378
|
+
return key === void 0 ? void 0 : carry[key];
|
|
1379
|
+
}
|
|
1380
|
+
function staleMessage(f, s, commit) {
|
|
1381
|
+
const how = s.how === "renamed" ? `renamed to ${s.to}` : s.how;
|
|
1382
|
+
const then = s.how === "deleted" ? "drop the fact, or cite where the code says it now" : "rewrite the fact with its line as it is now, or drop it";
|
|
1383
|
+
return `${f.kind} ${f.id} cites ${s.file}, which was ${how} since commit ${f.commit}; read it again at ${commit}, then ${then}`;
|
|
1384
|
+
}
|
|
1385
|
+
function refsOf(f) {
|
|
1386
|
+
const out = [];
|
|
1387
|
+
const add = (field, id) => {
|
|
1388
|
+
if (id !== void 0)
|
|
1389
|
+
out.push({ field, id });
|
|
1390
|
+
};
|
|
1391
|
+
if (f.kind === "call")
|
|
1392
|
+
add("from", f.from);
|
|
1393
|
+
if (f.kind === "store")
|
|
1394
|
+
add("under", f.under);
|
|
1395
|
+
if (f.kind === "field") {
|
|
1396
|
+
add("owner", f.owner);
|
|
1397
|
+
add("mappedTo", f.mappedTo);
|
|
1398
|
+
}
|
|
1399
|
+
if (f.kind === "flow") {
|
|
1400
|
+
add("entry", f.entry);
|
|
1401
|
+
f.steps.forEach((s, i) => add(`steps.${i + 1}.ref`, s.ref));
|
|
1402
|
+
}
|
|
1403
|
+
return out;
|
|
1404
|
+
}
|
|
1405
|
+
async function verifyFacts(o) {
|
|
1406
|
+
const carriedFrom = /* @__PURE__ */ new Map();
|
|
1407
|
+
const stale = /* @__PURE__ */ new Map();
|
|
1408
|
+
const corrected = applyCorrections(o.facts, o.corrections ?? []);
|
|
1409
|
+
const facts = [...corrected.facts].sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0).map((f) => {
|
|
1410
|
+
if (!o.commit || sameCommit(f.commit, o.commit))
|
|
1411
|
+
return f;
|
|
1412
|
+
const changes = changesFor(o.carry, f.commit);
|
|
1413
|
+
if (!changes)
|
|
1414
|
+
return f;
|
|
1415
|
+
const s = staleCite(f, changes);
|
|
1416
|
+
if (s) {
|
|
1417
|
+
stale.set(f.id, s);
|
|
1418
|
+
return f;
|
|
1419
|
+
}
|
|
1420
|
+
carriedFrom.set(f.id, f.commit);
|
|
1421
|
+
return { ...f, commit: o.commit };
|
|
1422
|
+
});
|
|
1423
|
+
const readLines = (path) => {
|
|
1424
|
+
const text = insideRepo(path) && !isSecretPath(path) ? o.source.read(path) : void 0;
|
|
1425
|
+
return text === void 0 ? void 0 : text.replace(/\r\n?/g, "\n").split("\n");
|
|
1426
|
+
};
|
|
1427
|
+
const linesCache = /* @__PURE__ */ new Map();
|
|
1428
|
+
const linesOf2 = (path) => {
|
|
1429
|
+
if (linesCache.has(path)) {
|
|
1430
|
+
const held = linesCache.get(path);
|
|
1431
|
+
linesCache.delete(path);
|
|
1432
|
+
linesCache.set(path, held);
|
|
1433
|
+
return held;
|
|
1434
|
+
}
|
|
1435
|
+
const lines = readLines(path);
|
|
1436
|
+
linesCache.set(path, lines);
|
|
1437
|
+
if (linesCache.size > LINES_HELD)
|
|
1438
|
+
linesCache.delete(linesCache.keys().next().value);
|
|
1439
|
+
return lines;
|
|
1440
|
+
};
|
|
1441
|
+
let fileList;
|
|
1442
|
+
const deriveEnv = { lines: linesOf2, files: () => fileList ??= o.source.list() };
|
|
1443
|
+
const checkCite = (cite2, claims, label, withHint = true) => {
|
|
1444
|
+
const where = `${cite2.file}:${cite2.line}`;
|
|
1445
|
+
if (!insideRepo(cite2.file))
|
|
1446
|
+
return {
|
|
1447
|
+
code: "outside-repo",
|
|
1448
|
+
message: `${label} cites ${cite2.file}, which is outside the repo`
|
|
1449
|
+
};
|
|
1450
|
+
if (isSecretPath(cite2.file))
|
|
1451
|
+
return {
|
|
1452
|
+
code: "secret-path",
|
|
1453
|
+
message: `${label} cites ${cite2.file}, a file whose lines are never quoted into a map; cite where the code reads the setting instead`
|
|
1454
|
+
};
|
|
1455
|
+
const lines = linesOf2(cite2.file);
|
|
1456
|
+
if (lines === void 0)
|
|
1457
|
+
return {
|
|
1458
|
+
code: "no-file",
|
|
1459
|
+
message: `${label} cites ${cite2.file}, which is not in the repo at this commit`
|
|
1460
|
+
};
|
|
1461
|
+
if (cite2.line > lines.length)
|
|
1462
|
+
return {
|
|
1463
|
+
code: "no-line",
|
|
1464
|
+
message: `${label} cites line ${cite2.line} of ${cite2.file}, which has ${lines.length} lines`
|
|
1465
|
+
};
|
|
1466
|
+
const parts = cite2.with ?? [];
|
|
1467
|
+
const near = (ls, n) => ls.slice(Math.max(0, n - 2), n + 1);
|
|
1468
|
+
const partLines = [];
|
|
1469
|
+
for (const [i, p] of parts.entries()) {
|
|
1470
|
+
const pl = `${label} part ${i + 1}`;
|
|
1471
|
+
if (!insideRepo(p.file))
|
|
1472
|
+
return {
|
|
1473
|
+
code: "outside-repo",
|
|
1474
|
+
message: `${pl} cites ${p.file}, which is outside the repo`
|
|
1475
|
+
};
|
|
1476
|
+
if (isSecretPath(p.file))
|
|
1477
|
+
return {
|
|
1478
|
+
code: "secret-path",
|
|
1479
|
+
message: `${pl} cites ${p.file}, a file never quoted into a map`
|
|
1480
|
+
};
|
|
1481
|
+
const ls = linesOf2(p.file);
|
|
1482
|
+
if (ls === void 0)
|
|
1483
|
+
return {
|
|
1484
|
+
code: "no-file",
|
|
1485
|
+
message: `${pl} cites ${p.file}, which is not in the repo at this commit`
|
|
1486
|
+
};
|
|
1487
|
+
if (p.line > ls.length)
|
|
1488
|
+
return {
|
|
1489
|
+
code: "no-line",
|
|
1490
|
+
message: `${pl} cites line ${p.line} of ${p.file}, which has ${ls.length} lines`
|
|
1491
|
+
};
|
|
1492
|
+
if (!claimOnLines(ls, p.line, routeOrWord(p.value)) && !claimOnLines(ls, p.line, { kind: "word", value: p.value.replace(/^\/+|\/+$/g, "") }) && // a part named at a camelCase start (`Catalog` in `CatalogController`), as the path check does
|
|
1493
|
+
!partsNamePath(near(ls, p.line), p.value))
|
|
1494
|
+
return {
|
|
1495
|
+
code: "claim-not-on-line",
|
|
1496
|
+
message: `${pl} says "${p.value}" is written at ${p.file}:${p.line} (or the line either side), and it isn't`
|
|
1497
|
+
};
|
|
1498
|
+
partLines.push(...near(ls, p.line));
|
|
1499
|
+
}
|
|
1500
|
+
let derived;
|
|
1501
|
+
const derivedHere = () => derived ??= [
|
|
1502
|
+
{ file: cite2.file, line: cite2.line },
|
|
1503
|
+
...parts.map((p) => ({ file: p.file, line: p.line }))
|
|
1504
|
+
].flatMap((at) => derivedNamesAt(at.file, linesOf2(at.file) ?? [], at.line, deriveEnv));
|
|
1505
|
+
const holds = (c) => claimOnLines(lines, cite2.line, c) || // a framework that routes by file place (Razor Pages, WebForms, Next.js)
|
|
1506
|
+
c.kind === "route" && pageRouteMatches(cite2.file, lines, (p) => linesOf2(p) !== void 0, c.value) || parts.length > 0 && (c.kind === "route" ? partsNamePath([...near(lines, cite2.line), ...partLines], c.value) : partLines.some((l) => lineNamesClaim(l, c)) || // a word put together from parts (`namespace:task`, a key a helper prefixes)
|
|
1507
|
+
c.kind === "word" && partsBuildWord(c.value, parts.map((p) => p.value), near(lines, cite2.line))) || derivedHolds(c, derivedHere(), partLines, partsNamePath);
|
|
1508
|
+
if (claims && !claims.some(holds))
|
|
1509
|
+
return {
|
|
1510
|
+
code: "claim-not-on-line",
|
|
1511
|
+
message: `${label} claims ${claims.map((c) => `"${c.value}"`).join(" or ")}, which ${where} (or the line either side)${parts.length ? " and its parts" : ""} does not name${parts.length || !withHint ? "" : "; if it is put together from other lines (a base URL, a prefix, a context path), cite them in cite.with"}`
|
|
1512
|
+
};
|
|
1513
|
+
if (claims && !parts.length) {
|
|
1514
|
+
const naming = [cite2.line - 1, cite2.line, cite2.line + 1].map((n) => lines[n - 1]).filter((l) => l !== void 0 && claims.some((c) => lineNamesClaim(l, c)));
|
|
1515
|
+
if (naming.length && !/(^|\/)_redirects$/.test(cite2.file) && naming.every(isCommentLine))
|
|
1516
|
+
return {
|
|
1517
|
+
code: "claim-in-comment",
|
|
1518
|
+
message: `${label} claims ${claims.map((c) => `"${c.value}"`).join(" or ")}, which ${where} writes only in a comment; cite the code that does it`
|
|
1519
|
+
};
|
|
1520
|
+
}
|
|
1521
|
+
const line = lines[cite2.line - 1] ?? "";
|
|
1522
|
+
if (cite2.quote && !quoteNotRequired(cite2.file, line)) {
|
|
1523
|
+
const near2 = [cite2.line - 1, cite2.line, cite2.line + 1].map((n) => lines[n - 1] ?? "");
|
|
1524
|
+
if (!near2.some((l) => quoteMatches(l, cite2.quote)))
|
|
1525
|
+
return {
|
|
1526
|
+
code: "quote-mismatch",
|
|
1527
|
+
message: `${label} quotes "${cite2.quote}", but ${where} reads "${line.trim().slice(0, 200)}"`
|
|
1528
|
+
};
|
|
1529
|
+
}
|
|
1530
|
+
return void 0;
|
|
1531
|
+
};
|
|
1532
|
+
const found = /* @__PURE__ */ new Map();
|
|
1533
|
+
const other = /* @__PURE__ */ new Set();
|
|
1534
|
+
const problem = (fact, code2, message) => found.set(fact.id, [...found.get(fact.id) ?? [], { code: code2, message }]);
|
|
1535
|
+
const add = (fact, p) => {
|
|
1536
|
+
if (p)
|
|
1537
|
+
problem(fact, p.code, p.message);
|
|
1538
|
+
};
|
|
1539
|
+
const stepClaims = (kind, value) => {
|
|
1540
|
+
if (kind === "validate")
|
|
1541
|
+
return value.includes("/") ? [routeOrWord(value), { kind: "word", value }] : [routeOrWord(value)];
|
|
1542
|
+
return NEVER_CHECKED_STEPS.includes(kind) ? void 0 : [routeOrWord(value)];
|
|
1543
|
+
};
|
|
1544
|
+
for (const f of facts) {
|
|
1545
|
+
const label = `${f.kind} ${f.id}`;
|
|
1546
|
+
if (o.commit && !sameCommit(f.commit, o.commit)) {
|
|
1547
|
+
other.add(f.id);
|
|
1548
|
+
const early = !insideRepo(f.cite.file) ? "outside-repo" : isSecretPath(f.cite.file) ? "secret-path" : void 0;
|
|
1549
|
+
if (early)
|
|
1550
|
+
add(f, checkCite(f.cite, void 0, label));
|
|
1551
|
+
else if (stale.has(f.id))
|
|
1552
|
+
problem(f, "cite-changed", staleMessage(f, stale.get(f.id), o.commit));
|
|
1553
|
+
else
|
|
1554
|
+
problem(f, "wrong-commit", `${label} was read at ${f.commit}, not at ${o.commit}; read it again`);
|
|
1555
|
+
continue;
|
|
1556
|
+
}
|
|
1557
|
+
add(f, checkCite(f.cite, claimsOf(f), label));
|
|
1558
|
+
if (f.kind === "entry" && f.authCite)
|
|
1559
|
+
add(f, checkCite({ ...f.authCite }, [{ kind: "word", value: f.authCite.value }], `${label} authCite${f.auth ? ` (auth ${f.auth})` : ""}`, false));
|
|
1560
|
+
if (f.kind === "entry" && f.authCite && f.auth) {
|
|
1561
|
+
const bad = authPolicyMismatch(f.auth, f.authCite.file, f.authCite.value, linesOf2);
|
|
1562
|
+
if (bad)
|
|
1563
|
+
problem(f, "auth-policy-kind", `${label} claims auth ${f.auth} on a line that attaches ${f.authCite.value}, a ${bad.element} policy (${bad.path}), which ${bad.makes ? `checks ${bad.makes.map(authChecks).join(" or ")}, not ${authChecks(f.auth)}` : "authenticates nobody"}; cite the line that attaches the check, or write the auth the policy makes`);
|
|
1564
|
+
}
|
|
1565
|
+
if (f.kind === "entry")
|
|
1566
|
+
(f.scopes ?? []).forEach((s, i) => add(f, checkCite(s, [{ kind: "word", value: s.value }], `${label} scope ${i + 1} ("${s.value}")`)));
|
|
1567
|
+
if (f.kind === "entry")
|
|
1568
|
+
(f.callers ?? []).forEach((c, i) => add(f, checkCite(c, [{ kind: "word", value: c.value }], `${label} caller ${i + 1} (${c.as} "${c.value}")`)));
|
|
1569
|
+
if (f.kind === "framework") {
|
|
1570
|
+
const bad = frameworkNameProblem(f);
|
|
1571
|
+
if (bad)
|
|
1572
|
+
problem(f, "framework-name", `${label} ${bad}`);
|
|
1573
|
+
if (f.version !== void 0)
|
|
1574
|
+
add(f, checkCite(f.cite, [{ kind: "word", value: f.version }], `${label} version`, false));
|
|
1575
|
+
}
|
|
1576
|
+
if (f.kind === "flow") {
|
|
1577
|
+
f.steps.forEach((s, i) => add(f, checkCite(s.cite, stepClaims(s.kind, s.cite.value), `${label} step ${i + 1}`)));
|
|
1578
|
+
f.errors.forEach((e, i) => add(f, checkCite(e.cite, [routeOrWord(e.cite.value)], `${label} error ${i + 1}`)));
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1581
|
+
const ids = new Set(facts.map((f) => f.id));
|
|
1582
|
+
for (const f of facts) {
|
|
1583
|
+
if (other.has(f.id))
|
|
1584
|
+
continue;
|
|
1585
|
+
for (const d of refsOf(f).filter((r) => !ids.has(r.id)))
|
|
1586
|
+
problem(f, "dangling-ref", `${f.kind} ${f.id} names ${d.field} "${d.id}", which is no fact in this file`);
|
|
1587
|
+
}
|
|
1588
|
+
const byId = new Map(facts.map((f) => [f.id, f]));
|
|
1589
|
+
for (const f of facts) {
|
|
1590
|
+
if (other.has(f.id) || f.kind !== "flow")
|
|
1591
|
+
continue;
|
|
1592
|
+
f.steps.forEach((s, i) => {
|
|
1593
|
+
if (s.kind !== "call-out")
|
|
1594
|
+
return;
|
|
1595
|
+
const to = s.ref === void 0 ? void 0 : byId.get(s.ref);
|
|
1596
|
+
if (to?.kind === "call" || to?.kind === "config")
|
|
1597
|
+
return;
|
|
1598
|
+
if (s.ref !== void 0 && !to)
|
|
1599
|
+
return;
|
|
1600
|
+
const what = s.ref === void 0 ? "names no fact" : `names ${to.kind} ${s.ref}`;
|
|
1601
|
+
problem(f, "call-out-no-call", `flow ${f.id} step ${i + 1} is a call-out that ${what} (${s.cite.file}:${s.cite.line}): give it ref, the id of the call fact it makes, or, for a call through a cloud SDK with no path, the config fact of the client's setting; or make it another kind of step if it calls nothing`);
|
|
1602
|
+
});
|
|
1603
|
+
}
|
|
1604
|
+
for (const f of facts) {
|
|
1605
|
+
if (other.has(f.id) || f.kind !== "flow")
|
|
1606
|
+
continue;
|
|
1607
|
+
f.steps.forEach((s, i) => {
|
|
1608
|
+
if (s.kind !== "publish")
|
|
1609
|
+
return;
|
|
1610
|
+
const to = s.ref === void 0 ? void 0 : byId.get(s.ref);
|
|
1611
|
+
if (to?.kind === "call" && (to.callType === "event-topic" || to.callType === "mq-queue"))
|
|
1612
|
+
return;
|
|
1613
|
+
if (s.ref !== void 0 && !to)
|
|
1614
|
+
return;
|
|
1615
|
+
const what = to === void 0 ? "names no fact" : `names ${to.kind === "call" ? `a ${to.callType} call` : to.kind} ${s.ref}`;
|
|
1616
|
+
problem(f, "publish-no-event", `flow ${f.id} step ${i + 1} is a publish that ${what} (${s.cite.file}:${s.cite.line}): give it ref, the id of the event-topic or mq-queue call fact it publishes (write that call fact if there is none); or make it another kind of step if it publishes nothing`);
|
|
1617
|
+
});
|
|
1618
|
+
}
|
|
1619
|
+
const rejected = /* @__PURE__ */ new Map();
|
|
1620
|
+
for (const f of facts) {
|
|
1621
|
+
const ps = found.get(f.id);
|
|
1622
|
+
if (!ps?.length)
|
|
1623
|
+
continue;
|
|
1624
|
+
rejected.set(f.id, ps.length === 1 ? { fact: f, ...ps[0] } : {
|
|
1625
|
+
fact: f,
|
|
1626
|
+
code: ps[0].code,
|
|
1627
|
+
message: ps.map((p, i) => `(${i + 1}) ${p.message}`).join(" "),
|
|
1628
|
+
problems: ps
|
|
1629
|
+
});
|
|
1630
|
+
}
|
|
1631
|
+
for (let changed = true; changed; ) {
|
|
1632
|
+
changed = false;
|
|
1633
|
+
for (const f of facts) {
|
|
1634
|
+
if (rejected.has(f.id))
|
|
1635
|
+
continue;
|
|
1636
|
+
const r = refsOf(f).find((x) => rejected.has(x.id));
|
|
1637
|
+
if (r) {
|
|
1638
|
+
rejected.set(f.id, {
|
|
1639
|
+
fact: f,
|
|
1640
|
+
code: "ref-rejected",
|
|
1641
|
+
message: `${f.kind} ${f.id} names ${r.field} "${r.id}", which was rejected (${rejected.get(r.id).code})`
|
|
1642
|
+
});
|
|
1643
|
+
changed = true;
|
|
1644
|
+
}
|
|
1645
|
+
}
|
|
1646
|
+
}
|
|
1647
|
+
let redacted = 0;
|
|
1648
|
+
let quotesDropped = 0;
|
|
1649
|
+
const fixQuote = (c) => {
|
|
1650
|
+
if (!c.quote)
|
|
1651
|
+
return c;
|
|
1652
|
+
const line = (linesOf2(c.file) ?? [])[c.line - 1] ?? "";
|
|
1653
|
+
if (quoteNotRequired(c.file, line) && !quoteMatches(line, c.quote)) {
|
|
1654
|
+
quotesDropped++;
|
|
1655
|
+
const { quote: _drop, ...rest } = c;
|
|
1656
|
+
return rest;
|
|
1657
|
+
}
|
|
1658
|
+
const q = redactSecrets(c.quote);
|
|
1659
|
+
if (q !== c.quote)
|
|
1660
|
+
redacted++;
|
|
1661
|
+
return { ...c, quote: q };
|
|
1662
|
+
};
|
|
1663
|
+
const authUncited = [];
|
|
1664
|
+
const accepted = facts.filter((f) => !rejected.has(f.id)).map((f) => {
|
|
1665
|
+
o.room?.();
|
|
1666
|
+
const verified = f.by === "person" && f.verified !== "none" ? f.verified : "line";
|
|
1667
|
+
const base = { ...f, verified, cite: fixQuote(f.cite) };
|
|
1668
|
+
if (base.kind === "entry" && base.auth !== void 0 && !base.authCite && f.by !== "person")
|
|
1669
|
+
authUncited.push({
|
|
1670
|
+
id: base.id,
|
|
1671
|
+
auth: base.auth,
|
|
1672
|
+
cite: `${base.cite.file}:${base.cite.line}`
|
|
1673
|
+
});
|
|
1674
|
+
if (base.kind === "flow")
|
|
1675
|
+
return {
|
|
1676
|
+
...base,
|
|
1677
|
+
steps: base.steps.map((s) => ({ ...s, cite: fixQuote(s.cite) })),
|
|
1678
|
+
errors: base.errors.map((e) => ({ ...e, cite: fixQuote(e.cite) }))
|
|
1679
|
+
};
|
|
1680
|
+
return base;
|
|
1681
|
+
});
|
|
1682
|
+
const excludeRes = (o.exclude ?? []).map(globToRegExp);
|
|
1683
|
+
const sources = [];
|
|
1684
|
+
const excluded = [];
|
|
1685
|
+
for (const p of o.source.list()) {
|
|
1686
|
+
o.room?.();
|
|
1687
|
+
const ls = readLines(p) ?? [""];
|
|
1688
|
+
if (!isSourceFile(p, ls[0] ?? "", ls))
|
|
1689
|
+
continue;
|
|
1690
|
+
if (excludeRes.some((re) => re.test(p)))
|
|
1691
|
+
excluded.push(p);
|
|
1692
|
+
else
|
|
1693
|
+
sources.push(p);
|
|
1694
|
+
}
|
|
1695
|
+
const touched = new Set(o.routed ?? []);
|
|
1696
|
+
for (const f of accepted)
|
|
1697
|
+
for (const p of citedFiles(f))
|
|
1698
|
+
touched.add(p);
|
|
1699
|
+
const allFiles = o.source.list();
|
|
1700
|
+
const isSource = new Set(sources);
|
|
1701
|
+
const reviewedSet = /* @__PURE__ */ new Set();
|
|
1702
|
+
const reviewed = [];
|
|
1703
|
+
const reviewedProblems = [];
|
|
1704
|
+
for (const r of o.reviewed ?? []) {
|
|
1705
|
+
const refuse = (message) => reviewedProblems.push({
|
|
1706
|
+
...r.line !== void 0 ? { line: r.line } : {},
|
|
1707
|
+
path: r.path,
|
|
1708
|
+
why: r.why,
|
|
1709
|
+
message: `${r.line !== void 0 ? `line ${r.line}: ` : ""}${r.path} ${message}`
|
|
1710
|
+
});
|
|
1711
|
+
if (!insideRepo(r.path)) {
|
|
1712
|
+
refuse("is outside the repo");
|
|
1713
|
+
continue;
|
|
1714
|
+
}
|
|
1715
|
+
if (isSecretPath(r.path)) {
|
|
1716
|
+
refuse("is a secret's file, which is never read into a map");
|
|
1717
|
+
continue;
|
|
1718
|
+
}
|
|
1719
|
+
const re = isGlob(r.path) ? globToRegExp(r.path) : void 0;
|
|
1720
|
+
const matched = re ? allFiles.filter((p) => re.test(p)) : allFiles.filter((p) => p === r.path);
|
|
1721
|
+
if (!matched.length) {
|
|
1722
|
+
refuse(re ? "matches no file in the repo at this commit; name the files you read" : "is not in the repo at this commit; name a file you read");
|
|
1723
|
+
continue;
|
|
1724
|
+
}
|
|
1725
|
+
const files = matched.filter((p) => isSource.has(p) && !touched.has(p)).sort();
|
|
1726
|
+
for (const p of files)
|
|
1727
|
+
reviewedSet.add(p);
|
|
1728
|
+
reviewed.push({ path: r.path, why: r.why, files });
|
|
1729
|
+
}
|
|
1730
|
+
const read = sources.filter((p) => touched.has(p)).sort();
|
|
1731
|
+
const reviewedFiles = sources.filter((p) => reviewedSet.has(p)).sort();
|
|
1732
|
+
const unknown = sources.filter((p) => !touched.has(p) && !reviewedSet.has(p)).sort();
|
|
1733
|
+
const total = read.length + reviewedFiles.length + unknown.length;
|
|
1734
|
+
const ratio = total === 0 ? 1 : Math.round(read.length / total * 1e3) / 1e3;
|
|
1735
|
+
return {
|
|
1736
|
+
accepted,
|
|
1737
|
+
// a rejected fact goes back as it was read, at its own commit, even one that was carried
|
|
1738
|
+
rejected: [...rejected.values()].map((r) => carriedFrom.has(r.fact.id) ? { ...r, fact: { ...r.fact, commit: carriedFrom.get(r.fact.id) } } : r).sort((a, b) => a.fact.id < b.fact.id ? -1 : a.fact.id > b.fact.id ? 1 : 0),
|
|
1739
|
+
coverage: { read, reviewed: reviewedFiles, unknown, excluded: excluded.sort(), ratio },
|
|
1740
|
+
redacted,
|
|
1741
|
+
quotesDropped,
|
|
1742
|
+
carried: accepted.flatMap((f) => carriedFrom.has(f.id) ? [{ id: f.id, from: carriedFrom.get(f.id) }] : []),
|
|
1743
|
+
corrections: { applied: corrected.applied, stale: corrected.stale },
|
|
1744
|
+
entriesNoFlowFollows: entriesNoFlowFollows(accepted),
|
|
1745
|
+
reviewed,
|
|
1746
|
+
reviewedProblems,
|
|
1747
|
+
authUncited: authUncited.sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0),
|
|
1748
|
+
frameworkGaps: frameworkGaps(accepted)
|
|
1749
|
+
};
|
|
1750
|
+
}
|
|
1751
|
+
|
|
1752
|
+
// packages/verify/dist/batches.js
|
|
1753
|
+
var BUILD = /(^|\/)(pom\.xml|build\.gradle(\.kts)?|settings\.gradle(\.kts)?|package\.json|go\.mod|Cargo\.toml|pyproject\.toml|setup\.py|requirements[^/]*\.txt|Gemfile|[^/]+\.gemspec|composer\.json|[^/]+\.(csproj|sln|tf|tfvars|bicep|hcl)|Dockerfile[^/]*|Containerfile|Jenkinsfile|Makefile|Procfile|(docker-)?compose[^/]*\.ya?ml|Chart\.yaml|values[^/]*\.ya?ml|azure-pipelines[^/]*\.ya?ml|\.gitlab-ci\.yml|bitbucket-pipelines\.yml|serverless\.ya?ml|template\.ya?ml|manifest\.ya?ml)$/;
|
|
1754
|
+
var DEPLOY_DIR = /(^|\/)(\.github\/workflows|\.circleci|k8s|kubernetes|helm|charts|deploy|deployment|deployments|manifests|infra|terraform|pipelines?)\//;
|
|
1755
|
+
var CONFIG = /(^|\/)((application|bootstrap)[^/]*\.(ya?ml|properties)|appsettings[^/]*\.json|[^/]+\.(properties|conf|ini|toml)|web\.config|config\/[^/]+\.(ya?ml|json))$/;
|
|
1756
|
+
var ENTRY_NAME = /(^|[._-])(main|app|application|server|program|startup|index|lambda|handler|function|cli)$|(controller|resource|router|routes?|handlers?|listener|consumer|subscriber|endpoints?|api|job|scheduler|worker|webhook|servlet|page|screen|view)$/i;
|
|
1757
|
+
var ENTRY_DIR = /(^|\/)(controllers?|routes|routers|handlers|api|endpoints|jobs|listeners|consumers|functions|pages|screens)\//i;
|
|
1758
|
+
var BOUNDARY_NAME = /(client|gateway|repository|repo|dao|mapper|entity|model|dto|request|response|publisher|producer|store|cache|schema)$/i;
|
|
1759
|
+
var BOUNDARY_FILE = /\.(sql|proto|graphql|gql|avsc|wsdl|xsd)$|(^|\/)(openapi|swagger)[^/]*\.(ya?ml|json)$/i;
|
|
1760
|
+
var BOUNDARY_DIR = /(^|\/)(clients?|gateways?|repositories|dao|models?|entities|dto|migrations?)\//i;
|
|
1761
|
+
var TEST = /(^|\/)(test|tests|__tests__|spec|specs|e2e|it)\/|[._-](test|spec)\.[^/.]+$|(Test|Tests|IT|Spec)\.[^/.]+$|(^|\/)test_[^/]+$|_test\.go$/;
|
|
1762
|
+
var stem = (path) => {
|
|
1763
|
+
const base = path.slice(path.lastIndexOf("/") + 1);
|
|
1764
|
+
const dot = base.indexOf(".");
|
|
1765
|
+
return dot > 0 ? base.slice(0, dot) : base;
|
|
1766
|
+
};
|
|
1767
|
+
function readingRank(path) {
|
|
1768
|
+
if (TEST.test(path))
|
|
1769
|
+
return 5;
|
|
1770
|
+
if (BUILD.test(path) || DEPLOY_DIR.test(path))
|
|
1771
|
+
return 0;
|
|
1772
|
+
if (CONFIG.test(path))
|
|
1773
|
+
return 1;
|
|
1774
|
+
const s = stem(path);
|
|
1775
|
+
if (ENTRY_NAME.test(s) || ENTRY_DIR.test(path))
|
|
1776
|
+
return 2;
|
|
1777
|
+
if (BOUNDARY_NAME.test(s) || BOUNDARY_FILE.test(path) || BOUNDARY_DIR.test(path))
|
|
1778
|
+
return 3;
|
|
1779
|
+
return 4;
|
|
1780
|
+
}
|
|
1781
|
+
var FILE_KINDS = [
|
|
1782
|
+
"frontend",
|
|
1783
|
+
"backend",
|
|
1784
|
+
"routes",
|
|
1785
|
+
"data",
|
|
1786
|
+
"deploy",
|
|
1787
|
+
"config",
|
|
1788
|
+
"protocols",
|
|
1789
|
+
"flows",
|
|
1790
|
+
"gateway",
|
|
1791
|
+
"mocks"
|
|
1792
|
+
];
|
|
1793
|
+
var SSO_FILE = /(^|\/)(sso|oidc|keycloak|okta|auth0)[^/]*\.[a-z]+$/i;
|
|
1794
|
+
var GATEWAY_FILE = /(^|\/)(apiproxy|sharedflowbundle)\/|(^|\/)(targetservers?|kvms?)\.json$|(^|\/)resources\/edge\/(env|org|api)\//i;
|
|
1795
|
+
var GATEWAY_KINDS = ["config", "gateway"];
|
|
1796
|
+
var ANY_KINDS = FILE_KINDS.filter((k) => k !== "gateway" && k !== "mocks");
|
|
1797
|
+
var BACKEND_KINDS = ["backend", "routes", "data", "protocols", "flows"];
|
|
1798
|
+
var FRONTEND_KINDS = ["frontend", "flows"];
|
|
1799
|
+
var PAGE_KINDS = ["frontend", "routes", "flows"];
|
|
1800
|
+
var CODE_KINDS = ["frontend", ...BACKEND_KINDS];
|
|
1801
|
+
var DATAFILE_KINDS = [
|
|
1802
|
+
"routes",
|
|
1803
|
+
"data",
|
|
1804
|
+
"deploy",
|
|
1805
|
+
"config",
|
|
1806
|
+
"protocols"
|
|
1807
|
+
];
|
|
1808
|
+
var SERVER_CODE = /\.(java|kt|kts|scala|groovy|cs|vb|fs|py|rb|go|rs|php|c|cc|cpp|h|hpp|cob|cbl|cpy|jcl)$/i;
|
|
1809
|
+
var DEVICE_CODE = /\.(swift|m|mm)$/i;
|
|
1810
|
+
var SCRIPT_CODE = /\.(ts|js|mjs|cjs)$/i;
|
|
1811
|
+
var SPA_FILE = /\.(tsx|jsx|vue|svelte)$|\.(component|directive|pipe)\.([jt]s|html)$/i;
|
|
1812
|
+
var PAGE_FILE = /\.(html?|cshtml|razor|aspx|ascx|master|jsp|ftl|hbs)$/i;
|
|
1813
|
+
var PAGE_CODE = /\.(cshtml|razor|aspx|ascx|master)\.(cs|vb)$/i;
|
|
1814
|
+
var SERVER_UI = /(^|\/)(app|pages|routes|server|api)\//;
|
|
1815
|
+
var FRONT_DIR = /(^|\/)(components|hooks|composables|views|screens|public|static|assets|wwwroot|ClientApp|frontend|front-end)\//i;
|
|
1816
|
+
var BACK_SCRIPT = /\.(controller|resolver|gateway|entity|repository|module|guard|middleware|consumer|processor)\.[jt]s$|(^|\/)(server|functions|lambdas?|src\/main)\//;
|
|
1817
|
+
var DEVICE_DIR = /(^|\/)(android|ios|mobile|ui|screens?|activities|fragments|compose)\//i;
|
|
1818
|
+
var CONFIG_CODE = /(^|\/)(config|configuration|settings|environments?)(\/|\.[^/]+$)|(^|\/)environment[^/]*\.[jt]s$/i;
|
|
1819
|
+
var IAC = /\.(tf|tfvars|hcl|bicep)$|(^|\/)(serverless|template)[^/]*\.ya?ml$|(^|\/)(cdk|pulumi|terraform)\//;
|
|
1820
|
+
var DATA_FILE = /\.sql$|(^|\/)(migrations?|db|database|changelogs?|liquibase|flyway)\//i;
|
|
1821
|
+
var CONTRACT = /\.(proto|avsc|wsdl|xsd)$/i;
|
|
1822
|
+
var API_SCHEMA = /\.(graphql|gql)$|(^|\/)(openapi|swagger|asyncapi)[^/]*\.(ya?ml|json)$/i;
|
|
1823
|
+
var STRUCTURED = /\.(ya?ml|json|xml|config|properties|toml|ini|conf)$/i;
|
|
1824
|
+
function fileKinds(path) {
|
|
1825
|
+
if (GATEWAY_FILE.test(path))
|
|
1826
|
+
return [...GATEWAY_KINDS];
|
|
1827
|
+
const k = /* @__PURE__ */ new Set();
|
|
1828
|
+
const add = (ks) => ks.forEach((x) => k.add(x));
|
|
1829
|
+
if (BUILD.test(path) || DEPLOY_DIR.test(path)) {
|
|
1830
|
+
add(["deploy", "config"]);
|
|
1831
|
+
if (IAC.test(path))
|
|
1832
|
+
add(DATAFILE_KINDS);
|
|
1833
|
+
}
|
|
1834
|
+
if (CONFIG.test(path))
|
|
1835
|
+
add(["config"]);
|
|
1836
|
+
if (DATA_FILE.test(path))
|
|
1837
|
+
add(["data"]);
|
|
1838
|
+
if (CONTRACT.test(path))
|
|
1839
|
+
add(["protocols"]);
|
|
1840
|
+
if (API_SCHEMA.test(path))
|
|
1841
|
+
add(["routes", "protocols"]);
|
|
1842
|
+
const isCode = SERVER_CODE.test(path) || DEVICE_CODE.test(path) || SCRIPT_CODE.test(path);
|
|
1843
|
+
if (PAGE_CODE.test(path) || DEVICE_CODE.test(path))
|
|
1844
|
+
add(CODE_KINDS);
|
|
1845
|
+
else if (SPA_FILE.test(path))
|
|
1846
|
+
add(/\.(tsx|jsx)$/i.test(path) && SERVER_UI.test(path) ? CODE_KINDS : FRONTEND_KINDS);
|
|
1847
|
+
else if (PAGE_FILE.test(path))
|
|
1848
|
+
add(PAGE_KINDS);
|
|
1849
|
+
else if (SERVER_CODE.test(path))
|
|
1850
|
+
add(/\.(kt|java)$/i.test(path) && DEVICE_DIR.test(path) ? CODE_KINDS : BACKEND_KINDS);
|
|
1851
|
+
else if (SCRIPT_CODE.test(path))
|
|
1852
|
+
add(BACK_SCRIPT.test(path) ? BACKEND_KINDS : FRONT_DIR.test(path) ? FRONTEND_KINDS : CODE_KINDS);
|
|
1853
|
+
if (isCode && CONFIG_CODE.test(path))
|
|
1854
|
+
add(["config"]);
|
|
1855
|
+
const mock = MOCK_FILE.test(path) || SSO_FILE.test(path);
|
|
1856
|
+
if (!k.size && !(mock && STRUCTURED.test(path)))
|
|
1857
|
+
add(STRUCTURED.test(path) ? DATAFILE_KINDS : ANY_KINDS);
|
|
1858
|
+
if (mock)
|
|
1859
|
+
add(["mocks"]);
|
|
1860
|
+
return FILE_KINDS.filter((x) => k.has(x));
|
|
1861
|
+
}
|
|
1862
|
+
function kindsOf(paths) {
|
|
1863
|
+
const k = /* @__PURE__ */ new Set();
|
|
1864
|
+
for (const p of paths)
|
|
1865
|
+
for (const x of fileKinds(p))
|
|
1866
|
+
k.add(x);
|
|
1867
|
+
return FILE_KINDS.filter((x) => k.has(x));
|
|
1868
|
+
}
|
|
1869
|
+
function readingOrder(paths) {
|
|
1870
|
+
return [...paths].map((p) => ({ p, r: readingRank(p) })).sort((a, b) => a.r - b.r || (a.p < b.p ? -1 : a.p > b.p ? 1 : 0)).map((x) => x.p);
|
|
1871
|
+
}
|
|
1872
|
+
var linesOf = (text) => {
|
|
1873
|
+
const t = text.replace(/\r\n?/g, "\n");
|
|
1874
|
+
return (t.endsWith("\n") ? t.slice(0, -1) : t).split("\n");
|
|
1875
|
+
};
|
|
1876
|
+
function numberedSlice(text, s) {
|
|
1877
|
+
return linesOf(text).slice(s.from - 1, s.to).map((l, i) => `${s.from + i}| ${l}`).join("\n");
|
|
1878
|
+
}
|
|
1879
|
+
var header = (s) => `${s.path} lines ${s.from}-${s.to} of ${s.of} (numbered as in the file)`;
|
|
1880
|
+
function sliceText(source, s) {
|
|
1881
|
+
return `${header(s)}
|
|
1882
|
+
${numberedSlice(source.read(s.path) ?? "", s)}`;
|
|
1883
|
+
}
|
|
1884
|
+
var tokensOf = (chars) => Math.ceil(chars / 4);
|
|
1885
|
+
function planBatches(source, o) {
|
|
1886
|
+
const room = Math.max(1, o.room);
|
|
1887
|
+
const excludeRes = (o.exclude ?? []).map(globToRegExp);
|
|
1888
|
+
const excluded = [];
|
|
1889
|
+
const paths = [];
|
|
1890
|
+
const texts = /* @__PURE__ */ new Map();
|
|
1891
|
+
for (const p of source.list()) {
|
|
1892
|
+
const text = source.read(p);
|
|
1893
|
+
if (text === void 0)
|
|
1894
|
+
continue;
|
|
1895
|
+
const ls = linesOf(text);
|
|
1896
|
+
if (!isSourceFile(p, ls[0] ?? "", ls))
|
|
1897
|
+
continue;
|
|
1898
|
+
if (excludeRes.some((re) => re.test(p))) {
|
|
1899
|
+
excluded.push(p);
|
|
1900
|
+
continue;
|
|
1901
|
+
}
|
|
1902
|
+
if (!text.trim())
|
|
1903
|
+
continue;
|
|
1904
|
+
paths.push(p);
|
|
1905
|
+
texts.set(p, text);
|
|
1906
|
+
}
|
|
1907
|
+
const costs = /* @__PURE__ */ new Map();
|
|
1908
|
+
const overhead = (kinds2) => {
|
|
1909
|
+
if (!o.overhead)
|
|
1910
|
+
return 0;
|
|
1911
|
+
const key = kinds2.join(",");
|
|
1912
|
+
if (!costs.has(key))
|
|
1913
|
+
costs.set(key, o.overhead(kinds2));
|
|
1914
|
+
return costs.get(key);
|
|
1915
|
+
};
|
|
1916
|
+
const slices = [];
|
|
1917
|
+
let lines = 0;
|
|
1918
|
+
let leastRoom = room;
|
|
1919
|
+
for (const path of readingOrder(paths)) {
|
|
1920
|
+
const kinds2 = fileKinds(path);
|
|
1921
|
+
const left = room - overhead(kinds2);
|
|
1922
|
+
leastRoom = Math.min(leastRoom, left);
|
|
1923
|
+
const fileRoom = Math.max(1, left);
|
|
1924
|
+
const all2 = linesOf(texts.get(path));
|
|
1925
|
+
const of = all2.length;
|
|
1926
|
+
lines += of;
|
|
1927
|
+
const sizes = all2.map((l, i) => `${i + 1}| ${l}`.length + 1);
|
|
1928
|
+
const cost = (from2, chars) => tokensOf(header({ path, from: from2, to: of, of }).length + chars);
|
|
1929
|
+
const total = sizes.reduce((n, c) => n + c, 0);
|
|
1930
|
+
if (cost(1, total) <= fileRoom) {
|
|
1931
|
+
slices.push({ path, from: 1, to: of, of, tokens: cost(1, total), kinds: kinds2 });
|
|
1932
|
+
continue;
|
|
1933
|
+
}
|
|
1934
|
+
let from = 1;
|
|
1935
|
+
while (from <= of) {
|
|
1936
|
+
let to = from;
|
|
1937
|
+
let chars = sizes[from - 1];
|
|
1938
|
+
while (to < of && cost(from, chars + sizes[to]) <= fileRoom)
|
|
1939
|
+
chars += sizes[to++];
|
|
1940
|
+
slices.push({ path, from, to, of, tokens: cost(from, chars), kinds: kinds2 });
|
|
1941
|
+
from = to + 1;
|
|
1942
|
+
}
|
|
1943
|
+
}
|
|
1944
|
+
const batches = [];
|
|
1945
|
+
let current = [];
|
|
1946
|
+
let kinds = /* @__PURE__ */ new Set();
|
|
1947
|
+
let used = 0;
|
|
1948
|
+
for (const { kinds: own, ...s } of slices) {
|
|
1949
|
+
const joined = FILE_KINDS.filter((x) => kinds.has(x) || own.includes(x));
|
|
1950
|
+
if (current.length && used + s.tokens + overhead(joined) > room) {
|
|
1951
|
+
batches.push(current);
|
|
1952
|
+
current = [];
|
|
1953
|
+
kinds = /* @__PURE__ */ new Set();
|
|
1954
|
+
used = 0;
|
|
1955
|
+
}
|
|
1956
|
+
current.push(s);
|
|
1957
|
+
own.forEach((x) => kinds.add(x));
|
|
1958
|
+
used += s.tokens;
|
|
1959
|
+
}
|
|
1960
|
+
if (current.length)
|
|
1961
|
+
batches.push(current);
|
|
1962
|
+
return {
|
|
1963
|
+
files: paths.length,
|
|
1964
|
+
lines,
|
|
1965
|
+
tokens: slices.reduce((n, s) => n + s.tokens, 0),
|
|
1966
|
+
batches,
|
|
1967
|
+
excluded: excluded.sort(),
|
|
1968
|
+
leastRoom
|
|
1969
|
+
};
|
|
1970
|
+
}
|
|
1971
|
+
|
|
1972
|
+
// packages/verify/dist/vendored.js
|
|
1973
|
+
var LICENSE_FILE = /^(LICEN[CS]E|COPYING)([._-][^/]*)?$/i;
|
|
1974
|
+
var BUILD_FILE = /^(pom\.xml|build\.gradle(\.kts)?|settings\.gradle(\.kts)?|package\.json|go\.mod|Cargo\.toml|pyproject\.toml|setup\.py|setup\.cfg|composer\.json|Gemfile|[^/]+\.(csproj|vbproj|fsproj|sln))$/i;
|
|
1975
|
+
var AGGREGATOR = /^(pom\.xml|settings\.gradle(\.kts)?|package\.json|pnpm-workspace\.yaml|lerna\.json|Cargo\.toml|go\.work)$/;
|
|
1976
|
+
var byPath = (a, b) => a < b ? -1 : a > b ? 1 : 0;
|
|
1977
|
+
var nameOf = (path) => path.slice(path.lastIndexOf("/") + 1);
|
|
1978
|
+
var dirOf = (path) => path.includes("/") ? path.slice(0, path.lastIndexOf("/")) : "";
|
|
1979
|
+
var under = (path, dir) => !dir || path.startsWith(`${dir}/`);
|
|
1980
|
+
var quoted = (s) => [...s.matchAll(/["']([^"'\n]+)["']/g)].map((m) => m[1]);
|
|
1981
|
+
var clean = (m) => m.trim().replace(/^\.\//, "").replace(/\/pom\.xml$/, "").replace(/\/+$/, "");
|
|
1982
|
+
function json(text) {
|
|
1983
|
+
try {
|
|
1984
|
+
return JSON.parse(text);
|
|
1985
|
+
} catch {
|
|
1986
|
+
return void 0;
|
|
1987
|
+
}
|
|
1988
|
+
}
|
|
1989
|
+
var strings = (v) => Array.isArray(v) ? v.filter((x) => typeof x === "string") : [];
|
|
1990
|
+
function buildModules(path, text) {
|
|
1991
|
+
const name = nameOf(path);
|
|
1992
|
+
const dir = dirOf(path);
|
|
1993
|
+
let found = [];
|
|
1994
|
+
if (name === "pom.xml")
|
|
1995
|
+
found = [...text.matchAll(/<module>\s*([^<]+?)\s*<\/module>/g)].map((m) => m[1]);
|
|
1996
|
+
else if (/^settings\.gradle(\.kts)?$/.test(name)) {
|
|
1997
|
+
const calls2 = /\binclude(Build|Flat)?\b\s*(\(([^)]*)\)|((?:\s*["'][^"'\n]+["']\s*,?)+))/g;
|
|
1998
|
+
for (const m of text.matchAll(calls2))
|
|
1999
|
+
for (const q of quoted(m[3] ?? m[4] ?? ""))
|
|
2000
|
+
found.push(m[1] === "Build" ? q : q.replace(/^:/, "").replace(/:/g, "/"));
|
|
2001
|
+
for (const m of text.matchAll(/projectDir\s*=\s*(?:file|new\s+File)\s*\(([^)]*)\)/g)) {
|
|
2002
|
+
const q = quoted(m[1]);
|
|
2003
|
+
if (q.length)
|
|
2004
|
+
found.push(q[q.length - 1]);
|
|
2005
|
+
}
|
|
2006
|
+
} else if (name === "package.json") {
|
|
2007
|
+
const w = json(text)?.workspaces;
|
|
2008
|
+
found = Array.isArray(w) ? strings(w) : strings(w?.packages);
|
|
2009
|
+
} else if (name === "lerna.json")
|
|
2010
|
+
found = strings(json(text)?.packages);
|
|
2011
|
+
else if (name === "pnpm-workspace.yaml") {
|
|
2012
|
+
let inPackages = false;
|
|
2013
|
+
for (const line of text.split(/\r?\n/)) {
|
|
2014
|
+
if (/^packages:\s*$/.test(line))
|
|
2015
|
+
inPackages = true;
|
|
2016
|
+
else if (/^\S/.test(line))
|
|
2017
|
+
inPackages = false;
|
|
2018
|
+
else if (inPackages) {
|
|
2019
|
+
const m = /^\s*-\s*["']?([^"'#]+?)["']?\s*(#.*)?$/.exec(line);
|
|
2020
|
+
if (m)
|
|
2021
|
+
found.push(m[1]);
|
|
2022
|
+
}
|
|
2023
|
+
}
|
|
2024
|
+
} else if (name === "Cargo.toml") {
|
|
2025
|
+
const m = /\[workspace\][^[]*?members\s*=\s*\[([^\]]*)\]/s.exec(text);
|
|
2026
|
+
if (m)
|
|
2027
|
+
found = quoted(m[1]);
|
|
2028
|
+
} else if (name === "go.work") {
|
|
2029
|
+
for (const m of text.matchAll(/^\s*use\s*(\(([^)]*)\)|(\S+))/gm))
|
|
2030
|
+
found.push(...m[2] ? m[2].split(/\s+/).filter(Boolean) : [m[3]]);
|
|
2031
|
+
}
|
|
2032
|
+
return found.filter((m) => !m.startsWith("!")).map(clean).filter(Boolean).map((m) => dir ? `${dir}/${m}` : m);
|
|
2033
|
+
}
|
|
2034
|
+
var JAVA_LIKE = /\.(java|kt|kts|scala|groovy)$/i;
|
|
2035
|
+
var DOTNET = /\.(cs|vb|fs)$/i;
|
|
2036
|
+
var rootOf = (name) => name.split(".").slice(0, 2).join(".").toLowerCase();
|
|
2037
|
+
function packageRoot(path, text) {
|
|
2038
|
+
const re = JAVA_LIKE.test(path) ? /^\s*package\s+([A-Za-z_][\w.]*)/m : DOTNET.test(path) ? /^\s*namespace\s+([A-Za-z_][\w.]*)/m : void 0;
|
|
2039
|
+
const m = re?.exec(text);
|
|
2040
|
+
return m ? rootOf(m[1]) : void 0;
|
|
2041
|
+
}
|
|
2042
|
+
var PACKAGE_SAMPLE = 2e3;
|
|
2043
|
+
function packageRoots(paths, inits, read) {
|
|
2044
|
+
const roots = /* @__PURE__ */ new Set();
|
|
2045
|
+
let readCount = 0;
|
|
2046
|
+
for (const p of paths) {
|
|
2047
|
+
if (/\.py$/i.test(p)) {
|
|
2048
|
+
let d = dirOf(p);
|
|
2049
|
+
let top;
|
|
2050
|
+
while (d && inits.has(d)) {
|
|
2051
|
+
top = d;
|
|
2052
|
+
d = dirOf(d);
|
|
2053
|
+
}
|
|
2054
|
+
if (top)
|
|
2055
|
+
roots.add(nameOf(top).toLowerCase());
|
|
2056
|
+
} else if ((JAVA_LIKE.test(p) || DOTNET.test(p)) && readCount < PACKAGE_SAMPLE) {
|
|
2057
|
+
readCount++;
|
|
2058
|
+
const r = packageRoot(p, read(p) ?? "");
|
|
2059
|
+
if (r)
|
|
2060
|
+
roots.add(r);
|
|
2061
|
+
}
|
|
2062
|
+
}
|
|
2063
|
+
return roots;
|
|
2064
|
+
}
|
|
2065
|
+
var listed = (names, max = 3) => names.length > max ? `${names.slice(0, max).join(", ")} and ${names.length - max} more` : names.join(", ");
|
|
2066
|
+
function vendoredProjects(all2, read) {
|
|
2067
|
+
const files = [...all2].sort(byPath);
|
|
2068
|
+
const licenses = /* @__PURE__ */ new Map();
|
|
2069
|
+
const builds = /* @__PURE__ */ new Map();
|
|
2070
|
+
for (const p of files) {
|
|
2071
|
+
const d = dirOf(p);
|
|
2072
|
+
if (LICENSE_FILE.test(nameOf(p)) && !licenses.has(d))
|
|
2073
|
+
licenses.set(d, p);
|
|
2074
|
+
if (BUILD_FILE.test(nameOf(p)))
|
|
2075
|
+
builds.set(d, [...builds.get(d) ?? [], nameOf(p)]);
|
|
2076
|
+
}
|
|
2077
|
+
const modules = [];
|
|
2078
|
+
if (read) {
|
|
2079
|
+
for (const p of files)
|
|
2080
|
+
if (AGGREGATOR.test(nameOf(p)))
|
|
2081
|
+
for (const m of buildModules(p, read(p) ?? ""))
|
|
2082
|
+
modules.push({ from: dirOf(p), re: globToRegExp(m) });
|
|
2083
|
+
}
|
|
2084
|
+
const included = (dir) => modules.some((m) => m.from !== dir && under(dir, m.from) && m.re.test(dir));
|
|
2085
|
+
const nested = [];
|
|
2086
|
+
const copied = [];
|
|
2087
|
+
for (const [dir, license] of licenses) {
|
|
2088
|
+
if (!dir)
|
|
2089
|
+
continue;
|
|
2090
|
+
const own = builds.get(dir);
|
|
2091
|
+
if (!own)
|
|
2092
|
+
copied.push(dir);
|
|
2093
|
+
else if (read && !included(dir))
|
|
2094
|
+
nested.push({
|
|
2095
|
+
dir,
|
|
2096
|
+
at: `${license}:1`,
|
|
2097
|
+
why: `a project of its own (${[nameOf(license), ...own].sort(byPath).join(", ")}) that the repo's build doesn't include as a module: vendored third-party code? Check before accepting`
|
|
2098
|
+
});
|
|
2099
|
+
}
|
|
2100
|
+
const out = [...nested];
|
|
2101
|
+
if (copied.length) {
|
|
2102
|
+
const inits = new Set(files.filter((p) => nameOf(p) === "__init__.py").map(dirOf));
|
|
2103
|
+
const apart = [...copied, ...nested.map((n) => n.dir)];
|
|
2104
|
+
const text = read ?? (() => void 0);
|
|
2105
|
+
const mine = packageRoots(files.filter((p) => !apart.some((d) => under(p, d))), inits, text);
|
|
2106
|
+
for (const dir of copied) {
|
|
2107
|
+
const theirs = packageRoots(files.filter((p) => under(p, dir)), inits, text);
|
|
2108
|
+
const shared = [...theirs].some((r) => mine.has(r));
|
|
2109
|
+
if (shared)
|
|
2110
|
+
continue;
|
|
2111
|
+
const known = theirs.size > 0 && mine.size > 0;
|
|
2112
|
+
out.push({
|
|
2113
|
+
dir,
|
|
2114
|
+
at: `${licenses.get(dir)}:1`,
|
|
2115
|
+
why: known ? `its own LICENSE and no build file, and its packages (${listed([...theirs].sort(byPath))}) aren't the repo's own (${listed([...mine].sort(byPath))}): a vendored copy of third-party code? Check before accepting` : "its own LICENSE and no build file: a vendored copy of third-party code? Check before accepting"
|
|
2116
|
+
});
|
|
2117
|
+
}
|
|
2118
|
+
}
|
|
2119
|
+
return out.sort((a, b) => byPath(a.dir, b.dir));
|
|
2120
|
+
}
|
|
2121
|
+
|
|
2122
|
+
// packages/verify/dist/read-plan.js
|
|
2123
|
+
var PROPOSE_MIN_TOKENS = 1e4;
|
|
2124
|
+
var PROPOSE_MIN_SHARE = 0.02;
|
|
2125
|
+
var PROPOSE_DATA_SHARE = 0.9;
|
|
2126
|
+
var SHOWN_PROPOSALS = 10;
|
|
2127
|
+
var DOCS_TOP = /^(docs?|documentation|site|website|javadoc|apidocs)\//i;
|
|
2128
|
+
var DATA_EXT2 = /\.(json|xml|ya?ml)$/i;
|
|
2129
|
+
var TEXT_PAGE = /\.(html?|xhtml)$/i;
|
|
2130
|
+
var PRODUCT_PAGES = /(^|\/)(src|app|apps|web|www|wwwroot|webapp|public|static|assets|resources|templates?|views?|pages|layouts?|partials|components|emails?|mail|ui|client|frontend|site)\//i;
|
|
2131
|
+
var SPA_PAGE = /\.(component|directive|pipe)\.html$/i;
|
|
2132
|
+
var sum = (fs) => fs.reduce((n, f) => n + f.tokens, 0);
|
|
2133
|
+
var plural = (n, w) => `${n} ${w}${n === 1 ? "" : "s"}`;
|
|
2134
|
+
var extOf = (path) => /\.[^./]+$/.exec(path)?.[0].toLowerCase() ?? path.split("/").pop();
|
|
2135
|
+
var byPath2 = (a, b) => a < b ? -1 : a > b ? 1 : 0;
|
|
2136
|
+
var under2 = (path, dir) => path.startsWith(`${dir}/`);
|
|
2137
|
+
var whatOf = (v) => v.why.replace(/: (a )?vendored [^:]*\? Check before accepting$/, "");
|
|
2138
|
+
function natureOf(path) {
|
|
2139
|
+
if (DOCS_TOP.test(path))
|
|
2140
|
+
return "docs";
|
|
2141
|
+
if (TEXT_PAGE.test(path))
|
|
2142
|
+
return PRODUCT_PAGES.test(path) || SPA_PAGE.test(path) ? void 0 : "page";
|
|
2143
|
+
if (!DATA_EXT2.test(path))
|
|
2144
|
+
return void 0;
|
|
2145
|
+
const kinds = fileKinds(path);
|
|
2146
|
+
return kinds.length === DATAFILE_KINDS.length && kinds.every((k) => DATAFILE_KINDS.includes(k)) ? "data" : void 0;
|
|
2147
|
+
}
|
|
2148
|
+
function proposeExclusions(files, all2, read, o = {}) {
|
|
2149
|
+
const before = sum(files);
|
|
2150
|
+
const floor = o.saysNotProduct ? PROPOSE_MIN_TOKENS / 4 : PROPOSE_MIN_TOKENS;
|
|
2151
|
+
const share = o.saysNotProduct ? PROPOSE_MIN_SHARE / 4 : PROPOSE_MIN_SHARE;
|
|
2152
|
+
const min = Math.max(floor, share * before);
|
|
2153
|
+
const vendored = vendoredProjects(all2, read);
|
|
2154
|
+
const vendoredOf = (path) => vendored.find((v) => under2(path, v.dir));
|
|
2155
|
+
const folders = /* @__PURE__ */ new Map();
|
|
2156
|
+
for (const f of files) {
|
|
2157
|
+
const nature = vendoredOf(f.path) ? "vendored" : natureOf(f.path);
|
|
2158
|
+
const keeps = !nature && readingRank(f.path) <= 1;
|
|
2159
|
+
const parts = f.path.split("/");
|
|
2160
|
+
for (let d = 1; d < parts.length; d++) {
|
|
2161
|
+
const key = parts.slice(0, d).join("/");
|
|
2162
|
+
const x = folders.get(key) ?? { files: [], odd: [], keeps: false };
|
|
2163
|
+
x.files.push(f);
|
|
2164
|
+
if (nature)
|
|
2165
|
+
x.odd.push({ f, nature });
|
|
2166
|
+
x.keeps ||= keeps;
|
|
2167
|
+
folders.set(key, x);
|
|
2168
|
+
}
|
|
2169
|
+
}
|
|
2170
|
+
const biggest = (fs) => [...fs].sort((a, b) => b.tokens - a.tokens || byPath2(a.path, b.path))[0];
|
|
2171
|
+
const counts = (fs) => {
|
|
2172
|
+
const n = /* @__PURE__ */ new Map();
|
|
2173
|
+
for (const f of fs)
|
|
2174
|
+
n.set(extOf(f.path), (n.get(extOf(f.path)) ?? 0) + 1);
|
|
2175
|
+
return [...n.entries()].sort((a, b) => b[1] - a[1] || byPath2(a[0], b[0])).map(([ext, c]) => plural(c, `${ext} file`)).join(", ");
|
|
2176
|
+
};
|
|
2177
|
+
const candidates = /* @__PURE__ */ new Map();
|
|
2178
|
+
for (const [dir, x] of folders) {
|
|
2179
|
+
const tokens = sum(x.files);
|
|
2180
|
+
if (tokens < min || x.keeps)
|
|
2181
|
+
continue;
|
|
2182
|
+
const self = vendored.find((v) => v.dir === dir);
|
|
2183
|
+
if (self) {
|
|
2184
|
+
candidates.set(dir, {
|
|
2185
|
+
path: `${dir}/**`,
|
|
2186
|
+
why: self.why,
|
|
2187
|
+
at: self.at,
|
|
2188
|
+
files: x.files.length,
|
|
2189
|
+
tokens
|
|
2190
|
+
});
|
|
2191
|
+
continue;
|
|
2192
|
+
}
|
|
2193
|
+
if (sum(x.odd.map((o2) => o2.f)) < PROPOSE_DATA_SHARE * tokens)
|
|
2194
|
+
continue;
|
|
2195
|
+
const docsTop = DOCS_TOP.test(`${dir}/`);
|
|
2196
|
+
const pages = x.odd.some((o2) => o2.nature === "page");
|
|
2197
|
+
const others = x.files.length - x.odd.length;
|
|
2198
|
+
if (!docsTop && pages && others)
|
|
2199
|
+
continue;
|
|
2200
|
+
const odd = x.odd.filter((o2) => o2.nature !== "vendored").map((o2) => o2.f);
|
|
2201
|
+
const inside = vendored.filter((v) => under2(v.dir, dir) && !vendored.some((w) => w !== v && under2(v.dir, w.dir)));
|
|
2202
|
+
if (!odd.length && inside.length < 2)
|
|
2203
|
+
continue;
|
|
2204
|
+
const tokensOf2 = (v) => sum(x.files.filter((f) => under2(f.path, v.dir)));
|
|
2205
|
+
const [main] = [...inside].sort((a, b) => tokensOf2(b) - tokensOf2(a) || byPath2(a.dir, b.dir));
|
|
2206
|
+
const kind = docsTop ? "documentation, not the product" : pages ? "documents, not code" : "data, not code";
|
|
2207
|
+
const projects = `${plural(inside.length, "vendored project")}: ${inside.length === 1 ? `${inside[0].dir}, ${whatOf(inside[0])}` : inside.map((v) => v.dir).join(", ")}`;
|
|
2208
|
+
candidates.set(dir, {
|
|
2209
|
+
path: `${dir}/**`,
|
|
2210
|
+
why: odd.length ? `${kind}: ${counts(odd)}${others ? `; also ${plural(others, "other file")}` : ""}${inside.length ? `; with ${projects}` : ""}` : `vendored code, not the product: ${projects}`,
|
|
2211
|
+
// Its biggest vendored project's LICENSE when vendored code is most of it.
|
|
2212
|
+
at: main && 2 * sum(inside.flatMap((v) => x.files.filter((f) => under2(f.path, v.dir)))) >= tokens ? main.at : `${biggest(odd).path}:1`,
|
|
2213
|
+
files: x.files.length,
|
|
2214
|
+
tokens
|
|
2215
|
+
});
|
|
2216
|
+
}
|
|
2217
|
+
const depth = (d) => d.split("/").length;
|
|
2218
|
+
const picked = [];
|
|
2219
|
+
for (const dir of [...candidates.keys()].sort((a, b) => depth(a) - depth(b) || byPath2(a, b)))
|
|
2220
|
+
if (!picked.some((p) => dir.startsWith(`${p}/`)))
|
|
2221
|
+
picked.push(dir);
|
|
2222
|
+
const proposals = picked.map((d) => candidates.get(d)).sort((a, b) => b.tokens - a.tokens || byPath2(a.path, b.path));
|
|
2223
|
+
return { proposals, before, after: before - proposals.reduce((n, p) => n + p.tokens, 0) };
|
|
2224
|
+
}
|
|
2225
|
+
function exclusionYaml(proposals) {
|
|
2226
|
+
if (!proposals.length)
|
|
2227
|
+
return "";
|
|
2228
|
+
const q = (s) => JSON.stringify(s);
|
|
2229
|
+
return [
|
|
2230
|
+
"exclude:",
|
|
2231
|
+
...proposals.map((p) => ` - { path: ${q(p.path)}, why: ${q(p.why)}, at: ${q(p.at)} }`),
|
|
2232
|
+
""
|
|
2233
|
+
].join("\n");
|
|
2234
|
+
}
|
|
2235
|
+
var SESSION_TOKENS = 15e4;
|
|
2236
|
+
function unitsOf(dir, fs, room) {
|
|
2237
|
+
const prefix = dir ? `${dir}/` : "";
|
|
2238
|
+
const direct = [];
|
|
2239
|
+
const children = /* @__PURE__ */ new Map();
|
|
2240
|
+
for (const f of fs) {
|
|
2241
|
+
const rest = f.path.slice(prefix.length);
|
|
2242
|
+
const slash = rest.indexOf("/");
|
|
2243
|
+
if (slash < 0)
|
|
2244
|
+
direct.push(f);
|
|
2245
|
+
else {
|
|
2246
|
+
const child = prefix + rest.slice(0, slash);
|
|
2247
|
+
children.set(child, [...children.get(child) ?? [], f]);
|
|
2248
|
+
}
|
|
2249
|
+
}
|
|
2250
|
+
if (dir && (sum(fs) <= room || !children.size))
|
|
2251
|
+
return [{ glob: `${dir}/**`, files: fs.length, tokens: sum(fs) }];
|
|
2252
|
+
const out = direct.length ? [{ glob: `${prefix}*`, files: direct.length, tokens: sum(direct) }] : [];
|
|
2253
|
+
for (const child of [...children.keys()].sort(byPath2))
|
|
2254
|
+
out.push(...unitsOf(child, children.get(child), room));
|
|
2255
|
+
return out;
|
|
2256
|
+
}
|
|
2257
|
+
function planParts(files, room = SESSION_TOKENS) {
|
|
2258
|
+
const total = sum(files);
|
|
2259
|
+
if (total <= room)
|
|
2260
|
+
return [];
|
|
2261
|
+
const sorted = [...files].sort((a, b) => byPath2(a.path, b.path));
|
|
2262
|
+
const target = total / Math.ceil(total / room);
|
|
2263
|
+
const parts = [];
|
|
2264
|
+
let current;
|
|
2265
|
+
for (const u of unitsOf("", sorted, room)) {
|
|
2266
|
+
if (current && (current.tokens + u.tokens > room || current.tokens >= target)) {
|
|
2267
|
+
parts.push(current);
|
|
2268
|
+
current = void 0;
|
|
2269
|
+
}
|
|
2270
|
+
current ??= { paths: [], files: 0, tokens: 0 };
|
|
2271
|
+
current.paths.push(u.glob);
|
|
2272
|
+
current.files += u.files;
|
|
2273
|
+
current.tokens += u.tokens;
|
|
2274
|
+
}
|
|
2275
|
+
if (current)
|
|
2276
|
+
parts.push(current);
|
|
2277
|
+
return parts;
|
|
2278
|
+
}
|
|
2279
|
+
|
|
2280
|
+
// packages/diagrams/dist/views.js
|
|
2281
|
+
var VIEW_NAMES = ["context", "container", "data", "event", "sequence"];
|
|
2282
|
+
var ViewError = class extends Error {
|
|
2283
|
+
};
|
|
2284
|
+
var cmp = (a, b) => a < b ? -1 : a > b ? 1 : 0;
|
|
2285
|
+
var slug = (s) => s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
2286
|
+
var normPath = (p) => p.replace(/\{[^}]*\}/g, "{}").replace(/\/+$/, "") || "/";
|
|
2287
|
+
function legendOf(status) {
|
|
2288
|
+
if (status === "planned-new")
|
|
2289
|
+
return "new";
|
|
2290
|
+
if (status === "planned-modify")
|
|
2291
|
+
return "modify";
|
|
2292
|
+
if (status === "planned-retire")
|
|
2293
|
+
return "retire";
|
|
2294
|
+
return void 0;
|
|
2295
|
+
}
|
|
2296
|
+
var withLegend = (status) => {
|
|
2297
|
+
const l = legendOf(status);
|
|
2298
|
+
return l ? { legend: l } : {};
|
|
2299
|
+
};
|
|
2300
|
+
var Catalog = class {
|
|
2301
|
+
byId = /* @__PURE__ */ new Map();
|
|
2302
|
+
systems = /* @__PURE__ */ new Map();
|
|
2303
|
+
components = /* @__PURE__ */ new Map();
|
|
2304
|
+
endpoints = /* @__PURE__ */ new Map();
|
|
2305
|
+
stores = /* @__PURE__ */ new Map();
|
|
2306
|
+
interfaces;
|
|
2307
|
+
tables;
|
|
2308
|
+
constructor(records2) {
|
|
2309
|
+
const live = records2.filter((r) => r.status !== "retired").sort((a, b) => cmp(a.id, b.id));
|
|
2310
|
+
for (const r of live) {
|
|
2311
|
+
if (r.kind !== "CuratedDecision")
|
|
2312
|
+
this.byId.set(r.id, r);
|
|
2313
|
+
if (r.kind === "System")
|
|
2314
|
+
this.systems.set(r.id, r);
|
|
2315
|
+
else if (r.kind === "Component")
|
|
2316
|
+
this.components.set(r.id, r);
|
|
2317
|
+
else if (r.kind === "Endpoint")
|
|
2318
|
+
this.endpoints.set(r.id, r);
|
|
2319
|
+
else if (r.kind === "DataStore")
|
|
2320
|
+
this.stores.set(r.id, r);
|
|
2321
|
+
}
|
|
2322
|
+
this.interfaces = live.filter((r) => r.kind === "Interface");
|
|
2323
|
+
this.tables = live.filter((r) => r.kind === "Table");
|
|
2324
|
+
}
|
|
2325
|
+
isSystem(id) {
|
|
2326
|
+
return this.systems.has(id) || [...this.components.values()].some((c) => c.system === id);
|
|
2327
|
+
}
|
|
2328
|
+
/** A component's system; a system is its own. */
|
|
2329
|
+
systemOf(id) {
|
|
2330
|
+
return this.components.get(id)?.system ?? (this.isSystem(id) ? id : void 0);
|
|
2331
|
+
}
|
|
2332
|
+
boundaryOf(id) {
|
|
2333
|
+
const s = this.systemOf(id);
|
|
2334
|
+
return s ? this.systems.get(s)?.boundary : void 0;
|
|
2335
|
+
}
|
|
2336
|
+
systemName(id) {
|
|
2337
|
+
return this.systems.get(id)?.name ?? id;
|
|
2338
|
+
}
|
|
2339
|
+
/** Does `drawn` (a component or system) stand for this interface end? */
|
|
2340
|
+
same(drawn, end) {
|
|
2341
|
+
return drawn === end || !this.components.has(drawn) && this.systemOf(end) === drawn;
|
|
2342
|
+
}
|
|
2343
|
+
componentsOf(system) {
|
|
2344
|
+
return [...this.components.values()].filter((c) => c.system === system).map((c) => c.id);
|
|
2345
|
+
}
|
|
2346
|
+
/** A node for a component or system end of an interface. */
|
|
2347
|
+
endNode(id, group) {
|
|
2348
|
+
const sys = this.components.has(id) ? void 0 : this.systems.get(id);
|
|
2349
|
+
const boundary = this.boundaryOf(id);
|
|
2350
|
+
return {
|
|
2351
|
+
id,
|
|
2352
|
+
label: sys ? sys.name : id,
|
|
2353
|
+
kind: this.components.has(id) ? "component" : this.isSystem(id) ? "system" : "box",
|
|
2354
|
+
...boundary ? { boundary } : {},
|
|
2355
|
+
...withLegend((sys ?? this.components.get(id))?.status),
|
|
2356
|
+
...group ? { group } : {}
|
|
2357
|
+
};
|
|
2358
|
+
}
|
|
2359
|
+
/** The topic an async interface carries. */
|
|
2360
|
+
topicOf(i) {
|
|
2361
|
+
const ep = i.endpoint ? this.endpoints.get(i.endpoint) : void 0;
|
|
2362
|
+
return ep?.endpointType === "event-topic" ? ep.pathOrTopic : i.normalizedPath;
|
|
2363
|
+
}
|
|
2364
|
+
/**
|
|
2365
|
+
* An interface's label in the grammar reconcile reads back: pattern code, then METHOD and
|
|
2366
|
+
* path, or the topic. The path is the endpoint's own template when it normalizes to the
|
|
2367
|
+
* interface's path, because that is what people recognise.
|
|
2368
|
+
*/
|
|
2369
|
+
interfaceLabel(i) {
|
|
2370
|
+
let what;
|
|
2371
|
+
if (i.sync === "async")
|
|
2372
|
+
what = this.topicOf(i);
|
|
2373
|
+
else {
|
|
2374
|
+
const ep = i.endpoint ? this.endpoints.get(i.endpoint) : void 0;
|
|
2375
|
+
const path = ep?.endpointType === "rest" && normPath(ep.pathOrTopic) === normPath(i.normalizedPath) ? ep.pathOrTopic : i.normalizedPath;
|
|
2376
|
+
what = [i.method?.toUpperCase(), path].filter(Boolean).join(" ");
|
|
2377
|
+
}
|
|
2378
|
+
return [i.pattern?.code, what].filter(Boolean).join(" ");
|
|
2379
|
+
}
|
|
2380
|
+
interfaceEdge(i, from, to) {
|
|
2381
|
+
return {
|
|
2382
|
+
id: i.id,
|
|
2383
|
+
from,
|
|
2384
|
+
to,
|
|
2385
|
+
label: this.interfaceLabel(i),
|
|
2386
|
+
async: i.sync === "async",
|
|
2387
|
+
...withLegend(i.status)
|
|
2388
|
+
};
|
|
2389
|
+
}
|
|
2390
|
+
};
|
|
2391
|
+
var byEdge = (a, b) => cmp(a.from, b.from) || cmp(a.to, b.to) || cmp(a.label, b.label) || cmp(a.id, b.id);
|
|
2392
|
+
function contextView(records2) {
|
|
2393
|
+
const c = new Catalog(records2);
|
|
2394
|
+
const nodes = /* @__PURE__ */ new Map();
|
|
2395
|
+
for (const id of c.systems.keys())
|
|
2396
|
+
nodes.set(id, c.endNode(id));
|
|
2397
|
+
for (const comp of c.components.values())
|
|
2398
|
+
if (!nodes.has(comp.system))
|
|
2399
|
+
nodes.set(comp.system, c.endNode(comp.system));
|
|
2400
|
+
const edges = [];
|
|
2401
|
+
for (const i of c.interfaces) {
|
|
2402
|
+
const from = c.systemOf(i.from) ?? i.from;
|
|
2403
|
+
const to = c.systemOf(i.to) ?? i.to;
|
|
2404
|
+
for (const end of [from, to])
|
|
2405
|
+
if (!nodes.has(end))
|
|
2406
|
+
nodes.set(end, c.endNode(end));
|
|
2407
|
+
edges.push(c.interfaceEdge(i, from, to));
|
|
2408
|
+
}
|
|
2409
|
+
return {
|
|
2410
|
+
type: "context",
|
|
2411
|
+
title: "Context view",
|
|
2412
|
+
groups: [],
|
|
2413
|
+
nodes: [...nodes.values()].sort((a, b) => cmp(a.id, b.id)),
|
|
2414
|
+
edges: edges.sort(byEdge)
|
|
2415
|
+
};
|
|
2416
|
+
}
|
|
2417
|
+
function containerView(records2, system) {
|
|
2418
|
+
const c = new Catalog(records2);
|
|
2419
|
+
if (!c.isSystem(system))
|
|
2420
|
+
throw new ViewError(`No system "${system}" in the catalog.`);
|
|
2421
|
+
const own = c.componentsOf(system).sort(cmp);
|
|
2422
|
+
const inside = (id) => id === system || own.includes(id);
|
|
2423
|
+
const sys = c.systems.get(system);
|
|
2424
|
+
const groups = [];
|
|
2425
|
+
const nodes = /* @__PURE__ */ new Map();
|
|
2426
|
+
const groupOf = (s) => {
|
|
2427
|
+
const rec = c.systems.get(s);
|
|
2428
|
+
return {
|
|
2429
|
+
id: s,
|
|
2430
|
+
label: c.systemName(s),
|
|
2431
|
+
kind: "system",
|
|
2432
|
+
...rec?.boundary ? { boundary: rec.boundary } : {},
|
|
2433
|
+
...withLegend(rec?.status)
|
|
2434
|
+
};
|
|
2435
|
+
};
|
|
2436
|
+
if (own.length > 0) {
|
|
2437
|
+
groups.push(groupOf(system));
|
|
2438
|
+
for (const id of own)
|
|
2439
|
+
nodes.set(id, c.endNode(id, system));
|
|
2440
|
+
} else
|
|
2441
|
+
nodes.set(system, c.endNode(system));
|
|
2442
|
+
const edges = [];
|
|
2443
|
+
const neighbourGroups = /* @__PURE__ */ new Map();
|
|
2444
|
+
for (const i of c.interfaces) {
|
|
2445
|
+
if (!inside(i.from) && !inside(i.to))
|
|
2446
|
+
continue;
|
|
2447
|
+
for (const end of [i.from, i.to]) {
|
|
2448
|
+
if (nodes.has(end))
|
|
2449
|
+
continue;
|
|
2450
|
+
const comp = c.components.get(end);
|
|
2451
|
+
if (comp) {
|
|
2452
|
+
if (!neighbourGroups.has(comp.system))
|
|
2453
|
+
neighbourGroups.set(comp.system, groupOf(comp.system));
|
|
2454
|
+
nodes.set(end, c.endNode(end, comp.system));
|
|
2455
|
+
} else
|
|
2456
|
+
nodes.set(end, c.endNode(end));
|
|
2457
|
+
}
|
|
2458
|
+
edges.push(c.interfaceEdge(i, i.from, i.to));
|
|
2459
|
+
}
|
|
2460
|
+
for (const id of own)
|
|
2461
|
+
for (const to of c.components.get(id)?.uses ?? [])
|
|
2462
|
+
if (nodes.has(to))
|
|
2463
|
+
edges.push({ id: `uses.${id}.${to}`, from: id, to, label: "uses", async: false });
|
|
2464
|
+
groups.push(...[...neighbourGroups.values()].sort((a, b) => cmp(a.id, b.id)));
|
|
2465
|
+
const order = new Map(groups.map((g, n) => [g.id, n]));
|
|
2466
|
+
const sorted = [...nodes.values()].sort((a, b) => (a.group !== void 0 ? order.get(a.group) : groups.length) - (b.group !== void 0 ? order.get(b.group) : groups.length) || cmp(a.id, b.id));
|
|
2467
|
+
return {
|
|
2468
|
+
type: "container",
|
|
2469
|
+
title: `Container view of ${sys?.name ?? system}`,
|
|
2470
|
+
groups,
|
|
2471
|
+
nodes: sorted,
|
|
2472
|
+
edges: edges.sort(byEdge)
|
|
2473
|
+
};
|
|
2474
|
+
}
|
|
2475
|
+
function scopeOf(c, focus) {
|
|
2476
|
+
if (c.components.has(focus))
|
|
2477
|
+
return /* @__PURE__ */ new Set([focus]);
|
|
2478
|
+
if (c.isSystem(focus))
|
|
2479
|
+
return /* @__PURE__ */ new Set([focus, ...c.componentsOf(focus)]);
|
|
2480
|
+
return void 0;
|
|
2481
|
+
}
|
|
2482
|
+
function dataView(records2, focus) {
|
|
2483
|
+
const c = new Catalog(records2);
|
|
2484
|
+
let tables = c.tables;
|
|
2485
|
+
if (focus !== void 0) {
|
|
2486
|
+
const scope = scopeOf(c, focus);
|
|
2487
|
+
if (scope)
|
|
2488
|
+
tables = tables.filter((t) => [...t.readers, ...t.writers, ...t.ownerComponent ? [t.ownerComponent] : []].some((x) => scope.has(x)));
|
|
2489
|
+
else if (c.stores.has(focus) || tables.some((t) => t.dataStore === focus))
|
|
2490
|
+
tables = tables.filter((t) => t.dataStore === focus);
|
|
2491
|
+
else if (c.byId.get(focus)?.kind === "Table")
|
|
2492
|
+
tables = tables.filter((t) => t.id === focus);
|
|
2493
|
+
else
|
|
2494
|
+
throw new ViewError(`"${focus}" isn't in the catalog.`);
|
|
2495
|
+
}
|
|
2496
|
+
const groups = [...new Set(tables.map((t) => t.dataStore))].sort(cmp).map((s) => {
|
|
2497
|
+
const store = c.stores.get(s);
|
|
2498
|
+
return {
|
|
2499
|
+
id: s,
|
|
2500
|
+
label: store ? `${s} (${store.storeType})` : s,
|
|
2501
|
+
kind: "datastore",
|
|
2502
|
+
...withLegend(store?.status)
|
|
2503
|
+
};
|
|
2504
|
+
});
|
|
2505
|
+
const nodes = [];
|
|
2506
|
+
for (const g of groups)
|
|
2507
|
+
for (const t of tables.filter((x) => x.dataStore === g.id))
|
|
2508
|
+
nodes.push({ id: t.id, label: t.name, kind: "table", group: g.id, ...withLegend(t.status) });
|
|
2509
|
+
const users = [...new Set(tables.flatMap((t) => [...t.readers, ...t.writers]))].sort(cmp);
|
|
2510
|
+
for (const u of users)
|
|
2511
|
+
nodes.push(c.endNode(u));
|
|
2512
|
+
const edges = [];
|
|
2513
|
+
for (const u of users)
|
|
2514
|
+
for (const t of tables) {
|
|
2515
|
+
const reads = t.readers.includes(u);
|
|
2516
|
+
const writes = t.writers.includes(u);
|
|
2517
|
+
if (!reads && !writes)
|
|
2518
|
+
continue;
|
|
2519
|
+
edges.push({
|
|
2520
|
+
id: `access.${u}.${t.id}`,
|
|
2521
|
+
from: u,
|
|
2522
|
+
to: t.id,
|
|
2523
|
+
label: reads && writes ? "reads and writes" : reads ? "reads" : "writes",
|
|
2524
|
+
async: false
|
|
2525
|
+
});
|
|
2526
|
+
}
|
|
2527
|
+
return {
|
|
2528
|
+
type: "data",
|
|
2529
|
+
title: focus ? `Data view of ${focus}` : "Data view",
|
|
2530
|
+
groups,
|
|
2531
|
+
nodes,
|
|
2532
|
+
edges: edges.sort(byEdge)
|
|
2533
|
+
};
|
|
2534
|
+
}
|
|
2535
|
+
function eventView(records2, focus) {
|
|
2536
|
+
const c = new Catalog(records2);
|
|
2537
|
+
const topics = /* @__PURE__ */ new Map();
|
|
2538
|
+
const topic = (name, id = `topic.${slug(name)}`, status) => {
|
|
2539
|
+
let t = topics.get(name);
|
|
2540
|
+
if (!t) {
|
|
2541
|
+
t = { id, label: name, ...withLegend(status), pubs: /* @__PURE__ */ new Map(), subs: /* @__PURE__ */ new Map() };
|
|
2542
|
+
topics.set(name, t);
|
|
2543
|
+
}
|
|
2544
|
+
return t;
|
|
2545
|
+
};
|
|
2546
|
+
for (const ep of c.endpoints.values())
|
|
2547
|
+
if (ep.endpointType === "event-topic")
|
|
2548
|
+
topic(ep.pathOrTopic, ep.id, ep.status);
|
|
2549
|
+
for (const i of c.interfaces) {
|
|
2550
|
+
if (i.sync !== "async")
|
|
2551
|
+
continue;
|
|
2552
|
+
const t = topic(c.topicOf(i));
|
|
2553
|
+
if (!t.pubs.has(i.from))
|
|
2554
|
+
t.pubs.set(i.from, legendOf(i.status));
|
|
2555
|
+
if (!t.subs.has(i.to))
|
|
2556
|
+
t.subs.set(i.to, legendOf(i.status));
|
|
2557
|
+
}
|
|
2558
|
+
for (const ep of c.endpoints.values()) {
|
|
2559
|
+
if (ep.endpointType !== "event-topic")
|
|
2560
|
+
continue;
|
|
2561
|
+
const t = topics.get(ep.pathOrTopic);
|
|
2562
|
+
const known = c.components.has(ep.component) || c.isSystem(ep.component);
|
|
2563
|
+
if (known && !t.pubs.has(ep.component))
|
|
2564
|
+
t.pubs.set(ep.component, t.legend);
|
|
2565
|
+
}
|
|
2566
|
+
let chosen = [...topics.values()];
|
|
2567
|
+
if (focus !== void 0) {
|
|
2568
|
+
const byId = chosen.find((t) => t.id === focus);
|
|
2569
|
+
const scope = scopeOf(c, focus);
|
|
2570
|
+
if (byId)
|
|
2571
|
+
chosen = [byId];
|
|
2572
|
+
else if (scope)
|
|
2573
|
+
chosen = chosen.filter((t) => [...t.pubs.keys(), ...t.subs.keys()].some((x) => scope.has(x)));
|
|
2574
|
+
else
|
|
2575
|
+
throw new ViewError(`"${focus}" isn't in the catalog.`);
|
|
2576
|
+
}
|
|
2577
|
+
chosen.sort((a, b) => cmp(a.id, b.id));
|
|
2578
|
+
const ends = [...new Set(chosen.flatMap((t) => [...t.pubs.keys(), ...t.subs.keys()]))].sort(cmp);
|
|
2579
|
+
const nodes = [
|
|
2580
|
+
...ends.map((e) => c.endNode(e)),
|
|
2581
|
+
...chosen.map((t) => ({
|
|
2582
|
+
id: t.id,
|
|
2583
|
+
label: t.label,
|
|
2584
|
+
kind: "topic",
|
|
2585
|
+
...t.legend ? { legend: t.legend } : {}
|
|
2586
|
+
}))
|
|
2587
|
+
];
|
|
2588
|
+
const edges = [];
|
|
2589
|
+
for (const t of chosen) {
|
|
2590
|
+
for (const [p, legend] of [...t.pubs].sort((a, b) => cmp(a[0], b[0])))
|
|
2591
|
+
edges.push({
|
|
2592
|
+
id: `pub.${p}.${t.id}`,
|
|
2593
|
+
from: p,
|
|
2594
|
+
to: t.id,
|
|
2595
|
+
label: "publishes",
|
|
2596
|
+
async: true,
|
|
2597
|
+
...legend ? { legend } : {}
|
|
2598
|
+
});
|
|
2599
|
+
for (const [s, legend] of [...t.subs].sort((a, b) => cmp(a[0], b[0])))
|
|
2600
|
+
edges.push({
|
|
2601
|
+
id: `sub.${t.id}.${s}`,
|
|
2602
|
+
from: t.id,
|
|
2603
|
+
to: s,
|
|
2604
|
+
label: "delivers to",
|
|
2605
|
+
async: true,
|
|
2606
|
+
...legend ? { legend } : {}
|
|
2607
|
+
});
|
|
2608
|
+
}
|
|
2609
|
+
return {
|
|
2610
|
+
type: "event",
|
|
2611
|
+
title: focus ? `Event view of ${focus}` : "Event view",
|
|
2612
|
+
groups: [],
|
|
2613
|
+
nodes,
|
|
2614
|
+
edges
|
|
2615
|
+
};
|
|
2616
|
+
}
|
|
2617
|
+
function sequenceView(records2, ids) {
|
|
2618
|
+
const c = new Catalog(records2);
|
|
2619
|
+
if (ids.length === 0)
|
|
2620
|
+
throw new ViewError("Name at least one record for the sequence.");
|
|
2621
|
+
const participants = /* @__PURE__ */ new Map();
|
|
2622
|
+
const steps = [];
|
|
2623
|
+
const topics = new Map([...c.endpoints.values()].filter((e) => e.endpointType === "event-topic").map((e) => [e.id, e]));
|
|
2624
|
+
const add = (id) => {
|
|
2625
|
+
if (participants.has(id))
|
|
2626
|
+
return;
|
|
2627
|
+
const rec = c.byId.get(id);
|
|
2628
|
+
const label = rec?.kind === "System" ? rec.name : rec?.kind === "Endpoint" && rec.endpointType === "event-topic" ? rec.pathOrTopic : id;
|
|
2629
|
+
participants.set(id, { id, label, ...withLegend(rec?.status) });
|
|
2630
|
+
};
|
|
2631
|
+
const message = (from, to, label, async, status) => steps.push({ kind: "message", from, to, label, async, ...withLegend(status) });
|
|
2632
|
+
const access = (comp, table) => {
|
|
2633
|
+
const r = table.readers.includes(comp);
|
|
2634
|
+
const w = table.writers.includes(comp);
|
|
2635
|
+
return r && w ? "reads and writes" : r ? "reads" : w ? "writes" : void 0;
|
|
2636
|
+
};
|
|
2637
|
+
const link2 = (a, b) => {
|
|
2638
|
+
const between = (x, y) => c.interfaces.filter((i) => c.same(x, i.from) && c.same(y, i.to)).sort((p, q) => cmp(c.interfaceLabel(p), c.interfaceLabel(q)) || cmp(p.id, q.id));
|
|
2639
|
+
for (const [x, y] of [
|
|
2640
|
+
[a, b],
|
|
2641
|
+
[b, a]
|
|
2642
|
+
]) {
|
|
2643
|
+
const found = between(x, y);
|
|
2644
|
+
if (found.length > 0) {
|
|
2645
|
+
for (const i of found)
|
|
2646
|
+
message(x, y, c.interfaceLabel(i), i.sync === "async", i.status);
|
|
2647
|
+
return;
|
|
2648
|
+
}
|
|
2649
|
+
}
|
|
2650
|
+
for (const [comp, other] of [
|
|
2651
|
+
[a, b],
|
|
2652
|
+
[b, a]
|
|
2653
|
+
]) {
|
|
2654
|
+
const rec = c.byId.get(other);
|
|
2655
|
+
if (rec?.kind === "Table") {
|
|
2656
|
+
const how = access(comp, rec);
|
|
2657
|
+
if (how)
|
|
2658
|
+
return message(comp, other, how, false);
|
|
2659
|
+
}
|
|
2660
|
+
const t = topics.get(other);
|
|
2661
|
+
if (t) {
|
|
2662
|
+
const on = c.interfaces.filter((i) => i.sync === "async" && c.topicOf(i) === t.pathOrTopic);
|
|
2663
|
+
if (t.component === comp || on.some((i) => c.same(comp, i.from)))
|
|
2664
|
+
return message(comp, other, "publishes", true);
|
|
2665
|
+
if (on.some((i) => c.same(comp, i.to)))
|
|
2666
|
+
return message(other, comp, "delivers to", true);
|
|
2667
|
+
}
|
|
2668
|
+
}
|
|
2669
|
+
steps.push({ kind: "note", over: [a, b], text: `No known link between ${a} and ${b}` });
|
|
2670
|
+
};
|
|
2671
|
+
let prev;
|
|
2672
|
+
for (const raw of ids) {
|
|
2673
|
+
const rec = c.byId.get(raw);
|
|
2674
|
+
if (!rec && !c.isSystem(raw))
|
|
2675
|
+
throw new ViewError(`"${raw}" isn't in the catalog.`);
|
|
2676
|
+
if (rec?.kind === "Interface") {
|
|
2677
|
+
add(rec.from);
|
|
2678
|
+
add(rec.to);
|
|
2679
|
+
message(rec.from, rec.to, c.interfaceLabel(rec), rec.sync === "async", rec.status);
|
|
2680
|
+
prev = rec.to;
|
|
2681
|
+
continue;
|
|
2682
|
+
}
|
|
2683
|
+
let id = raw;
|
|
2684
|
+
if (rec?.kind === "Endpoint" && rec.endpointType !== "event-topic")
|
|
2685
|
+
id = rec.component;
|
|
2686
|
+
else if (rec && rec.kind !== "Component" && rec.kind !== "System" && rec.kind !== "Table" && rec.kind !== "Endpoint")
|
|
2687
|
+
throw new ViewError(`${raw} is a ${rec.kind}; a sequence shows components, systems, interfaces, endpoints, tables and topics.`);
|
|
2688
|
+
add(id);
|
|
2689
|
+
if (prev !== void 0 && prev !== id)
|
|
2690
|
+
link2(prev, id);
|
|
2691
|
+
prev = id;
|
|
2692
|
+
}
|
|
2693
|
+
return {
|
|
2694
|
+
type: "sequence",
|
|
2695
|
+
title: `Sequence view: ${ids.join(", ")}`,
|
|
2696
|
+
participants: [...participants.values()],
|
|
2697
|
+
steps
|
|
2698
|
+
};
|
|
2699
|
+
}
|
|
2700
|
+
var SIGN = { new: "+", modify: "\u25C7", retire: "\u2715" };
|
|
2701
|
+
var stronger = (a, b) => a === "new" || b === "new" ? "new" : a === "modify" || b === "modify" ? "modify" : a ?? b;
|
|
2702
|
+
function lineLegend(marks) {
|
|
2703
|
+
if (marks.size === 1) {
|
|
2704
|
+
const [m] = [...marks];
|
|
2705
|
+
if (m === "added")
|
|
2706
|
+
return "new";
|
|
2707
|
+
if (m === "removed")
|
|
2708
|
+
return "retire";
|
|
2709
|
+
if (m === "same")
|
|
2710
|
+
return void 0;
|
|
2711
|
+
}
|
|
2712
|
+
return "modify";
|
|
2713
|
+
}
|
|
2714
|
+
function changeView(records2, change, flows, known = []) {
|
|
2715
|
+
const c = new Catalog(records2);
|
|
2716
|
+
const inSpecs = new Set(known);
|
|
2717
|
+
const isKnown = (id) => c.components.has(id) || c.isSystem(id) || inSpecs.has(id);
|
|
2718
|
+
const legends = /* @__PURE__ */ new Map();
|
|
2719
|
+
const mark = (id, l) => legends.set(id, stronger(legends.get(id), l));
|
|
2720
|
+
const tables = /* @__PURE__ */ new Set();
|
|
2721
|
+
const comps = /* @__PURE__ */ new Set();
|
|
2722
|
+
const lines = /* @__PURE__ */ new Map();
|
|
2723
|
+
const line = (from, to, label, async, m) => {
|
|
2724
|
+
const key = `${from}->${to}:${label}`;
|
|
2725
|
+
const l = lines.get(key) ?? { from, to, label, async, marks: /* @__PURE__ */ new Set() };
|
|
2726
|
+
l.marks.add(m);
|
|
2727
|
+
lines.set(key, l);
|
|
2728
|
+
};
|
|
2729
|
+
for (const f of flows) {
|
|
2730
|
+
comps.add(f.component);
|
|
2731
|
+
const touched = f.change !== "modify" || f.steps.some((s) => s.mark !== "same");
|
|
2732
|
+
mark(f.component, f.change === "new" && !isKnown(f.component) ? "new" : touched ? "modify" : void 0);
|
|
2733
|
+
for (const s of f.steps) {
|
|
2734
|
+
if (s.to) {
|
|
2735
|
+
comps.add(s.to.component);
|
|
2736
|
+
mark(s.to.component, s.to.new && s.mark !== "removed" ? isKnown(s.to.component) ? "modify" : "new" : void 0);
|
|
2737
|
+
const moved = s.mark === "changed" && s.was?.to;
|
|
2738
|
+
line(f.component, s.to.component, s.to.entry, s.kind === "publish", moved ? "added" : s.mark);
|
|
2739
|
+
if (moved) {
|
|
2740
|
+
comps.add(moved.component);
|
|
2741
|
+
line(f.component, moved.component, moved.entry, s.kind === "publish", "removed");
|
|
2742
|
+
}
|
|
2743
|
+
}
|
|
2744
|
+
if (s.store) {
|
|
2745
|
+
tables.add(s.store.table);
|
|
2746
|
+
mark(s.store.table, s.store.new && s.mark !== "removed" ? "new" : s.store.migration && s.mark !== "removed" ? "modify" : void 0);
|
|
2747
|
+
line(f.component, s.store.table, s.kind, false, s.mark);
|
|
2748
|
+
}
|
|
2749
|
+
}
|
|
2750
|
+
}
|
|
2751
|
+
const withMark = (id) => {
|
|
2752
|
+
const l = legends.get(id);
|
|
2753
|
+
return l ? { legend: l } : {};
|
|
2754
|
+
};
|
|
2755
|
+
const marked = (label, l) => l ? `${SIGN[l]} ${label}` : label;
|
|
2756
|
+
const storeOf = (t) => c.byId.get(t)?.dataStore ?? t.split(".")[0];
|
|
2757
|
+
const systems = [
|
|
2758
|
+
...new Set([...comps].map((id) => c.systemOf(id)).filter((s) => !!s))
|
|
2759
|
+
].sort(cmp);
|
|
2760
|
+
const stores2 = [...new Set([...tables].map(storeOf))].sort(cmp);
|
|
2761
|
+
const groups = [
|
|
2762
|
+
...systems.map((s) => {
|
|
2763
|
+
const rec = c.systems.get(s);
|
|
2764
|
+
return {
|
|
2765
|
+
id: s,
|
|
2766
|
+
label: c.systemName(s),
|
|
2767
|
+
kind: "system",
|
|
2768
|
+
...rec?.boundary ? { boundary: rec.boundary } : {}
|
|
2769
|
+
};
|
|
2770
|
+
}),
|
|
2771
|
+
...stores2.map((s) => {
|
|
2772
|
+
const store = c.stores.get(s);
|
|
2773
|
+
return { id: s, label: store ? `${s} (${store.storeType})` : s, kind: "datastore" };
|
|
2774
|
+
})
|
|
2775
|
+
];
|
|
2776
|
+
const nodes = [];
|
|
2777
|
+
for (const id of comps) {
|
|
2778
|
+
const sys = c.components.has(id) ? c.systemOf(id) : void 0;
|
|
2779
|
+
const base = c.components.has(id) || c.isSystem(id) ? c.endNode(id, sys) : { id, label: id, kind: "component" };
|
|
2780
|
+
const { legend: _status, ...rest } = base;
|
|
2781
|
+
nodes.push({ ...rest, label: marked(rest.label, legends.get(id)), ...withMark(id) });
|
|
2782
|
+
}
|
|
2783
|
+
for (const t of tables) {
|
|
2784
|
+
const rec = c.byId.get(t);
|
|
2785
|
+
nodes.push({
|
|
2786
|
+
id: t,
|
|
2787
|
+
label: marked(rec?.name ?? t.slice(storeOf(t).length + 1), legends.get(t)),
|
|
2788
|
+
kind: "table",
|
|
2789
|
+
group: storeOf(t),
|
|
2790
|
+
...withMark(t)
|
|
2791
|
+
});
|
|
2792
|
+
}
|
|
2793
|
+
const order = new Map(groups.map((g, n) => [g.id, n]));
|
|
2794
|
+
nodes.sort((a, b) => (a.group !== void 0 ? order.get(a.group) : groups.length) - (b.group !== void 0 ? order.get(b.group) : groups.length) || cmp(a.id, b.id));
|
|
2795
|
+
const edges = [...lines.values()].map((l) => {
|
|
2796
|
+
const legend = lineLegend(l.marks);
|
|
2797
|
+
return {
|
|
2798
|
+
id: `change.${l.from}.${l.to}.${slug(l.label)}`,
|
|
2799
|
+
from: l.from,
|
|
2800
|
+
to: l.to,
|
|
2801
|
+
label: marked(l.label, legend),
|
|
2802
|
+
async: l.async,
|
|
2803
|
+
...legend ? { legend } : {}
|
|
2804
|
+
};
|
|
2805
|
+
});
|
|
2806
|
+
return {
|
|
2807
|
+
type: "diagram",
|
|
2808
|
+
title: `Change ${change.id}: ${change.title}`,
|
|
2809
|
+
...change.source ? { source: change.source } : {},
|
|
2810
|
+
groups,
|
|
2811
|
+
nodes,
|
|
2812
|
+
edges: edges.sort(byEdge)
|
|
2813
|
+
};
|
|
2814
|
+
}
|
|
2815
|
+
function buildView(records2, name, focus) {
|
|
2816
|
+
switch (name) {
|
|
2817
|
+
case "context":
|
|
2818
|
+
return contextView(records2);
|
|
2819
|
+
case "container":
|
|
2820
|
+
if (!focus)
|
|
2821
|
+
throw new ViewError("A container view needs a system: --focus <system>.");
|
|
2822
|
+
return containerView(records2, focus);
|
|
2823
|
+
case "data":
|
|
2824
|
+
return dataView(records2, focus);
|
|
2825
|
+
case "event":
|
|
2826
|
+
return eventView(records2, focus);
|
|
2827
|
+
case "sequence":
|
|
2828
|
+
if (!focus)
|
|
2829
|
+
throw new ViewError("A sequence view needs record ids: --focus <id>,<id>,...");
|
|
2830
|
+
return sequenceView(records2, focus.split(",").map((s) => s.trim()).filter(Boolean));
|
|
2831
|
+
}
|
|
2832
|
+
}
|
|
2833
|
+
|
|
2834
|
+
// packages/diagrams/dist/mermaid.js
|
|
2835
|
+
var RESERVED = /* @__PURE__ */ new Set([
|
|
2836
|
+
"end",
|
|
2837
|
+
"graph",
|
|
2838
|
+
"subgraph",
|
|
2839
|
+
"flowchart",
|
|
2840
|
+
"class",
|
|
2841
|
+
"classdef",
|
|
2842
|
+
"style",
|
|
2843
|
+
"linkstyle",
|
|
2844
|
+
"click",
|
|
2845
|
+
"direction",
|
|
2846
|
+
"default",
|
|
2847
|
+
"participant",
|
|
2848
|
+
"actor",
|
|
2849
|
+
"note",
|
|
2850
|
+
"loop",
|
|
2851
|
+
"alt",
|
|
2852
|
+
"else",
|
|
2853
|
+
"opt",
|
|
2854
|
+
"par",
|
|
2855
|
+
"and",
|
|
2856
|
+
"rect",
|
|
2857
|
+
"critical",
|
|
2858
|
+
"break",
|
|
2859
|
+
"box",
|
|
2860
|
+
"autonumber",
|
|
2861
|
+
"activate",
|
|
2862
|
+
"deactivate"
|
|
2863
|
+
]);
|
|
2864
|
+
var LEGEND_NODES = [
|
|
2865
|
+
["new", "legend_new", "+ New"],
|
|
2866
|
+
["modify", "legend_modify", "\u25C7 Modify"],
|
|
2867
|
+
["retire", "legend_retire", "\u2715 Retire"]
|
|
2868
|
+
];
|
|
2869
|
+
var Ids = class {
|
|
2870
|
+
used = /* @__PURE__ */ new Set(["legend", ...LEGEND_NODES.map((l) => l[1])]);
|
|
2871
|
+
map = /* @__PURE__ */ new Map();
|
|
2872
|
+
get(key, raw) {
|
|
2873
|
+
const known = this.map.get(key);
|
|
2874
|
+
if (known)
|
|
2875
|
+
return known;
|
|
2876
|
+
let base = raw.replace(/[^A-Za-z0-9_]/g, "_");
|
|
2877
|
+
if (!/^[A-Za-z]/.test(base))
|
|
2878
|
+
base = `n_${base}`;
|
|
2879
|
+
if (RESERVED.has(base.toLowerCase()))
|
|
2880
|
+
base = `${base}_`;
|
|
2881
|
+
let id = base;
|
|
2882
|
+
for (let n = 2; this.used.has(id); n++)
|
|
2883
|
+
id = `${base}_${n}`;
|
|
2884
|
+
this.used.add(id);
|
|
2885
|
+
this.map.set(key, id);
|
|
2886
|
+
return id;
|
|
2887
|
+
}
|
|
2888
|
+
};
|
|
2889
|
+
var esc = (s) => s.replace(/"/g, "#quot;");
|
|
2890
|
+
var HEADER = "Generated from the catalog by Atticus: change the catalog, not this file.";
|
|
2891
|
+
var header2 = (v) => `%% ${v.title}. ${v.source ? `Rendered by Atticus from ${v.source}.` : HEADER}`;
|
|
2892
|
+
var CLASS_DEFS = [
|
|
2893
|
+
["product", "fill:#dae8fc,stroke:#6c8ebf"],
|
|
2894
|
+
["internal", "fill:#d5e8d4,stroke:#82b366"],
|
|
2895
|
+
["vendor", "fill:#f5f5f5,stroke:#666666,stroke-dasharray:5 5"],
|
|
2896
|
+
["plannedNew", "stroke:#2e7d32,stroke-width:3px,stroke-dasharray:6 3"],
|
|
2897
|
+
["plannedModify", "stroke:#ed6c02,stroke-width:3px"],
|
|
2898
|
+
["plannedRetire", "stroke:#b71c1c,stroke-width:3px,stroke-dasharray:2 2"]
|
|
2899
|
+
];
|
|
2900
|
+
var LEGEND_CLASS = {
|
|
2901
|
+
new: "plannedNew",
|
|
2902
|
+
modify: "plannedModify",
|
|
2903
|
+
retire: "plannedRetire"
|
|
2904
|
+
};
|
|
2905
|
+
var LINK_STYLE = {
|
|
2906
|
+
new: "stroke:#2e7d32,stroke-width:2px,stroke-dasharray:6 3",
|
|
2907
|
+
modify: "stroke:#ed6c02,stroke-width:2px",
|
|
2908
|
+
retire: "stroke:#b71c1c,stroke-width:2px,stroke-dasharray:2 2"
|
|
2909
|
+
};
|
|
2910
|
+
function shape(n, id) {
|
|
2911
|
+
const l = esc(n.label);
|
|
2912
|
+
if (n.kind === "table")
|
|
2913
|
+
return `${id}[("${l}")]`;
|
|
2914
|
+
if (n.kind === "topic")
|
|
2915
|
+
return `${id}>"${l}"]`;
|
|
2916
|
+
return `${id}["${l}"]`;
|
|
2917
|
+
}
|
|
2918
|
+
function flow(v) {
|
|
2919
|
+
const ids = new Ids();
|
|
2920
|
+
const gid = (id) => ids.get(`g:${id}`, id);
|
|
2921
|
+
const nid = (id) => ids.get(`n:${id}`, id);
|
|
2922
|
+
const lines = [header2(v), "flowchart LR"];
|
|
2923
|
+
const classes = new Map(CLASS_DEFS.map(([c]) => [c, /* @__PURE__ */ new Set()]));
|
|
2924
|
+
const tag = (id, x) => {
|
|
2925
|
+
if (x.boundary)
|
|
2926
|
+
classes.get(x.boundary)?.add(id);
|
|
2927
|
+
if (x.legend)
|
|
2928
|
+
classes.get(LEGEND_CLASS[x.legend]).add(id);
|
|
2929
|
+
};
|
|
2930
|
+
for (const g of v.groups) {
|
|
2931
|
+
const id = gid(g.id);
|
|
2932
|
+
tag(id, g);
|
|
2933
|
+
lines.push(` subgraph ${id}["${esc(g.label)}"]`);
|
|
2934
|
+
for (const n of v.nodes.filter((x) => x.group === g.id)) {
|
|
2935
|
+
lines.push(` ${shape(n, nid(n.id))}`);
|
|
2936
|
+
tag(nid(n.id), n);
|
|
2937
|
+
}
|
|
2938
|
+
lines.push(" end");
|
|
2939
|
+
}
|
|
2940
|
+
const grouped = new Set(v.groups.map((g) => g.id));
|
|
2941
|
+
for (const n of v.nodes.filter((x) => x.group === void 0 || !grouped.has(x.group))) {
|
|
2942
|
+
lines.push(` ${shape(n, nid(n.id))}`);
|
|
2943
|
+
tag(nid(n.id), n);
|
|
2944
|
+
}
|
|
2945
|
+
const links = { new: [], modify: [], retire: [] };
|
|
2946
|
+
v.edges.forEach((e, i) => {
|
|
2947
|
+
const label = e.label ? `|"${esc(e.label)}"|` : "";
|
|
2948
|
+
lines.push(` ${nid(e.from)} ${e.async ? "-.->" : "-->"}${label} ${nid(e.to)}`);
|
|
2949
|
+
if (e.legend)
|
|
2950
|
+
links[e.legend].push(i);
|
|
2951
|
+
});
|
|
2952
|
+
const planned = [...v.groups, ...v.nodes, ...v.edges].some((x) => x.legend);
|
|
2953
|
+
if (planned) {
|
|
2954
|
+
lines.push(' subgraph legend["Legend"]');
|
|
2955
|
+
for (const [legend, id, text] of LEGEND_NODES) {
|
|
2956
|
+
lines.push(` ${id}["${text}"]`);
|
|
2957
|
+
classes.get(LEGEND_CLASS[legend]).add(id);
|
|
2958
|
+
}
|
|
2959
|
+
lines.push(" end");
|
|
2960
|
+
}
|
|
2961
|
+
for (const [c, def] of CLASS_DEFS)
|
|
2962
|
+
if (classes.get(c).size > 0)
|
|
2963
|
+
lines.push(` classDef ${c} ${def}`);
|
|
2964
|
+
for (const [c] of CLASS_DEFS) {
|
|
2965
|
+
const members = [...classes.get(c)].sort(cmp);
|
|
2966
|
+
if (members.length > 0)
|
|
2967
|
+
lines.push(` class ${members.join(",")} ${c}`);
|
|
2968
|
+
}
|
|
2969
|
+
for (const legend of ["new", "modify", "retire"])
|
|
2970
|
+
if (links[legend].length > 0)
|
|
2971
|
+
lines.push(` linkStyle ${links[legend].join(",")} ${LINK_STYLE[legend]}`);
|
|
2972
|
+
return `${lines.join("\n")}
|
|
2973
|
+
`;
|
|
2974
|
+
}
|
|
2975
|
+
var seqText = (s) => s.replace(/;/g, "#59;").replace(/\s+/g, " ");
|
|
2976
|
+
var suffix = (legend) => legend ? ` (${legend})` : "";
|
|
2977
|
+
function sequence(v) {
|
|
2978
|
+
const ids = new Ids();
|
|
2979
|
+
const pid = (id) => ids.get(`p:${id}`, id);
|
|
2980
|
+
const lines = [header2(v), "sequenceDiagram"];
|
|
2981
|
+
for (const p of v.participants)
|
|
2982
|
+
lines.push(` participant ${pid(p.id)} as ${seqText(p.label + suffix(p.legend))}`);
|
|
2983
|
+
for (const s of v.steps)
|
|
2984
|
+
lines.push(s.kind === "message" ? ` ${pid(s.from)}${s.reply ? "-->>" : s.async ? "-)" : "->>"}${pid(s.to)}: ${seqText(s.label + suffix(s.legend))}` : ` Note over ${s.over.map(pid).join(",")}: ${seqText(s.text)}`);
|
|
2985
|
+
return `${lines.join("\n")}
|
|
2986
|
+
`;
|
|
2987
|
+
}
|
|
2988
|
+
function toMermaid(view) {
|
|
2989
|
+
return view.type === "sequence" ? sequence(view) : flow(view);
|
|
2990
|
+
}
|
|
2991
|
+
|
|
2992
|
+
// packages/diagrams/dist/drawio.js
|
|
2993
|
+
import { deflateRawSync } from "node:zlib";
|
|
2994
|
+
var BOX = { rounded: "1", whiteSpace: "wrap", html: "0" };
|
|
2995
|
+
var BOUNDARY = {
|
|
2996
|
+
product: { fillColor: "#dae8fc", strokeColor: "#6c8ebf" },
|
|
2997
|
+
internal: { fillColor: "#d5e8d4", strokeColor: "#82b366" },
|
|
2998
|
+
vendor: { fillColor: "#f5f5f5", strokeColor: "#666666" }
|
|
2999
|
+
};
|
|
3000
|
+
var SHAPES = {
|
|
3001
|
+
system: { ...BOX, ...BOUNDARY.product, atticusKind: "system" },
|
|
3002
|
+
component: { ...BOX, atticusKind: "component" },
|
|
3003
|
+
vendor: {
|
|
3004
|
+
shape: "cloud",
|
|
3005
|
+
whiteSpace: "wrap",
|
|
3006
|
+
html: "0",
|
|
3007
|
+
...BOUNDARY.vendor,
|
|
3008
|
+
atticusKind: "vendor"
|
|
3009
|
+
},
|
|
3010
|
+
datastore: {
|
|
3011
|
+
shape: "cylinder3",
|
|
3012
|
+
whiteSpace: "wrap",
|
|
3013
|
+
html: "0",
|
|
3014
|
+
boundedLbl: "1",
|
|
3015
|
+
size: "12",
|
|
3016
|
+
fillColor: "#fff2cc",
|
|
3017
|
+
strokeColor: "#d6b656",
|
|
3018
|
+
atticusKind: "datastore"
|
|
3019
|
+
},
|
|
3020
|
+
topic: {
|
|
3021
|
+
shape: "parallelogram",
|
|
3022
|
+
perimeter: "parallelogramPerimeter",
|
|
3023
|
+
whiteSpace: "wrap",
|
|
3024
|
+
html: "0",
|
|
3025
|
+
fillColor: "#e1d5e7",
|
|
3026
|
+
strokeColor: "#9673a6",
|
|
3027
|
+
atticusKind: "topic"
|
|
3028
|
+
},
|
|
3029
|
+
group: {
|
|
3030
|
+
rounded: "0",
|
|
3031
|
+
whiteSpace: "wrap",
|
|
3032
|
+
html: "0",
|
|
3033
|
+
container: "1",
|
|
3034
|
+
collapsible: "0",
|
|
3035
|
+
dashed: "1",
|
|
3036
|
+
fillColor: "none",
|
|
3037
|
+
verticalAlign: "top",
|
|
3038
|
+
align: "left",
|
|
3039
|
+
spacingLeft: "8",
|
|
3040
|
+
fontStyle: "1",
|
|
3041
|
+
atticusKind: "group"
|
|
3042
|
+
}
|
|
3043
|
+
};
|
|
3044
|
+
var LEGEND = {
|
|
3045
|
+
new: { legend: "new", strokeColor: "#2e7d32", strokeWidth: "3", dashed: "1" },
|
|
3046
|
+
modify: { legend: "modify", strokeColor: "#ed6c02", strokeWidth: "3" },
|
|
3047
|
+
retire: {
|
|
3048
|
+
legend: "retire",
|
|
3049
|
+
strokeColor: "#b71c1c",
|
|
3050
|
+
strokeWidth: "3",
|
|
3051
|
+
dashed: "1",
|
|
3052
|
+
dashPattern: "2 2"
|
|
3053
|
+
}
|
|
3054
|
+
};
|
|
3055
|
+
var EDGE = {
|
|
3056
|
+
edgeStyle: "orthogonalEdgeStyle",
|
|
3057
|
+
rounded: "1",
|
|
3058
|
+
html: "0",
|
|
3059
|
+
endArrow: "block",
|
|
3060
|
+
endFill: "1"
|
|
3061
|
+
};
|
|
3062
|
+
var styleText = (...parts) => Object.entries(Object.assign({}, ...parts)).map(([k, v]) => `${k}=${v}`).join(";") + ";";
|
|
3063
|
+
var attr = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/\n/g, " ");
|
|
3064
|
+
function nodeStyle(n) {
|
|
3065
|
+
const legend = n.legend ? LEGEND[n.legend] : void 0;
|
|
3066
|
+
if (n.kind === "table")
|
|
3067
|
+
return styleText(SHAPES.datastore, { atticusKind: "table" }, legend);
|
|
3068
|
+
if (n.kind === "topic")
|
|
3069
|
+
return styleText(SHAPES.topic, legend);
|
|
3070
|
+
if (n.boundary === "vendor")
|
|
3071
|
+
return styleText(SHAPES.vendor, n.kind === "component" ? { atticusKind: "component" } : {}, legend);
|
|
3072
|
+
const base = n.kind === "system" ? SHAPES.system : SHAPES.component;
|
|
3073
|
+
return styleText(base, n.boundary ? BOUNDARY[n.boundary] : void 0, legend);
|
|
3074
|
+
}
|
|
3075
|
+
function groupStyle(g) {
|
|
3076
|
+
return styleText(SHAPES.group, g.kind === "datastore" ? { atticusKind: "datastore-group" } : {}, g.boundary ? { strokeColor: BOUNDARY[g.boundary].strokeColor } : void 0, g.legend ? LEGEND[g.legend] : void 0);
|
|
3077
|
+
}
|
|
3078
|
+
function boxText(x) {
|
|
3079
|
+
if (x.kind !== "system" && x.kind !== "component")
|
|
3080
|
+
return x.label;
|
|
3081
|
+
return slug(x.label) === x.id ? x.label : x.id;
|
|
3082
|
+
}
|
|
3083
|
+
var W = 160;
|
|
3084
|
+
var H = 60;
|
|
3085
|
+
var GAP = 80;
|
|
3086
|
+
var PER_ROW = 4;
|
|
3087
|
+
function toDrawio(view, o = {}) {
|
|
3088
|
+
if (view.type === "sequence")
|
|
3089
|
+
throw new Error("A sequence view can't be exported to draw.io: render it as Mermaid.");
|
|
3090
|
+
const cells = [];
|
|
3091
|
+
const cellOf = /* @__PURE__ */ new Map();
|
|
3092
|
+
const grouped = new Set(view.groups.map((g) => g.id));
|
|
3093
|
+
const blocks = [];
|
|
3094
|
+
const vertex = (id, value, style, parent, x, y2, w, h) => cells.push(` <mxCell id="${attr(id)}" value="${attr(value)}" style="${attr(style)}" vertex="1" parent="${attr(parent)}">
|
|
3095
|
+
<mxGeometry x="${x}" y="${y2}" width="${w}" height="${h}" as="geometry"/>
|
|
3096
|
+
</mxCell>`);
|
|
3097
|
+
for (const g of view.groups) {
|
|
3098
|
+
const members = view.nodes.filter((n) => n.group === g.id);
|
|
3099
|
+
const gw = W + 40;
|
|
3100
|
+
const gh = 40 + members.length * (H + 20);
|
|
3101
|
+
blocks.push({
|
|
3102
|
+
w: gw,
|
|
3103
|
+
h: gh,
|
|
3104
|
+
emit: (x, y2) => {
|
|
3105
|
+
const gcell = `group.${g.id}`;
|
|
3106
|
+
vertex(gcell, boxText(g), groupStyle(g), "1", x, y2, gw, gh);
|
|
3107
|
+
members.forEach((n, i) => {
|
|
3108
|
+
const cell = `node.${n.id}`;
|
|
3109
|
+
cellOf.set(n.id, cell);
|
|
3110
|
+
vertex(cell, boxText(n), nodeStyle(n), gcell, 20, 30 + i * (H + 20), W, H);
|
|
3111
|
+
});
|
|
3112
|
+
}
|
|
3113
|
+
});
|
|
3114
|
+
}
|
|
3115
|
+
for (const n of view.nodes.filter((x) => x.group === void 0 || !grouped.has(x.group)))
|
|
3116
|
+
blocks.push({
|
|
3117
|
+
w: W,
|
|
3118
|
+
h: H,
|
|
3119
|
+
emit: (x, y2) => {
|
|
3120
|
+
const cell = `node.${n.id}`;
|
|
3121
|
+
cellOf.set(n.id, cell);
|
|
3122
|
+
vertex(cell, boxText(n), nodeStyle(n), "1", x, y2, W, H);
|
|
3123
|
+
}
|
|
3124
|
+
});
|
|
3125
|
+
let y = 40;
|
|
3126
|
+
for (let r = 0; r < blocks.length; r += PER_ROW) {
|
|
3127
|
+
const row = blocks.slice(r, r + PER_ROW);
|
|
3128
|
+
let x = 40;
|
|
3129
|
+
for (const b of row) {
|
|
3130
|
+
b.emit(x, y);
|
|
3131
|
+
x += b.w + GAP;
|
|
3132
|
+
}
|
|
3133
|
+
y += Math.max(...row.map((b) => b.h)) + GAP;
|
|
3134
|
+
}
|
|
3135
|
+
for (const e of view.edges) {
|
|
3136
|
+
const source = cellOf.get(e.from);
|
|
3137
|
+
const target = cellOf.get(e.to);
|
|
3138
|
+
if (!source || !target)
|
|
3139
|
+
continue;
|
|
3140
|
+
cells.push(` <mxCell id="${attr(`edge.${e.id}`)}" value="${attr(e.label)}" style="${attr(edgeStyle(e))}" edge="1" parent="1" source="${attr(source)}" target="${attr(target)}">
|
|
3141
|
+
<mxGeometry relative="1" as="geometry"/>
|
|
3142
|
+
</mxCell>`);
|
|
3143
|
+
}
|
|
3144
|
+
return [
|
|
3145
|
+
`<mxfile host="atticus">`,
|
|
3146
|
+
` <diagram id="${attr(view.type)}" name="${attr(o.page ?? view.title)}">`,
|
|
3147
|
+
` <mxGraphModel grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1169" pageHeight="827">`,
|
|
3148
|
+
` <root>`,
|
|
3149
|
+
` <mxCell id="0"/>`,
|
|
3150
|
+
` <mxCell id="1" parent="0"/>`,
|
|
3151
|
+
...cells,
|
|
3152
|
+
` </root>`,
|
|
3153
|
+
` </mxGraphModel>`,
|
|
3154
|
+
` </diagram>`,
|
|
3155
|
+
`</mxfile>`,
|
|
3156
|
+
``
|
|
3157
|
+
].join("\n");
|
|
3158
|
+
}
|
|
3159
|
+
function edgeStyle(e) {
|
|
3160
|
+
return styleText(EDGE, e.async ? { dashed: "1" } : void 0, e.legend ? { legend: e.legend, strokeColor: LEGEND[e.legend].strokeColor, strokeWidth: "2" } : void 0);
|
|
3161
|
+
}
|
|
3162
|
+
var compress = (xml) => deflateRawSync(Buffer.from(encodeURIComponent(xml), "utf8"), { level: 9 }).toString("base64");
|
|
3163
|
+
function shapeLibrary() {
|
|
3164
|
+
const base = [
|
|
3165
|
+
["System", SHAPES.system, W, H],
|
|
3166
|
+
["Component", SHAPES.component, W, H],
|
|
3167
|
+
["Vendor", SHAPES.vendor, W, H + 20],
|
|
3168
|
+
["Data store", SHAPES.datastore, 120, 80],
|
|
3169
|
+
["Topic", SHAPES.topic, W, H]
|
|
3170
|
+
];
|
|
3171
|
+
const entries = base.flatMap(([title, style, w, h]) => [void 0, "new", "modify", "retire"].map((legend) => {
|
|
3172
|
+
const value = attr(title);
|
|
3173
|
+
const st = attr(styleText(style, legend ? LEGEND[legend] : void 0));
|
|
3174
|
+
const xml = `<mxGraphModel><root><mxCell id="0"/><mxCell id="1" parent="0"/><mxCell id="2" value="${value}" style="${st}" vertex="1" parent="1"><mxGeometry width="${w}" height="${h}" as="geometry"/></mxCell></root></mxGraphModel>`;
|
|
3175
|
+
return {
|
|
3176
|
+
xml: compress(xml),
|
|
3177
|
+
w,
|
|
3178
|
+
h,
|
|
3179
|
+
aspect: "fixed",
|
|
3180
|
+
title: legend ? `${title} (${legend})` : title
|
|
3181
|
+
};
|
|
3182
|
+
}));
|
|
3183
|
+
return `<mxlibrary>${JSON.stringify(entries)}</mxlibrary>
|
|
3184
|
+
`;
|
|
3185
|
+
}
|
|
3186
|
+
function viewFromDiagram(diagram, source) {
|
|
3187
|
+
const multi = new Set(diagram.nodes.map((n) => n.page)).size > 1;
|
|
3188
|
+
const key = (page, cell) => multi ? `${page}:${cell}` : cell;
|
|
3189
|
+
const nodes = diagram.nodes.map((n) => ({
|
|
3190
|
+
id: key(n.page, n.cell),
|
|
3191
|
+
label: n.label,
|
|
3192
|
+
kind: "box",
|
|
3193
|
+
...n.legend ? { legend: n.legend } : {}
|
|
3194
|
+
}));
|
|
3195
|
+
const find = (page, label) => diagram.nodes.find((n) => n.page === page && n.label === label);
|
|
3196
|
+
const edges = [];
|
|
3197
|
+
for (const e of diagram.edges) {
|
|
3198
|
+
const from = find(e.page, e.from);
|
|
3199
|
+
const to = find(e.page, e.to);
|
|
3200
|
+
if (!from || !to)
|
|
3201
|
+
continue;
|
|
3202
|
+
edges.push({
|
|
3203
|
+
id: key(e.page, e.cell),
|
|
3204
|
+
from: key(from.page, from.cell),
|
|
3205
|
+
to: key(to.page, to.cell),
|
|
3206
|
+
label: e.label,
|
|
3207
|
+
async: e.topic !== void 0,
|
|
3208
|
+
...e.legend ? { legend: e.legend } : {}
|
|
3209
|
+
});
|
|
3210
|
+
}
|
|
3211
|
+
return { type: "diagram", title: "Diagram view", source, groups: [], nodes, edges };
|
|
3212
|
+
}
|
|
3213
|
+
|
|
3214
|
+
// packages/diagrams/dist/hierarchy.js
|
|
3215
|
+
var NoSuchNode = class extends Error {
|
|
3216
|
+
};
|
|
3217
|
+
var KIND_ORDER = [
|
|
3218
|
+
"estate",
|
|
3219
|
+
"system",
|
|
3220
|
+
"repo",
|
|
3221
|
+
"component",
|
|
3222
|
+
"endpoint",
|
|
3223
|
+
"topic",
|
|
3224
|
+
"client",
|
|
3225
|
+
"store",
|
|
3226
|
+
"table",
|
|
3227
|
+
"deployment",
|
|
3228
|
+
"environment",
|
|
3229
|
+
"unknown"
|
|
3230
|
+
];
|
|
3231
|
+
var LENS_KINDS = {
|
|
3232
|
+
architecture: /* @__PURE__ */ new Set([
|
|
3233
|
+
"estate",
|
|
3234
|
+
"system",
|
|
3235
|
+
"repo",
|
|
3236
|
+
"component",
|
|
3237
|
+
"endpoint",
|
|
3238
|
+
"topic",
|
|
3239
|
+
"client",
|
|
3240
|
+
"store",
|
|
3241
|
+
"unknown"
|
|
3242
|
+
]),
|
|
3243
|
+
repo: /* @__PURE__ */ new Set([
|
|
3244
|
+
"estate",
|
|
3245
|
+
"system",
|
|
3246
|
+
"repo",
|
|
3247
|
+
"component",
|
|
3248
|
+
"endpoint",
|
|
3249
|
+
"topic",
|
|
3250
|
+
"client",
|
|
3251
|
+
"store",
|
|
3252
|
+
"table",
|
|
3253
|
+
"unknown"
|
|
3254
|
+
]),
|
|
3255
|
+
data: /* @__PURE__ */ new Set(["estate", "system", "repo", "component", "store", "table", "unknown"]),
|
|
3256
|
+
integration: /* @__PURE__ */ new Set([
|
|
3257
|
+
"estate",
|
|
3258
|
+
"system",
|
|
3259
|
+
"repo",
|
|
3260
|
+
"component",
|
|
3261
|
+
"endpoint",
|
|
3262
|
+
"topic",
|
|
3263
|
+
"client",
|
|
3264
|
+
"unknown"
|
|
3265
|
+
]),
|
|
3266
|
+
infrastructure: /* @__PURE__ */ new Set(["estate", "system", "repo", "component", "deployment", "environment"])
|
|
3267
|
+
};
|
|
3268
|
+
var LENS_EDGES = {
|
|
3269
|
+
architecture: /* @__PURE__ */ new Set(["calls", "publishes", "subscribes", "uses", "reads", "writes"]),
|
|
3270
|
+
repo: /* @__PURE__ */ new Set(["calls", "publishes", "subscribes", "uses", "reads", "writes", "references"]),
|
|
3271
|
+
data: /* @__PURE__ */ new Set(["reads", "writes", "references"]),
|
|
3272
|
+
integration: /* @__PURE__ */ new Set(["calls", "publishes", "subscribes"]),
|
|
3273
|
+
infrastructure: /* @__PURE__ */ new Set(["deployed-to"])
|
|
3274
|
+
};
|
|
3275
|
+
var LENS_ANCHORS = {
|
|
3276
|
+
data: /* @__PURE__ */ new Set(["store", "table"]),
|
|
3277
|
+
integration: /* @__PURE__ */ new Set(["endpoint", "topic", "client"]),
|
|
3278
|
+
infrastructure: /* @__PURE__ */ new Set(["deployment", "environment"])
|
|
3279
|
+
};
|
|
3280
|
+
var DETAILED_LIMIT = 60;
|
|
3281
|
+
var MAX_EVIDENCE = 5;
|
|
3282
|
+
var TOPIC_TYPES = /* @__PURE__ */ new Set(["event-topic", "mq-queue"]);
|
|
3283
|
+
var slug2 = (s) => s.toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^[^a-z0-9]+/, "").slice(0, 120) || "x";
|
|
3284
|
+
function viewKey(lens, node, mode = "node") {
|
|
3285
|
+
return [lens, ...mode === "focus" ? ["focus"] : [], node.replace(/:/g, "-")].join(".");
|
|
3286
|
+
}
|
|
3287
|
+
function evidenceOf(es) {
|
|
3288
|
+
return es.map((e) => ({
|
|
3289
|
+
source: e.source,
|
|
3290
|
+
...e.repo ? { repo: e.repo } : {},
|
|
3291
|
+
at: e.path ? e.line ? `${e.path}:${e.line}` : e.path : e.repo ?? e.source
|
|
3292
|
+
}));
|
|
3293
|
+
}
|
|
3294
|
+
function mergeEvidence(...lists) {
|
|
3295
|
+
const seen = /* @__PURE__ */ new Map();
|
|
3296
|
+
for (const l of lists)
|
|
3297
|
+
for (const e of l)
|
|
3298
|
+
seen.set(`${e.repo ?? ""}\0${e.at}\0${e.source}`, e);
|
|
3299
|
+
return [...seen.entries()].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([, e]) => e).slice(0, MAX_EVIDENCE);
|
|
3300
|
+
}
|
|
3301
|
+
var STATE = {
|
|
3302
|
+
current: "current",
|
|
3303
|
+
"planned-new": "new",
|
|
3304
|
+
"planned-modify": "changed",
|
|
3305
|
+
"planned-retire": "retired",
|
|
3306
|
+
retired: "retired"
|
|
3307
|
+
};
|
|
3308
|
+
function recordGaps(r) {
|
|
3309
|
+
const g = [];
|
|
3310
|
+
if (r.confidence === "inferred")
|
|
3311
|
+
g.push("inferred");
|
|
3312
|
+
for (const f of r.flags ?? [])
|
|
3313
|
+
g.push(f);
|
|
3314
|
+
return g;
|
|
3315
|
+
}
|
|
3316
|
+
var props = (o) => {
|
|
3317
|
+
const out = {};
|
|
3318
|
+
for (const [k, v] of Object.entries(o)) {
|
|
3319
|
+
if (v === void 0 || v === null)
|
|
3320
|
+
continue;
|
|
3321
|
+
if (Array.isArray(v)) {
|
|
3322
|
+
if (v.length)
|
|
3323
|
+
out[k] = v.map(String);
|
|
3324
|
+
} else if (typeof v === "string" || typeof v === "number" || typeof v === "boolean")
|
|
3325
|
+
out[k] = v;
|
|
3326
|
+
}
|
|
3327
|
+
return out;
|
|
3328
|
+
};
|
|
3329
|
+
var all = (rs, k) => rs.filter((r) => r.kind === k).sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
|
|
3330
|
+
function buildEstate(records2) {
|
|
3331
|
+
const nodes = /* @__PURE__ */ new Map();
|
|
3332
|
+
const atoms = [];
|
|
3333
|
+
const add = (n) => {
|
|
3334
|
+
if (!nodes.has(n.id))
|
|
3335
|
+
nodes.set(n.id, n);
|
|
3336
|
+
return n.id;
|
|
3337
|
+
};
|
|
3338
|
+
const fromRecord = (r, id, kind, label, parent, p, extraGaps = []) => ({
|
|
3339
|
+
id,
|
|
3340
|
+
kind,
|
|
3341
|
+
label,
|
|
3342
|
+
parent,
|
|
3343
|
+
record: r,
|
|
3344
|
+
state: STATE[r.status] ?? "current",
|
|
3345
|
+
gaps: [...recordGaps(r), ...extraGaps],
|
|
3346
|
+
evidence: evidenceOf(r.evidence),
|
|
3347
|
+
properties: props({ ...p, confidence: r.confidence }),
|
|
3348
|
+
next: {
|
|
3349
|
+
command: `atticus impact ${r.id}`,
|
|
3350
|
+
why: "See what a change here reaches, with owners and open questions."
|
|
3351
|
+
}
|
|
3352
|
+
});
|
|
3353
|
+
add({
|
|
3354
|
+
id: "estate",
|
|
3355
|
+
kind: "estate",
|
|
3356
|
+
label: "Estate",
|
|
3357
|
+
state: "current",
|
|
3358
|
+
gaps: [],
|
|
3359
|
+
evidence: [],
|
|
3360
|
+
properties: {},
|
|
3361
|
+
next: {
|
|
3362
|
+
command: "atticus map <repo>",
|
|
3363
|
+
why: "Read a repo again when its code has changed: your assistant writes its facts, then atticus verify <repo> --write puts them here."
|
|
3364
|
+
}
|
|
3365
|
+
});
|
|
3366
|
+
const systemRecords = new Map(all(records2, "System").map((s) => [s.id, s]));
|
|
3367
|
+
const components = new Map(all(records2, "Component").map((c) => [c.id, c]));
|
|
3368
|
+
const named = /* @__PURE__ */ new Set();
|
|
3369
|
+
for (const c of components.values())
|
|
3370
|
+
if (!systemRecords.has(c.system))
|
|
3371
|
+
named.add(c.system);
|
|
3372
|
+
const outside = /* @__PURE__ */ new Set();
|
|
3373
|
+
for (const i of all(records2, "Interface"))
|
|
3374
|
+
for (const end of [i.from, i.to])
|
|
3375
|
+
if (!components.has(end) && !systemRecords.has(end) && !named.has(end))
|
|
3376
|
+
outside.add(end);
|
|
3377
|
+
const systems = /* @__PURE__ */ new Set([...systemRecords.keys(), ...named, ...outside]);
|
|
3378
|
+
for (const id of [...named, ...outside].sort())
|
|
3379
|
+
add({
|
|
3380
|
+
id: `system:${id}`,
|
|
3381
|
+
kind: "system",
|
|
3382
|
+
label: id,
|
|
3383
|
+
parent: "estate",
|
|
3384
|
+
state: "current",
|
|
3385
|
+
gaps: [],
|
|
3386
|
+
evidence: [],
|
|
3387
|
+
properties: { boundary: outside.has(id) ? "outside" : "product" },
|
|
3388
|
+
next: outside.has(id) ? {
|
|
3389
|
+
command: `atticus query "kind:interface to:${id}"`,
|
|
3390
|
+
why: "List the calls made to it: it is outside the estate, named by the code that calls it."
|
|
3391
|
+
} : {
|
|
3392
|
+
command: `atticus query "system:${id}"`,
|
|
3393
|
+
why: "List what the catalog holds of it: the manifest names it, and no System record says more yet."
|
|
3394
|
+
}
|
|
3395
|
+
});
|
|
3396
|
+
for (const s of systemRecords.values())
|
|
3397
|
+
add({
|
|
3398
|
+
...fromRecord(s, `system:${s.id}`, "system", s.name || s.id, "estate", {
|
|
3399
|
+
boundary: s.boundary,
|
|
3400
|
+
owner: s.owner,
|
|
3401
|
+
repos: s.repos,
|
|
3402
|
+
tags: s.tags
|
|
3403
|
+
}),
|
|
3404
|
+
next: {
|
|
3405
|
+
command: `atticus guide ${s.id}`,
|
|
3406
|
+
why: "Read the system's guide: its parts, calls, data and what isn't known yet."
|
|
3407
|
+
}
|
|
3408
|
+
});
|
|
3409
|
+
const repoSystem = /* @__PURE__ */ new Map();
|
|
3410
|
+
for (const s of systemRecords.values())
|
|
3411
|
+
for (const repo of s.repos)
|
|
3412
|
+
if (!repoSystem.has(repo))
|
|
3413
|
+
repoSystem.set(repo, s.id);
|
|
3414
|
+
for (const c of components.values())
|
|
3415
|
+
if (c.repo && !repoSystem.has(c.repo))
|
|
3416
|
+
repoSystem.set(c.repo, c.system);
|
|
3417
|
+
const repoNode = (repo) => `repo:${slug2(repo)}`;
|
|
3418
|
+
for (const [repo, sys] of [...repoSystem.entries()].sort(([a], [b]) => a < b ? -1 : 1)) {
|
|
3419
|
+
const parent = systems.has(sys) ? `system:${sys}` : "estate";
|
|
3420
|
+
const ev = [...components.values()].filter((c) => c.repo === repo).flatMap((c) => evidenceOf(c.evidence).filter((e) => !e.repo || e.repo === repo));
|
|
3421
|
+
add({
|
|
3422
|
+
id: repoNode(repo),
|
|
3423
|
+
kind: "repo",
|
|
3424
|
+
label: repo,
|
|
3425
|
+
parent,
|
|
3426
|
+
state: "current",
|
|
3427
|
+
gaps: parent === "estate" ? ["no-system"] : [],
|
|
3428
|
+
evidence: mergeEvidence(ev),
|
|
3429
|
+
properties: { name: repo },
|
|
3430
|
+
next: {
|
|
3431
|
+
command: `atticus map ${repo}`,
|
|
3432
|
+
why: "Read the repo again when its code has changed; Map \u203A Trust says how far its map is trusted now."
|
|
3433
|
+
}
|
|
3434
|
+
});
|
|
3435
|
+
}
|
|
3436
|
+
const unknown = (ref) => add({
|
|
3437
|
+
id: `unknown:${slug2(ref)}`,
|
|
3438
|
+
kind: "unknown",
|
|
3439
|
+
label: ref,
|
|
3440
|
+
parent: "estate",
|
|
3441
|
+
state: "current",
|
|
3442
|
+
gaps: ["not-in-catalog"],
|
|
3443
|
+
evidence: [],
|
|
3444
|
+
properties: {},
|
|
3445
|
+
next: {
|
|
3446
|
+
command: `atticus query "refs:${ref}"`,
|
|
3447
|
+
why: "Find the records that name it; the catalog has no record of its own for it."
|
|
3448
|
+
}
|
|
3449
|
+
});
|
|
3450
|
+
const partNode = (ref) => components.has(ref) ? `component:${ref}` : systems.has(ref) ? `system:${ref}` : unknown(ref);
|
|
3451
|
+
for (const c of components.values()) {
|
|
3452
|
+
const gaps = [];
|
|
3453
|
+
let parent;
|
|
3454
|
+
if (c.repo)
|
|
3455
|
+
parent = repoNode(c.repo);
|
|
3456
|
+
else {
|
|
3457
|
+
gaps.push("no-repo");
|
|
3458
|
+
parent = systems.has(c.system) ? `system:${c.system}` : "estate";
|
|
3459
|
+
}
|
|
3460
|
+
if (!systems.has(c.system))
|
|
3461
|
+
gaps.push("no-system");
|
|
3462
|
+
add(fromRecord(c, `component:${c.id}`, "component", c.id, parent, { type: c.type, runtime: c.runtime, repo: c.repo, path: c.path }, gaps));
|
|
3463
|
+
}
|
|
3464
|
+
const endpointNode = /* @__PURE__ */ new Map();
|
|
3465
|
+
for (const e of all(records2, "Endpoint")) {
|
|
3466
|
+
const topic = TOPIC_TYPES.has(e.endpointType);
|
|
3467
|
+
const id = `${topic ? "topic" : "endpoint"}:${e.id}`;
|
|
3468
|
+
const gaps = [];
|
|
3469
|
+
let parent = `component:${e.component}`;
|
|
3470
|
+
if (!components.has(e.component)) {
|
|
3471
|
+
gaps.push("no-component");
|
|
3472
|
+
parent = systems.has(e.component) ? `system:${e.component}` : "estate";
|
|
3473
|
+
}
|
|
3474
|
+
add(fromRecord(e, id, topic ? "topic" : "endpoint", !topic && e.method ? `${e.method} ${e.pathOrTopic}` : e.pathOrTopic, parent, {
|
|
3475
|
+
endpointType: e.endpointType,
|
|
3476
|
+
method: e.method,
|
|
3477
|
+
pathOrTopic: e.pathOrTopic,
|
|
3478
|
+
auth: e.auth
|
|
3479
|
+
}, gaps));
|
|
3480
|
+
endpointNode.set(e.id, id);
|
|
3481
|
+
}
|
|
3482
|
+
const storeNode = /* @__PURE__ */ new Map();
|
|
3483
|
+
for (const d of all(records2, "DataStore")) {
|
|
3484
|
+
const owner = d.ownerComponent && components.has(d.ownerComponent);
|
|
3485
|
+
const id = `store:${d.id}`;
|
|
3486
|
+
add(fromRecord(d, id, "store", d.id, owner ? `component:${d.ownerComponent}` : "estate", { storeType: d.storeType, entitiesHeld: d.entitiesHeld }, owner ? [] : ["no-owner"]));
|
|
3487
|
+
storeNode.set(d.id, id);
|
|
3488
|
+
}
|
|
3489
|
+
const tables = all(records2, "Table");
|
|
3490
|
+
for (const t of tables) {
|
|
3491
|
+
const store = storeNode.get(t.dataStore);
|
|
3492
|
+
add(fromRecord(t, `table:${t.id}`, "table", t.name, store ?? "estate", { tableKind: t.tableKind, retention: t.retention, dataStore: t.dataStore }, store ? [] : ["no-store"]));
|
|
3493
|
+
}
|
|
3494
|
+
for (const d of all(records2, "Deployment")) {
|
|
3495
|
+
const env = `environment:${slug2(d.env)}`;
|
|
3496
|
+
add({
|
|
3497
|
+
id: env,
|
|
3498
|
+
kind: "environment",
|
|
3499
|
+
label: d.env,
|
|
3500
|
+
parent: "estate",
|
|
3501
|
+
state: "current",
|
|
3502
|
+
gaps: [],
|
|
3503
|
+
evidence: [],
|
|
3504
|
+
properties: { env: d.env },
|
|
3505
|
+
next: {
|
|
3506
|
+
command: `atticus query "kind:deployment env:${d.env}"`,
|
|
3507
|
+
why: "List what is deployed here."
|
|
3508
|
+
}
|
|
3509
|
+
});
|
|
3510
|
+
const parent = components.has(d.component) ? `component:${d.component}` : "estate";
|
|
3511
|
+
const id = add(fromRecord(d, `deployment:${d.id}`, "deployment", `${d.env} on ${d.platform}`, parent, { env: d.env, platform: d.platform, replicas: d.replicas }, parent === "estate" ? ["no-component"] : []));
|
|
3512
|
+
atoms.push({
|
|
3513
|
+
from: id,
|
|
3514
|
+
to: env,
|
|
3515
|
+
type: "deployed-to",
|
|
3516
|
+
evidence: evidenceOf(d.evidence),
|
|
3517
|
+
gaps: recordGaps(d)
|
|
3518
|
+
});
|
|
3519
|
+
}
|
|
3520
|
+
for (const i of all(records2, "Interface")) {
|
|
3521
|
+
const from = partNode(i.from);
|
|
3522
|
+
const to = partNode(i.to);
|
|
3523
|
+
const ev = evidenceOf(i.evidence);
|
|
3524
|
+
const gaps = [...recordGaps(i), ...i.conflicts.length ? ["conflicts"] : []];
|
|
3525
|
+
const target = i.endpoint ? endpointNode.get(i.endpoint) : void 0;
|
|
3526
|
+
if (target?.startsWith("topic:")) {
|
|
3527
|
+
atoms.push({ from, to: target, type: "publishes", evidence: ev, gaps });
|
|
3528
|
+
atoms.push({ from: to, to: target, type: "subscribes", evidence: ev, gaps });
|
|
3529
|
+
continue;
|
|
3530
|
+
}
|
|
3531
|
+
let caller = from;
|
|
3532
|
+
if (from.startsWith("component:")) {
|
|
3533
|
+
caller = add({
|
|
3534
|
+
id: `client:${i.from}.${slug2(i.to)}`,
|
|
3535
|
+
kind: "client",
|
|
3536
|
+
label: `Calls ${i.to}`,
|
|
3537
|
+
parent: from,
|
|
3538
|
+
state: "current",
|
|
3539
|
+
gaps: [],
|
|
3540
|
+
evidence: [],
|
|
3541
|
+
properties: { target: i.to },
|
|
3542
|
+
next: {
|
|
3543
|
+
command: `atticus query "kind:interface from:${i.from} to:${i.to}"`,
|
|
3544
|
+
why: "List each call this client makes."
|
|
3545
|
+
}
|
|
3546
|
+
});
|
|
3547
|
+
const c = nodes.get(caller);
|
|
3548
|
+
const callerSide = evidenceOf(i.evidence.filter((x) => x.side === "caller"));
|
|
3549
|
+
c.evidence = mergeEvidence(c.evidence, callerSide.length ? callerSide : ev);
|
|
3550
|
+
c.gaps = [.../* @__PURE__ */ new Set([...c.gaps, ...gaps])].sort();
|
|
3551
|
+
}
|
|
3552
|
+
atoms.push({
|
|
3553
|
+
from: caller,
|
|
3554
|
+
to: target ?? to,
|
|
3555
|
+
type: "calls",
|
|
3556
|
+
sync: i.sync,
|
|
3557
|
+
via: i.via,
|
|
3558
|
+
evidence: ev,
|
|
3559
|
+
gaps: [...gaps, ...i.endpoint && !target ? ["no-endpoint"] : []]
|
|
3560
|
+
});
|
|
3561
|
+
}
|
|
3562
|
+
for (const c of components.values())
|
|
3563
|
+
for (const u of c.uses ?? [])
|
|
3564
|
+
atoms.push({
|
|
3565
|
+
from: `component:${c.id}`,
|
|
3566
|
+
to: partNode(u),
|
|
3567
|
+
type: "uses",
|
|
3568
|
+
evidence: evidenceOf(c.evidence),
|
|
3569
|
+
gaps: recordGaps(c)
|
|
3570
|
+
});
|
|
3571
|
+
for (const t of tables) {
|
|
3572
|
+
const ev = evidenceOf(t.evidence);
|
|
3573
|
+
const part = (ref) => {
|
|
3574
|
+
const n = partNode(ref);
|
|
3575
|
+
const repo = components.get(ref)?.repo;
|
|
3576
|
+
const mine = repo ? ev.filter((e) => e.repo === repo) : [];
|
|
3577
|
+
return { n, ev: mine.length ? mine : ev };
|
|
3578
|
+
};
|
|
3579
|
+
for (const r of t.readers) {
|
|
3580
|
+
const p = part(r);
|
|
3581
|
+
atoms.push({
|
|
3582
|
+
from: p.n,
|
|
3583
|
+
to: `table:${t.id}`,
|
|
3584
|
+
type: "reads",
|
|
3585
|
+
evidence: p.ev,
|
|
3586
|
+
gaps: recordGaps(t)
|
|
3587
|
+
});
|
|
3588
|
+
}
|
|
3589
|
+
for (const w of t.writers) {
|
|
3590
|
+
const p = part(w);
|
|
3591
|
+
atoms.push({
|
|
3592
|
+
from: p.n,
|
|
3593
|
+
to: `table:${t.id}`,
|
|
3594
|
+
type: "writes",
|
|
3595
|
+
evidence: p.ev,
|
|
3596
|
+
gaps: recordGaps(t)
|
|
3597
|
+
});
|
|
3598
|
+
}
|
|
3599
|
+
}
|
|
3600
|
+
const tableIds = new Set(tables.map((t) => t.id));
|
|
3601
|
+
for (const k of all(records2, "IndexConstraint")) {
|
|
3602
|
+
if (k.constraintKind !== "foreign-key" || !k.references)
|
|
3603
|
+
continue;
|
|
3604
|
+
if (!tableIds.has(k.table))
|
|
3605
|
+
continue;
|
|
3606
|
+
atoms.push({
|
|
3607
|
+
from: `table:${k.table}`,
|
|
3608
|
+
to: tableIds.has(k.references.table) ? `table:${k.references.table}` : unknown(k.references.table),
|
|
3609
|
+
type: "references",
|
|
3610
|
+
evidence: evidenceOf(k.evidence),
|
|
3611
|
+
gaps: recordGaps(k)
|
|
3612
|
+
});
|
|
3613
|
+
}
|
|
3614
|
+
const kids = /* @__PURE__ */ new Map();
|
|
3615
|
+
for (const n of nodes.values()) {
|
|
3616
|
+
if (!n.parent)
|
|
3617
|
+
continue;
|
|
3618
|
+
if (!nodes.has(n.parent))
|
|
3619
|
+
n.parent = "estate";
|
|
3620
|
+
const l = kids.get(n.parent) ?? [];
|
|
3621
|
+
l.push(n.id);
|
|
3622
|
+
kids.set(n.parent, l);
|
|
3623
|
+
}
|
|
3624
|
+
const rank = (id) => KIND_ORDER.indexOf(nodes.get(id).kind);
|
|
3625
|
+
for (const l of kids.values())
|
|
3626
|
+
l.sort((a, b) => rank(a) - rank(b) || (a < b ? -1 : a > b ? 1 : 0));
|
|
3627
|
+
for (const n of nodes.values())
|
|
3628
|
+
n.gaps = [...new Set(n.gaps)].sort();
|
|
3629
|
+
const est = { nodes, kids, atoms, visible: /* @__PURE__ */ new Map() };
|
|
3630
|
+
for (const lens of Object.keys(LENS_KINDS))
|
|
3631
|
+
est.visible.set(lens, visibleIn(est, lens));
|
|
3632
|
+
return est;
|
|
3633
|
+
}
|
|
3634
|
+
function visibleIn(est, lens) {
|
|
3635
|
+
const kinds = LENS_KINDS[lens];
|
|
3636
|
+
const anchors = LENS_ANCHORS[lens];
|
|
3637
|
+
const ok = (id) => kinds.has(est.nodes.get(id).kind);
|
|
3638
|
+
if (!anchors)
|
|
3639
|
+
return new Set([...est.nodes.keys()].filter(ok));
|
|
3640
|
+
const seeds = /* @__PURE__ */ new Set();
|
|
3641
|
+
for (const n of est.nodes.values())
|
|
3642
|
+
if (anchors.has(n.kind))
|
|
3643
|
+
seeds.add(n.id);
|
|
3644
|
+
for (const a of est.atoms)
|
|
3645
|
+
if (LENS_EDGES[lens].has(a.type)) {
|
|
3646
|
+
seeds.add(a.from);
|
|
3647
|
+
seeds.add(a.to);
|
|
3648
|
+
}
|
|
3649
|
+
const out = /* @__PURE__ */ new Set(["estate"]);
|
|
3650
|
+
for (const s of seeds)
|
|
3651
|
+
for (let id = s; id; id = est.nodes.get(id)?.parent)
|
|
3652
|
+
if (ok(id))
|
|
3653
|
+
out.add(id);
|
|
3654
|
+
return out;
|
|
3655
|
+
}
|
|
3656
|
+
var ancestors = (est, id) => {
|
|
3657
|
+
const out = [];
|
|
3658
|
+
for (let p = est.nodes.get(id)?.parent; p; p = est.nodes.get(p)?.parent)
|
|
3659
|
+
out.unshift(p);
|
|
3660
|
+
return out;
|
|
3661
|
+
};
|
|
3662
|
+
var lift = (est, lens, id) => {
|
|
3663
|
+
const vis = est.visible.get(lens);
|
|
3664
|
+
let cur = id;
|
|
3665
|
+
while (cur && !vis.has(cur))
|
|
3666
|
+
cur = est.nodes.get(cur)?.parent;
|
|
3667
|
+
return cur ?? "estate";
|
|
3668
|
+
};
|
|
3669
|
+
var kidsIn = (est, lens, id) => (est.kids.get(id) ?? []).filter((k) => est.visible.get(lens).has(k));
|
|
3670
|
+
function toNode(est, lens, id) {
|
|
3671
|
+
const r = est.nodes.get(id);
|
|
3672
|
+
return {
|
|
3673
|
+
id: r.id,
|
|
3674
|
+
kind: r.kind,
|
|
3675
|
+
label: r.label,
|
|
3676
|
+
...r.parent ? { parent: r.parent } : {},
|
|
3677
|
+
childCount: kidsIn(est, lens, id).length,
|
|
3678
|
+
state: r.state,
|
|
3679
|
+
gaps: r.gaps,
|
|
3680
|
+
evidence: r.evidence,
|
|
3681
|
+
properties: r.properties,
|
|
3682
|
+
...r.record ? { record: { kind: r.record.kind, id: r.record.id } } : {},
|
|
3683
|
+
...r.next ? { next: r.next } : {}
|
|
3684
|
+
};
|
|
3685
|
+
}
|
|
3686
|
+
function need(est, lens, id) {
|
|
3687
|
+
if (!est.nodes.has(id))
|
|
3688
|
+
throw new NoSuchNode(`There is no node ${id}.`);
|
|
3689
|
+
if (!est.visible.get(lens).has(id))
|
|
3690
|
+
throw new NoSuchNode(`There is no node ${id} in the ${lens} lens.`);
|
|
3691
|
+
}
|
|
3692
|
+
function merge(mapped) {
|
|
3693
|
+
const by = /* @__PURE__ */ new Map();
|
|
3694
|
+
for (const { from, to, atom } of mapped) {
|
|
3695
|
+
const id = `${atom.type}:${from}>${to}`;
|
|
3696
|
+
const e = by.get(id);
|
|
3697
|
+
if (!e) {
|
|
3698
|
+
by.set(id, {
|
|
3699
|
+
id,
|
|
3700
|
+
from,
|
|
3701
|
+
to,
|
|
3702
|
+
type: atom.type,
|
|
3703
|
+
...atom.sync ? { sync: atom.sync } : {},
|
|
3704
|
+
...atom.via ? { via: atom.via } : {},
|
|
3705
|
+
count: 1,
|
|
3706
|
+
state: "current",
|
|
3707
|
+
gaps: [...new Set(atom.gaps)].sort(),
|
|
3708
|
+
evidence: mergeEvidence(atom.evidence)
|
|
3709
|
+
});
|
|
3710
|
+
continue;
|
|
3711
|
+
}
|
|
3712
|
+
e.count++;
|
|
3713
|
+
if (atom.sync && e.sync !== atom.sync)
|
|
3714
|
+
e.sync = "mixed";
|
|
3715
|
+
if (atom.via && e.via !== atom.via)
|
|
3716
|
+
e.via = "mixed";
|
|
3717
|
+
e.gaps = [.../* @__PURE__ */ new Set([...e.gaps, ...atom.gaps])].sort();
|
|
3718
|
+
e.evidence = mergeEvidence(e.evidence, atom.evidence);
|
|
3719
|
+
}
|
|
3720
|
+
return [...by.values()].sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
|
|
3721
|
+
}
|
|
3722
|
+
function reduceUses(edges) {
|
|
3723
|
+
const uses = edges.filter((e) => e.type === "uses");
|
|
3724
|
+
const out = /* @__PURE__ */ new Map();
|
|
3725
|
+
for (const e of uses)
|
|
3726
|
+
out.set(e.from, [...out.get(e.from) ?? [], e.to]);
|
|
3727
|
+
const reachesWithout = (from, to, skip) => {
|
|
3728
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3729
|
+
const stack = (out.get(from) ?? []).filter((n) => !(from === skip.from && n === skip.to));
|
|
3730
|
+
while (stack.length) {
|
|
3731
|
+
const n = stack.pop();
|
|
3732
|
+
if (n === to)
|
|
3733
|
+
return true;
|
|
3734
|
+
if (seen.has(n))
|
|
3735
|
+
continue;
|
|
3736
|
+
seen.add(n);
|
|
3737
|
+
stack.push(...out.get(n) ?? []);
|
|
3738
|
+
}
|
|
3739
|
+
return false;
|
|
3740
|
+
};
|
|
3741
|
+
const drop = new Set(uses.filter((e) => reachesWithout(e.from, e.to, e)).map((e) => e.id));
|
|
3742
|
+
return { edges: edges.filter((e) => !drop.has(e.id)), hidden: drop.size };
|
|
3743
|
+
}
|
|
3744
|
+
function nodeView(est, id, o) {
|
|
3745
|
+
const { lens } = o;
|
|
3746
|
+
const depth = Math.max(1, Math.min(3, o.depth ?? 1));
|
|
3747
|
+
need(est, lens, id);
|
|
3748
|
+
const shown = [];
|
|
3749
|
+
let level = [id];
|
|
3750
|
+
for (let d = 0; d < depth; d++) {
|
|
3751
|
+
level = level.flatMap((n) => kidsIn(est, lens, n));
|
|
3752
|
+
shown.push(...level);
|
|
3753
|
+
}
|
|
3754
|
+
const shownSet = new Set(shown);
|
|
3755
|
+
const upToN = [...ancestors(est, id), id];
|
|
3756
|
+
const inside = (x) => x === id || ancestors(est, x).includes(id);
|
|
3757
|
+
const place = (x) => {
|
|
3758
|
+
if (x === id)
|
|
3759
|
+
return x;
|
|
3760
|
+
if (inside(x)) {
|
|
3761
|
+
let cur = x;
|
|
3762
|
+
while (cur && !shownSet.has(cur) && cur !== id)
|
|
3763
|
+
cur = est.nodes.get(cur)?.parent;
|
|
3764
|
+
return cur ?? id;
|
|
3765
|
+
}
|
|
3766
|
+
if (upToN.includes(x))
|
|
3767
|
+
return x;
|
|
3768
|
+
const ax = [...ancestors(est, x), x];
|
|
3769
|
+
let i = 0;
|
|
3770
|
+
while (i < ax.length && ax[i] === upToN[i])
|
|
3771
|
+
i++;
|
|
3772
|
+
return ax[i] ?? x;
|
|
3773
|
+
};
|
|
3774
|
+
const types = LENS_EDGES[lens];
|
|
3775
|
+
const mapped = [];
|
|
3776
|
+
for (const atom of est.atoms) {
|
|
3777
|
+
if (!types.has(atom.type))
|
|
3778
|
+
continue;
|
|
3779
|
+
const a = lift(est, lens, atom.from);
|
|
3780
|
+
const b = lift(est, lens, atom.to);
|
|
3781
|
+
if (!inside(a) && !inside(b))
|
|
3782
|
+
continue;
|
|
3783
|
+
const from = place(a);
|
|
3784
|
+
const to = place(b);
|
|
3785
|
+
if (from === to)
|
|
3786
|
+
continue;
|
|
3787
|
+
mapped.push({ from, to, atom });
|
|
3788
|
+
}
|
|
3789
|
+
let edges = merge(mapped);
|
|
3790
|
+
let hidden = 0;
|
|
3791
|
+
if (!o.all)
|
|
3792
|
+
({ edges, hidden } = reduceUses(edges));
|
|
3793
|
+
const outside = [...new Set(edges.flatMap((e) => [e.from, e.to]))].filter((n) => n !== id && !shownSet.has(n)).sort();
|
|
3794
|
+
const children = shown.map((n) => toNode(est, lens, n));
|
|
3795
|
+
const firstTable = children.find((c) => c.kind === "table");
|
|
3796
|
+
const suggest = lens === "data" && firstTable ? { mode: "focus", id: firstTable.id } : shown.length + outside.length > DETAILED_LIMIT ? { mode: "group" } : void 0;
|
|
3797
|
+
return {
|
|
3798
|
+
lens,
|
|
3799
|
+
node: toNode(est, lens, id),
|
|
3800
|
+
path: ancestors(est, id).map((n) => toNode(est, lens, n)),
|
|
3801
|
+
children,
|
|
3802
|
+
outside: outside.map((n) => toNode(est, lens, n)),
|
|
3803
|
+
edges,
|
|
3804
|
+
reduced: !o.all,
|
|
3805
|
+
hiddenEdges: hidden,
|
|
3806
|
+
...suggest ? { suggest } : {}
|
|
3807
|
+
};
|
|
3808
|
+
}
|
|
3809
|
+
function focusView(est, id, o) {
|
|
3810
|
+
const { lens } = o;
|
|
3811
|
+
need(est, lens, id);
|
|
3812
|
+
const inside = (x) => x === id || ancestors(est, x).includes(id);
|
|
3813
|
+
const types = LENS_EDGES[lens];
|
|
3814
|
+
const ends = est.atoms.filter((a) => types.has(a.type)).map((atom) => {
|
|
3815
|
+
const a = lift(est, lens, atom.from);
|
|
3816
|
+
const b = lift(est, lens, atom.to);
|
|
3817
|
+
return { from: inside(a) ? id : a, to: inside(b) ? id : b, atom };
|
|
3818
|
+
}).filter((e) => e.from !== e.to);
|
|
3819
|
+
const near = /* @__PURE__ */ new Set();
|
|
3820
|
+
for (const e of ends) {
|
|
3821
|
+
if (e.from === id)
|
|
3822
|
+
near.add(e.to);
|
|
3823
|
+
if (e.to === id)
|
|
3824
|
+
near.add(e.from);
|
|
3825
|
+
}
|
|
3826
|
+
const set = /* @__PURE__ */ new Set([id, ...near]);
|
|
3827
|
+
return {
|
|
3828
|
+
lens,
|
|
3829
|
+
node: toNode(est, lens, id),
|
|
3830
|
+
path: ancestors(est, id).map((n) => toNode(est, lens, n)),
|
|
3831
|
+
neighbours: [...near].sort().map((n) => toNode(est, lens, n)),
|
|
3832
|
+
edges: merge(ends.filter((e) => set.has(e.from) && set.has(e.to)))
|
|
3833
|
+
};
|
|
3834
|
+
}
|
|
3835
|
+
|
|
3836
|
+
// packages/diagrams/dist/flow-sequence.js
|
|
3837
|
+
var CALLER = {
|
|
3838
|
+
endpoint: "Caller not known",
|
|
3839
|
+
listener: "A message",
|
|
3840
|
+
job: "A schedule",
|
|
3841
|
+
action: "A person",
|
|
3842
|
+
page: "A person"
|
|
3843
|
+
};
|
|
3844
|
+
function flowSequenceView(flow2, o = {}) {
|
|
3845
|
+
const participants = [];
|
|
3846
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3847
|
+
const lane = (id, label = id) => {
|
|
3848
|
+
if (seen.has(id))
|
|
3849
|
+
return;
|
|
3850
|
+
seen.add(id);
|
|
3851
|
+
participants.push({ id, label });
|
|
3852
|
+
};
|
|
3853
|
+
const caller = flow2.trigger.caller?.who ?? CALLER[flow2.trigger.kind];
|
|
3854
|
+
lane("caller", caller);
|
|
3855
|
+
lane(flow2.component);
|
|
3856
|
+
const steps = [
|
|
3857
|
+
{ kind: "message", from: "caller", to: flow2.component, label: flow2.trigger.what, async: false }
|
|
3858
|
+
];
|
|
3859
|
+
flow2.steps.forEach((s, i) => {
|
|
3860
|
+
const n = `${i + 1}. ${s.kind}`;
|
|
3861
|
+
if ((s.kind === "call-out" || s.kind === "publish") && s.to) {
|
|
3862
|
+
const target = s.kind === "publish" ? s.to.entry : s.to.component;
|
|
3863
|
+
lane(target);
|
|
3864
|
+
steps.push({
|
|
3865
|
+
kind: "message",
|
|
3866
|
+
from: flow2.component,
|
|
3867
|
+
to: target,
|
|
3868
|
+
label: `${n}: ${s.to.entry}`,
|
|
3869
|
+
async: s.kind === "publish"
|
|
3870
|
+
});
|
|
3871
|
+
} else if (s.store && (s.kind === "read" || s.kind === "lookup" || s.kind === "write")) {
|
|
3872
|
+
lane(s.store.table);
|
|
3873
|
+
const cols = s.store.columns.length ? ` (${s.store.columns.join(", ")})` : "";
|
|
3874
|
+
steps.push({
|
|
3875
|
+
kind: "message",
|
|
3876
|
+
from: flow2.component,
|
|
3877
|
+
to: s.store.table,
|
|
3878
|
+
label: `${n}: ${s.what}${cols}`,
|
|
3879
|
+
async: false
|
|
3880
|
+
});
|
|
3881
|
+
} else if (s.kind === "respond") {
|
|
3882
|
+
steps.push({
|
|
3883
|
+
kind: "message",
|
|
3884
|
+
from: flow2.component,
|
|
3885
|
+
to: "caller",
|
|
3886
|
+
label: `${n}: ${s.what}`,
|
|
3887
|
+
async: false,
|
|
3888
|
+
reply: true
|
|
3889
|
+
});
|
|
3890
|
+
} else
|
|
3891
|
+
steps.push({ kind: "note", over: [flow2.component], text: `${n}: ${s.what}` });
|
|
3892
|
+
});
|
|
3893
|
+
return {
|
|
3894
|
+
type: "sequence",
|
|
3895
|
+
title: flow2.name,
|
|
3896
|
+
...o.source ? { source: o.source } : {},
|
|
3897
|
+
participants,
|
|
3898
|
+
steps
|
|
3899
|
+
};
|
|
3900
|
+
}
|
|
3901
|
+
|
|
3902
|
+
// packages/verify/dist/map.js
|
|
3903
|
+
import { posix } from "node:path";
|
|
3904
|
+
var cmp2 = (a, b) => a < b ? -1 : a > b ? 1 : 0;
|
|
3905
|
+
var HEADER2 = (repo, what) => `<!-- Rendered by atticus map render ${repo} from ${what} (ADR 0124). Don't edit: correct the facts and run atticus verify ${repo} --write. -->`;
|
|
3906
|
+
function trustOf(report) {
|
|
3907
|
+
const r = report;
|
|
3908
|
+
if (!r || typeof r.accepted !== "number" || typeof r.rejected !== "number")
|
|
3909
|
+
return void 0;
|
|
3910
|
+
const ratio = typeof r.coverage?.ratio === "number" ? r.coverage.ratio : 0;
|
|
3911
|
+
const unknown = Array.isArray(r.coverage?.unknown) ? r.coverage.unknown.length : 0;
|
|
3912
|
+
const commit = typeof r.commit === "string" ? r.commit : void 0;
|
|
3913
|
+
const reviewed = typeof r.coverage?.reviewed === "number" ? r.coverage.reviewed : 0;
|
|
3914
|
+
return {
|
|
3915
|
+
accepted: r.accepted,
|
|
3916
|
+
rejected: r.rejected,
|
|
3917
|
+
ratio,
|
|
3918
|
+
unknown,
|
|
3919
|
+
...reviewed > 0 ? { reviewed } : {},
|
|
3920
|
+
...commit ? { commit } : {}
|
|
3921
|
+
};
|
|
3922
|
+
}
|
|
3923
|
+
function codeLinkerFor(o) {
|
|
3924
|
+
let web;
|
|
3925
|
+
const url = o.url?.trim();
|
|
3926
|
+
if (url && /^https?:\/\//i.test(url))
|
|
3927
|
+
web = url;
|
|
3928
|
+
else if (url) {
|
|
3929
|
+
const scp = /^[^@/\s]+@([^:/\s]+):(.+)$/.exec(url);
|
|
3930
|
+
if (scp)
|
|
3931
|
+
web = `https://${scp[1]}/${scp[2]}`;
|
|
3932
|
+
}
|
|
3933
|
+
if (web) {
|
|
3934
|
+
const base = web.replace(/\/+$/, "").replace(/\.git$/, "");
|
|
3935
|
+
const ado = /(^https?:\/\/dev\.azure\.com\/)|(\.visualstudio\.com\/)/i.test(base);
|
|
3936
|
+
return (path, line, commit) => {
|
|
3937
|
+
const p = encodeURI(path);
|
|
3938
|
+
if (ado)
|
|
3939
|
+
return `${base}?path=/${p}${commit ? `&version=GC${commit}` : ""}${line ? `&line=${line}` : ""}`;
|
|
3940
|
+
return `${base}/blob/${commit ?? "HEAD"}/${p}${line ? `#L${line}` : ""}`;
|
|
3941
|
+
};
|
|
3942
|
+
}
|
|
3943
|
+
const dir = (o.dir ?? "").replace(/\/+$/, "");
|
|
3944
|
+
return (path, line, _commit, from) => {
|
|
3945
|
+
const target = dir ? `${dir}/${path}` : path;
|
|
3946
|
+
const rel = posix.relative(posix.dirname(from), target);
|
|
3947
|
+
return `${encodeURI(rel)}${line ? `#L${line}` : ""}`;
|
|
3948
|
+
};
|
|
3949
|
+
}
|
|
3950
|
+
function ownEvidence(r, repo) {
|
|
3951
|
+
const ev = r.evidence.filter((e) => e.path && (!e.repo || e.repo === repo));
|
|
3952
|
+
return ev[0];
|
|
3953
|
+
}
|
|
3954
|
+
function cite(c, e) {
|
|
3955
|
+
if (!e?.path)
|
|
3956
|
+
return "(no citation)";
|
|
3957
|
+
const commit = e.commit ?? c.i.trust?.commit;
|
|
3958
|
+
const at = `${e.path}${e.line ? `:${e.line}` : ""}`;
|
|
3959
|
+
return `[${at}](${c.i.codeLink(e.path, e.line, commit, c.from)})`;
|
|
3960
|
+
}
|
|
3961
|
+
var code = (s) => `\`${s.replace(/`/g, "'")}\``;
|
|
3962
|
+
var list = (xs) => xs.join(", ");
|
|
3963
|
+
var pct = (r) => `${Math.round(r * 100)}%`;
|
|
3964
|
+
var plural2 = (n, one, many = `${one}s`) => `${n} ${n === 1 ? one : many}`;
|
|
3965
|
+
function records(c, kind) {
|
|
3966
|
+
return c.i.records.filter((r) => r.kind === kind).sort((a, b) => cmp2(a.id, b.id));
|
|
3967
|
+
}
|
|
3968
|
+
function link(from, to) {
|
|
3969
|
+
return encodeURI(posix.relative(posix.dirname(from), to));
|
|
3970
|
+
}
|
|
3971
|
+
function questionsFor(c, e) {
|
|
3972
|
+
if (!e?.path || !e.line)
|
|
3973
|
+
return [];
|
|
3974
|
+
const at = `${e.path}:${e.line}`;
|
|
3975
|
+
return (c.i.questions ?? []).filter((q) => q.evidence.includes(at));
|
|
3976
|
+
}
|
|
3977
|
+
function asked(c, e) {
|
|
3978
|
+
const qs = questionsFor(c, e).sort((a, b) => cmp2(a.id, b.id));
|
|
3979
|
+
if (!qs.length)
|
|
3980
|
+
return "";
|
|
3981
|
+
return ` Asked: ${qs.map((q) => `[${q.question}](${link(c.from, `${c.i.paths.questions}/${q.id}.yaml`)})`).join("; ")}`;
|
|
3982
|
+
}
|
|
3983
|
+
var APIGEE_FILE = /(^|\/)(apiproxy|sharedflowbundle)\//;
|
|
3984
|
+
var lineKey = (e) => e?.path ? `${e.path}:${e.line ?? ""}` : "";
|
|
3985
|
+
function callersOf(c, e) {
|
|
3986
|
+
const own = ownEvidence(e, c.i.repo);
|
|
3987
|
+
const at = lineKey(own);
|
|
3988
|
+
const route = at ? records(c, "GatewayRoute").find((r) => lineKey(r.evidence[0]) === at) : void 0;
|
|
3989
|
+
if (!route && !(own?.path && APIGEE_FILE.test(own.path)))
|
|
3990
|
+
return void 0;
|
|
3991
|
+
if (!route)
|
|
3992
|
+
return "callers unknown";
|
|
3993
|
+
const grants = records(c, "AccessGrant").filter((g) => g.resource === route.id);
|
|
3994
|
+
const kindOf = (g) => g.flags?.find((f) => f.startsWith("caller:"))?.slice("caller:".length) ?? g.subjectKind;
|
|
3995
|
+
const grantAt = new Map(grants.map((g) => [lineKey(g.evidence[0]), g]));
|
|
3996
|
+
const linked = /* @__PURE__ */ new Set();
|
|
3997
|
+
const declared = [];
|
|
3998
|
+
const scanned = /* @__PURE__ */ new Set();
|
|
3999
|
+
for (const i of records(c, "Interface").filter((x) => x.route === route.id)) {
|
|
4000
|
+
if (i.confidence !== "asserted") {
|
|
4001
|
+
scanned.add(i.from);
|
|
4002
|
+
continue;
|
|
4003
|
+
}
|
|
4004
|
+
const g = i.evidence.map((x) => grantAt.get(lineKey(x))).find((x) => !!x);
|
|
4005
|
+
if (g)
|
|
4006
|
+
linked.add(g.id);
|
|
4007
|
+
const sys = c.byId.get(i.from);
|
|
4008
|
+
const name = sys?.kind === "System" ? sys.name : i.from;
|
|
4009
|
+
declared.push(`${name} (declared${g ? `; ${kindOf(g)} ${g.subject}` : ""})`);
|
|
4010
|
+
}
|
|
4011
|
+
const order = (k) => k === "product" ? 1 : 0;
|
|
4012
|
+
const named = grants.filter((g) => !linked.has(g.id)).sort((a, b) => order(kindOf(a)) - order(kindOf(b)) || cmp2(a.subject, b.subject)).map((g) => `${kindOf(g)} ${g.subject}`);
|
|
4013
|
+
const all2 = [...new Set(declared)].sort(cmp2).concat(named, [...scanned].sort(cmp2));
|
|
4014
|
+
return all2.length ? `callers ${list(all2)}` : "callers unknown";
|
|
4015
|
+
}
|
|
4016
|
+
function serves(c) {
|
|
4017
|
+
const eps = records(c, "Endpoint").filter((e) => c.comps.has(e.component));
|
|
4018
|
+
if (!eps.length)
|
|
4019
|
+
return ["Nothing read yet."];
|
|
4020
|
+
const out = [];
|
|
4021
|
+
for (const comp of [...new Set(eps.map((e) => e.component))].sort(cmp2)) {
|
|
4022
|
+
out.push(`### ${comp}`, "");
|
|
4023
|
+
const mine = eps.filter((e) => e.component === comp).sort((a, b) => cmp2(a.pathOrTopic, b.pathOrTopic) || cmp2(a.method ?? "", b.method ?? "") || cmp2(a.id, b.id));
|
|
4024
|
+
for (const e of mine) {
|
|
4025
|
+
const what = [e.method, e.pathOrTopic].filter(Boolean).join(" ");
|
|
4026
|
+
const notes = [e.endpointType, ...e.auth ? [`auth ${e.auth}`] : []];
|
|
4027
|
+
const who = callersOf(c, e);
|
|
4028
|
+
if (who)
|
|
4029
|
+
notes.push(who);
|
|
4030
|
+
for (const side of ["request", "response"]) {
|
|
4031
|
+
const s = c.byId.get(`${e.id}.${side}`);
|
|
4032
|
+
if (s?.kind === "Schema" && s.fields.length)
|
|
4033
|
+
notes.push(`${side}: ${list(s.fields.map((f) => f.name))}`);
|
|
4034
|
+
}
|
|
4035
|
+
out.push(`- ${code(what)} (${notes.join("; ")}): ${cite(c, ownEvidence(e, c.i.repo))}`);
|
|
4036
|
+
}
|
|
4037
|
+
out.push("");
|
|
4038
|
+
}
|
|
4039
|
+
return out.slice(0, -1);
|
|
4040
|
+
}
|
|
4041
|
+
var LOOSE_CALLS = /* @__PURE__ */ new Set(["http-call", "call", "event-publish", "graphql-call", "grpc-call"]);
|
|
4042
|
+
function targetName(c, id) {
|
|
4043
|
+
const r = c.byId.get(id);
|
|
4044
|
+
if (r?.kind === "System")
|
|
4045
|
+
return `${id} (${r.name}, ${r.boundary})`;
|
|
4046
|
+
if (r?.kind === "Component")
|
|
4047
|
+
return `${id} (${r.system}${r.repo ? `, ${r.repo}` : ""})`;
|
|
4048
|
+
return id;
|
|
4049
|
+
}
|
|
4050
|
+
function gatewayHops(c, i) {
|
|
4051
|
+
if (i.via !== "gateway")
|
|
4052
|
+
return [];
|
|
4053
|
+
const order = i.evidence.filter((e) => e.side === "gateway" && e.path).map((e) => `${e.repo ?? ""}|${e.path}:${e.line}`);
|
|
4054
|
+
if (!order.length)
|
|
4055
|
+
return [];
|
|
4056
|
+
return records(c, "GatewayRoute").flatMap((r) => {
|
|
4057
|
+
const own = r.evidence[0];
|
|
4058
|
+
const at = own?.path ? `${own.repo ?? ""}|${own.path}:${own.line}` : "";
|
|
4059
|
+
const n = order.indexOf(at);
|
|
4060
|
+
return n < 0 ? [] : [{ r, n }];
|
|
4061
|
+
}).sort((a, b) => a.n - b.n || cmp2(a.r.id, b.r.id)).map((x) => x.r);
|
|
4062
|
+
}
|
|
4063
|
+
function hopText(r) {
|
|
4064
|
+
const what = [
|
|
4065
|
+
...r.policies.length ? [list(r.policies)] : [],
|
|
4066
|
+
r.scopes.length ? `scopes ${list(r.scopes)}` : "no scope"
|
|
4067
|
+
];
|
|
4068
|
+
return `${r.proxy} ${r.basepath} (${what.join("; ")})`;
|
|
4069
|
+
}
|
|
4070
|
+
function calls(c) {
|
|
4071
|
+
const ifs = records(c, "Interface").filter((i) => c.comps.has(i.from));
|
|
4072
|
+
const loose = records(c, "Unresolved").filter((u) => u.owner && c.comps.has(u.owner) && LOOSE_CALLS.has(u.unresolvedKind));
|
|
4073
|
+
if (!ifs.length && !loose.length)
|
|
4074
|
+
return ["Nothing read yet."];
|
|
4075
|
+
const out = [];
|
|
4076
|
+
for (const to of [...new Set(ifs.map((i) => i.to))].sort(cmp2)) {
|
|
4077
|
+
out.push(`### ${targetName(c, to)}`, "");
|
|
4078
|
+
for (const i of ifs.filter((x) => x.to === to).sort((a, b) => cmp2(a.normalizedPath ?? "", b.normalizedPath ?? "") || cmp2(a.id, b.id))) {
|
|
4079
|
+
const what = [i.method, i.normalizedPath].filter(Boolean).join(" ");
|
|
4080
|
+
const req = c.byId.get(`${i.id}.request`);
|
|
4081
|
+
const hops = gatewayHops(c, i);
|
|
4082
|
+
const notes = [
|
|
4083
|
+
`from ${i.from}`,
|
|
4084
|
+
i.sync,
|
|
4085
|
+
hops.length ? `via ${hops.map(hopText).join(" then ")}` : `via ${i.via}`,
|
|
4086
|
+
...hops.length && i.auth ? [`the caller meets ${i.auth}`] : [],
|
|
4087
|
+
...i.endpoint ? [`reaches ${i.endpoint}`] : [],
|
|
4088
|
+
...req?.kind === "Schema" && req.fields.length ? [`sends ${list(req.fields.map((f) => f.name))}`] : []
|
|
4089
|
+
];
|
|
4090
|
+
const e = i.evidence.find((x) => x.side === "caller" && x.repo === c.i.repo) ?? ownEvidence(i, c.i.repo);
|
|
4091
|
+
out.push(`- ${code(what || i.id)} (${notes.join("; ")}): ${cite(c, e)}`);
|
|
4092
|
+
}
|
|
4093
|
+
out.push("");
|
|
4094
|
+
}
|
|
4095
|
+
if (loose.length) {
|
|
4096
|
+
out.push("### Not joined yet", "");
|
|
4097
|
+
for (const u of loose.sort((a, b) => cmp2(a.raw, b.raw) || cmp2(a.id, b.id))) {
|
|
4098
|
+
const e = ownEvidence(u, c.i.repo);
|
|
4099
|
+
out.push(`- ${code(u.raw)} (from ${u.owner}; ${u.unresolvedKind}): ${cite(c, e)}.${asked(c, e)}`);
|
|
4100
|
+
}
|
|
4101
|
+
out.push("");
|
|
4102
|
+
}
|
|
4103
|
+
return out.slice(0, -1);
|
|
4104
|
+
}
|
|
4105
|
+
function stores(c) {
|
|
4106
|
+
const tables = records(c, "Table").filter((t) => t.ownerComponent && c.comps.has(t.ownerComponent) || t.readers.some((x) => c.comps.has(x)) || t.writers.some((x) => c.comps.has(x)));
|
|
4107
|
+
if (!tables.length)
|
|
4108
|
+
return ["Nothing read yet."];
|
|
4109
|
+
const columns = records(c, "Column");
|
|
4110
|
+
const out = [];
|
|
4111
|
+
for (const ds of [...new Set(tables.map((t) => t.dataStore))].sort(cmp2)) {
|
|
4112
|
+
const store = c.byId.get(ds);
|
|
4113
|
+
out.push(`### ${ds}${store?.kind === "DataStore" ? ` (${store.storeType})` : ""}`, "");
|
|
4114
|
+
for (const t of tables.filter((x) => x.dataStore === ds)) {
|
|
4115
|
+
const how = [
|
|
4116
|
+
...t.readers.length ? [`read by ${list(t.readers)}`] : [],
|
|
4117
|
+
...t.writers.length ? [`written by ${list(t.writers)}`] : []
|
|
4118
|
+
];
|
|
4119
|
+
out.push(`- ${code(t.name)} (${t.tableKind}${how.length ? `; ${how.join("; ")}` : ""}): ${cite(c, ownEvidence(t, c.i.repo))}`);
|
|
4120
|
+
for (const col of columns.filter((x) => x.table === t.id).sort((a, b) => cmp2(a.name, b.name))) {
|
|
4121
|
+
const notes = [
|
|
4122
|
+
col.type,
|
|
4123
|
+
...col.nullable === false ? ["not null"] : [],
|
|
4124
|
+
...col.classification ? [col.classification] : []
|
|
4125
|
+
];
|
|
4126
|
+
out.push(` - ${code(col.name)} (${notes.join("; ")}): ${cite(c, ownEvidence(col, c.i.repo))}`);
|
|
4127
|
+
}
|
|
4128
|
+
}
|
|
4129
|
+
out.push("");
|
|
4130
|
+
}
|
|
4131
|
+
return out.slice(0, -1);
|
|
4132
|
+
}
|
|
4133
|
+
function deploys(c) {
|
|
4134
|
+
const ds = records(c, "Deployment").filter((d) => c.comps.has(d.component));
|
|
4135
|
+
if (!ds.length)
|
|
4136
|
+
return ["Nothing read yet."];
|
|
4137
|
+
return ds.map((d) => {
|
|
4138
|
+
const notes = [
|
|
4139
|
+
`env ${d.env}`,
|
|
4140
|
+
...d.replicas !== void 0 ? [plural2(d.replicas, "replica")] : []
|
|
4141
|
+
];
|
|
4142
|
+
return `- ${code(d.component)} on ${d.platform} (${notes.join("; ")}): ${cite(c, ownEvidence(d, c.i.repo))}`;
|
|
4143
|
+
});
|
|
4144
|
+
}
|
|
4145
|
+
var NAMES_A_SYSTEM = /^[a-z][a-z0-9+.-]*:\/\//i;
|
|
4146
|
+
function config(c) {
|
|
4147
|
+
const ss = records(c, "Setting").filter((s) => c.comps.has(s.component) && (s.target || NAMES_A_SYSTEM.test(s.valueOrRef)));
|
|
4148
|
+
if (!ss.length)
|
|
4149
|
+
return ["Nothing read yet."];
|
|
4150
|
+
return ss.sort((a, b) => cmp2(a.key, b.key) || cmp2(a.id, b.id)).map((s) => {
|
|
4151
|
+
const notes = [s.settingKind, `in ${s.component}`, ...s.target ? [`for ${s.target}`] : []];
|
|
4152
|
+
return `- ${code(`${s.key} = ${s.valueOrRef}`)} (${notes.join("; ")}): ${cite(c, ownEvidence(s, c.i.repo))}`;
|
|
4153
|
+
});
|
|
4154
|
+
}
|
|
4155
|
+
var flowFile = (f) => `flows/${f.id}.md`;
|
|
4156
|
+
function flowsList(c) {
|
|
4157
|
+
if (!c.i.flows.length)
|
|
4158
|
+
return ["Nothing read yet."];
|
|
4159
|
+
return sortedFlows(c.i.flows).map((f) => {
|
|
4160
|
+
const notes = [
|
|
4161
|
+
`starts at ${code(f.trigger.what)}`,
|
|
4162
|
+
plural2(f.steps.length, "step"),
|
|
4163
|
+
plural2(f.errors.length, "error path")
|
|
4164
|
+
];
|
|
4165
|
+
return `- [${f.name}](${flowFile(f)}) (${notes.join("; ")}): ${cite(c, f.trigger.evidence[0])}`;
|
|
4166
|
+
});
|
|
4167
|
+
}
|
|
4168
|
+
function contracts(c, files) {
|
|
4169
|
+
const out = [];
|
|
4170
|
+
for (const [rel] of Object.entries(files).filter(([k]) => k.startsWith("contracts/"))) {
|
|
4171
|
+
const comp = rel.slice("contracts/".length, -".openapi.yaml".length);
|
|
4172
|
+
const n = restEndpoints(c, comp).length;
|
|
4173
|
+
out.push(`- [${rel.slice("contracts/".length)}](${rel}): OpenAPI 3.1, ${plural2(n, "REST entry", "REST entries")} of ${comp}`);
|
|
4174
|
+
}
|
|
4175
|
+
const events = records(c, "Endpoint").filter((e) => c.comps.has(e.component) && (e.endpointType === "event-topic" || e.endpointType === "mq-queue"));
|
|
4176
|
+
const publishes = records(c, "Interface").filter((i) => c.comps.has(i.from) && i.sync === "async");
|
|
4177
|
+
if (events.length || publishes.length) {
|
|
4178
|
+
out.push("", "Events (listed here; no AsyncAPI file is written):", "");
|
|
4179
|
+
for (const e of events)
|
|
4180
|
+
out.push(`- ${code(e.pathOrTopic)} (${e.endpointType}, listened to by ${e.component}): ${cite(c, ownEvidence(e, c.i.repo))}`);
|
|
4181
|
+
for (const i of publishes)
|
|
4182
|
+
out.push(`- ${code(i.normalizedPath ?? i.id)} (published by ${i.from} to ${i.to}): ${cite(c, ownEvidence(i, c.i.repo))}`);
|
|
4183
|
+
}
|
|
4184
|
+
return out.length ? out : ["Nothing read yet."];
|
|
4185
|
+
}
|
|
4186
|
+
function openQuestions(c) {
|
|
4187
|
+
const qs = [...c.i.questions ?? []].sort((a, b) => cmp2(a.question, b.question) || cmp2(a.id, b.id));
|
|
4188
|
+
if (!qs.length)
|
|
4189
|
+
return ["None open."];
|
|
4190
|
+
return qs.map((q) => `- [${q.question}](${link(c.from, `${c.i.paths.questions}/${q.id}.yaml`)}): cites ${list(q.evidence.map((e) => code(e)))}`);
|
|
4191
|
+
}
|
|
4192
|
+
function aboutLine(a, repo) {
|
|
4193
|
+
if (!a)
|
|
4194
|
+
return [`**What it is:** Nothing says yet: \`atticus repo describe ${repo}\`.`, ""];
|
|
4195
|
+
const who = a.by ? `, ${a.by}` : "";
|
|
4196
|
+
return [
|
|
4197
|
+
a.trust === "suggested" ? `**What it is (unconfirmed draft${who}):** ${a.text} \u2014 drafted from the code and waiting for a person: \`atticus repo describe ${repo} --accept\`.` : `**What it is${who ? ` (${a.by})` : ""}:** ${a.text}`,
|
|
4198
|
+
"",
|
|
4199
|
+
"A person's note from the manifest, not read from the code: where the two disagree, the facts below are what the code says.",
|
|
4200
|
+
""
|
|
4201
|
+
];
|
|
4202
|
+
}
|
|
4203
|
+
function builtOnLine(c) {
|
|
4204
|
+
const f = c.i.frameworks;
|
|
4205
|
+
if (!f?.built.length)
|
|
4206
|
+
return [];
|
|
4207
|
+
const one = (b) => `${b.name}${b.version ? ` ${b.version}` : ""}${b.component ? ` (${b.component})` : ""} ${cite(c, b.cite)}`;
|
|
4208
|
+
return [
|
|
4209
|
+
`**Built on:** ${f.built.map(one).join(", ")} \u2014 read from the build files, each on the line that declares it.`,
|
|
4210
|
+
"",
|
|
4211
|
+
...f.gaps?.length ? [
|
|
4212
|
+
...f.gaps.map((g) => `**Open: ${g.framework} shows none of its patterns here.** ${g.question}`),
|
|
4213
|
+
""
|
|
4214
|
+
] : []
|
|
4215
|
+
];
|
|
4216
|
+
}
|
|
4217
|
+
function trustLine(t, repo) {
|
|
4218
|
+
if (!t)
|
|
4219
|
+
return `**Trust:** No verify report yet: run \`atticus verify ${repo}\`.`;
|
|
4220
|
+
return `**Trust:** ${plural2(t.accepted, "fact")} verified on their lines; coverage ${pct(t.ratio)} of source files (${plural2(t.unknown, "unknown file")}: cited by no fact and opened through no Atticus tool)${t.reviewed ? `; ${plural2(t.reviewed, "file")} reviewed by the reader with nothing to record (each with why, in the verify report)` : ""}; ${t.rejected} rejected.${t.commit ? ` Commit ${t.commit}.` : ""}`;
|
|
4221
|
+
}
|
|
4222
|
+
var sortedFlows = (fs) => [...fs].sort((a, b) => cmp2(a.id, b.id));
|
|
4223
|
+
var CONFIRMED = {
|
|
4224
|
+
check: "Confirmed by the line check",
|
|
4225
|
+
ai: "Confirmed by the assistant's check",
|
|
4226
|
+
person: "Confirmed by a person"
|
|
4227
|
+
};
|
|
4228
|
+
function flowPage(c, f) {
|
|
4229
|
+
const spec = `${c.i.paths.specs}/${f.id}.yaml`;
|
|
4230
|
+
const out = [
|
|
4231
|
+
`# ${f.name}`,
|
|
4232
|
+
"",
|
|
4233
|
+
HEADER2(c.i.repo, spec),
|
|
4234
|
+
"",
|
|
4235
|
+
`- Component: ${f.component}`,
|
|
4236
|
+
`- Starts at: ${f.trigger.kind} ${code(f.trigger.what)}${f.trigger.ref ? ` (${code(f.trigger.ref)})` : ""}: ${cite(c, f.trigger.evidence[0])}`,
|
|
4237
|
+
`- Spec: [${posix.basename(spec)}](${link(c.from, spec)})`,
|
|
4238
|
+
"",
|
|
4239
|
+
"## Steps",
|
|
4240
|
+
""
|
|
4241
|
+
];
|
|
4242
|
+
f.steps.forEach((s, n) => {
|
|
4243
|
+
const head = s.to && (s.kind === "call-out" || s.kind === "publish") ? ` to ${s.to.component} ${code(s.to.entry)}` : s.store ? ` ${code(s.store.table)}` : "";
|
|
4244
|
+
out.push(`${n + 1}. **${s.kind}**${head}: ${s.what}. ${cite(c, s.evidence[0])}`);
|
|
4245
|
+
const fields = [];
|
|
4246
|
+
const d = s.data;
|
|
4247
|
+
if (d)
|
|
4248
|
+
for (const k of ["sends", "returns", "reads", "checks", "from", "to"]) {
|
|
4249
|
+
const names = d[k] ?? [];
|
|
4250
|
+
if (names.length)
|
|
4251
|
+
fields.push(`${k} ${list(names)}`);
|
|
4252
|
+
}
|
|
4253
|
+
if (s.store?.columns.length)
|
|
4254
|
+
fields.push(`columns ${list(s.store.columns)}`);
|
|
4255
|
+
if (fields.length)
|
|
4256
|
+
out.push(` - Fields: ${fields.join("; ")}`);
|
|
4257
|
+
if (s.refs.length)
|
|
4258
|
+
out.push(` - Refs: ${list(s.refs.map(code))}`);
|
|
4259
|
+
out.push(` - ${s.confirmedBy ? CONFIRMED[s.confirmedBy] : "Not confirmed yet"}`);
|
|
4260
|
+
});
|
|
4261
|
+
out.push("", "## Errors", "");
|
|
4262
|
+
if (!f.errors.length)
|
|
4263
|
+
out.push("None read.");
|
|
4264
|
+
for (const e of f.errors)
|
|
4265
|
+
out.push(`- When ${e.when}: ${e.then}. ${cite(c, e.evidence[0])}`);
|
|
4266
|
+
out.push("", "## Sequence", "", "```mermaid", toMermaid(flowSequenceView(f, { source: spec })).trimEnd(), "```", "");
|
|
4267
|
+
return out.join("\n");
|
|
4268
|
+
}
|
|
4269
|
+
function restEndpoints(c, comp) {
|
|
4270
|
+
return records(c, "Endpoint").filter((e) => e.component === comp && e.endpointType === "rest");
|
|
4271
|
+
}
|
|
4272
|
+
var OPENAPI_KEYS = [
|
|
4273
|
+
"openapi",
|
|
4274
|
+
"info",
|
|
4275
|
+
"title",
|
|
4276
|
+
"version",
|
|
4277
|
+
"description",
|
|
4278
|
+
"paths",
|
|
4279
|
+
"operationId",
|
|
4280
|
+
"summary",
|
|
4281
|
+
"parameters",
|
|
4282
|
+
"name",
|
|
4283
|
+
"in",
|
|
4284
|
+
"required",
|
|
4285
|
+
"requestBody",
|
|
4286
|
+
"responses",
|
|
4287
|
+
"content",
|
|
4288
|
+
"schema",
|
|
4289
|
+
"type",
|
|
4290
|
+
"properties",
|
|
4291
|
+
"items"
|
|
4292
|
+
];
|
|
4293
|
+
function jsonType(t) {
|
|
4294
|
+
const x = (t ?? "").toLowerCase().replace(/\(.*$/, "").trim();
|
|
4295
|
+
if (/^(int|integer|long|short|bigint|smallint|int32|int64|byte)$/.test(x))
|
|
4296
|
+
return { type: "integer" };
|
|
4297
|
+
if (/^(decimal|numeric|number|double|float|real|bigdecimal|money)$/.test(x))
|
|
4298
|
+
return { type: "number" };
|
|
4299
|
+
if (/^(bool|boolean)$/.test(x))
|
|
4300
|
+
return { type: "boolean" };
|
|
4301
|
+
if (/(\[\]$)|^(list|array|set)\b/.test(x))
|
|
4302
|
+
return { type: "array", items: {} };
|
|
4303
|
+
if (!x || /^(string|str|text|varchar|char|uuid|date|datetime|timestamp|instant)$/.test(x))
|
|
4304
|
+
return { type: "string" };
|
|
4305
|
+
return { type: "string", "x-atticus-type": t };
|
|
4306
|
+
}
|
|
4307
|
+
function bodyOf(s) {
|
|
4308
|
+
if (s?.kind !== "Schema" || !s.fields.length)
|
|
4309
|
+
return void 0;
|
|
4310
|
+
const properties = {};
|
|
4311
|
+
for (const f of [...s.fields].sort((a, b) => cmp2(a.name, b.name)))
|
|
4312
|
+
properties[f.name] = {
|
|
4313
|
+
...jsonType(f.type),
|
|
4314
|
+
...f.classification ? { "x-atticus-classification": f.classification } : {}
|
|
4315
|
+
};
|
|
4316
|
+
return { content: { "application/json": { schema: { type: "object", properties } } } };
|
|
4317
|
+
}
|
|
4318
|
+
function openApiOf(i, comp) {
|
|
4319
|
+
const c = ctxOf(i, "");
|
|
4320
|
+
const eps = restEndpoints(c, comp);
|
|
4321
|
+
if (!eps.length)
|
|
4322
|
+
return void 0;
|
|
4323
|
+
const paths = {};
|
|
4324
|
+
for (const e of eps) {
|
|
4325
|
+
const params = [...e.pathOrTopic.matchAll(/\{([^}]+)\}/g)].map((m) => ({
|
|
4326
|
+
name: m[1],
|
|
4327
|
+
in: "path",
|
|
4328
|
+
required: true,
|
|
4329
|
+
schema: { type: "string" }
|
|
4330
|
+
}));
|
|
4331
|
+
const req = bodyOf(c.byId.get(`${e.id}.request`));
|
|
4332
|
+
const res = bodyOf(c.byId.get(`${e.id}.response`));
|
|
4333
|
+
const ev = ownEvidence(e, i.repo);
|
|
4334
|
+
(paths[e.pathOrTopic] ??= {})[(e.method ?? "get").toLowerCase()] = {
|
|
4335
|
+
operationId: e.id,
|
|
4336
|
+
...params.length ? { parameters: params } : {},
|
|
4337
|
+
...req ? { requestBody: { required: true, ...req } } : {},
|
|
4338
|
+
responses: res ? { "200": { description: "What the code returns", ...res } } : { default: { description: "The response isn't read yet." } },
|
|
4339
|
+
...e.auth ? { "x-atticus-auth": e.auth } : {},
|
|
4340
|
+
...ev?.path ? { "x-atticus-evidence": `${ev.path}${ev.line ? `:${ev.line}` : ""}` } : {}
|
|
4341
|
+
};
|
|
4342
|
+
}
|
|
4343
|
+
const commit = eps.map((e) => ownEvidence(e, i.repo)?.commit).find(Boolean) ?? i.trust?.commit;
|
|
4344
|
+
const doc = {
|
|
4345
|
+
openapi: "3.1.0",
|
|
4346
|
+
info: {
|
|
4347
|
+
title: comp,
|
|
4348
|
+
version: commit ?? "unversioned",
|
|
4349
|
+
description: `The REST entries of ${comp} as ${i.repo}'s code shows them${commit ? ` at ${commit}` : ""}, from the assistant's verified facts (ADR 0124). Rendered by atticus map render ${i.repo}; don't edit.`
|
|
4350
|
+
},
|
|
4351
|
+
paths
|
|
4352
|
+
};
|
|
4353
|
+
return toCanonicalYaml(doc, OPENAPI_KEYS);
|
|
4354
|
+
}
|
|
4355
|
+
function ctxOf(i, file) {
|
|
4356
|
+
const comps = new Set(i.records.filter((r) => r.kind === "Component" && r.repo === i.repo).map((r) => r.id));
|
|
4357
|
+
for (const f of i.flows)
|
|
4358
|
+
comps.add(f.component);
|
|
4359
|
+
return {
|
|
4360
|
+
i,
|
|
4361
|
+
comps,
|
|
4362
|
+
byId: new Map(i.records.map((r) => [r.id, r])),
|
|
4363
|
+
from: `${i.paths.map}/${file}`
|
|
4364
|
+
};
|
|
4365
|
+
}
|
|
4366
|
+
function renderMap(i) {
|
|
4367
|
+
const files = {};
|
|
4368
|
+
const base = ctxOf(i, "map.md");
|
|
4369
|
+
for (const comp of [...base.comps].sort(cmp2)) {
|
|
4370
|
+
const yaml = openApiOf(i, comp);
|
|
4371
|
+
if (yaml)
|
|
4372
|
+
files[`contracts/${comp}.openapi.yaml`] = yaml;
|
|
4373
|
+
}
|
|
4374
|
+
for (const f of sortedFlows(i.flows))
|
|
4375
|
+
files[flowFile(f)] = flowPage(ctxOf(i, flowFile(f)), f);
|
|
4376
|
+
const section = (title, body) => [`## ${title}`, "", ...body, ""];
|
|
4377
|
+
files["map.md"] = [
|
|
4378
|
+
`# The map of ${i.repo}`,
|
|
4379
|
+
"",
|
|
4380
|
+
HEADER2(i.repo, "the catalog and its specs"),
|
|
4381
|
+
"",
|
|
4382
|
+
...aboutLine(i.about, i.repo),
|
|
4383
|
+
...builtOnLine(base),
|
|
4384
|
+
trustLine(i.trust, i.repo),
|
|
4385
|
+
"",
|
|
4386
|
+
...section("What it serves", serves(base)),
|
|
4387
|
+
...section("What it calls", calls(base)),
|
|
4388
|
+
...section("What it stores", stores(base)),
|
|
4389
|
+
...section("Where it runs", deploys(base)),
|
|
4390
|
+
...section("Config that names other systems", config(base)),
|
|
4391
|
+
...section("Flows", flowsList(base)),
|
|
4392
|
+
...section("Contracts", contracts(base, files)),
|
|
4393
|
+
...section("Open questions", openQuestions(base))
|
|
4394
|
+
].join("\n");
|
|
4395
|
+
return Object.fromEntries(Object.entries(files).sort(([a], [b]) => cmp2(a, b)));
|
|
4396
|
+
}
|
|
4397
|
+
|
|
4398
|
+
// packages/verify/dist/carry.js
|
|
4399
|
+
function rereadPlan(o) {
|
|
4400
|
+
let current = 0;
|
|
4401
|
+
let carried = 0;
|
|
4402
|
+
const reread = [];
|
|
4403
|
+
const unknown = /* @__PURE__ */ new Set();
|
|
4404
|
+
const used = /* @__PURE__ */ new Map();
|
|
4405
|
+
for (const f of [...o.facts].sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0)) {
|
|
4406
|
+
if (sameCommit(f.commit, o.commit)) {
|
|
4407
|
+
current++;
|
|
4408
|
+
continue;
|
|
4409
|
+
}
|
|
4410
|
+
const changes = changesFor(o.carry, f.commit);
|
|
4411
|
+
if (!changes) {
|
|
4412
|
+
unknown.add(f.commit);
|
|
4413
|
+
continue;
|
|
4414
|
+
}
|
|
4415
|
+
used.set(f.commit, changes);
|
|
4416
|
+
const s = staleCite(f, changes);
|
|
4417
|
+
if (s)
|
|
4418
|
+
reread.push({ id: f.id, kind: f.kind, from: f.commit, ...s });
|
|
4419
|
+
else
|
|
4420
|
+
carried++;
|
|
4421
|
+
}
|
|
4422
|
+
const excludeRes = (o.exclude ?? []).map(globToRegExp);
|
|
4423
|
+
const status = /* @__PURE__ */ new Map();
|
|
4424
|
+
for (const changes of used.values())
|
|
4425
|
+
for (const c of changes)
|
|
4426
|
+
if (c.status !== "deleted")
|
|
4427
|
+
status.set(c.path, c.status);
|
|
4428
|
+
const readable = [...status.keys()].filter((p) => {
|
|
4429
|
+
const text = o.source.read(p);
|
|
4430
|
+
if (text === void 0 || !text.trim())
|
|
4431
|
+
return false;
|
|
4432
|
+
const ls = text.split(/\r?\n/);
|
|
4433
|
+
if (!isSourceFile(p, ls[0] ?? "", ls))
|
|
4434
|
+
return false;
|
|
4435
|
+
return !excludeRes.some((re) => re.test(p));
|
|
4436
|
+
});
|
|
4437
|
+
const files = readingOrder(readable).map((path) => ({
|
|
4438
|
+
path,
|
|
4439
|
+
status: status.get(path),
|
|
4440
|
+
facts: reread.filter((r) => (r.to ?? r.file) === path).length
|
|
4441
|
+
}));
|
|
4442
|
+
return {
|
|
4443
|
+
commit: o.commit,
|
|
4444
|
+
from: [...used.entries()].map(([commit, changes]) => ({ commit, changed: changes.length })).sort((a, b) => a.commit < b.commit ? -1 : 1),
|
|
4445
|
+
current,
|
|
4446
|
+
carried,
|
|
4447
|
+
reread,
|
|
4448
|
+
files,
|
|
4449
|
+
unknown: [...unknown].sort()
|
|
4450
|
+
};
|
|
4451
|
+
}
|
|
4452
|
+
function onlyFiles(source, paths) {
|
|
4453
|
+
const keep = new Set(paths);
|
|
4454
|
+
return { list: () => source.list().filter((p) => keep.has(p)), read: (p) => source.read(p) };
|
|
4455
|
+
}
|
|
4456
|
+
|
|
4457
|
+
export {
|
|
4458
|
+
insideRepo,
|
|
4459
|
+
dirSource,
|
|
4460
|
+
correctionFor,
|
|
4461
|
+
correctedFact,
|
|
4462
|
+
applyCorrections,
|
|
4463
|
+
frameworkFacts,
|
|
4464
|
+
frameworkTopics,
|
|
4465
|
+
frameworkTopicsOfType,
|
|
4466
|
+
frameworkGaps,
|
|
4467
|
+
frameworksToLearn,
|
|
4468
|
+
parseReviewed,
|
|
4469
|
+
sameCommit,
|
|
4470
|
+
staleMessage,
|
|
4471
|
+
verifyFacts,
|
|
4472
|
+
FILE_KINDS,
|
|
4473
|
+
kindsOf,
|
|
4474
|
+
numberedSlice,
|
|
4475
|
+
sliceText,
|
|
4476
|
+
planBatches,
|
|
4477
|
+
SHOWN_PROPOSALS,
|
|
4478
|
+
proposeExclusions,
|
|
4479
|
+
exclusionYaml,
|
|
4480
|
+
SESSION_TOKENS,
|
|
4481
|
+
planParts,
|
|
4482
|
+
VIEW_NAMES,
|
|
4483
|
+
ViewError,
|
|
4484
|
+
changeView,
|
|
4485
|
+
buildView,
|
|
4486
|
+
toMermaid,
|
|
4487
|
+
toDrawio,
|
|
4488
|
+
shapeLibrary,
|
|
4489
|
+
viewFromDiagram,
|
|
4490
|
+
NoSuchNode,
|
|
4491
|
+
viewKey,
|
|
4492
|
+
buildEstate,
|
|
4493
|
+
nodeView,
|
|
4494
|
+
focusView,
|
|
4495
|
+
trustOf,
|
|
4496
|
+
codeLinkerFor,
|
|
4497
|
+
renderMap,
|
|
4498
|
+
rereadPlan,
|
|
4499
|
+
onlyFiles
|
|
4500
|
+
};
|