🍴 Meu Garfo é uma visualização em grafo dos CNPJs cuducos.tngl.io/meu-garfo
1

Configure Feed

Select the types of activity you want to include in your feed.

1st commit

author
Eduardo Cuducos
date (May 29, 2026, 8:36 PM -0400) commit 665e9cbb
+4111
+3
.gitignore
··· 1 + dist/ 2 + elm-stuff/ 3 + node_modules/
+15
.tangled/workflows/check.yaml
··· 1 + when: 2 + - event: ["push", "pull_request"] 3 + branch: ["main"] 4 + 5 + engine: "nixery" 6 + 7 + dependencies: 8 + nixpkgs: 9 + - nodejs 10 + 11 + steps: 12 + - name: "Install" 13 + command: "npm ci" 14 + - name: "Build" 15 + command: "npm run build"
+675
LICENSE
··· 1 + GNU GENERAL PUBLIC LICENSE 2 + Version 3, 29 June 2007 3 + 4 + Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/> 5 + Everyone is permitted to copy and distribute verbatim copies 6 + of this license document, but changing it is not allowed. 7 + 8 + Preamble 9 + 10 + The GNU General Public License is a free, copyleft license for 11 + software and other kinds of works. 12 + 13 + The licenses for most software and other practical works are designed 14 + to take away your freedom to share and change the works. By contrast, 15 + the GNU General Public License is intended to guarantee your freedom to 16 + share and change all versions of a program--to make sure it remains free 17 + software for all its users. We, the Free Software Foundation, use the 18 + GNU General Public License for most of our software; it applies also to 19 + any other work released this way by its authors. You can apply it to 20 + your programs, too. 21 + 22 + When we speak of free software, we are referring to freedom, not 23 + price. Our General Public Licenses are designed to make sure that you 24 + have the freedom to distribute copies of free software (and charge for 25 + them if you wish), that you receive source code or can get it if you 26 + want it, that you can change the software or use pieces of it in new 27 + free programs, and that you know you can do these things. 28 + 29 + To protect your rights, we need to prevent others from denying you 30 + these rights or asking you to surrender the rights. Therefore, you have 31 + certain responsibilities if you distribute copies of the software, or if 32 + you modify it: responsibilities to respect the freedom of others. 33 + 34 + For example, if you distribute copies of such a program, whether 35 + gratis or for a fee, you must pass on to the recipients the same 36 + freedoms that you received. You must make sure that they, too, receive 37 + or can get the source code. And you must show them these terms so they 38 + know their rights. 39 + 40 + Developers that use the GNU GPL protect your rights with two steps: 41 + (1) assert copyright on the software, and (2) offer you this License 42 + giving you legal permission to copy, distribute and/or modify it. 43 + 44 + For the developers' and authors' protection, the GPL clearly explains 45 + that there is no warranty for this free software. For both users' and 46 + authors' sake, the GPL requires that modified versions be marked as 47 + changed, so that their problems will not be attributed erroneously to 48 + authors of previous versions. 49 + 50 + Some devices are designed to deny users access to install or run 51 + modified versions of the software inside them, although the manufacturer 52 + can do so. This is fundamentally incompatible with the aim of 53 + protecting users' freedom to change the software. The systematic 54 + pattern of such abuse occurs in the area of products for individuals to 55 + use, which is precisely where it is most unacceptable. Therefore, we 56 + have designed this version of the GPL to prohibit the practice for those 57 + products. If such problems arise substantially in other domains, we 58 + stand ready to extend this provision to those domains in future versions 59 + of the GPL, as needed to protect the freedom of users. 60 + 61 + Finally, every program is threatened constantly by software patents. 62 + States should not allow patents to restrict development and use of 63 + software on general-purpose computers, but in those that do, we wish to 64 + avoid the special danger that patents applied to a free program could 65 + make it effectively proprietary. To prevent this, the GPL assures that 66 + patents cannot be used to render the program non-free. 67 + 68 + The precise terms and conditions for copying, distribution and 69 + modification follow. 70 + 71 + TERMS AND CONDITIONS 72 + 73 + 0. Definitions. 74 + 75 + "This License" refers to version 3 of the GNU General Public License. 76 + 77 + "Copyright" also means copyright-like laws that apply to other kinds of 78 + works, such as semiconductor masks. 79 + 80 + "The Program" refers to any copyrightable work licensed under this 81 + License. Each licensee is addressed as "you". "Licensees" and 82 + "recipients" may be individuals or organizations. 83 + 84 + To "modify" a work means to copy from or adapt all or part of the work 85 + in a fashion requiring copyright permission, other than the making of an 86 + exact copy. The resulting work is called a "modified version" of the 87 + earlier work or a work "based on" the earlier work. 88 + 89 + A "covered work" means either the unmodified Program or a work based 90 + on the Program. 91 + 92 + To "propagate" a work means to do anything with it that, without 93 + permission, would make you directly or secondarily liable for 94 + infringement under applicable copyright law, except executing it on a 95 + computer or modifying a private copy. Propagation includes copying, 96 + distribution (with or without modification), making available to the 97 + public, and in some countries other activities as well. 98 + 99 + To "convey" a work means any kind of propagation that enables other 100 + parties to make or receive copies. Mere interaction with a user through 101 + a computer network, with no transfer of a copy, is not conveying. 102 + 103 + An interactive user interface displays "Appropriate Legal Notices" 104 + to the extent that it includes a convenient and prominently visible 105 + feature that (1) displays an appropriate copyright notice, and (2) 106 + tells the user that there is no warranty for the work (except to the 107 + extent that warranties are provided), that licensees may convey the 108 + work under this License, and how to view a copy of this License. If 109 + the interface presents a list of user commands or options, such as a 110 + menu, a prominent item in the list meets this criterion. 111 + 112 + 1. Source Code. 113 + 114 + The "source code" for a work means the preferred form of the work 115 + for making modifications to it. "Object code" means any non-source 116 + form of a work. 117 + 118 + A "Standard Interface" means an interface that either is an official 119 + standard defined by a recognized standards body, or, in the case of 120 + interfaces specified for a particular programming language, one that 121 + is widely used among developers working in that language. 122 + 123 + The "System Libraries" of an executable work include anything, other 124 + than the work as a whole, that (a) is included in the normal form of 125 + packaging a Major Component, but which is not part of that Major 126 + Component, and (b) serves only to enable use of the work with that 127 + Major Component, or to implement a Standard Interface for which an 128 + implementation is available to the public in source code form. A 129 + "Major Component", in this context, means a major essential component 130 + (kernel, window system, and so on) of the specific operating system 131 + (if any) on which the executable work runs, or a compiler used to 132 + produce the work, or an object code interpreter used to run it. 133 + 134 + The "Corresponding Source" for a work in object code form means all 135 + the source code needed to generate, install, and (for an executable 136 + work) run the object code and to modify the work, including scripts to 137 + control those activities. However, it does not include the work's 138 + System Libraries, or general-purpose tools or generally available free 139 + programs which are used unmodified in performing those activities but 140 + which are not part of the work. For example, Corresponding Source 141 + includes interface definition files associated with source files for 142 + the work, and the source code for shared libraries and dynamically 143 + linked subprograms that the work is specifically designed to require, 144 + such as by intimate data communication or control flow between those 145 + subprograms and other parts of the work. 146 + 147 + The Corresponding Source need not include anything that users 148 + can regenerate automatically from other parts of the Corresponding 149 + Source. 150 + 151 + The Corresponding Source for a work in source code form is that 152 + same work. 153 + 154 + 2. Basic Permissions. 155 + 156 + All rights granted under this License are granted for the term of 157 + copyright on the Program, and are irrevocable provided the stated 158 + conditions are met. This License explicitly affirms your unlimited 159 + permission to run the unmodified Program. The output from running a 160 + covered work is covered by this License only if the output, given its 161 + content, constitutes a covered work. This License acknowledges your 162 + rights of fair use or other equivalent, as provided by copyright law. 163 + 164 + You may make, run and propagate covered works that you do not 165 + convey, without conditions so long as your license otherwise remains 166 + in force. You may convey covered works to others for the sole purpose 167 + of having them make modifications exclusively for you, or provide you 168 + with facilities for running those works, provided that you comply with 169 + the terms of this License in conveying all material for which you do 170 + not control copyright. Those thus making or running the covered works 171 + for you must do so exclusively on your behalf, under your direction 172 + and control, on terms that prohibit them from making any copies of 173 + your copyrighted material outside their relationship with you. 174 + 175 + Conveying under any other circumstances is permitted solely under 176 + the conditions stated below. Sublicensing is not allowed; section 10 177 + makes it unnecessary. 178 + 179 + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 + 181 + No covered work shall be deemed part of an effective technological 182 + measure under any applicable law fulfilling obligations under article 183 + 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 + similar laws prohibiting or restricting circumvention of such 185 + measures. 186 + 187 + When you convey a covered work, you waive any legal power to forbid 188 + circumvention of technological measures to the extent such circumvention 189 + is effected by exercising rights under this License with respect to 190 + the covered work, and you disclaim any intention to limit operation or 191 + modification of the work as a means of enforcing, against the work's 192 + users, your or third parties' legal rights to forbid circumvention of 193 + technological measures. 194 + 195 + 4. Conveying Verbatim Copies. 196 + 197 + You may convey verbatim copies of the Program's source code as you 198 + receive it, in any medium, provided that you conspicuously and 199 + appropriately publish on each copy an appropriate copyright notice; 200 + keep intact all notices stating that this License and any 201 + non-permissive terms added in accord with section 7 apply to the code; 202 + keep intact all notices of the absence of any warranty; and give all 203 + recipients a copy of this License along with the Program. 204 + 205 + You may charge any price or no price for each copy that you convey, 206 + and you may offer support or warranty protection for a fee. 207 + 208 + 5. Conveying Modified Source Versions. 209 + 210 + You may convey a work based on the Program, or the modifications to 211 + produce it from the Program, in the form of source code under the 212 + terms of section 4, provided that you also meet all of these conditions: 213 + 214 + a) The work must carry prominent notices stating that you modified 215 + it, and giving a relevant date. 216 + 217 + b) The work must carry prominent notices stating that it is 218 + released under this License and any conditions added under section 219 + 7. This requirement modifies the requirement in section 4 to 220 + "keep intact all notices". 221 + 222 + c) You must license the entire work, as a whole, under this 223 + License to anyone who comes into possession of a copy. This 224 + License will therefore apply, along with any applicable section 7 225 + additional terms, to the whole of the work, and all its parts, 226 + regardless of how they are packaged. This License gives no 227 + permission to license the work in any other way, but it does not 228 + invalidate such permission if you have separately received it. 229 + 230 + d) If the work has interactive user interfaces, each must display 231 + Appropriate Legal Notices; however, if the Program has interactive 232 + interfaces that do not display Appropriate Legal Notices, your 233 + work need not make them do so. 234 + 235 + A compilation of a covered work with other separate and independent 236 + works, which are not by their nature extensions of the covered work, 237 + and which are not combined with it such as to form a larger program, 238 + in or on a volume of a storage or distribution medium, is called an 239 + "aggregate" if the compilation and its resulting copyright are not 240 + used to limit the access or legal rights of the compilation's users 241 + beyond what the individual works permit. Inclusion of a covered work 242 + in an aggregate does not cause this License to apply to the other 243 + parts of the aggregate. 244 + 245 + 6. Conveying Non-Source Forms. 246 + 247 + You may convey a covered work in object code form under the terms 248 + of sections 4 and 5, provided that you also convey the 249 + machine-readable Corresponding Source under the terms of this License, 250 + in one of these ways: 251 + 252 + a) Convey the object code in, or embodied in, a physical product 253 + (including a physical distribution medium), accompanied by the 254 + Corresponding Source fixed on a durable physical medium 255 + customarily used for software interchange. 256 + 257 + b) Convey the object code in, or embodied in, a physical product 258 + (including a physical distribution medium), accompanied by a 259 + written offer, valid for at least three years and valid for as 260 + long as you offer spare parts or customer support for that product 261 + model, to give anyone who possesses the object code either (1) a 262 + copy of the Corresponding Source for all the software in the 263 + product that is covered by this License, on a durable physical 264 + medium customarily used for software interchange, for a price no 265 + more than your reasonable cost of physically performing this 266 + conveying of source, or (2) access to copy the 267 + Corresponding Source from a network server at no charge. 268 + 269 + c) Convey individual copies of the object code with a copy of the 270 + written offer to provide the Corresponding Source. This 271 + alternative is allowed only occasionally and noncommercially, and 272 + only if you received the object code with such an offer, in accord 273 + with subsection 6b. 274 + 275 + d) Convey the object code by offering access from a designated 276 + place (gratis or for a charge), and offer equivalent access to the 277 + Corresponding Source in the same way through the same place at no 278 + further charge. You need not require recipients to copy the 279 + Corresponding Source along with the object code. If the place to 280 + copy the object code is a network server, the Corresponding Source 281 + may be on a different server (operated by you or a third party) 282 + that supports equivalent copying facilities, provided you maintain 283 + clear directions next to the object code saying where to find the 284 + Corresponding Source. Regardless of what server hosts the 285 + Corresponding Source, you remain obligated to ensure that it is 286 + available for as long as needed to satisfy these requirements. 287 + 288 + e) Convey the object code using peer-to-peer transmission, provided 289 + you inform other peers where the object code and Corresponding 290 + Source of the work are being offered to the general public at no 291 + charge under subsection 6d. 292 + 293 + A separable portion of the object code, whose source code is excluded 294 + from the Corresponding Source as a System Library, need not be 295 + included in conveying the object code work. 296 + 297 + A "User Product" is either (1) a "consumer product", which means any 298 + tangible personal property which is normally used for personal, family, 299 + or household purposes, or (2) anything designed or sold for incorporation 300 + into a dwelling. In determining whether a product is a consumer product, 301 + doubtful cases shall be resolved in favor of coverage. For a particular 302 + product received by a particular user, "normally used" refers to a 303 + typical or common use of that class of product, regardless of the status 304 + of the particular user or of the way in which the particular user 305 + actually uses, or expects or is expected to use, the product. A product 306 + is a consumer product regardless of whether the product has substantial 307 + commercial, industrial or non-consumer uses, unless such uses represent 308 + the only significant mode of use of the product. 309 + 310 + "Installation Information" for a User Product means any methods, 311 + procedures, authorization keys, or other information required to install 312 + and execute modified versions of a covered work in that User Product from 313 + a modified version of its Corresponding Source. The information must 314 + suffice to ensure that the continued functioning of the modified object 315 + code is in no case prevented or interfered with solely because 316 + modification has been made. 317 + 318 + If you convey an object code work under this section in, or with, or 319 + specifically for use in, a User Product, and the conveying occurs as 320 + part of a transaction in which the right of possession and use of the 321 + User Product is transferred to the recipient in perpetuity or for a 322 + fixed term (regardless of how the transaction is characterized), the 323 + Corresponding Source conveyed under this section must be accompanied 324 + by the Installation Information. But this requirement does not apply 325 + if neither you nor any third party retains the ability to install 326 + modified object code on the User Product (for example, the work has 327 + been installed in ROM). 328 + 329 + The requirement to provide Installation Information does not include a 330 + requirement to continue to provide support service, warranty, or updates 331 + for a work that has been modified or installed by the recipient, or for 332 + the User Product in which it has been modified or installed. Access to a 333 + network may be denied when the modification itself materially and 334 + adversely affects the operation of the network or violates the rules and 335 + protocols for communication across the network. 336 + 337 + Corresponding Source conveyed, and Installation Information provided, 338 + in accord with this section must be in a format that is publicly 339 + documented (and with an implementation available to the public in 340 + source code form), and must require no special password or key for 341 + unpacking, reading or copying. 342 + 343 + 7. Additional Terms. 344 + 345 + "Additional permissions" are terms that supplement the terms of this 346 + License by making exceptions from one or more of its conditions. 347 + Additional permissions that are applicable to the entire Program shall 348 + be treated as though they were included in this License, to the extent 349 + that they are valid under applicable law. If additional permissions 350 + apply only to part of the Program, that part may be used separately 351 + under those permissions, but the entire Program remains governed by 352 + this License without regard to the additional permissions. 353 + 354 + When you convey a copy of a covered work, you may at your option 355 + remove any additional permissions from that copy, or from any part of 356 + it. (Additional permissions may be written to require their own 357 + removal in certain cases when you modify the work.) You may place 358 + additional permissions on material, added by you to a covered work, 359 + for which you have or can give appropriate copyright permission. 360 + 361 + Notwithstanding any other provision of this License, for material you 362 + add to a covered work, you may (if authorized by the copyright holders of 363 + that material) supplement the terms of this License with terms: 364 + 365 + a) Disclaiming warranty or limiting liability differently from the 366 + terms of sections 15 and 16 of this License; or 367 + 368 + b) Requiring preservation of specified reasonable legal notices or 369 + author attributions in that material or in the Appropriate Legal 370 + Notices displayed by works containing it; or 371 + 372 + c) Prohibiting misrepresentation of the origin of that material, or 373 + requiring that modified versions of such material be marked in 374 + reasonable ways as different from the original version; or 375 + 376 + d) Limiting the use for publicity purposes of names of licensors or 377 + authors of the material; or 378 + 379 + e) Declining to grant rights under trademark law for use of some 380 + trade names, trademarks, or service marks; or 381 + 382 + f) Requiring indemnification of licensors and authors of that 383 + material by anyone who conveys the material (or modified versions of 384 + it) with contractual assumptions of liability to the recipient, for 385 + any liability that these contractual assumptions directly impose on 386 + those licensors and authors. 387 + 388 + All other non-permissive additional terms are considered "further 389 + restrictions" within the meaning of section 10. If the Program as you 390 + received it, or any part of it, contains a notice stating that it is 391 + governed by this License along with a term that is a further 392 + restriction, you may remove that term. If a license document contains 393 + a further restriction but permits relicensing or conveying under this 394 + License, you may add to a covered work material governed by the terms 395 + of that license document, provided that the further restriction does 396 + not survive such relicensing or conveying. 397 + 398 + If you add terms to a covered work in accord with this section, you 399 + must place, in the relevant source files, a statement of the 400 + additional terms that apply to those files, or a notice indicating 401 + where to find the applicable terms. 402 + 403 + Additional terms, permissive or non-permissive, may be stated in the 404 + form of a separately written license, or stated as exceptions; 405 + the above requirements apply either way. 406 + 407 + 8. Termination. 408 + 409 + You may not propagate or modify a covered work except as expressly 410 + provided under this License. Any attempt otherwise to propagate or 411 + modify it is void, and will automatically terminate your rights under 412 + this License (including any patent licenses granted under the third 413 + paragraph of section 11). 414 + 415 + However, if you cease all violation of this License, then your 416 + license from a particular copyright holder is reinstated (a) 417 + provisionally, unless and until the copyright holder explicitly and 418 + finally terminates your license, and (b) permanently, if the copyright 419 + holder fails to notify you of the violation by some reasonable means 420 + prior to 60 days after the cessation. 421 + 422 + Moreover, your license from a particular copyright holder is 423 + reinstated permanently if the copyright holder notifies you of the 424 + violation by some reasonable means, this is the first time you have 425 + received notice of violation of this License (for any work) from that 426 + copyright holder, and you cure the violation prior to 30 days after 427 + your receipt of the notice. 428 + 429 + Termination of your rights under this section does not terminate the 430 + licenses of parties who have received copies or rights from you under 431 + this License. If your rights have been terminated and not permanently 432 + reinstated, you do not qualify to receive new licenses for the same 433 + material under section 10. 434 + 435 + 9. Acceptance Not Required for Having Copies. 436 + 437 + You are not required to accept this License in order to receive or 438 + run a copy of the Program. Ancillary propagation of a covered work 439 + occurring solely as a consequence of using peer-to-peer transmission 440 + to receive a copy likewise does not require acceptance. However, 441 + nothing other than this License grants you permission to propagate or 442 + modify any covered work. These actions infringe copyright if you do 443 + not accept this License. Therefore, by modifying or propagating a 444 + covered work, you indicate your acceptance of this License to do so. 445 + 446 + 10. Automatic Licensing of Downstream Recipients. 447 + 448 + Each time you convey a covered work, the recipient automatically 449 + receives a license from the original licensors, to run, modify and 450 + propagate that work, subject to this License. You are not responsible 451 + for enforcing compliance by third parties with this License. 452 + 453 + An "entity transaction" is a transaction transferring control of an 454 + organization, or substantially all assets of one, or subdividing an 455 + organization, or merging organizations. If propagation of a covered 456 + work results from an entity transaction, each party to that 457 + transaction who receives a copy of the work also receives whatever 458 + licenses to the work the party's predecessor in interest had or could 459 + give under the previous paragraph, plus a right to possession of the 460 + Corresponding Source of the work from the predecessor in interest, if 461 + the predecessor has it or can get it with reasonable efforts. 462 + 463 + You may not impose any further restrictions on the exercise of the 464 + rights granted or affirmed under this License. For example, you may 465 + not impose a license fee, royalty, or other charge for exercise of 466 + rights granted under this License, and you may not initiate litigation 467 + (including a cross-claim or counterclaim in a lawsuit) alleging that 468 + any patent claim is infringed by making, using, selling, offering for 469 + sale, or importing the Program or any portion of it. 470 + 471 + 11. Patents. 472 + 473 + A "contributor" is a copyright holder who authorizes use under this 474 + License of the Program or a work on which the Program is based. The 475 + work thus licensed is called the contributor's "contributor version". 476 + 477 + A contributor's "essential patent claims" are all patent claims 478 + owned or controlled by the contributor, whether already acquired or 479 + hereafter acquired, that would be infringed by some manner, permitted 480 + by this License, of making, using, or selling its contributor version, 481 + but do not include claims that would be infringed only as a 482 + consequence of further modification of the contributor version. For 483 + purposes of this definition, "control" includes the right to grant 484 + patent sublicenses in a manner consistent with the requirements of 485 + this License. 486 + 487 + Each contributor grants you a non-exclusive, worldwide, royalty-free 488 + patent license under the contributor's essential patent claims, to 489 + make, use, sell, offer for sale, import and otherwise run, modify and 490 + propagate the contents of its contributor version. 491 + 492 + In the following three paragraphs, a "patent license" is any express 493 + agreement or commitment, however denominated, not to enforce a patent 494 + (such as an express permission to practice a patent or covenant not to 495 + sue for patent infringement). To "grant" such a patent license to a 496 + party means to make such an agreement or commitment not to enforce a 497 + patent against the party. 498 + 499 + If you convey a covered work, knowingly relying on a patent license, 500 + and the Corresponding Source of the work is not available for anyone 501 + to copy, free of charge and under the terms of this License, through a 502 + publicly available network server or other readily accessible means, 503 + then you must either (1) cause the Corresponding Source to be so 504 + available, or (2) arrange to deprive yourself of the benefit of the 505 + patent license for this particular work, or (3) arrange, in a manner 506 + consistent with the requirements of this License, to extend the patent 507 + license to downstream recipients. "Knowingly relying" means you have 508 + actual knowledge that, but for the patent license, your conveying the 509 + covered work in a country, or your recipient's use of the covered work 510 + in a country, would infringe one or more identifiable patents in that 511 + country that you have reason to believe are valid. 512 + 513 + If, pursuant to or in connection with a single transaction or 514 + arrangement, you convey, or propagate by procuring conveyance of, a 515 + covered work, and grant a patent license to some of the parties 516 + receiving the covered work authorizing them to use, propagate, modify 517 + or convey a specific copy of the covered work, then the patent license 518 + you grant is automatically extended to all recipients of the covered 519 + work and works based on it. 520 + 521 + A patent license is "discriminatory" if it does not include within 522 + the scope of its coverage, prohibits the exercise of, or is 523 + conditioned on the non-exercise of one or more of the rights that are 524 + specifically granted under this License. You may not convey a covered 525 + work if you are a party to an arrangement with a third party that is 526 + in the business of distributing software, under which you make payment 527 + to the third party based on the extent of your activity of conveying 528 + the work, and under which the third party grants, to any of the 529 + parties who would receive the covered work from you, a discriminatory 530 + patent license (a) in connection with copies of the covered work 531 + conveyed by you (or copies made from those copies), or (b) primarily 532 + for and in connection with specific products or compilations that 533 + contain the covered work, unless you entered into that arrangement, 534 + or that patent license was granted, prior to 28 March 2007. 535 + 536 + Nothing in this License shall be construed as excluding or limiting 537 + any implied license or other defenses to infringement that may 538 + otherwise be available to you under applicable patent law. 539 + 540 + 12. No Surrender of Others' Freedom. 541 + 542 + If conditions are imposed on you (whether by court order, agreement or 543 + otherwise) that contradict the conditions of this License, they do not 544 + excuse you from the conditions of this License. If you cannot convey a 545 + covered work so as to satisfy simultaneously your obligations under this 546 + License and any other pertinent obligations, then as a consequence you may 547 + not convey it at all. For example, if you agree to terms that obligate you 548 + to collect a royalty for further conveying from those to whom you convey 549 + the Program, the only way you could satisfy both those terms and this 550 + License would be to refrain entirely from conveying the Program. 551 + 552 + 13. Use with the GNU Affero General Public License. 553 + 554 + Notwithstanding any other provision of this License, you have 555 + permission to link or combine any covered work with a work licensed 556 + under version 3 of the GNU Affero General Public License into a single 557 + combined work, and to convey the resulting work. The terms of this 558 + License will continue to apply to the part which is the covered work, 559 + but the special requirements of the GNU Affero General Public License, 560 + section 13, concerning interaction through a network will apply to the 561 + combination as such. 562 + 563 + 14. Revised Versions of this License. 564 + 565 + The Free Software Foundation may publish revised and/or new versions of 566 + the GNU General Public License from time to time. Such new versions will 567 + be similar in spirit to the present version, but may differ in detail to 568 + address new problems or concerns. 569 + 570 + Each version is given a distinguishing version number. If the 571 + Program specifies that a certain numbered version of the GNU General 572 + Public License "or any later version" applies to it, you have the 573 + option of following the terms and conditions either of that numbered 574 + version or of any later version published by the Free Software 575 + Foundation. If the Program does not specify a version number of the 576 + GNU General Public License, you may choose any version ever published 577 + by the Free Software Foundation. 578 + 579 + If the Program specifies that a proxy can decide which future 580 + versions of the GNU General Public License can be used, that proxy's 581 + public statement of acceptance of a version permanently authorizes you 582 + to choose that version for the Program. 583 + 584 + Later license versions may give you additional or different 585 + permissions. However, no additional obligations are imposed on any 586 + author or copyright holder as a result of your choosing to follow a 587 + later version. 588 + 589 + 15. Disclaimer of Warranty. 590 + 591 + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 + APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 + HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 + OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 + THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 + PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 + IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 + ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 + 600 + 16. Limitation of Liability. 601 + 602 + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 + WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 + THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 + GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 + USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 + DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 + PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 + EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 + SUCH DAMAGES. 611 + 612 + 17. Interpretation of Sections 15 and 16. 613 + 614 + If the disclaimer of warranty and limitation of liability provided 615 + above cannot be given local legal effect according to their terms, 616 + reviewing courts shall apply local law that most closely approximates 617 + an absolute waiver of all civil liability in connection with the 618 + Program, unless a warranty or assumption of liability accompanies a 619 + copy of the Program in return for a fee. 620 + 621 + END OF TERMS AND CONDITIONS 622 + 623 + How to Apply These Terms to Your New Programs 624 + 625 + If you develop a new program, and you want it to be of the greatest 626 + possible use to the public, the best way to achieve this is to make it 627 + free software which everyone can redistribute and change under these terms. 628 + 629 + To do so, attach the following notices to the program. It is safest 630 + to attach them to the start of each source file to most effectively 631 + state the exclusion of warranty; and each file should have at least 632 + the "copyright" line and a pointer to where the full notice is found. 633 + 634 + <one line to give the program's name and a brief idea of what it does.> 635 + Copyright (C) <year> <name of author> 636 + 637 + This program is free software: you can redistribute it and/or modify 638 + it under the terms of the GNU General Public License as published by 639 + the Free Software Foundation, either version 3 of the License, or 640 + (at your option) any later version. 641 + 642 + This program is distributed in the hope that it will be useful, 643 + but WITHOUT ANY WARRANTY; without even the implied warranty of 644 + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 + GNU General Public License for more details. 646 + 647 + You should have received a copy of the GNU General Public License 648 + along with this program. If not, see <https://www.gnu.org/licenses/>. 649 + 650 + Also add information on how to contact you by electronic and paper mail. 651 + 652 + If the program does terminal interaction, make it output a short 653 + notice like this when it starts in an interactive mode: 654 + 655 + <program> Copyright (C) <year> <name of author> 656 + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 + This is free software, and you are welcome to redistribute it 658 + under certain conditions; type `show c' for details. 659 + 660 + The hypothetical commands `show w' and `show c' should show the appropriate 661 + parts of the General Public License. Of course, your program's commands 662 + might be different; for a GUI interface, you would use an "about box". 663 + 664 + You should also get your employer (if you work as a programmer) or school, 665 + if any, to sign a "copyright disclaimer" for the program, if necessary. 666 + For more information on this, and how to apply and follow the GNU GPL, see 667 + <https://www.gnu.org/licenses/>. 668 + 669 + The GNU General Public License does not permit incorporating your program 670 + into proprietary programs. If your program is a subroutine library, you 671 + may consider it more useful to permit linking proprietary applications with 672 + the library. If this is what you want to do, use the GNU Lesser General 673 + Public License instead of this License. But first, please read 674 + <https://www.gnu.org/licenses/why-not-lgpl.html>. 675 +
+27
README.md
··· 1 + # Meu Garfo 2 + 3 + Visualização em grafo dos dados de quadro societário das empresas brasileiras de acordo com os dados da Receita Federal, utilizando a [API (experimental) do Minha Receita](https://docs.minhareceita.org/grafo/). 4 + 5 + ### Configuração 6 + 7 + As URLs das APIs podem ser configuradas via variáveis de ambiente durante o build: 8 + 9 + | Nome | Descrição | Valor padrão | 10 + | --- | --- | --- | 11 + | `GRAPH_API_URL` | URL da API de dados para o grafo | `https://grafo.minhareceita.org` | 12 + | `JSON_API_URL` | URL base para os dados de cada empresa | `https://minhareceita.org` | 13 + 14 + ## Rodando localmente 15 + 16 + ```console 17 + $ npm install 18 + $ npm start 19 + ``` 20 + 21 + Para gerar o build de produção: 22 + 23 + ```console 24 + $ npm run build 25 + ``` 26 + 27 + Os arquivos serão gerados na pasta `dist/`.
+31
configure.js
··· 1 + const fs = require('fs'); 2 + const path = require('path'); 3 + 4 + const config = { 5 + graphApi: process.env.GRAPH_API_URL || 'https://grafo.minhareceita.org', 6 + jsonApi: process.env.JSON_API_URL || 'https://minhareceita.org', 7 + }; 8 + 9 + const distPath = path.join(__dirname, 'dist'); 10 + const templatePath = path.join(__dirname, 'index.html.template'); 11 + const outputPath = path.join(distPath, 'index.html'); 12 + 13 + try { 14 + if (!fs.existsSync(distPath)) { 15 + fs.mkdirSync(distPath); 16 + } 17 + 18 + let template = fs.readFileSync(templatePath, 'utf8'); 19 + 20 + const result = template 21 + .replace('{{GRAPH_API_URL}}', config.graphApi) 22 + .replace('{{JSON_API_URL}}', config.jsonApi); 23 + 24 + fs.writeFileSync(outputPath, result); 25 + console.log(`Configured dist/index.html with:`); 26 + console.log(` GRAPH_API_URL: ${config.graphApi}`); 27 + console.log(` JSON_API_URL: ${config.jsonApi}`); 28 + } catch (err) { 29 + console.error('Error configuring index.html:', err.message); 30 + process.exit(1); 31 + }
+86
deploy.js
··· 1 + const { execSync } = require("node:child_process"); 2 + const { mkdtempSync, rmSync, cpSync, readdirSync } = require("node:fs"); 3 + const { tmpdir } = require("node:os"); 4 + const { join } = require("node:path"); 5 + 6 + const SITE_BRANCH = process.env.SITE_BRANCH || "site"; 7 + 8 + const run = (cmd, opts = {}) => execSync(cmd, { stdio: "inherit", ...opts }); 9 + 10 + const capture = (cmd, opts = {}) => 11 + execSync(cmd, { stdio: ["ignore", "pipe", "inherit"], ...opts }) 12 + .toString() 13 + .trim(); 14 + 15 + if (capture("git status --porcelain")) { 16 + console.error("Working tree has uncommitted changes. Commit or stash first."); 17 + process.exit(1); 18 + } 19 + 20 + let REMOTE; 21 + try { 22 + const upstream = capture("git rev-parse --abbrev-ref @{upstream}"); 23 + REMOTE = upstream.split("/")[0]; 24 + } catch { 25 + console.error( 26 + "Current branch has no upstream. Set one with `git push -u <remote> <branch>` first.", 27 + ); 28 + process.exit(1); 29 + } 30 + 31 + run("npm run build"); 32 + 33 + const workdir = mkdtempSync(join(tmpdir(), "meu-garfo-deploy-")); 34 + let worktreeAdded = false; 35 + 36 + try { 37 + const remoteHas = 38 + capture(`git ls-remote --heads ${REMOTE} ${SITE_BRANCH}`) !== ""; 39 + 40 + if (remoteHas) { 41 + run(`git fetch ${REMOTE} ${SITE_BRANCH}`); 42 + run(`git worktree add --force ${workdir} ${REMOTE}/${SITE_BRANCH}`); 43 + run(`git -C ${workdir} checkout -B ${SITE_BRANCH}`); 44 + } else { 45 + run(`git worktree add --force --orphan -B ${SITE_BRANCH} ${workdir}`); 46 + } 47 + worktreeAdded = true; 48 + 49 + for (const entry of readdirSync(workdir)) { 50 + if (entry === ".git") continue; 51 + rmSync(join(workdir, entry), { recursive: true, force: true }); 52 + } 53 + 54 + for (const entry of readdirSync("dist")) { 55 + cpSync(join("dist", entry), join(workdir, entry), { recursive: true }); 56 + } 57 + 58 + run(`git -C ${workdir} add -A`); 59 + const hasChanges = capture(`git -C ${workdir} status --porcelain`) !== ""; 60 + const sourceSha = capture("git rev-parse --short HEAD"); 61 + 62 + if (!hasChanges && remoteHas) { 63 + console.log( 64 + `No changes to deploy on ${SITE_BRANCH} (source at ${sourceSha}).`, 65 + ); 66 + } else { 67 + run(`git -C ${workdir} commit -m "Deploy ${sourceSha}" --allow-empty`, { 68 + env: { 69 + ...process.env, 70 + GIT_COMMITTER_NAME: process.env.GIT_COMMITTER_NAME || "deploy", 71 + GIT_COMMITTER_EMAIL: process.env.GIT_COMMITTER_EMAIL || "deploy@local", 72 + }, 73 + }); 74 + run(`git -C ${workdir} push ${REMOTE} ${SITE_BRANCH}`); 75 + } 76 + } finally { 77 + if (worktreeAdded) { 78 + try { 79 + run(`git worktree remove --force ${workdir}`); 80 + } catch { 81 + rmSync(workdir, { recursive: true, force: true }); 82 + } 83 + } else { 84 + rmSync(workdir, { recursive: true, force: true }); 85 + } 86 + }
+51
elm.json
··· 1 + { 2 + "type": "application", 3 + "source-directories": [ 4 + "src" 5 + ], 6 + "elm-version": "0.19.1", 7 + "dependencies": { 8 + "direct": { 9 + "elm/browser": "1.0.2", 10 + "elm/core": "1.0.5", 11 + "elm/html": "1.0.1", 12 + "elm/http": "2.0.0", 13 + "elm/json": "1.1.4", 14 + "elm/svg": "1.0.1", 15 + "elm/url": "1.0.0", 16 + "gampleman/elm-visualization": "2.4.2" 17 + }, 18 + "indirect": { 19 + "avh4/elm-color": "1.0.0", 20 + "elm/bytes": "1.0.8", 21 + "elm/file": "1.0.5", 22 + "elm/parser": "1.1.0", 23 + "elm/random": "1.0.0", 24 + "elm/regex": "1.0.0", 25 + "elm/time": "1.0.0", 26 + "elm/virtual-dom": "1.0.5", 27 + "elm-community/list-extra": "8.7.0", 28 + "elmcraft/core-extra": "2.3.0", 29 + "folkertdev/elm-deque": "3.0.1", 30 + "folkertdev/one-true-path-experiment": "6.0.1", 31 + "folkertdev/svg-path-lowlevel": "4.0.1", 32 + "gampleman/elm-rosetree": "1.1.0", 33 + "ianmackenzie/elm-1d-parameter": "1.0.1", 34 + "ianmackenzie/elm-float-extra": "1.1.0", 35 + "ianmackenzie/elm-geometry": "3.11.0", 36 + "ianmackenzie/elm-interval": "3.1.0", 37 + "ianmackenzie/elm-triangular-mesh": "1.1.0", 38 + "ianmackenzie/elm-units": "2.10.0", 39 + "ianmackenzie/elm-units-interval": "3.2.0", 40 + "ianmackenzie/elm-units-prefixed": "2.8.0", 41 + "justinmimbs/date": "4.1.0", 42 + "justinmimbs/time-extra": "1.2.0", 43 + "rtfeldman/elm-hex": "1.0.0", 44 + "ryan-haskell/date-format": "1.0.0" 45 + } 46 + }, 47 + "test-dependencies": { 48 + "direct": {}, 49 + "indirect": {} 50 + } 51 + }
+253
index.html.template
··· 1 + <!DOCTYPE html> 2 + <html lang="pt-br" data-theme="light"> 3 + <head> 4 + <meta charset="UTF-8"> 5 + <meta name="viewport" content="width=device-width, initial-scale=1"> 6 + <title>Meu Garfo</title> 7 + <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css"> 8 + <style> 9 + :root { 10 + --pico-primary: #784c87; 11 + --pico-primary-hover: #603d6c; 12 + --pico-primary-focus: rgba(120, 76, 135, 0.25); 13 + --pico-primary-inverse: #fff; 14 + --pico-h1-color: #784c87; 15 + } 16 + 17 + h1 { 18 + color: #784c87 !important; 19 + } 20 + 21 + body { 22 + margin: 0; 23 + padding: 0; 24 + height: 100vh; 25 + width: 100vw; 26 + display: flex; 27 + flex-direction: column; 28 + overflow: hidden; 29 + } 30 + 31 + #elm { 32 + flex: 1; 33 + display: flex; 34 + flex-direction: column; 35 + height: 100vh; 36 + } 37 + 38 + .app-root { 39 + display: flex; 40 + flex-direction: column; 41 + height: 100vh; 42 + width: 100vw; 43 + } 44 + 45 + /* Header & Navigation */ 46 + .app-header { 47 + flex: 0 0 auto; 48 + padding: 0.25rem 1rem; 49 + border-bottom: 1px solid var(--pico-muted-border-color); 50 + background-color: var(--pico-card-background-color); 51 + margin-bottom: 0; 52 + z-index: 10; 53 + } 54 + 55 + /* Footer */ 56 + .app-footer { 57 + flex: 0 0 auto; 58 + width: 100%; 59 + padding: 0.25rem 0; 60 + border-top: 1px solid var(--pico-muted-border-color); 61 + background-color: var(--pico-card-background-color); 62 + text-align: center; 63 + margin: 0; 64 + z-index: 10; 65 + } 66 + 67 + .app-footer small { 68 + display: inline-block; 69 + margin: 0 auto; 70 + } 71 + 72 + .app-header h1 { 73 + margin: 0; 74 + font-size: 1.1rem; 75 + color: #784c87 !important; 76 + white-space: nowrap; 77 + } 78 + 79 + /* Search Controls */ 80 + .controls { 81 + display: flex; 82 + gap: 1rem; 83 + align-items: center; 84 + margin: 0; 85 + padding: 0; 86 + list-style: none; 87 + } 88 + 89 + .controls li { 90 + display: flex; 91 + align-items: center; 92 + gap: 0.5rem; 93 + padding: 0; 94 + } 95 + 96 + .controls input, 97 + .controls button { 98 + margin-bottom: 0; 99 + font-size: 0.9rem; 100 + padding: 0.25rem 0.75rem; 101 + } 102 + 103 + .cnpj-input { 104 + width: 180px; 105 + } 106 + 107 + .depth-input { 108 + width: 70px; 109 + } 110 + 111 + /* Graph Container */ 112 + .graph-viewport { 113 + flex: 1; 114 + position: relative; 115 + overflow: hidden; 116 + background-color: #fafafa; 117 + } 118 + 119 + .graph-svg { 120 + display: block; 121 + width: 100%; 122 + height: 100%; 123 + } 124 + 125 + /* Status & Feedback */ 126 + .status-overlay { 127 + position: absolute; 128 + bottom: 1rem; 129 + right: 1rem; 130 + background: rgba(255, 255, 255, 0.85); 131 + padding: 0.25rem 0.75rem; 132 + border-radius: var(--pico-border-radius); 133 + box-shadow: var(--pico-card-box-shadow); 134 + pointer-events: none; 135 + } 136 + 137 + .error-overlay { 138 + position: absolute; 139 + top: 1rem; 140 + left: 50%; 141 + transform: translateX(-50%); 142 + z-index: 100; 143 + max-width: 90%; 144 + background: rgba(255, 0, 0, 0.05); 145 + border: 1px solid #ff4136; 146 + color: #ff4136; 147 + padding: 0.25rem 0.75rem; 148 + border-radius: var(--pico-border-radius); 149 + font-size: 10px; 150 + line-height: 1.2; 151 + } 152 + 153 + .error-overlay small { 154 + color: #ff4136; 155 + font-size: inherit !important; 156 + } 157 + 158 + /* Graph Elements */ 159 + .edge { 160 + stroke: #89a1c9; 161 + stroke-opacity: 0.4; 162 + stroke-width: 1; 163 + } 164 + 165 + .node-label { 166 + font-size: 10px; 167 + fill: var(--pico-color); 168 + user-select: text; 169 + } 170 + 171 + .node-company { 172 + fill: #784c87; 173 + stroke: #fff; 174 + stroke-width: 1.5; 175 + } 176 + 177 + .node-person { 178 + fill: #89a1c9; 179 + stroke: #fff; 180 + stroke-width: 1.5; 181 + } 182 + 183 + .visited { 184 + opacity: 0.6; 185 + stroke-width: 1; 186 + } 187 + 188 + .expandable { 189 + cursor: pointer; 190 + stroke: #fff; 191 + stroke-width: 2.5; 192 + } 193 + 194 + .expandable:hover { 195 + stroke: #784c87; 196 + filter: brightness(1.1); 197 + } 198 + 199 + .node-error { 200 + font-size: 10px; 201 + fill: #ff4136; 202 + font-style: italic; 203 + user-select: text; 204 + } 205 + 206 + .errored { 207 + fill: #ff4136 !important; 208 + stroke: #ff4136; 209 + opacity: 0.5; 210 + } 211 + 212 + .node-group.root .node-label { 213 + font-size: 13px; 214 + font-weight: 600; 215 + } 216 + 217 + circle.root { 218 + fill: #784c87 !important; 219 + stroke: #784c87; 220 + stroke-width: 3; 221 + } 222 + 223 + .external-link-icon { 224 + fill: #784c87; 225 + cursor: pointer; 226 + font-size: 10px; 227 + } 228 + 229 + .external-link-icon:hover { 230 + fill: #603d6c; 231 + text-decoration: underline; 232 + } 233 + .loading-indicator { 234 + color: var(--pico-primary); 235 + font-weight: bold; 236 + margin-left: 5px; 237 + } 238 + </style> 239 + <script src="main.js"></script> 240 + </head> 241 + <body> 242 + <div id="elm"></div> 243 + <script> 244 + var app = Elm.Main.init({ 245 + node: document.getElementById('elm'), 246 + flags: { 247 + graphApi: "{{GRAPH_API_URL}}", 248 + jsonApi: "{{JSON_API_URL}}" 249 + } 250 + }); 251 + </script> 252 + </body> 253 + </html>
+1470
package-lock.json
··· 1 + { 2 + "name": "minha-receita-graph", 3 + "version": "1.0.0", 4 + "lockfileVersion": 3, 5 + "requires": true, 6 + "packages": { 7 + "": { 8 + "name": "minha-receita-graph", 9 + "version": "1.0.0", 10 + "devDependencies": { 11 + "elm": "^0.19.1-5", 12 + "elm-live": "^4.0.2" 13 + } 14 + }, 15 + "node_modules/ajv": { 16 + "version": "6.15.0", 17 + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", 18 + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", 19 + "dev": true, 20 + "dependencies": { 21 + "fast-deep-equal": "^3.1.1", 22 + "fast-json-stable-stringify": "^2.0.0", 23 + "json-schema-traverse": "^0.4.1", 24 + "uri-js": "^4.2.2" 25 + }, 26 + "funding": { 27 + "type": "github", 28 + "url": "https://github.com/sponsors/epoberezkin" 29 + } 30 + }, 31 + "node_modules/ansi-regex": { 32 + "version": "2.1.1", 33 + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", 34 + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", 35 + "dev": true, 36 + "engines": { 37 + "node": ">=0.10.0" 38 + } 39 + }, 40 + "node_modules/ansi-styles": { 41 + "version": "2.2.1", 42 + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", 43 + "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", 44 + "dev": true, 45 + "engines": { 46 + "node": ">=0.10.0" 47 + } 48 + }, 49 + "node_modules/anymatch": { 50 + "version": "3.1.3", 51 + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", 52 + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", 53 + "dev": true, 54 + "dependencies": { 55 + "normalize-path": "^3.0.0", 56 + "picomatch": "^2.0.4" 57 + }, 58 + "engines": { 59 + "node": ">= 8" 60 + } 61 + }, 62 + "node_modules/asn1": { 63 + "version": "0.2.6", 64 + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", 65 + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", 66 + "dev": true, 67 + "dependencies": { 68 + "safer-buffer": "~2.1.0" 69 + } 70 + }, 71 + "node_modules/assert-plus": { 72 + "version": "1.0.0", 73 + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", 74 + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", 75 + "dev": true, 76 + "engines": { 77 + "node": ">=0.8" 78 + } 79 + }, 80 + "node_modules/async-limiter": { 81 + "version": "1.0.1", 82 + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", 83 + "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", 84 + "dev": true 85 + }, 86 + "node_modules/asynckit": { 87 + "version": "0.4.0", 88 + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 89 + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", 90 + "dev": true 91 + }, 92 + "node_modules/aws-sign2": { 93 + "version": "0.7.0", 94 + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", 95 + "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", 96 + "dev": true, 97 + "engines": { 98 + "node": "*" 99 + } 100 + }, 101 + "node_modules/aws4": { 102 + "version": "1.13.2", 103 + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz", 104 + "integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==", 105 + "dev": true 106 + }, 107 + "node_modules/bcrypt-pbkdf": { 108 + "version": "1.0.2", 109 + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", 110 + "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", 111 + "dev": true, 112 + "dependencies": { 113 + "tweetnacl": "^0.14.3" 114 + } 115 + }, 116 + "node_modules/binary-extensions": { 117 + "version": "2.3.0", 118 + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", 119 + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", 120 + "dev": true, 121 + "engines": { 122 + "node": ">=8" 123 + }, 124 + "funding": { 125 + "url": "https://github.com/sponsors/sindresorhus" 126 + } 127 + }, 128 + "node_modules/braces": { 129 + "version": "3.0.3", 130 + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", 131 + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", 132 + "dev": true, 133 + "dependencies": { 134 + "fill-range": "^7.1.1" 135 + }, 136 + "engines": { 137 + "node": ">=8" 138 + } 139 + }, 140 + "node_modules/caseless": { 141 + "version": "0.12.0", 142 + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", 143 + "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", 144 + "dev": true 145 + }, 146 + "node_modules/chalk": { 147 + "version": "1.1.3", 148 + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", 149 + "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", 150 + "dev": true, 151 + "dependencies": { 152 + "ansi-styles": "^2.2.1", 153 + "escape-string-regexp": "^1.0.2", 154 + "has-ansi": "^2.0.0", 155 + "strip-ansi": "^3.0.0", 156 + "supports-color": "^2.0.0" 157 + }, 158 + "engines": { 159 + "node": ">=0.10.0" 160 + } 161 + }, 162 + "node_modules/charenc": { 163 + "version": "0.0.2", 164 + "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", 165 + "integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==", 166 + "dev": true, 167 + "engines": { 168 + "node": "*" 169 + } 170 + }, 171 + "node_modules/chokidar": { 172 + "version": "3.0.2", 173 + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.0.2.tgz", 174 + "integrity": "sha512-c4PR2egjNjI1um6bamCQ6bUNPDiyofNQruHvKgHQ4gDUP/ITSVSzNsiI5OWtHOsX323i5ha/kk4YmOZ1Ktg7KA==", 175 + "dev": true, 176 + "dependencies": { 177 + "anymatch": "^3.0.1", 178 + "braces": "^3.0.2", 179 + "glob-parent": "^5.0.0", 180 + "is-binary-path": "^2.1.0", 181 + "is-glob": "^4.0.1", 182 + "normalize-path": "^3.0.0", 183 + "readdirp": "^3.1.1" 184 + }, 185 + "engines": { 186 + "node": ">= 8" 187 + }, 188 + "optionalDependencies": { 189 + "fsevents": "^2.0.6" 190 + } 191 + }, 192 + "node_modules/combined-stream": { 193 + "version": "1.0.8", 194 + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", 195 + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", 196 + "dev": true, 197 + "dependencies": { 198 + "delayed-stream": "~1.0.0" 199 + }, 200 + "engines": { 201 + "node": ">= 0.8" 202 + } 203 + }, 204 + "node_modules/commander": { 205 + "version": "2.17.1", 206 + "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", 207 + "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==", 208 + "dev": true 209 + }, 210 + "node_modules/core-util-is": { 211 + "version": "1.0.2", 212 + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 213 + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", 214 + "dev": true 215 + }, 216 + "node_modules/crocks": { 217 + "version": "0.12.1", 218 + "resolved": "https://registry.npmjs.org/crocks/-/crocks-0.12.1.tgz", 219 + "integrity": "sha512-2qCRJwBmPlRQXzd50k9gt9PaItultOP8lj/cKSH2Eai9aeBuNqAnDuyolAm9TGn6Pw/4BgbxtPJLU1S+tQ4WMQ==", 220 + "dev": true 221 + }, 222 + "node_modules/cross-spawn": { 223 + "version": "5.0.1", 224 + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.0.1.tgz", 225 + "integrity": "sha512-77q+/Kkp43OBZUppmezGBqwB1qdjGk8y1Kb6zdPaYVz8qKFRdGpL6TRLqJhlhG5RhtGkNnKaeEYCt7b/vtYteg==", 226 + "dev": true, 227 + "dependencies": { 228 + "lru-cache": "^4.0.1", 229 + "shebang-command": "^1.2.0", 230 + "which": "^1.2.9" 231 + } 232 + }, 233 + "node_modules/crypt": { 234 + "version": "0.0.2", 235 + "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", 236 + "integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==", 237 + "dev": true, 238 + "engines": { 239 + "node": "*" 240 + } 241 + }, 242 + "node_modules/dashdash": { 243 + "version": "1.14.1", 244 + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", 245 + "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", 246 + "dev": true, 247 + "dependencies": { 248 + "assert-plus": "^1.0.0" 249 + }, 250 + "engines": { 251 + "node": ">=0.10" 252 + } 253 + }, 254 + "node_modules/debug": { 255 + "version": "2.6.9", 256 + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 257 + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 258 + "dev": true, 259 + "dependencies": { 260 + "ms": "2.0.0" 261 + } 262 + }, 263 + "node_modules/default-gateway": { 264 + "version": "4.2.0", 265 + "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-4.2.0.tgz", 266 + "integrity": "sha512-h6sMrVB1VMWVrW13mSc6ia/DwYYw5MN6+exNu1OaJeFac5aSAvwM7lZ0NVfTABuSkQelr4h5oebg3KB1XPdjgA==", 267 + "dev": true, 268 + "dependencies": { 269 + "execa": "^1.0.0", 270 + "ip-regex": "^2.1.0" 271 + }, 272 + "engines": { 273 + "node": ">=6" 274 + } 275 + }, 276 + "node_modules/delayed-stream": { 277 + "version": "1.0.0", 278 + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 279 + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", 280 + "dev": true, 281 + "engines": { 282 + "node": ">=0.4.0" 283 + } 284 + }, 285 + "node_modules/depd": { 286 + "version": "1.1.2", 287 + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", 288 + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", 289 + "dev": true, 290 + "engines": { 291 + "node": ">= 0.6" 292 + } 293 + }, 294 + "node_modules/destroy": { 295 + "version": "1.0.4", 296 + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", 297 + "integrity": "sha512-3NdhDuEXnfun/z7x9GOElY49LoqVHoGScmOKwmxhsS8N5Y+Z8KyPPDnaSzqWgYt/ji4mqwfTS34Htrk0zPIXVg==", 298 + "dev": true 299 + }, 300 + "node_modules/ecc-jsbn": { 301 + "version": "0.1.2", 302 + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", 303 + "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", 304 + "dev": true, 305 + "dependencies": { 306 + "jsbn": "~0.1.0", 307 + "safer-buffer": "^2.1.0" 308 + } 309 + }, 310 + "node_modules/ee-first": { 311 + "version": "1.1.1", 312 + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 313 + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", 314 + "dev": true 315 + }, 316 + "node_modules/elm": { 317 + "version": "0.19.1-5", 318 + "resolved": "https://registry.npmjs.org/elm/-/elm-0.19.1-5.tgz", 319 + "integrity": "sha512-dyBoPvFiNLvxOStQJdyq28gZEjS/enZXdZ5yyCtNtDEMbFJJVQq4pYNRKvhrKKdlxNot6d96iQe1uczoqO5yvA==", 320 + "dev": true, 321 + "hasInstallScript": true, 322 + "dependencies": { 323 + "request": "^2.88.0" 324 + }, 325 + "bin": { 326 + "elm": "bin/elm" 327 + }, 328 + "engines": { 329 + "node": ">=7.0.0" 330 + } 331 + }, 332 + "node_modules/elm-hot": { 333 + "version": "1.1.4", 334 + "resolved": "https://registry.npmjs.org/elm-hot/-/elm-hot-1.1.4.tgz", 335 + "integrity": "sha512-qPDP/o/Fkifriaxaf3E7hHFB5L6Ijihyg8is4A6xna6/h/zebUiNssbQrxywI2oxNUkr6W/leEu/WlIC1tmVnw==", 336 + "dev": true 337 + }, 338 + "node_modules/elm-live": { 339 + "version": "4.0.2", 340 + "resolved": "https://registry.npmjs.org/elm-live/-/elm-live-4.0.2.tgz", 341 + "integrity": "sha512-4I3UvJxF6MubC14VsgtV11B0zBxaaKtdKKsWquoaa5a3UHBIGW83qgTnt/NxOj4omOLfupaftmDaE4yRMTgTcw==", 342 + "dev": true, 343 + "dependencies": { 344 + "chalk": "^1.1.1", 345 + "chokidar": "3.0.2", 346 + "commander": "2.17.1", 347 + "crocks": "0.12.1", 348 + "cross-spawn": "5.0.1", 349 + "elm-hot": "1.1.4", 350 + "finalhandler": "1.1.2", 351 + "http-proxy": "1.17.0", 352 + "internal-ip": "4.3.0", 353 + "mime": "2.4.3", 354 + "open": "6.4.0", 355 + "pem": "1.14.2", 356 + "serve-static": "1.14.1", 357 + "ws": "7.1.1" 358 + }, 359 + "bin": { 360 + "elm-live": "bin/elm-live.js" 361 + }, 362 + "engines": { 363 + "node": ">= 10.0.0" 364 + } 365 + }, 366 + "node_modules/encodeurl": { 367 + "version": "1.0.2", 368 + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 369 + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", 370 + "dev": true, 371 + "engines": { 372 + "node": ">= 0.8" 373 + } 374 + }, 375 + "node_modules/end-of-stream": { 376 + "version": "1.4.5", 377 + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", 378 + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", 379 + "dev": true, 380 + "dependencies": { 381 + "once": "^1.4.0" 382 + } 383 + }, 384 + "node_modules/es6-promisify": { 385 + "version": "6.1.1", 386 + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-6.1.1.tgz", 387 + "integrity": "sha512-HBL8I3mIki5C1Cc9QjKUenHtnG0A5/xA8Q/AllRcfiwl2CZFXGK7ddBiCoRwAix4i2KxcQfjtIVcrVbB3vbmwg==", 388 + "dev": true 389 + }, 390 + "node_modules/escape-html": { 391 + "version": "1.0.3", 392 + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 393 + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", 394 + "dev": true 395 + }, 396 + "node_modules/escape-string-regexp": { 397 + "version": "1.0.5", 398 + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 399 + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", 400 + "dev": true, 401 + "engines": { 402 + "node": ">=0.8.0" 403 + } 404 + }, 405 + "node_modules/etag": { 406 + "version": "1.8.1", 407 + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 408 + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", 409 + "dev": true, 410 + "engines": { 411 + "node": ">= 0.6" 412 + } 413 + }, 414 + "node_modules/eventemitter3": { 415 + "version": "3.1.2", 416 + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz", 417 + "integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==", 418 + "dev": true 419 + }, 420 + "node_modules/execa": { 421 + "version": "1.0.0", 422 + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", 423 + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", 424 + "dev": true, 425 + "dependencies": { 426 + "cross-spawn": "^6.0.0", 427 + "get-stream": "^4.0.0", 428 + "is-stream": "^1.1.0", 429 + "npm-run-path": "^2.0.0", 430 + "p-finally": "^1.0.0", 431 + "signal-exit": "^3.0.0", 432 + "strip-eof": "^1.0.0" 433 + }, 434 + "engines": { 435 + "node": ">=6" 436 + } 437 + }, 438 + "node_modules/execa/node_modules/cross-spawn": { 439 + "version": "6.0.6", 440 + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.6.tgz", 441 + "integrity": "sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==", 442 + "dev": true, 443 + "dependencies": { 444 + "nice-try": "^1.0.4", 445 + "path-key": "^2.0.1", 446 + "semver": "^5.5.0", 447 + "shebang-command": "^1.2.0", 448 + "which": "^1.2.9" 449 + }, 450 + "engines": { 451 + "node": ">=4.8" 452 + } 453 + }, 454 + "node_modules/extend": { 455 + "version": "3.0.2", 456 + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", 457 + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", 458 + "dev": true 459 + }, 460 + "node_modules/extsprintf": { 461 + "version": "1.3.0", 462 + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", 463 + "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", 464 + "dev": true, 465 + "engines": [ 466 + "node >=0.6.0" 467 + ] 468 + }, 469 + "node_modules/fast-deep-equal": { 470 + "version": "3.1.3", 471 + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", 472 + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", 473 + "dev": true 474 + }, 475 + "node_modules/fast-json-stable-stringify": { 476 + "version": "2.1.0", 477 + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", 478 + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", 479 + "dev": true 480 + }, 481 + "node_modules/fill-range": { 482 + "version": "7.1.1", 483 + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", 484 + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", 485 + "dev": true, 486 + "dependencies": { 487 + "to-regex-range": "^5.0.1" 488 + }, 489 + "engines": { 490 + "node": ">=8" 491 + } 492 + }, 493 + "node_modules/finalhandler": { 494 + "version": "1.1.2", 495 + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", 496 + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", 497 + "dev": true, 498 + "dependencies": { 499 + "debug": "2.6.9", 500 + "encodeurl": "~1.0.2", 501 + "escape-html": "~1.0.3", 502 + "on-finished": "~2.3.0", 503 + "parseurl": "~1.3.3", 504 + "statuses": "~1.5.0", 505 + "unpipe": "~1.0.0" 506 + }, 507 + "engines": { 508 + "node": ">= 0.8" 509 + } 510 + }, 511 + "node_modules/follow-redirects": { 512 + "version": "1.16.0", 513 + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", 514 + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", 515 + "dev": true, 516 + "funding": [ 517 + { 518 + "type": "individual", 519 + "url": "https://github.com/sponsors/RubenVerborgh" 520 + } 521 + ], 522 + "engines": { 523 + "node": ">=4.0" 524 + }, 525 + "peerDependenciesMeta": { 526 + "debug": { 527 + "optional": true 528 + } 529 + } 530 + }, 531 + "node_modules/forever-agent": { 532 + "version": "0.6.1", 533 + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", 534 + "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", 535 + "dev": true, 536 + "engines": { 537 + "node": "*" 538 + } 539 + }, 540 + "node_modules/form-data": { 541 + "version": "2.3.3", 542 + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", 543 + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", 544 + "dev": true, 545 + "dependencies": { 546 + "asynckit": "^0.4.0", 547 + "combined-stream": "^1.0.6", 548 + "mime-types": "^2.1.12" 549 + }, 550 + "engines": { 551 + "node": ">= 0.12" 552 + } 553 + }, 554 + "node_modules/fresh": { 555 + "version": "0.5.2", 556 + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 557 + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", 558 + "dev": true, 559 + "engines": { 560 + "node": ">= 0.6" 561 + } 562 + }, 563 + "node_modules/fsevents": { 564 + "version": "2.3.3", 565 + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", 566 + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", 567 + "dev": true, 568 + "hasInstallScript": true, 569 + "optional": true, 570 + "os": [ 571 + "darwin" 572 + ], 573 + "engines": { 574 + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 575 + } 576 + }, 577 + "node_modules/get-stream": { 578 + "version": "4.1.0", 579 + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", 580 + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", 581 + "dev": true, 582 + "dependencies": { 583 + "pump": "^3.0.0" 584 + }, 585 + "engines": { 586 + "node": ">=6" 587 + } 588 + }, 589 + "node_modules/getpass": { 590 + "version": "0.1.7", 591 + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", 592 + "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", 593 + "dev": true, 594 + "dependencies": { 595 + "assert-plus": "^1.0.0" 596 + } 597 + }, 598 + "node_modules/glob-parent": { 599 + "version": "5.1.2", 600 + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", 601 + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", 602 + "dev": true, 603 + "dependencies": { 604 + "is-glob": "^4.0.1" 605 + }, 606 + "engines": { 607 + "node": ">= 6" 608 + } 609 + }, 610 + "node_modules/har-schema": { 611 + "version": "2.0.0", 612 + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", 613 + "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==", 614 + "dev": true, 615 + "engines": { 616 + "node": ">=4" 617 + } 618 + }, 619 + "node_modules/har-validator": { 620 + "version": "5.1.5", 621 + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", 622 + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", 623 + "deprecated": "this library is no longer supported", 624 + "dev": true, 625 + "dependencies": { 626 + "ajv": "^6.12.3", 627 + "har-schema": "^2.0.0" 628 + }, 629 + "engines": { 630 + "node": ">=6" 631 + } 632 + }, 633 + "node_modules/has-ansi": { 634 + "version": "2.0.0", 635 + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", 636 + "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==", 637 + "dev": true, 638 + "dependencies": { 639 + "ansi-regex": "^2.0.0" 640 + }, 641 + "engines": { 642 + "node": ">=0.10.0" 643 + } 644 + }, 645 + "node_modules/http-errors": { 646 + "version": "1.7.3", 647 + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz", 648 + "integrity": "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==", 649 + "dev": true, 650 + "dependencies": { 651 + "depd": "~1.1.2", 652 + "inherits": "2.0.4", 653 + "setprototypeof": "1.1.1", 654 + "statuses": ">= 1.5.0 < 2", 655 + "toidentifier": "1.0.0" 656 + }, 657 + "engines": { 658 + "node": ">= 0.6" 659 + } 660 + }, 661 + "node_modules/http-proxy": { 662 + "version": "1.17.0", 663 + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.17.0.tgz", 664 + "integrity": "sha512-Taqn+3nNvYRfJ3bGvKfBSRwy1v6eePlm3oc/aWVxZp57DQr5Eq3xhKJi7Z4hZpS8PC3H4qI+Yly5EmFacGuA/g==", 665 + "dev": true, 666 + "dependencies": { 667 + "eventemitter3": "^3.0.0", 668 + "follow-redirects": "^1.0.0", 669 + "requires-port": "^1.0.0" 670 + }, 671 + "engines": { 672 + "node": ">=4.0.0" 673 + } 674 + }, 675 + "node_modules/http-signature": { 676 + "version": "1.2.0", 677 + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", 678 + "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", 679 + "dev": true, 680 + "dependencies": { 681 + "assert-plus": "^1.0.0", 682 + "jsprim": "^1.2.2", 683 + "sshpk": "^1.7.0" 684 + }, 685 + "engines": { 686 + "node": ">=0.8", 687 + "npm": ">=1.3.7" 688 + } 689 + }, 690 + "node_modules/inherits": { 691 + "version": "2.0.4", 692 + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 693 + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 694 + "dev": true 695 + }, 696 + "node_modules/internal-ip": { 697 + "version": "4.3.0", 698 + "resolved": "https://registry.npmjs.org/internal-ip/-/internal-ip-4.3.0.tgz", 699 + "integrity": "sha512-S1zBo1D6zcsyuC6PMmY5+55YMILQ9av8lotMx447Bq6SAgo/sDK6y6uUKmuYhW7eacnIhFfsPmCNYdDzsnnDCg==", 700 + "dev": true, 701 + "dependencies": { 702 + "default-gateway": "^4.2.0", 703 + "ipaddr.js": "^1.9.0" 704 + }, 705 + "engines": { 706 + "node": ">=6" 707 + } 708 + }, 709 + "node_modules/ip-regex": { 710 + "version": "2.1.0", 711 + "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", 712 + "integrity": "sha512-58yWmlHpp7VYfcdTwMTvwMmqx/Elfxjd9RXTDyMsbL7lLWmhMylLEqiYVLKuLzOZqVgiWXD9MfR62Vv89VRxkw==", 713 + "dev": true, 714 + "engines": { 715 + "node": ">=4" 716 + } 717 + }, 718 + "node_modules/ipaddr.js": { 719 + "version": "1.9.1", 720 + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", 721 + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", 722 + "dev": true, 723 + "engines": { 724 + "node": ">= 0.10" 725 + } 726 + }, 727 + "node_modules/is-binary-path": { 728 + "version": "2.1.0", 729 + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", 730 + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", 731 + "dev": true, 732 + "dependencies": { 733 + "binary-extensions": "^2.0.0" 734 + }, 735 + "engines": { 736 + "node": ">=8" 737 + } 738 + }, 739 + "node_modules/is-buffer": { 740 + "version": "1.1.6", 741 + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", 742 + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", 743 + "dev": true 744 + }, 745 + "node_modules/is-extglob": { 746 + "version": "2.1.1", 747 + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 748 + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", 749 + "dev": true, 750 + "engines": { 751 + "node": ">=0.10.0" 752 + } 753 + }, 754 + "node_modules/is-glob": { 755 + "version": "4.0.3", 756 + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", 757 + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", 758 + "dev": true, 759 + "dependencies": { 760 + "is-extglob": "^2.1.1" 761 + }, 762 + "engines": { 763 + "node": ">=0.10.0" 764 + } 765 + }, 766 + "node_modules/is-number": { 767 + "version": "7.0.0", 768 + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", 769 + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", 770 + "dev": true, 771 + "engines": { 772 + "node": ">=0.12.0" 773 + } 774 + }, 775 + "node_modules/is-stream": { 776 + "version": "1.1.0", 777 + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", 778 + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", 779 + "dev": true, 780 + "engines": { 781 + "node": ">=0.10.0" 782 + } 783 + }, 784 + "node_modules/is-typedarray": { 785 + "version": "1.0.0", 786 + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", 787 + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", 788 + "dev": true 789 + }, 790 + "node_modules/is-wsl": { 791 + "version": "1.1.0", 792 + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", 793 + "integrity": "sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw==", 794 + "dev": true, 795 + "engines": { 796 + "node": ">=4" 797 + } 798 + }, 799 + "node_modules/isexe": { 800 + "version": "2.0.0", 801 + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 802 + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", 803 + "dev": true 804 + }, 805 + "node_modules/isstream": { 806 + "version": "0.1.2", 807 + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", 808 + "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", 809 + "dev": true 810 + }, 811 + "node_modules/jsbn": { 812 + "version": "0.1.1", 813 + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", 814 + "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", 815 + "dev": true 816 + }, 817 + "node_modules/json-schema": { 818 + "version": "0.4.0", 819 + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", 820 + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", 821 + "dev": true 822 + }, 823 + "node_modules/json-schema-traverse": { 824 + "version": "0.4.1", 825 + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", 826 + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", 827 + "dev": true 828 + }, 829 + "node_modules/json-stringify-safe": { 830 + "version": "5.0.1", 831 + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", 832 + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", 833 + "dev": true 834 + }, 835 + "node_modules/jsprim": { 836 + "version": "1.4.2", 837 + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", 838 + "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", 839 + "dev": true, 840 + "dependencies": { 841 + "assert-plus": "1.0.0", 842 + "extsprintf": "1.3.0", 843 + "json-schema": "0.4.0", 844 + "verror": "1.10.0" 845 + }, 846 + "engines": { 847 + "node": ">=0.6.0" 848 + } 849 + }, 850 + "node_modules/lru-cache": { 851 + "version": "4.1.5", 852 + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", 853 + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", 854 + "dev": true, 855 + "dependencies": { 856 + "pseudomap": "^1.0.2", 857 + "yallist": "^2.1.2" 858 + } 859 + }, 860 + "node_modules/md5": { 861 + "version": "2.3.0", 862 + "resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz", 863 + "integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==", 864 + "dev": true, 865 + "dependencies": { 866 + "charenc": "0.0.2", 867 + "crypt": "0.0.2", 868 + "is-buffer": "~1.1.6" 869 + } 870 + }, 871 + "node_modules/mime": { 872 + "version": "2.4.3", 873 + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.3.tgz", 874 + "integrity": "sha512-QgrPRJfE+riq5TPZMcHZOtm8c6K/yYrMbKIoRfapfiGLxS8OTeIfRhUGW5LU7MlRa52KOAGCfUNruqLrIBvWZw==", 875 + "dev": true, 876 + "bin": { 877 + "mime": "cli.js" 878 + }, 879 + "engines": { 880 + "node": ">=4.0.0" 881 + } 882 + }, 883 + "node_modules/mime-db": { 884 + "version": "1.52.0", 885 + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", 886 + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", 887 + "dev": true, 888 + "engines": { 889 + "node": ">= 0.6" 890 + } 891 + }, 892 + "node_modules/mime-types": { 893 + "version": "2.1.35", 894 + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", 895 + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", 896 + "dev": true, 897 + "dependencies": { 898 + "mime-db": "1.52.0" 899 + }, 900 + "engines": { 901 + "node": ">= 0.6" 902 + } 903 + }, 904 + "node_modules/ms": { 905 + "version": "2.0.0", 906 + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 907 + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", 908 + "dev": true 909 + }, 910 + "node_modules/nice-try": { 911 + "version": "1.0.5", 912 + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", 913 + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", 914 + "dev": true 915 + }, 916 + "node_modules/normalize-path": { 917 + "version": "3.0.0", 918 + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", 919 + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", 920 + "dev": true, 921 + "engines": { 922 + "node": ">=0.10.0" 923 + } 924 + }, 925 + "node_modules/npm-run-path": { 926 + "version": "2.0.2", 927 + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", 928 + "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", 929 + "dev": true, 930 + "dependencies": { 931 + "path-key": "^2.0.0" 932 + }, 933 + "engines": { 934 + "node": ">=4" 935 + } 936 + }, 937 + "node_modules/oauth-sign": { 938 + "version": "0.9.0", 939 + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", 940 + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", 941 + "dev": true, 942 + "engines": { 943 + "node": "*" 944 + } 945 + }, 946 + "node_modules/on-finished": { 947 + "version": "2.3.0", 948 + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 949 + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", 950 + "dev": true, 951 + "dependencies": { 952 + "ee-first": "1.1.1" 953 + }, 954 + "engines": { 955 + "node": ">= 0.8" 956 + } 957 + }, 958 + "node_modules/once": { 959 + "version": "1.4.0", 960 + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 961 + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", 962 + "dev": true, 963 + "dependencies": { 964 + "wrappy": "1" 965 + } 966 + }, 967 + "node_modules/open": { 968 + "version": "6.4.0", 969 + "resolved": "https://registry.npmjs.org/open/-/open-6.4.0.tgz", 970 + "integrity": "sha512-IFenVPgF70fSm1keSd2iDBIDIBZkroLeuffXq+wKTzTJlBpesFWojV9lb8mzOfaAzM1sr7HQHuO0vtV0zYekGg==", 971 + "dev": true, 972 + "dependencies": { 973 + "is-wsl": "^1.1.0" 974 + }, 975 + "engines": { 976 + "node": ">=8" 977 + } 978 + }, 979 + "node_modules/os-tmpdir": { 980 + "version": "1.0.2", 981 + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", 982 + "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", 983 + "dev": true, 984 + "engines": { 985 + "node": ">=0.10.0" 986 + } 987 + }, 988 + "node_modules/p-finally": { 989 + "version": "1.0.0", 990 + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", 991 + "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", 992 + "dev": true, 993 + "engines": { 994 + "node": ">=4" 995 + } 996 + }, 997 + "node_modules/parseurl": { 998 + "version": "1.3.3", 999 + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 1000 + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", 1001 + "dev": true, 1002 + "engines": { 1003 + "node": ">= 0.8" 1004 + } 1005 + }, 1006 + "node_modules/path-key": { 1007 + "version": "2.0.1", 1008 + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", 1009 + "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", 1010 + "dev": true, 1011 + "engines": { 1012 + "node": ">=4" 1013 + } 1014 + }, 1015 + "node_modules/pem": { 1016 + "version": "1.14.2", 1017 + "resolved": "https://registry.npmjs.org/pem/-/pem-1.14.2.tgz", 1018 + "integrity": "sha512-TOnPtq3ZFnCniOZ+rka4pk8UIze9xG1qI+wNE7EmkiR/cg+53uVvk5QbkWZ7M6RsuOxzz62FW1hlAobJr/lTOA==", 1019 + "dev": true, 1020 + "dependencies": { 1021 + "es6-promisify": "^6.0.0", 1022 + "md5": "^2.2.1", 1023 + "os-tmpdir": "^1.0.1", 1024 + "which": "^1.3.1" 1025 + }, 1026 + "engines": { 1027 + "node": ">=6.0.0" 1028 + } 1029 + }, 1030 + "node_modules/performance-now": { 1031 + "version": "2.1.0", 1032 + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", 1033 + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", 1034 + "dev": true 1035 + }, 1036 + "node_modules/picomatch": { 1037 + "version": "2.3.2", 1038 + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", 1039 + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", 1040 + "dev": true, 1041 + "engines": { 1042 + "node": ">=8.6" 1043 + }, 1044 + "funding": { 1045 + "url": "https://github.com/sponsors/jonschlinkert" 1046 + } 1047 + }, 1048 + "node_modules/pseudomap": { 1049 + "version": "1.0.2", 1050 + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", 1051 + "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==", 1052 + "dev": true 1053 + }, 1054 + "node_modules/psl": { 1055 + "version": "1.15.0", 1056 + "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", 1057 + "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==", 1058 + "dev": true, 1059 + "dependencies": { 1060 + "punycode": "^2.3.1" 1061 + }, 1062 + "funding": { 1063 + "url": "https://github.com/sponsors/lupomontero" 1064 + } 1065 + }, 1066 + "node_modules/pump": { 1067 + "version": "3.0.4", 1068 + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", 1069 + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", 1070 + "dev": true, 1071 + "dependencies": { 1072 + "end-of-stream": "^1.1.0", 1073 + "once": "^1.3.1" 1074 + } 1075 + }, 1076 + "node_modules/punycode": { 1077 + "version": "2.3.1", 1078 + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", 1079 + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", 1080 + "dev": true, 1081 + "engines": { 1082 + "node": ">=6" 1083 + } 1084 + }, 1085 + "node_modules/qs": { 1086 + "version": "6.5.5", 1087 + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.5.tgz", 1088 + "integrity": "sha512-mzR4sElr1bfCaPJe7m8ilJ6ZXdDaGoObcYR0ZHSsktM/Lt21MVHj5De30GQH2eiZ1qGRTO7LCAzQsUeXTNexWQ==", 1089 + "dev": true, 1090 + "engines": { 1091 + "node": ">=0.6" 1092 + } 1093 + }, 1094 + "node_modules/range-parser": { 1095 + "version": "1.2.1", 1096 + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 1097 + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", 1098 + "dev": true, 1099 + "engines": { 1100 + "node": ">= 0.6" 1101 + } 1102 + }, 1103 + "node_modules/readdirp": { 1104 + "version": "3.6.0", 1105 + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", 1106 + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", 1107 + "dev": true, 1108 + "dependencies": { 1109 + "picomatch": "^2.2.1" 1110 + }, 1111 + "engines": { 1112 + "node": ">=8.10.0" 1113 + } 1114 + }, 1115 + "node_modules/request": { 1116 + "version": "2.88.2", 1117 + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", 1118 + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", 1119 + "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142", 1120 + "dev": true, 1121 + "dependencies": { 1122 + "aws-sign2": "~0.7.0", 1123 + "aws4": "^1.8.0", 1124 + "caseless": "~0.12.0", 1125 + "combined-stream": "~1.0.6", 1126 + "extend": "~3.0.2", 1127 + "forever-agent": "~0.6.1", 1128 + "form-data": "~2.3.2", 1129 + "har-validator": "~5.1.3", 1130 + "http-signature": "~1.2.0", 1131 + "is-typedarray": "~1.0.0", 1132 + "isstream": "~0.1.2", 1133 + "json-stringify-safe": "~5.0.1", 1134 + "mime-types": "~2.1.19", 1135 + "oauth-sign": "~0.9.0", 1136 + "performance-now": "^2.1.0", 1137 + "qs": "~6.5.2", 1138 + "safe-buffer": "^5.1.2", 1139 + "tough-cookie": "~2.5.0", 1140 + "tunnel-agent": "^0.6.0", 1141 + "uuid": "^3.3.2" 1142 + }, 1143 + "engines": { 1144 + "node": ">= 6" 1145 + } 1146 + }, 1147 + "node_modules/requires-port": { 1148 + "version": "1.0.0", 1149 + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", 1150 + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", 1151 + "dev": true 1152 + }, 1153 + "node_modules/safe-buffer": { 1154 + "version": "5.2.1", 1155 + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 1156 + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", 1157 + "dev": true, 1158 + "funding": [ 1159 + { 1160 + "type": "github", 1161 + "url": "https://github.com/sponsors/feross" 1162 + }, 1163 + { 1164 + "type": "patreon", 1165 + "url": "https://www.patreon.com/feross" 1166 + }, 1167 + { 1168 + "type": "consulting", 1169 + "url": "https://feross.org/support" 1170 + } 1171 + ] 1172 + }, 1173 + "node_modules/safer-buffer": { 1174 + "version": "2.1.2", 1175 + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 1176 + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", 1177 + "dev": true 1178 + }, 1179 + "node_modules/semver": { 1180 + "version": "5.7.2", 1181 + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", 1182 + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", 1183 + "dev": true, 1184 + "bin": { 1185 + "semver": "bin/semver" 1186 + } 1187 + }, 1188 + "node_modules/send": { 1189 + "version": "0.17.1", 1190 + "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", 1191 + "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", 1192 + "dev": true, 1193 + "dependencies": { 1194 + "debug": "2.6.9", 1195 + "depd": "~1.1.2", 1196 + "destroy": "~1.0.4", 1197 + "encodeurl": "~1.0.2", 1198 + "escape-html": "~1.0.3", 1199 + "etag": "~1.8.1", 1200 + "fresh": "0.5.2", 1201 + "http-errors": "~1.7.2", 1202 + "mime": "1.6.0", 1203 + "ms": "2.1.1", 1204 + "on-finished": "~2.3.0", 1205 + "range-parser": "~1.2.1", 1206 + "statuses": "~1.5.0" 1207 + }, 1208 + "engines": { 1209 + "node": ">= 0.8.0" 1210 + } 1211 + }, 1212 + "node_modules/send/node_modules/mime": { 1213 + "version": "1.6.0", 1214 + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 1215 + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", 1216 + "dev": true, 1217 + "bin": { 1218 + "mime": "cli.js" 1219 + }, 1220 + "engines": { 1221 + "node": ">=4" 1222 + } 1223 + }, 1224 + "node_modules/send/node_modules/ms": { 1225 + "version": "2.1.1", 1226 + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", 1227 + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", 1228 + "dev": true 1229 + }, 1230 + "node_modules/serve-static": { 1231 + "version": "1.14.1", 1232 + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", 1233 + "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", 1234 + "dev": true, 1235 + "dependencies": { 1236 + "encodeurl": "~1.0.2", 1237 + "escape-html": "~1.0.3", 1238 + "parseurl": "~1.3.3", 1239 + "send": "0.17.1" 1240 + }, 1241 + "engines": { 1242 + "node": ">= 0.8.0" 1243 + } 1244 + }, 1245 + "node_modules/setprototypeof": { 1246 + "version": "1.1.1", 1247 + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", 1248 + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==", 1249 + "dev": true 1250 + }, 1251 + "node_modules/shebang-command": { 1252 + "version": "1.2.0", 1253 + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", 1254 + "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", 1255 + "dev": true, 1256 + "dependencies": { 1257 + "shebang-regex": "^1.0.0" 1258 + }, 1259 + "engines": { 1260 + "node": ">=0.10.0" 1261 + } 1262 + }, 1263 + "node_modules/shebang-regex": { 1264 + "version": "1.0.0", 1265 + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", 1266 + "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", 1267 + "dev": true, 1268 + "engines": { 1269 + "node": ">=0.10.0" 1270 + } 1271 + }, 1272 + "node_modules/signal-exit": { 1273 + "version": "3.0.7", 1274 + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", 1275 + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", 1276 + "dev": true 1277 + }, 1278 + "node_modules/sshpk": { 1279 + "version": "1.18.0", 1280 + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz", 1281 + "integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==", 1282 + "dev": true, 1283 + "dependencies": { 1284 + "asn1": "~0.2.3", 1285 + "assert-plus": "^1.0.0", 1286 + "bcrypt-pbkdf": "^1.0.0", 1287 + "dashdash": "^1.12.0", 1288 + "ecc-jsbn": "~0.1.1", 1289 + "getpass": "^0.1.1", 1290 + "jsbn": "~0.1.0", 1291 + "safer-buffer": "^2.0.2", 1292 + "tweetnacl": "~0.14.0" 1293 + }, 1294 + "bin": { 1295 + "sshpk-conv": "bin/sshpk-conv", 1296 + "sshpk-sign": "bin/sshpk-sign", 1297 + "sshpk-verify": "bin/sshpk-verify" 1298 + }, 1299 + "engines": { 1300 + "node": ">=0.10.0" 1301 + } 1302 + }, 1303 + "node_modules/statuses": { 1304 + "version": "1.5.0", 1305 + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", 1306 + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", 1307 + "dev": true, 1308 + "engines": { 1309 + "node": ">= 0.6" 1310 + } 1311 + }, 1312 + "node_modules/strip-ansi": { 1313 + "version": "3.0.1", 1314 + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", 1315 + "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", 1316 + "dev": true, 1317 + "dependencies": { 1318 + "ansi-regex": "^2.0.0" 1319 + }, 1320 + "engines": { 1321 + "node": ">=0.10.0" 1322 + } 1323 + }, 1324 + "node_modules/strip-eof": { 1325 + "version": "1.0.0", 1326 + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", 1327 + "integrity": "sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==", 1328 + "dev": true, 1329 + "engines": { 1330 + "node": ">=0.10.0" 1331 + } 1332 + }, 1333 + "node_modules/supports-color": { 1334 + "version": "2.0.0", 1335 + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", 1336 + "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", 1337 + "dev": true, 1338 + "engines": { 1339 + "node": ">=0.8.0" 1340 + } 1341 + }, 1342 + "node_modules/to-regex-range": { 1343 + "version": "5.0.1", 1344 + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", 1345 + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", 1346 + "dev": true, 1347 + "dependencies": { 1348 + "is-number": "^7.0.0" 1349 + }, 1350 + "engines": { 1351 + "node": ">=8.0" 1352 + } 1353 + }, 1354 + "node_modules/toidentifier": { 1355 + "version": "1.0.0", 1356 + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", 1357 + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", 1358 + "dev": true, 1359 + "engines": { 1360 + "node": ">=0.6" 1361 + } 1362 + }, 1363 + "node_modules/tough-cookie": { 1364 + "version": "2.5.0", 1365 + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", 1366 + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", 1367 + "dev": true, 1368 + "dependencies": { 1369 + "psl": "^1.1.28", 1370 + "punycode": "^2.1.1" 1371 + }, 1372 + "engines": { 1373 + "node": ">=0.8" 1374 + } 1375 + }, 1376 + "node_modules/tunnel-agent": { 1377 + "version": "0.6.0", 1378 + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", 1379 + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", 1380 + "dev": true, 1381 + "dependencies": { 1382 + "safe-buffer": "^5.0.1" 1383 + }, 1384 + "engines": { 1385 + "node": "*" 1386 + } 1387 + }, 1388 + "node_modules/tweetnacl": { 1389 + "version": "0.14.5", 1390 + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", 1391 + "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", 1392 + "dev": true 1393 + }, 1394 + "node_modules/unpipe": { 1395 + "version": "1.0.0", 1396 + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 1397 + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", 1398 + "dev": true, 1399 + "engines": { 1400 + "node": ">= 0.8" 1401 + } 1402 + }, 1403 + "node_modules/uri-js": { 1404 + "version": "4.4.1", 1405 + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", 1406 + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", 1407 + "dev": true, 1408 + "dependencies": { 1409 + "punycode": "^2.1.0" 1410 + } 1411 + }, 1412 + "node_modules/uuid": { 1413 + "version": "3.4.0", 1414 + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", 1415 + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", 1416 + "deprecated": "uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).", 1417 + "dev": true, 1418 + "bin": { 1419 + "uuid": "bin/uuid" 1420 + } 1421 + }, 1422 + "node_modules/verror": { 1423 + "version": "1.10.0", 1424 + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", 1425 + "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", 1426 + "dev": true, 1427 + "engines": [ 1428 + "node >=0.6.0" 1429 + ], 1430 + "dependencies": { 1431 + "assert-plus": "^1.0.0", 1432 + "core-util-is": "1.0.2", 1433 + "extsprintf": "^1.2.0" 1434 + } 1435 + }, 1436 + "node_modules/which": { 1437 + "version": "1.3.1", 1438 + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", 1439 + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", 1440 + "dev": true, 1441 + "dependencies": { 1442 + "isexe": "^2.0.0" 1443 + }, 1444 + "bin": { 1445 + "which": "bin/which" 1446 + } 1447 + }, 1448 + "node_modules/wrappy": { 1449 + "version": "1.0.2", 1450 + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 1451 + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", 1452 + "dev": true 1453 + }, 1454 + "node_modules/ws": { 1455 + "version": "7.1.1", 1456 + "resolved": "https://registry.npmjs.org/ws/-/ws-7.1.1.tgz", 1457 + "integrity": "sha512-o41D/WmDeca0BqYhsr3nJzQyg9NF5X8l/UdnFNux9cS3lwB+swm8qGWX5rn+aD6xfBU3rGmtHij7g7x6LxFU3A==", 1458 + "dev": true, 1459 + "dependencies": { 1460 + "async-limiter": "^1.0.0" 1461 + } 1462 + }, 1463 + "node_modules/yallist": { 1464 + "version": "2.1.2", 1465 + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", 1466 + "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==", 1467 + "dev": true 1468 + } 1469 + } 1470 + }
+15
package.json
··· 1 + { 2 + "name": "minha-receita-graph", 3 + "version": "0.1.0", 4 + "description": "Meu Garfo", 5 + "main": "index.js", 6 + "scripts": { 7 + "start": "node configure.js && elm-live src/Main.elm --dir=dist --port=${PORT:-8000} -- --output=dist/main.js --debug", 8 + "build": "node configure.js && elm make src/Main.elm --output=dist/main.js", 9 + "deploy": "node deploy.js" 10 + }, 11 + "devDependencies": { 12 + "elm": "^0.19.1-5", 13 + "elm-live": "^4.0.2" 14 + } 15 + }
+150
src/Api.elm
··· 1 + module Api exposing (fetchCompanyName, queryEntity, responseDecoder) 2 + 3 + import Http 4 + import Json.Decode as Decode exposing (Decoder, field, string) 5 + import Types exposing (..) 6 + 7 + 8 + fetchCompanyName : String -> String -> Cmd Msg 9 + fetchCompanyName jsonApi cnpj = 10 + let 11 + url = 12 + if String.right 1 jsonApi == "/" then 13 + jsonApi ++ cnpj 14 + 15 + else 16 + jsonApi ++ "/" ++ cnpj 17 + in 18 + Http.get 19 + { url = url 20 + , expect = 21 + Http.expectStringResponse (GotCompanyName cnpj url) 22 + (\response -> 23 + case response of 24 + Http.BadStatus_ meta _ -> 25 + Err (Http.BadStatus meta.statusCode) 26 + 27 + Http.GoodStatus_ _ body -> 28 + case Decode.decodeString (Decode.field "razao_social" Decode.string) body of 29 + Ok name -> 30 + Ok name 31 + 32 + Err _ -> 33 + Err (Http.BadBody "Could not parse razao_social") 34 + 35 + Http.BadUrl_ url_ -> 36 + Err (Http.BadUrl url_) 37 + 38 + Http.Timeout_ -> 39 + Err Http.Timeout 40 + 41 + Http.NetworkError_ -> 42 + Err Http.NetworkError 43 + ) 44 + } 45 + 46 + 47 + queryEntity : String -> QueryType -> Int -> Cmd Msg 48 + queryEntity graphApi qType depth = 49 + let 50 + base = 51 + if String.right 1 graphApi == "/" then 52 + graphApi 53 + 54 + else 55 + graphApi ++ "/" 56 + 57 + ( path, id ) = 58 + case qType of 59 + CompanyCnpj cnpj -> 60 + ( "qsa/", cnpj ) 61 + 62 + CompanyAsPartner cnpj -> 63 + ( "cnpjs/", cnpj ) 64 + 65 + PersonUid uid -> 66 + ( "cnpjs/", uid ) 67 + 68 + url = 69 + base ++ path ++ id 70 + in 71 + Http.get 72 + { url = url 73 + , expect = Http.expectStringResponse (GotResponse id url depth) toApiResult 74 + } 75 + 76 + 77 + toApiResult : Http.Response String -> Result ApiError ApiResponse 78 + toApiResult response = 79 + case response of 80 + Http.BadUrl_ u -> 81 + Err (HttpError (Http.BadUrl u)) 82 + 83 + Http.Timeout_ -> 84 + Err (HttpError Http.Timeout) 85 + 86 + Http.NetworkError_ -> 87 + Err (HttpError Http.NetworkError) 88 + 89 + Http.BadStatus_ meta body -> 90 + if meta.statusCode == 404 then 91 + Err NotFound 92 + 93 + else if meta.statusCode == 400 then 94 + case Decode.decodeString (field "message" string) body of 95 + Ok msg -> 96 + Err (BadRequest msg) 97 + 98 + Err _ -> 99 + Err (HttpError (Http.BadStatus meta.statusCode)) 100 + 101 + else 102 + Err (HttpError (Http.BadStatus meta.statusCode)) 103 + 104 + Http.GoodStatus_ _ body -> 105 + case Decode.decodeString responseDecoder body of 106 + Ok value -> 107 + Ok value 108 + 109 + Err err -> 110 + Err (HttpError (Http.BadBody (Decode.errorToString err))) 111 + 112 + 113 + responseDecoder : Decoder ApiResponse 114 + responseDecoder = 115 + Decode.oneOf 116 + [ Decode.map CompanyResponse companyDecoder 117 + , Decode.map PartnerResponse partnerDecoder 118 + ] 119 + 120 + 121 + companyDecoder : Decoder CompanyData 122 + companyDecoder = 123 + Decode.map3 CompanyData 124 + (field "company_id" string) 125 + (field "name" string) 126 + (field "partners" (Decode.list partnerRefDecoder)) 127 + 128 + 129 + partnerRefDecoder : Decoder PartnerRef 130 + partnerRefDecoder = 131 + Decode.map3 PartnerRef 132 + (field "partner_id" (Decode.oneOf [ string, Decode.map String.fromInt Decode.int ])) 133 + (Decode.maybe (field "name" string)) 134 + (Decode.maybe (field "cpf" string)) 135 + 136 + 137 + partnerDecoder : Decoder PartnerData 138 + partnerDecoder = 139 + Decode.map4 PartnerData 140 + (Decode.oneOf [ field "partner_id" string, field "partnerd_id" string, field "partner_id" (Decode.map String.fromInt Decode.int) ]) 141 + (Decode.maybe (field "name" string)) 142 + (Decode.maybe (field "cpf" string)) 143 + (field "companies" (Decode.list companyRefDecoder)) 144 + 145 + 146 + companyRefDecoder : Decoder CompanyRef 147 + companyRefDecoder = 148 + Decode.map2 CompanyRef 149 + (field "cnpj" string) 150 + (field "name" string)
+76
src/Format.elm
··· 1 + module Format exposing (formatCnpj, formatCpf, mask, maskCnpjInput) 2 + 3 + 4 + mask : String -> String 5 + mask raw = 6 + let 7 + cleaned = 8 + String.filter (\c -> Char.isAlphaNum c || c == '*') raw 9 + in 10 + if String.length cleaned == 14 then 11 + formatCnpj cleaned 12 + 13 + else if String.length cleaned == 11 then 14 + formatCpf cleaned 15 + 16 + else 17 + raw 18 + 19 + 20 + maskCnpjInput : String -> String -> String 21 + maskCnpjInput previous raw = 22 + let 23 + previousAlnum = 24 + String.filter Char.isAlphaNum previous 25 + 26 + rawAlnum = 27 + String.filter Char.isAlphaNum raw 28 + 29 + deleting = 30 + String.length raw < String.length previous 31 + 32 + backspacedSeparator = 33 + deleting && String.length rawAlnum == String.length previousAlnum 34 + 35 + cleaned = 36 + if backspacedSeparator then 37 + String.dropRight 1 rawAlnum |> String.toUpper |> String.left 14 38 + 39 + else 40 + rawAlnum |> String.toUpper |> String.left 14 41 + 42 + chunks = 43 + [ ( 2, "." ), ( 5, "." ), ( 8, "/" ), ( 12, "-" ) ] 44 + 45 + insertAt ( pos, sep ) s = 46 + let 47 + threshold = 48 + if deleting then 49 + pos + 1 50 + 51 + else 52 + pos 53 + in 54 + if String.length s >= threshold && String.length s > 0 then 55 + String.left pos s ++ sep ++ String.dropLeft pos s 56 + 57 + else 58 + s 59 + in 60 + List.foldr insertAt cleaned chunks 61 + 62 + 63 + formatCnpj : String -> String 64 + formatCnpj s = 65 + maskCnpjInput "" s 66 + 67 + 68 + formatCpf : String -> String 69 + formatCpf s = 70 + String.left 3 s 71 + ++ "." 72 + ++ String.slice 3 6 s 73 + ++ "." 74 + ++ String.slice 6 9 s 75 + ++ "-" 76 + ++ String.slice 9 11 s
+56
src/Graph.elm
··· 1 + module Graph exposing (initSimulation, isSimulating, layout, tick) 2 + 3 + import Dict 4 + import Force 5 + import Set 6 + import Types exposing (..) 7 + 8 + 9 + makeSim : Float -> Float -> List String -> List ( String, String ) -> Force.State String 10 + makeSim w h nodeIds links = 11 + Force.iterations 120 12 + (Force.simulation 13 + [ Force.manyBodyStrength -200 nodeIds 14 + , Force.center (w / 2) (h / 2) 15 + , Force.links links 16 + , Force.collision 60 nodeIds 17 + ] 18 + ) 19 + 20 + 21 + initSimulation : Float -> Float -> List String -> Force.State String 22 + initSimulation w h nodeIds = 23 + makeSim w h nodeIds [] 24 + 25 + 26 + layout : Model -> Model 27 + layout model = 28 + { model 29 + | simulation = 30 + makeSim 31 + model.width 32 + model.height 33 + (Dict.keys model.nodes) 34 + (Set.toList model.edges) 35 + } 36 + 37 + 38 + isSimulating : Model -> Bool 39 + isSimulating model = 40 + not (Dict.isEmpty model.nodes) && not (Force.isCompleted model.simulation) 41 + 42 + 43 + tick : Model -> Model 44 + tick model = 45 + if not (isSimulating model) then 46 + model 47 + 48 + else 49 + let 50 + ( nextSim, nextNodes ) = 51 + Force.tick model.simulation (Dict.values model.nodes) 52 + 53 + updatedNodes = 54 + List.foldl (\n acc -> Dict.insert n.id n acc) model.nodes nextNodes 55 + in 56 + { model | simulation = nextSim, nodes = updatedNodes }
+732
src/Main.elm
··· 1 + module Main exposing (main) 2 + 3 + import Api 4 + import Format 5 + import Browser 6 + import Browser.Dom as Dom 7 + import Browser.Events 8 + import Browser.Navigation as Nav 9 + import Dict exposing (Dict) 10 + import Graph 11 + import Html exposing (Html) 12 + import Http 13 + import Json.Decode as Decode 14 + import Set exposing (Set) 15 + import Task 16 + import Types exposing (..) 17 + import Url exposing (Url) 18 + import View 19 + 20 + 21 + main : Program Flags Model Msg 22 + main = 23 + Browser.application 24 + { init = init 25 + , view = \model -> { title = "Meu Garfo", body = [ View.view model ] } 26 + , update = update 27 + , subscriptions = subscriptions 28 + , onUrlRequest = LinkClicked 29 + , onUrlChange = UrlChanged 30 + } 31 + 32 + 33 + type alias Flags = 34 + { graphApi : String 35 + , jsonApi : String 36 + } 37 + 38 + 39 + init : Flags -> Url -> Nav.Key -> ( Model, Cmd Msg ) 40 + init flags url key = 41 + let 42 + initialModel = 43 + { input = "" 44 + , nodes = Dict.empty 45 + , edges = Set.empty 46 + , visited = Set.empty 47 + , pending = Set.empty 48 + , queryQueue = [] 49 + , currentQuery = Nothing 50 + , error = Nothing 51 + , simulation = Graph.initSimulation 800 600 [] 52 + , width = 800 53 + , height = 600 54 + , graphApi = flags.graphApi 55 + , jsonApi = flags.jsonApi 56 + , dragNode = Nothing 57 + , startPos = Nothing 58 + , isPanning = False 59 + , zoom = 1.0 60 + , pan = { x = 0, y = 0 } 61 + , navKey = key 62 + , url = url 63 + } 64 + 65 + ( finalModel, initialCmd ) = 66 + case parseUrl url of 67 + Just cnpj -> 68 + let 69 + seeded = 70 + { initialModel | input = Format.mask cnpj } 71 + |> enqueueQueries (queriesFor cnpj True 0) 72 + in 73 + triggerNextQuery seeded 74 + 75 + Nothing -> 76 + ( initialModel, Cmd.none ) 77 + in 78 + ( finalModel 79 + , Cmd.batch 80 + [ initialCmd 81 + , Task.perform (\{ viewport } -> Resize viewport.width viewport.height) Dom.getViewport 82 + ] 83 + ) 84 + 85 + 86 + update : Msg -> Model -> ( Model, Cmd Msg ) 87 + update msg model = 88 + case msg of 89 + UpdateInput s -> 90 + ( { model | input = Format.maskCnpjInput model.input s }, Cmd.none ) 91 + 92 + Search -> 93 + let 94 + unmasked = 95 + String.filter Char.isAlphaNum model.input 96 + 97 + currentCnpj = 98 + parseUrl model.url 99 + in 100 + if String.isEmpty unmasked then 101 + ( { model | error = Just "Por favor, digite um CNPJ" }, Cmd.none ) 102 + 103 + else if currentCnpj == Just unmasked then 104 + ( model, Cmd.none ) 105 + 106 + else 107 + ( { model 108 + | nodes = Dict.empty 109 + , edges = Set.empty 110 + , visited = Set.empty 111 + , pending = Set.empty 112 + , queryQueue = [] 113 + , currentQuery = Nothing 114 + , error = Nothing 115 + , simulation = Graph.initSimulation model.width model.height [] 116 + , pan = { x = 0, y = 0 } 117 + , zoom = 1.0 118 + } 119 + , Nav.pushUrl model.navKey ("#" ++ unmasked) 120 + ) 121 + 122 + NodeClicked id -> 123 + case Dict.get id model.nodes of 124 + Just node -> 125 + let 126 + queries = 127 + case node.entity of 128 + Company _ cnpj -> 129 + queriesFor cnpj True node.depth 130 + 131 + Person _ _ -> 132 + [ QueryRequest id node.depth (PersonUid id) ] 133 + in 134 + triggerNextQuery (enqueueQueries queries model) 135 + 136 + Nothing -> 137 + ( model, Cmd.none ) 138 + 139 + GotResponse id url depth result -> 140 + let 141 + key = 142 + Maybe.map (\q -> queryKey q.queryType) model.currentQuery 143 + |> Maybe.withDefault ( id, "qsa" ) 144 + 145 + baseModel = 146 + { model 147 + | currentQuery = Nothing 148 + , pending = Set.remove key model.pending 149 + , visited = Set.insert key model.visited 150 + } 151 + in 152 + case result of 153 + Err apiErr -> 154 + triggerNextQuery (handleApiError id url model.currentQuery apiErr baseModel) 155 + 156 + Ok response -> 157 + let 158 + ( updatedModel, newQueue, extraCmd ) = 159 + processResponse response depth { baseModel | error = Nothing } 160 + 161 + ( nextModel, nextCmd ) = 162 + triggerNextQuery 163 + (Graph.layout (enqueueQueries newQueue updatedModel)) 164 + in 165 + ( nextModel, Cmd.batch [ extraCmd, nextCmd ] ) 166 + 167 + GotCompanyName cnpj url result -> 168 + case result of 169 + Ok name -> 170 + let 171 + updatedNodes = 172 + Dict.update cnpj 173 + (Maybe.map 174 + (\node -> 175 + { node 176 + | entity = 177 + case node.entity of 178 + Company _ id -> 179 + Company name id 180 + 181 + Person _ _ -> 182 + Company name cnpj 183 + } 184 + ) 185 + ) 186 + model.nodes 187 + in 188 + ( { model | nodes = updatedNodes }, Cmd.none ) 189 + 190 + Err _ -> 191 + ( model, Cmd.none ) 192 + 193 + Tick -> 194 + ( Graph.tick model, Cmd.none ) 195 + 196 + Resize w h -> 197 + ( { model | width = w, height = h }, Cmd.none ) 198 + 199 + InteractionStart id x y -> 200 + ( { model | dragNode = Just id, startPos = Just { x = x, y = y } }, Cmd.none ) 201 + 202 + PanStart -> 203 + ( { model | isPanning = True }, Cmd.none ) 204 + 205 + InteractionMove dx dy -> 206 + case model.dragNode of 207 + Just id -> 208 + let 209 + updatedNodes = 210 + Dict.update id 211 + (Maybe.map (\n -> { n | x = n.x + dx / model.zoom, y = n.y + dy / model.zoom, vx = 0, vy = 0 })) 212 + model.nodes 213 + in 214 + ( { model | nodes = updatedNodes }, Cmd.none ) 215 + 216 + Nothing -> 217 + if model.isPanning then 218 + ( { model | pan = { x = model.pan.x + dx, y = model.pan.y + dy } }, Cmd.none ) 219 + 220 + else 221 + ( model, Cmd.none ) 222 + 223 + InteractionEnd x y -> 224 + let 225 + isClick = 226 + case model.startPos of 227 + Just start -> 228 + let 229 + dx = 230 + x - start.x 231 + 232 + dy = 233 + y - start.y 234 + 235 + dist = 236 + sqrt (dx * dx + dy * dy) 237 + in 238 + dist < 5 239 + 240 + Nothing -> 241 + False 242 + 243 + newModel = 244 + { model | dragNode = Nothing, startPos = Nothing, isPanning = False } 245 + in 246 + case ( isClick, model.dragNode ) of 247 + ( True, Just id ) -> 248 + update (NodeClicked id) newModel 249 + 250 + _ -> 251 + ( newModel, Cmd.none ) 252 + 253 + Zoom delta -> 254 + let 255 + newZoom = 256 + clamp 0.1 5.0 (model.zoom - delta * 0.001) 257 + in 258 + ( { model | zoom = newZoom }, Cmd.none ) 259 + 260 + LinkClicked urlRequest -> 261 + case urlRequest of 262 + Browser.Internal url -> 263 + ( model, Nav.pushUrl model.navKey (Url.toString url) ) 264 + 265 + Browser.External href -> 266 + ( model, Nav.load href ) 267 + 268 + UrlChanged url -> 269 + case parseUrl url of 270 + Just cnpj -> 271 + let 272 + isSameCnpj = 273 + cnpj == String.filter (\c -> Char.isAlphaNum c || c == '*') model.input 274 + in 275 + if isSameCnpj && not (Dict.isEmpty model.nodes) then 276 + ( model, Cmd.none ) 277 + 278 + else 279 + let 280 + reset = 281 + { model 282 + | input = Format.mask cnpj 283 + , nodes = Dict.empty 284 + , edges = Set.empty 285 + , visited = Set.empty 286 + , pending = Set.empty 287 + , queryQueue = [] 288 + , currentQuery = Nothing 289 + , simulation = Graph.initSimulation model.width model.height [] 290 + , url = url 291 + } 292 + in 293 + triggerNextQuery (enqueueQueries (queriesFor cnpj True 0) reset) 294 + 295 + Nothing -> 296 + ( { model | url = url }, Cmd.none ) 297 + 298 + 299 + parseUrl : Url -> Maybe String 300 + parseUrl url = 301 + case url.fragment of 302 + Just frag -> 303 + let 304 + cnpj = 305 + String.split ";" frag 306 + |> List.head 307 + |> Maybe.withDefault "" 308 + in 309 + if String.isEmpty cnpj then 310 + Nothing 311 + 312 + else 313 + Just cnpj 314 + 315 + Nothing -> 316 + Nothing 317 + 318 + 319 + triggerNextQuery : Model -> ( Model, Cmd Msg ) 320 + triggerNextQuery model = 321 + if model.currentQuery == Nothing then 322 + case model.queryQueue of 323 + next :: rest -> 324 + let 325 + newModel = 326 + { model | queryQueue = rest, currentQuery = Just next } 327 + 328 + cmd = 329 + Api.queryEntity model.graphApi next.queryType next.depth 330 + in 331 + ( newModel, cmd ) 332 + 333 + [] -> 334 + ( model, Cmd.none ) 335 + 336 + else 337 + ( model, Cmd.none ) 338 + 339 + 340 + processResponse : ApiResponse -> Int -> Model -> ( Model, List QueryRequest, Cmd Msg ) 341 + processResponse response depth model = 342 + case response of 343 + CompanyResponse data -> 344 + let 345 + ( nx, ny ) = 346 + nodeOffset data.id model.width model.height 347 + 348 + newNode = 349 + case Dict.get data.id model.nodes of 350 + Just existing -> 351 + { existing | entity = Company data.name data.id } 352 + 353 + Nothing -> 354 + { id = data.id, entity = Company data.name data.id, depth = depth, x = nx, y = ny, vx = 0, vy = 0, error = Nothing } 355 + 356 + updatedNodes = 357 + Dict.insert data.id newNode model.nodes 358 + 359 + ( modelWithPartners, _, cmd ) = 360 + List.foldl (processPartnerEdge data.id depth) ( { model | nodes = updatedNodes }, [], Cmd.none ) data.partners 361 + in 362 + ( modelWithPartners 363 + , enqueueChildren data.id depth modelWithPartners 364 + , cmd 365 + ) 366 + 367 + PartnerResponse data -> 368 + let 369 + isCompany = 370 + isCnpj data.id 371 + 372 + ( nx, ny ) = 373 + nodeOffset data.id model.width model.height 374 + 375 + newNode = 376 + case Dict.get data.id model.nodes of 377 + Just existing -> 378 + if isCompany then 379 + existing 380 + 381 + else 382 + let 383 + name = 384 + Maybe.withDefault data.id data.name 385 + in 386 + case existing.entity of 387 + Person oldName oldCpf -> 388 + { existing | entity = Person name (if data.cpf /= Nothing then data.cpf else oldCpf) } 389 + 390 + _ -> 391 + { existing | entity = Person name data.cpf } 392 + 393 + Nothing -> 394 + if isCompany then 395 + { id = data.id, entity = Company (Maybe.withDefault data.id data.name) data.id, depth = depth, x = nx, y = ny, vx = 0, vy = 0, error = Nothing } 396 + 397 + else 398 + let 399 + name = 400 + Maybe.withDefault data.id data.name 401 + in 402 + { id = data.id, entity = Person name data.cpf, depth = depth, x = nx, y = ny, vx = 0, vy = 0, error = Nothing } 403 + 404 + updatedNodes = 405 + Dict.insert data.id newNode model.nodes 406 + 407 + ( modelWithCompanies, _, cmd ) = 408 + List.foldl (processCompanyEdge data.id depth) ( { model | nodes = updatedNodes }, [], Cmd.none ) data.companies 409 + in 410 + ( modelWithCompanies 411 + , enqueueChildren data.id depth modelWithCompanies 412 + , cmd 413 + ) 414 + 415 + 416 + processPartnerEdge : String -> Int -> PartnerRef -> ( Model, List QueryRequest, Cmd Msg ) -> ( Model, List QueryRequest, Cmd Msg ) 417 + processPartnerEdge companyId depth partner ( model, _, cmds ) = 418 + let 419 + edge = 420 + ( companyId, partner.id ) 421 + 422 + updatedEdges = 423 + Set.insert edge model.edges 424 + 425 + name = 426 + Maybe.withDefault partner.id partner.name 427 + 428 + isPartnerCnpj = 429 + isCnpj partner.id 430 + 431 + entity = 432 + if isPartnerCnpj then 433 + Company name partner.id 434 + 435 + else 436 + Person name partner.cpf 437 + 438 + ( nx, ny ) = 439 + spawnNear companyId partner.id model 440 + 441 + targetNode = 442 + case Dict.get partner.id model.nodes of 443 + Just existing -> 444 + if partner.name /= Nothing || partner.cpf /= Nothing then 445 + case existing.entity of 446 + Person oldName oldCpf -> 447 + { existing | entity = Person (if partner.name /= Nothing then name else oldName) (if partner.cpf /= Nothing then partner.cpf else oldCpf) } 448 + 449 + _ -> 450 + { existing | entity = entity } 451 + 452 + else 453 + existing 454 + 455 + Nothing -> 456 + { id = partner.id, entity = entity, depth = depth + 1, x = nx, y = ny, vx = 0, vy = 0, error = Nothing } 457 + 458 + newModel = 459 + { model | edges = updatedEdges, nodes = Dict.insert partner.id targetNode model.nodes } 460 + 461 + newCmd = 462 + if isPartnerCnpj && partner.name == Nothing then 463 + Cmd.batch [ cmds, Api.fetchCompanyName model.jsonApi partner.id ] 464 + 465 + else 466 + cmds 467 + in 468 + ( newModel, [], newCmd ) 469 + 470 + 471 + processCompanyEdge : String -> Int -> CompanyRef -> ( Model, List QueryRequest, Cmd Msg ) -> ( Model, List QueryRequest, Cmd Msg ) 472 + processCompanyEdge partnerId depth company ( model, _, cmds ) = 473 + let 474 + edge = 475 + ( company.id, partnerId ) 476 + 477 + updatedEdges = 478 + Set.insert edge model.edges 479 + 480 + ( nx, ny ) = 481 + spawnNear partnerId company.id model 482 + 483 + targetNode = 484 + case Dict.get company.id model.nodes of 485 + Just existing -> 486 + { existing | entity = Company company.name company.id } 487 + 488 + Nothing -> 489 + { id = company.id, entity = Company company.name company.id, depth = depth + 1, x = nx, y = ny, vx = 0, vy = 0, error = Nothing } 490 + 491 + newModel = 492 + { model | edges = updatedEdges, nodes = Dict.insert company.id targetNode model.nodes } 493 + in 494 + ( newModel, [], cmds ) 495 + 496 + 497 + isCnpj : String -> Bool 498 + isCnpj s = 499 + String.length (String.filter Char.isAlphaNum s) == 14 500 + 501 + 502 + maxDepth : Int 503 + maxDepth = 504 + 8 505 + 506 + 507 + softNodeLimit : Int 508 + softNodeLimit = 509 + 32 510 + 511 + 512 + queryKey : QueryType -> QueryKey 513 + queryKey qType = 514 + case qType of 515 + CompanyCnpj cnpj -> 516 + ( cnpj, "qsa" ) 517 + 518 + CompanyAsPartner cnpj -> 519 + ( cnpj, "cnpjs" ) 520 + 521 + PersonUid uid -> 522 + ( uid, "cnpjs" ) 523 + 524 + 525 + queriesFor : String -> Bool -> Int -> List QueryRequest 526 + queriesFor id isCompany depth = 527 + if isCompany then 528 + [ QueryRequest id depth (CompanyCnpj id) 529 + , QueryRequest id depth (CompanyAsPartner id) 530 + ] 531 + 532 + else 533 + [ QueryRequest id depth (PersonUid id) ] 534 + 535 + 536 + enqueueQueries : List QueryRequest -> Model -> Model 537 + enqueueQueries queries model = 538 + let 539 + accept q ( accModel, accQueue ) = 540 + let 541 + key = 542 + queryKey q.queryType 543 + in 544 + if Set.member key accModel.pending || Set.member key accModel.visited then 545 + ( accModel, accQueue ) 546 + 547 + else 548 + ( { accModel | pending = Set.insert key accModel.pending } 549 + , accQueue ++ [ q ] 550 + ) 551 + 552 + ( newModel, newQueue ) = 553 + List.foldl accept ( model, [] ) queries 554 + in 555 + { newModel | queryQueue = newModel.queryQueue ++ newQueue } 556 + 557 + 558 + enqueueChildren : String -> Int -> Model -> List QueryRequest 559 + enqueueChildren parentId depth model = 560 + if depth >= maxDepth || Dict.size model.nodes >= softNodeLimit then 561 + [] 562 + 563 + else 564 + let 565 + childDepth = 566 + depth + 1 567 + 568 + childEdges = 569 + Set.filter (\( s, t ) -> s == parentId || t == parentId) model.edges 570 + 571 + childIds = 572 + Set.foldl 573 + (\( s, t ) acc -> 574 + if s == parentId then 575 + Set.insert t acc 576 + 577 + else 578 + Set.insert s acc 579 + ) 580 + Set.empty 581 + childEdges 582 + in 583 + Set.foldl 584 + (\childId acc -> 585 + case Dict.get childId model.nodes of 586 + Just node -> 587 + let 588 + isCompany = 589 + case node.entity of 590 + Company _ _ -> 591 + True 592 + 593 + Person _ _ -> 594 + False 595 + in 596 + queriesFor childId isCompany childDepth ++ acc 597 + 598 + Nothing -> 599 + acc 600 + ) 601 + [] 602 + childIds 603 + 604 + 605 + spawnNear : String -> String -> Model -> ( Float, Float ) 606 + spawnNear parentId childId model = 607 + case Dict.get parentId model.nodes of 608 + Just parent -> 609 + let 610 + hash = 611 + String.foldl (\c acc -> acc * 31 + Char.toCode c) 0 childId 612 + 613 + angle = 614 + toFloat hash * 0.6180339887498949 615 + 616 + radius = 617 + 20 + toFloat (modBy 30 (abs hash)) 618 + in 619 + ( parent.x + cos angle * radius 620 + , parent.y + sin angle * radius 621 + ) 622 + 623 + Nothing -> 624 + nodeOffset childId model.width model.height 625 + 626 + 627 + nodeOffset : String -> Float -> Float -> ( Float, Float ) 628 + nodeOffset id w h = 629 + let 630 + hash = 631 + String.foldl (\c acc -> acc * 31 + Char.toCode c) 0 id 632 + 633 + angle = 634 + toFloat hash * 0.6180339887498949 635 + 636 + baseRadius = 637 + min w h / 3 638 + 639 + jitter = 640 + toFloat (modBy 80 (abs hash)) - 40 641 + in 642 + ( w / 2 + cos angle * (baseRadius + jitter) 643 + , h / 2 + sin angle * (baseRadius + jitter) 644 + ) 645 + 646 + 647 + httpErrorToString : String -> Http.Error -> String 648 + httpErrorToString url err = 649 + case err of 650 + Http.BadUrl s -> 651 + "URL inválida: " ++ s 652 + 653 + Http.Timeout -> 654 + "Tempo esgotado ao buscar " ++ url 655 + 656 + Http.NetworkError -> 657 + "Erro de rede ao buscar " ++ url ++ " (o servidor está rodando?)" 658 + 659 + Http.BadStatus i -> 660 + "Erro do servidor (status " ++ String.fromInt i ++ ") ao buscar " ++ url 661 + 662 + Http.BadBody s -> 663 + "Resposta inválida de " ++ url ++ ": " ++ s 664 + 665 + 666 + markNodeError : String -> Maybe String -> Model -> Model 667 + markNodeError id maybeMsg model = 668 + { model 669 + | nodes = 670 + Dict.update id 671 + (Maybe.map (\n -> { n | error = maybeMsg })) 672 + model.nodes 673 + } 674 + 675 + 676 + handleApiError : String -> String -> Maybe QueryRequest -> ApiError -> Model -> Model 677 + handleApiError id url currentQuery apiErr model = 678 + let 679 + wasPartnerLookup = 680 + case currentQuery |> Maybe.map .queryType of 681 + Just (CompanyAsPartner _) -> 682 + True 683 + 684 + _ -> 685 + False 686 + in 687 + case apiErr of 688 + NotFound -> 689 + if wasPartnerLookup then 690 + model 691 + 692 + else 693 + markNodeError id (Just "Não encontrado") model 694 + 695 + BadRequest msg -> 696 + let 697 + marked = 698 + markNodeError id (Just msg) model 699 + in 700 + if Dict.member id model.nodes then 701 + marked 702 + 703 + else 704 + { marked | error = Just msg } 705 + 706 + HttpError httpErr -> 707 + { model | error = Just (httpErrorToString url httpErr) } 708 + 709 + 710 + subscriptions : Model -> Sub Msg 711 + subscriptions model = 712 + let 713 + interaction = 714 + if model.dragNode /= Nothing || model.isPanning then 715 + [ Browser.Events.onMouseMove (Decode.map2 InteractionMove (Decode.field "movementX" Decode.float) (Decode.field "movementY" Decode.float)) ] 716 + 717 + else 718 + [] 719 + 720 + animation = 721 + if Graph.isSimulating model then 722 + [ Browser.Events.onAnimationFrame (\_ -> Tick) ] 723 + 724 + else 725 + [] 726 + 727 + always = 728 + [ Browser.Events.onMouseUp (Decode.map2 InteractionEnd (Decode.field "clientX" Decode.float) (Decode.field "clientY" Decode.float)) 729 + , Browser.Events.onResize (\w h -> Resize (toFloat w) (toFloat h)) 730 + ] 731 + in 732 + Sub.batch (interaction ++ animation ++ always)
+123
src/Types.elm
··· 1 + module Types exposing (..) 2 + 3 + import Browser 4 + import Browser.Navigation as Nav 5 + import Dict exposing (Dict) 6 + import Force 7 + import Http 8 + import Set exposing (Set) 9 + import Url exposing (Url) 10 + 11 + 12 + type Entity 13 + = Company String String -- Nome, CNPJ 14 + | Person String (Maybe String) -- Nome, CPF 15 + 16 + 17 + type alias Node = 18 + { id : String 19 + , entity : Entity 20 + , depth : Int 21 + , x : Float 22 + , y : Float 23 + , vx : Float 24 + , vy : Float 25 + , error : Maybe String 26 + } 27 + 28 + 29 + type ApiError 30 + = NotFound 31 + | BadRequest String 32 + | HttpError Http.Error 33 + 34 + 35 + type QueryType 36 + = CompanyCnpj String 37 + | CompanyAsPartner String 38 + | PersonUid String 39 + 40 + 41 + type alias QueryRequest = 42 + { term : String 43 + , depth : Int 44 + , queryType : QueryType 45 + } 46 + 47 + 48 + type alias QueryKey = 49 + ( String, String ) 50 + 51 + 52 + type alias Model = 53 + { input : String 54 + , nodes : Dict String Node 55 + , edges : Set ( String, String ) 56 + , visited : Set QueryKey 57 + , pending : Set QueryKey 58 + , queryQueue : List QueryRequest 59 + , currentQuery : Maybe QueryRequest 60 + , error : Maybe String 61 + , simulation : Force.State String 62 + , width : Float 63 + , height : Float 64 + , graphApi : String 65 + , jsonApi : String 66 + , dragNode : Maybe String 67 + , startPos : Maybe { x : Float, y : Float } -- To detect click vs drag 68 + , isPanning : Bool 69 + , zoom : Float 70 + , pan : { x : Float, y : Float } 71 + , navKey : Nav.Key 72 + , url : Url 73 + } 74 + 75 + 76 + type Msg 77 + = UpdateInput String 78 + | Search 79 + | NodeClicked String 80 + | GotResponse String String Int (Result ApiError ApiResponse) 81 + | GotCompanyName String String (Result Http.Error String) 82 + | Tick 83 + | Resize Float Float 84 + | InteractionStart String Float Float 85 + | PanStart 86 + | InteractionMove Float Float 87 + | InteractionEnd Float Float -- Pass current mouse pos to detect click 88 + | Zoom Float 89 + | LinkClicked Browser.UrlRequest 90 + | UrlChanged Url 91 + 92 + 93 + type ApiResponse 94 + = CompanyResponse CompanyData 95 + | PartnerResponse PartnerData 96 + 97 + 98 + type alias CompanyData = 99 + { id : String 100 + , name : String 101 + , partners : List PartnerRef 102 + } 103 + 104 + 105 + type alias PartnerRef = 106 + { id : String 107 + , name : Maybe String 108 + , cpf : Maybe String 109 + } 110 + 111 + 112 + type alias PartnerData = 113 + { id : String 114 + , name : Maybe String 115 + , cpf : Maybe String 116 + , companies : List CompanyRef 117 + } 118 + 119 + 120 + type alias CompanyRef = 121 + { id : String 122 + , name : String 123 + }
+348
src/View.elm
··· 1 + module View exposing (view) 2 + 3 + import Dict exposing (Dict) 4 + import Html exposing (Html, a, button, div, footer, h1, header, input, label, main_, nav, small, text, ul, li, span) 5 + import Html.Attributes exposing (class, for, id, placeholder, type_, value, attribute, href) 6 + import Html.Events exposing (onClick, onInput, preventDefaultOn, custom) 7 + import Json.Decode as Decode 8 + import Set exposing (Set) 9 + import Svg exposing (Svg, g, line, svg, text_, circle) 10 + import Svg.Attributes as SA 11 + import Svg.Events as SE 12 + import Types exposing (..) 13 + import Format 14 + 15 + 16 + view : Model -> Html Msg 17 + view model = 18 + let 19 + nodeList = 20 + Dict.values model.nodes 21 + 22 + companiesCount = 23 + List.filter 24 + (\n -> 25 + case n.entity of 26 + Company _ _ -> 27 + True 28 + 29 + _ -> 30 + False 31 + ) 32 + nodeList 33 + |> List.length 34 + 35 + peopleCount = 36 + List.filter 37 + (\n -> 38 + case n.entity of 39 + Person _ _ -> 40 + True 41 + 42 + _ -> 43 + False 44 + ) 45 + nodeList 46 + |> List.length 47 + in 48 + div [ class "app-root" ] 49 + [ header [ class "app-header" ] 50 + [ nav [] 51 + [ ul [] 52 + [ li [] [ h1 [] [ text "Meu Garfo" ] ] 53 + ] 54 + , ul [ class "controls" ] 55 + [ li [] 56 + [ label [ for "cnpj-input" ] [ text "CNPJ" ] 57 + , input 58 + [ id "cnpj-input" 59 + , class "cnpj-input" 60 + , placeholder "00.000.000/0000-00" 61 + , value model.input 62 + , onInput UpdateInput 63 + , onEnter Search 64 + , attribute "aria-label" "CNPJ" 65 + ] 66 + [] 67 + ] 68 + , li [] 69 + [ button 70 + [ onClick Search 71 + , Html.Attributes.disabled (model.currentQuery /= Nothing || not (List.isEmpty model.queryQueue)) 72 + , class "primary" 73 + ] 74 + [ text 75 + (if model.currentQuery /= Nothing || not (List.isEmpty model.queryQueue) then 76 + "Carregando..." 77 + 78 + else 79 + "Buscar" 80 + ) 81 + ] 82 + ] 83 + ] 84 + ] 85 + ] 86 + , main_ [ class "graph-viewport", onWheel Zoom ] 87 + [ svg 88 + [ SA.class "graph-svg" 89 + , SA.width (String.fromFloat model.width) 90 + , SA.height (String.fromFloat model.height) 91 + , onMouseDown PanStart 92 + ] 93 + [ g [ SA.transform ("translate(" ++ String.fromFloat model.pan.x ++ "," ++ String.fromFloat model.pan.y ++ ") scale(" ++ String.fromFloat model.zoom ++ ")") ] 94 + [ g [] (Set.toList model.edges |> List.filterMap (viewEdge model.nodes)) 95 + , g [] (Dict.values model.nodes |> List.map (viewNode model.jsonApi (fullyExplored model))) 96 + ] 97 + ] 98 + , case model.error of 99 + Just err -> 100 + div [ class "error-overlay" ] 101 + [ small [] [ text err ] ] 102 + 103 + Nothing -> 104 + text "" 105 + , div [ class "status-overlay" ] 106 + [ small [] 107 + [ text (String.fromInt companiesCount ++ " pessoas jurídicas, " ++ String.fromInt peopleCount ++ " pessoas físicas, " ++ String.fromInt (Set.size model.edges) ++ " conexões") 108 + , if model.currentQuery /= Nothing || not (List.isEmpty model.queryQueue) then 109 + span [ class "loading-indicator" ] [ text (" (Processando fila: " ++ String.fromInt (List.length model.queryQueue) ++ ")") ] 110 + 111 + else 112 + text "" 113 + ] 114 + ] 115 + ] 116 + , footer [ class "app-footer" ] 117 + [ small [] 118 + [ text "Parte do " 119 + , a [ href "https://apoia.se/minhareceita" ] [ text "Minha Receita" ] 120 + , text " 🩷" 121 + ] 122 + ] 123 + ] 124 + 125 + 126 + onWheel : (Float -> Msg) -> Html.Attribute Msg 127 + onWheel tagger = 128 + preventDefaultOn "wheel" (Decode.map (\delta -> ( tagger delta, True )) (Decode.field "deltaY" Decode.float)) 129 + 130 + 131 + onEnter : Msg -> Html.Attribute Msg 132 + onEnter msg = 133 + Html.Events.on "keydown" 134 + (Decode.field "key" Decode.string 135 + |> Decode.andThen 136 + (\key -> 137 + if key == "Enter" then 138 + Decode.succeed msg 139 + 140 + else 141 + Decode.fail "not Enter" 142 + ) 143 + ) 144 + 145 + 146 + onMouseDown : Msg -> Html.Attribute Msg 147 + onMouseDown msg = 148 + custom "mousedown" (Decode.succeed { message = msg, stopPropagation = False, preventDefault = False }) 149 + 150 + 151 + fullyExplored : Model -> Set String 152 + fullyExplored model = 153 + Dict.foldl 154 + (\id node acc -> 155 + let 156 + needed = 157 + case node.entity of 158 + Company _ _ -> 159 + [ ( id, "qsa" ), ( id, "cnpjs" ) ] 160 + 161 + Person _ _ -> 162 + [ ( id, "cnpjs" ) ] 163 + 164 + allDone = 165 + List.all (\k -> Set.member k model.visited) needed 166 + in 167 + if allDone then 168 + Set.insert id acc 169 + 170 + else 171 + acc 172 + ) 173 + Set.empty 174 + model.nodes 175 + 176 + 177 + viewEdge : Dict String Node -> ( String, String ) -> Maybe (Svg Msg) 178 + viewEdge nodes ( s, t ) = 179 + case ( Dict.get s nodes, Dict.get t nodes ) of 180 + ( Just sn, Just tn ) -> 181 + Just 182 + (line 183 + [ SA.x1 (String.fromFloat sn.x) 184 + , SA.y1 (String.fromFloat sn.y) 185 + , SA.x2 (String.fromFloat tn.x) 186 + , SA.y2 (String.fromFloat tn.y) 187 + , SA.class "edge" 188 + , stopPropagationOnMouseDown 189 + ] 190 + [] 191 + ) 192 + 193 + _ -> 194 + Nothing 195 + 196 + 197 + viewNode : String -> Set String -> Node -> Svg Msg 198 + viewNode jsonApi visited node = 199 + let 200 + isVisited = 201 + Set.member node.id visited 202 + 203 + isRoot = 204 + node.depth == 0 205 + 206 + ( nodeClass, baseRadius ) = 207 + case node.entity of 208 + Company _ _ -> 209 + ( "node-company", 8 ) 210 + 211 + Person _ _ -> 212 + ( "node-person", 5 ) 213 + 214 + radius = 215 + if isRoot then 216 + baseRadius + 6 217 + 218 + else if isVisited then 219 + baseRadius 220 + 221 + else 222 + baseRadius + 2 223 + 224 + labelStr = 225 + case node.entity of 226 + Company name cnpj -> 227 + let 228 + maskedCnpj = 229 + Format.mask cnpj 230 + in 231 + if String.isEmpty cnpj then 232 + name 233 + 234 + else if name == cnpj || String.isEmpty name then 235 + maskedCnpj 236 + 237 + else 238 + name ++ " (" ++ maskedCnpj ++ ")" 239 + 240 + Person name cpf -> 241 + case cpf of 242 + Just c -> 243 + let 244 + maskedCpf = 245 + Format.mask c 246 + in 247 + if name == maskedCpf then 248 + maskedCpf 249 + 250 + else 251 + name ++ " (" ++ maskedCpf ++ ")" 252 + 253 + Nothing -> 254 + name 255 + 256 + plainLabel = 257 + text_ 258 + [ SA.dy "18" 259 + , SA.textAnchor "middle" 260 + , SA.class "node-label" 261 + , stopPropagationOnMouseDown 262 + ] 263 + [ text labelStr ] 264 + 265 + labelElement = 266 + case ( node.entity, node.error ) of 267 + ( Company _ cnpj, Nothing ) -> 268 + let 269 + url = 270 + if String.right 1 jsonApi == "/" then 271 + jsonApi ++ cnpj 272 + 273 + else 274 + jsonApi ++ "/" ++ cnpj 275 + in 276 + Svg.a [ SA.xlinkHref url, attribute "href" url, attribute "target" "_blank", attribute "rel" "noopener noreferrer" ] 277 + [ text_ 278 + [ SA.dy "18" 279 + , SA.textAnchor "middle" 280 + , SA.class "node-label link" 281 + , stopPropagationOnMouseDown 282 + ] 283 + [ text labelStr ] 284 + ] 285 + 286 + _ -> 287 + plainLabel 288 + 289 + errorElement = 290 + case node.error of 291 + Just msg -> 292 + text_ 293 + [ SA.dy "32" 294 + , SA.textAnchor "middle" 295 + , SA.class "node-error" 296 + , stopPropagationOnMouseDown 297 + ] 298 + [ text msg ] 299 + 300 + Nothing -> 301 + text "" 302 + in 303 + g 304 + [ SA.transform ("translate(" ++ String.fromFloat node.x ++ "," ++ String.fromFloat node.y ++ ")") 305 + , SA.class 306 + (if isRoot then 307 + "node-group root" 308 + 309 + else 310 + "node-group" 311 + ) 312 + ] 313 + [ circle 314 + [ SA.r (String.fromFloat radius) 315 + , SA.class (nodeClass ++ (if isVisited then " visited" else " expandable") ++ (if node.error /= Nothing then " errored" else "") ++ (if isRoot then " root" else "")) 316 + , onNodeMouseDown node 317 + ] 318 + [] 319 + , labelElement 320 + , errorElement 321 + ] 322 + 323 + 324 + onNodeMouseDown : Node -> Svg.Attribute Msg 325 + onNodeMouseDown node = 326 + SE.custom "mousedown" 327 + (Decode.map3 328 + (\id x y -> 329 + { message = InteractionStart id x y 330 + , stopPropagation = True 331 + , preventDefault = False 332 + } 333 + ) 334 + (Decode.succeed node.id) 335 + (Decode.field "clientX" Decode.float) 336 + (Decode.field "clientY" Decode.float) 337 + ) 338 + 339 + 340 + stopPropagationOnMouseDown : Svg.Attribute Msg 341 + stopPropagationOnMouseDown = 342 + SE.custom "mousedown" 343 + (Decode.succeed 344 + { message = InteractionMove 0 0 345 + , stopPropagation = True 346 + , preventDefault = False 347 + } 348 + )