├── .DS_Store ├── .codesandbox ├── Dockerfile └── tasks.json ├── .devcontainer └── devcontainer.json ├── .dockerignore ├── .env.example ├── .github ├── dependabot.yml └── workflows │ ├── build-image.yml │ └── test.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── Dockerfile ├── LICENSE ├── README.md ├── catalog-info.yaml ├── fixtures └── pkg-json │ └── parse-test.json ├── integration-tests ├── __snapshots__ │ ├── file-collection.test.ts.snap │ └── mod.test.ts.snap ├── deps.test.ts ├── fetch.ts ├── mod.test.ts └── utils.ts ├── jest.config.js ├── package.json ├── run_tests.js ├── src ├── app_error.rs ├── cached.rs ├── main.rs ├── npm │ ├── dep_tree_builder.rs │ ├── mod.rs │ ├── package_content.rs │ └── package_data.rs ├── npm_replicator │ ├── changes.rs │ ├── error.rs │ ├── mod.rs │ ├── registry.rs │ ├── replication_task.rs │ └── types │ │ ├── changes.rs │ │ ├── document.rs │ │ └── mod.rs ├── package │ ├── mod.rs │ └── process.rs ├── router │ ├── custom_reply.rs │ ├── error_reply.rs │ ├── health.rs │ ├── mod.rs │ ├── routes.rs │ ├── routes_v2 │ │ ├── mod.rs │ │ ├── route_deps.rs │ │ ├── route_mod.rs │ │ └── route_npm_status.rs │ └── utils.rs ├── setup_tracing.rs └── utils │ ├── mod.rs │ ├── msgpack.rs │ ├── test_utils.rs │ └── time.rs └── yarn.lock /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codesandbox/sandpack-cdn/c7effd5a975b4c2d5ebdd871e9d447e8801dad84/.DS_Store -------------------------------------------------------------------------------- /.codesandbox/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM rust:1.69-bullseye 2 | 3 | # install cargo binstall to reduce image size 4 | WORKDIR /usr/local/cargo/bin 5 | RUN curl -L --output cargo-binstall.tgz https://github.com/cargo-bins/cargo-binstall/releases/download/v0.22.0/cargo-binstall-x86_64-unknown-linux-gnu.tgz && \ 6 | tar -xvzf cargo-binstall.tgz && \ 7 | chmod +x cargo-binstall && \ 8 | rm cargo-binstall.tgz 9 | 10 | RUN apt-get update && apt-get install -y protobuf-compiler libclang-dev 11 | 12 | RUN rustup component add rust-analyzer rustfmt rust-src clippy && \ 13 | cargo binstall -y cargo-watch 14 | 15 | WORKDIR /root 16 | -------------------------------------------------------------------------------- /.codesandbox/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // These tasks will run in order when initializing your CodeSandbox project. 3 | "setupTasks": [ 4 | { 5 | "name": "Install Dependencies", 6 | "command": "yarn install" 7 | }, 8 | { 9 | "name": "Install proto", 10 | "command": "apt update -y && apt install -y protobuf-compiler clang" 11 | } 12 | ], 13 | 14 | // These tasks can be run from CodeSandbox. Running one will open a log in the app. 15 | "tasks": { 16 | "ci:run_tests": { 17 | "name": "ci:run_tests", 18 | "command": "yarn ci:run_tests" 19 | }, 20 | "test": { 21 | "name": "test", 22 | "command": "yarn test" 23 | }, 24 | "cargo run": { 25 | "name": "cargo run", 26 | "command": "cargo run", 27 | "runAtStart": true 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // For format details, see https://aka.ms/devcontainer.json. For config options, see the 2 | // README at: https://github.com/devcontainers/templates/tree/main/src/rust 3 | { 4 | "name": "Rust", 5 | // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile 6 | "image": "mcr.microsoft.com/devcontainers/rust:1-1-bullseye", 7 | "features": { 8 | "ghcr.io/devcontainers/features/node": { 9 | "version": "lts", 10 | "nodeGypDependencies": "true", 11 | "nvmInstallPath": "/usr/local/share/nvm", 12 | "nvmVersion": "latest" 13 | } 14 | }, 15 | "customizations": { 16 | "vscode": { 17 | "extensions": ["rust-lang.rust-analyzer"] 18 | } 19 | } 20 | 21 | // Use 'mounts' to make the cargo cache persistent in a Docker Volume. 22 | // "mounts": [ 23 | // { 24 | // "source": "devcontainer-cargo-cache-${devcontainerId}", 25 | // "target": "/usr/local/cargo", 26 | // "type": "volume" 27 | // } 28 | // ] 29 | 30 | // Features to add to the dev container. More info: https://containers.dev/features. 31 | // "features": {}, 32 | 33 | // Use 'forwardPorts' to make a list of ports inside the container available locally. 34 | // "forwardPorts": [], 35 | 36 | // Use 'postCreateCommand' to run commands after the container is created. 37 | // "postCreateCommand": "rustc --version", 38 | 39 | // Configure tool-specific properties. 40 | // "customizations": {}, 41 | 42 | // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. 43 | // "remoteUser": "root" 44 | } 45 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | target 2 | node_modules 3 | temp_files 4 | .env 5 | *.db3 6 | *.sqlite 7 | Dockerfile 8 | # Ignore generated credentials from google-github-actions/auth 9 | gha-creds-*.json 10 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | PORT=8080 2 | ENVIRONMENT=staging 3 | OTEL_EXPORTER_OTLP_ENDPOINT=https://api.honeycomb.io:443 4 | OTEL_METADATA_AUTHORIZATION= 5 | NPM_ROCKS_DB=registry.rocksdb 6 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for more information: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | # https://containers.dev/guide/dependabot 6 | 7 | version: 2 8 | updates: 9 | - package-ecosystem: "devcontainers" 10 | directory: "/" 11 | schedule: 12 | interval: weekly 13 | -------------------------------------------------------------------------------- /.github/workflows/build-image.yml: -------------------------------------------------------------------------------- 1 | name: build-image 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'next' 7 | 8 | env: 9 | workload_identity_provider: projects/140364730894/locations/global/workloadIdentityPools/codesandbox-microservices-pool/providers/github-actions-provider 10 | service_account: github-image-pusher-sa@codesandbox-microservices.iam.gserviceaccount.com 11 | 12 | jobs: 13 | 14 | docker: 15 | runs-on: ubuntu-latest 16 | 17 | permissions: 18 | contents: 'read' 19 | id-token: 'write' 20 | 21 | steps: 22 | - name: Checkout 23 | uses: actions/checkout@v3 24 | 25 | - name: Set up Docker Buildx 26 | uses: docker/setup-buildx-action@v2 27 | with: 28 | driver: docker 29 | 30 | - name: Docker meta 31 | id: meta 32 | uses: docker/metadata-action@v4 33 | with: 34 | images: europe-docker.pkg.dev/codesandbox-microservices/codesandbox/sandpack-cdn 35 | tags: | 36 | type=sha 37 | 38 | - id: auth 39 | name: Authenticate to Google Cloud 40 | uses: google-github-actions/auth@v1 41 | with: 42 | token_format: 'access_token' 43 | workload_identity_provider: ${{ env.workload_identity_provider }} 44 | service_account: ${{ env.service_account }} 45 | 46 | - uses: docker/login-action@v2 47 | with: 48 | registry: europe-docker.pkg.dev 49 | username: 'oauth2accesstoken' 50 | password: '${{ steps.auth.outputs.access_token }}' 51 | 52 | - name: Build and push 53 | uses: docker/build-push-action@v3 54 | with: 55 | context: . 56 | push: ${{ github.event_name != 'pull_request' }} 57 | tags: ${{ steps.meta.outputs.tags }} 58 | labels: ${{ steps.meta.outputs.labels }} 59 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | on: 3 | push: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | unit-tests: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | - name: Install Protoc 12 | uses: arduino/setup-protoc@v1 13 | - name: Install Rust 14 | uses: actions-rs/toolchain@v1 15 | with: 16 | toolchain: stable 17 | profile: minimal 18 | override: 19 | - run: cargo check 20 | - run: cargo test 21 | - run: cargo clippy 22 | 23 | # Requires the whole npm registry db file 24 | # integration-tests: 25 | # runs-on: ubuntu-latest 26 | # steps: 27 | # - uses: actions/checkout@v2 28 | # - name: Setup Node.js 29 | # uses: actions/setup-node@v2 30 | # with: 31 | # node-version: 16 32 | # - name: Install Rust 33 | # uses: actions-rs/toolchain@v1 34 | # with: 35 | # toolchain: stable 36 | # profile: minimal 37 | # override: true 38 | # - name: Cargo build release 39 | # run: cargo build --release 40 | # - uses: bahmutov/npm-install@v1.1.0 41 | # - run: yarn ci:run_tests 42 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | temp_files 3 | .env* 4 | !.env.example 5 | .DS_Store 6 | node_modules 7 | *.sqlite 8 | *.db3 9 | # Ignore generated credentials from google-github-actions/auth 10 | gha-creds-*.json 11 | registry 12 | registry.rocksdb 13 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sandpack-cdn" 3 | version = "0.0.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | [profile.release-with-debug] 8 | inherits = "release" 9 | debug = true 10 | 11 | [dependencies] 12 | thiserror = "1.0.38" 13 | flate2 = "1.0.25" 14 | tar = "0.4.38" 15 | serde = { version = "1.0.152", features = ["derive"] } 16 | serde_json = "1.0.93" 17 | serde_bytes = "0.11.9" 18 | serde_with = "2.2.0" 19 | reqwest = { version = "0.11.22", features = [ 20 | "json", 21 | "brotli", 22 | "gzip", 23 | "deflate", 24 | ] } 25 | reqwest-middleware = "0.2.0" 26 | reqwest-retry = "0.2.1" 27 | tokio = { version = "1.25.0", features = ["full"] } 28 | url = "2.3.1" 29 | glob = "0.3.1" 30 | lazy_static = "1.4.0" 31 | chrono = { version = "0.4.31", features = ["serde"] } 32 | base64-simd = "0.8.0" 33 | node-semver = "2.1.0" 34 | parking_lot = "0.12.1" 35 | regex = "1.7.1" 36 | tracing = "0.1.37" 37 | tracing-subscriber = "0.3.16" 38 | tracing-opentelemetry = "0.18.0" 39 | tracing-futures = "0.2.5" 40 | tracing-tree = "0.2.2" 41 | opentelemetry = { version = "0.18.0", features = ["rt-tokio", "metrics"] } 42 | opentelemetry-otlp = { version = "0.11.0", features = [ 43 | "reqwest-client", 44 | "http-proto", 45 | "tls", 46 | "tls-roots", 47 | ] } 48 | tonic = "0.8.3" 49 | warp = { version = "0.3.6", features = ["compression"] } 50 | dotenv = "0.15.0" 51 | moka = { version = "0.12.1", features = ["future"] } 52 | rmp = "0.8.11" 53 | rmp-serde = "1.1.1" 54 | lru = "0.9.0" 55 | rocksdb = "0.20.1" 56 | opentelemetry-semantic-conventions = "0.10" 57 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM rust:bookworm AS builder 2 | 3 | # We need the nightly for some packages... 4 | CMD rustup default nightly 5 | WORKDIR /app 6 | 7 | RUN apt-get update && apt-get install -y protobuf-compiler libclang-dev libssl-dev 8 | 9 | # Installing all dependencies... 10 | RUN USER=root cargo new --bin sandpack-cdn 11 | WORKDIR /app/sandpack-cdn 12 | COPY Cargo.toml Cargo.lock ./ 13 | RUN cargo build --release 14 | RUN rm src/*.rs 15 | RUN rm ./target/release/deps/sandpack_cdn* 16 | 17 | # Copy the source 18 | COPY . . 19 | 20 | # Build (install) the binaries 21 | RUN cargo build --release 22 | 23 | 24 | FROM ubuntu:noble-20240605 25 | 26 | RUN apt-get update \ 27 | && apt-get install -y ca-certificates tzdata dumb-init \ 28 | && rm -rf /var/lib/apt/lists/* 29 | 30 | EXPOSE 8080 31 | ENV APP_USER=appuser 32 | 33 | RUN userdel ubuntu \ 34 | && rm -rf /home/ubuntu \ 35 | && groupadd $APP_USER \ 36 | && useradd --create-home -g $APP_USER $APP_USER 37 | 38 | WORKDIR /home/appuser 39 | 40 | COPY --chown=$APP_USER:$APP_USER --from=builder /app/sandpack-cdn/target/release/sandpack-cdn ./ 41 | 42 | USER $APP_USER 43 | RUN mkdir /home/$APP_USER/npm_db 44 | 45 | CMD ["dumb-init", "/home/appuser/sandpack-cdn"] 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, and 10 | distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by the 13 | copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all other 16 | entities that control, are controlled by, or are under common control with 17 | that entity. For the purposes of this definition, "control" means (i) the 18 | power, direct or indirect, to cause the direction or management of such 19 | entity, whether by contract or otherwise, or (ii) ownership of fifty percent 20 | (50%) or more of the outstanding shares, or (iii) beneficial ownership of 21 | such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity exercising 24 | permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation source, and 28 | configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical transformation 31 | or translation of a Source form, including but not limited to compiled 32 | object code, generated documentation, and conversions to other media types. 33 | 34 | "Work" shall mean the work of authorship, whether in Source or Object form, 35 | made available under the License, as indicated by a copyright notice that is 36 | included in or attached to the work (an example is provided in the Appendix 37 | below). 38 | 39 | "Derivative Works" shall mean any work, whether in Source or Object form, 40 | that is based on (or derived from) the Work and for which the editorial 41 | revisions, annotations, elaborations, or other modifications represent, as a 42 | whole, an original work of authorship. For the purposes of this License, 43 | Derivative Works shall not include works that remain separable from, or 44 | merely link (or bind by name) to the interfaces of, the Work and Derivative 45 | Works thereof. 46 | 47 | "Contribution" shall mean any work of authorship, including the original 48 | version of the Work and any modifications or additions to that Work or 49 | Derivative Works thereof, that is intentionally submitted to Licensor for 50 | inclusion in the Work by the copyright owner or by an individual or Legal 51 | Entity authorized to submit on behalf of the copyright owner. For the 52 | purposes of this definition, "submitted" means any form of electronic, 53 | verbal, or written communication sent to the Licensor or its 54 | representatives, including but not limited to communication on electronic 55 | mailing lists, source code control systems, and issue tracking systems that 56 | are managed by, or on behalf of, the Licensor for the purpose of discussing 57 | and improving the Work, but excluding communication that is conspicuously 58 | marked or otherwise designated in writing by the copyright owner as "Not a 59 | Contribution." 60 | 61 | "Contributor" shall mean Licensor and any individual or Legal Entity on 62 | behalf of whom a Contribution has been received by Licensor and subsequently 63 | incorporated within the Work. 64 | 65 | 2. Grant of Copyright License. Subject to the terms and conditions of this 66 | License, each Contributor hereby grants to You a perpetual, worldwide, 67 | non-exclusive, no-charge, royalty-free, irrevocable copyright license to 68 | reproduce, prepare Derivative Works of, publicly display, publicly perform, 69 | sublicense, and distribute the Work and such Derivative Works in Source or 70 | Object form. 71 | 72 | 3. Grant of Patent License. Subject to the terms and conditions of this 73 | License, each Contributor hereby grants to You a perpetual, worldwide, 74 | non-exclusive, no-charge, royalty-free, irrevocable (except as stated in 75 | this section) patent license to make, have made, use, offer to sell, sell, 76 | import, and otherwise transfer the Work, where such license applies only to 77 | those patent claims licensable by such Contributor that are necessarily 78 | infringed by their Contribution(s) alone or by combination of their 79 | Contribution(s) with the Work to which such Contribution(s) was submitted. 80 | If You institute patent litigation against any entity (including a 81 | cross-claim or counterclaim in a lawsuit) alleging that the Work or a 82 | Contribution incorporated within the Work constitutes direct or contributory 83 | patent infringement, then any patent licenses granted to You under this 84 | License for that Work shall terminate as of the date such litigation is 85 | filed. 86 | 87 | 4. Redistribution. You may reproduce and distribute copies of the Work or 88 | Derivative Works thereof in any medium, with or without modifications, and 89 | in Source or Object form, provided that You meet the following conditions: 90 | 91 | (a) You must give any other recipients of the Work or Derivative Works a 92 | copy of this License; and 93 | 94 | (b) You must cause any modified files to carry prominent notices stating 95 | that You changed the files; and 96 | 97 | (c) You must retain, in the Source form of any Derivative Works that You 98 | distribute, all copyright, patent, trademark, and attribution notices from 99 | the Source form of the Work, excluding those notices that do not pertain to 100 | any part of the Derivative Works; and 101 | 102 | (d) If the Work includes a "NOTICE" text file as part of its distribution, 103 | then any Derivative Works that You distribute must include a readable copy 104 | of the attribution notices contained within such NOTICE file, excluding 105 | those notices that do not pertain to any part of the Derivative Works, in at 106 | least one of the following places: within a NOTICE text file distributed as 107 | part of the Derivative Works; within the Source form or documentation, if 108 | provided along with the Derivative Works; or, within a display generated by 109 | the Derivative Works, if and wherever such third-party notices normally 110 | appear. The contents of the NOTICE file are for informational purposes only 111 | and do not modify the License. You may add Your own attribution notices 112 | within Derivative Works that You distribute, alongside or as an addendum to 113 | the NOTICE text from the Work, provided that such additional attribution 114 | notices cannot be construed as modifying the License. 115 | 116 | You may add Your own copyright statement to Your modifications and may 117 | provide additional or different license terms and conditions for use, 118 | reproduction, or distribution of Your modifications, or for any such 119 | Derivative Works as a whole, provided Your use, reproduction, and 120 | distribution of the Work otherwise complies with the conditions stated in 121 | this License. 122 | 123 | 5. Submission of Contributions. Unless You explicitly state otherwise, any 124 | Contribution intentionally submitted for inclusion in the Work by You to the 125 | Licensor shall be under the terms and conditions of this License, without 126 | any additional terms or conditions. Notwithstanding the above, nothing 127 | herein shall supersede or modify the terms of any separate license agreement 128 | you may have executed with Licensor regarding such Contributions. 129 | 130 | 6. Trademarks. This License does not grant permission to use the trade names, 131 | trademarks, service marks, or product names of the Licensor, except as 132 | required for reasonable and customary use in describing the origin of the 133 | Work and reproducing the content of the NOTICE file. 134 | 135 | 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in 136 | writing, Licensor provides the Work (and each Contributor provides its 137 | Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 138 | KIND, either express or implied, including, without limitation, any 139 | warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or 140 | FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining 141 | the appropriateness of using or redistributing the Work and assume any risks 142 | associated with Your exercise of permissions under this License. 143 | 144 | 8. Limitation of Liability. In no event and under no legal theory, whether in 145 | tort (including negligence), contract, or otherwise, unless required by 146 | applicable law (such as deliberate and grossly negligent acts) or agreed to 147 | in writing, shall any Contributor be liable to You for damages, including 148 | any direct, indirect, special, incidental, or consequential damages of any 149 | character arising as a result of this License or out of the use or inability 150 | to use the Work (including but not limited to damages for loss of goodwill, 151 | work stoppage, computer failure or malfunction, or any and all other 152 | commercial damages or losses), even if such Contributor has been advised of 153 | the possibility of such damages. 154 | 155 | 9. Accepting Warranty or Additional Liability. While redistributing the Work or 156 | Derivative Works thereof, You may choose to offer, and charge a fee for, 157 | acceptance of support, warranty, indemnity, or other liability obligations 158 | and/or rights consistent with this License. However, in accepting such 159 | obligations, You may act only on Your own behalf and on Your sole 160 | responsibility, not on behalf of any other Contributor, and only if You 161 | agree to indemnify, defend, and hold each Contributor harmless for any 162 | liability incurred by, or claims asserted against, such Contributor by 163 | reason of your accepting any such warranty or additional liability. 164 | 165 | END OF TERMS AND CONDITIONS 166 | 167 | APPENDIX: How to apply the Apache License to your work. 168 | 169 | To apply the Apache License to your work, attach the following 170 | boilerplate notice, with the fields enclosed by brackets "[]" 171 | replaced with your own identifying information. (Don't include 172 | the brackets!) The text should be enclosed in the appropriate 173 | comment syntax for the file format. We also recommend that a 174 | file or class name and description of purpose be included on the 175 | same "printed page" as the copyright notice for easier 176 | identification within third-party archives. 177 | 178 | Copyright 2022 CodeSandbox BV 179 | 180 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use 181 | this file except in compliance with the License. You may obtain a copy of the 182 | License at 183 | 184 | http://www.apache.org/licenses/LICENSE-2.0 185 | 186 | Unless required by applicable law or agreed to in writing, software distributed 187 | under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 188 | CONDITIONS OF ANY KIND, either express or implied. See the License for the 189 | specific language governing permissions and limitations under the License. 190 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sandpack NPM CDN 2 | 3 | The sandpack cdn is used to serve npm modules in a browser-optimized way, by having the entire npm registry on disk and query it, download the needed files from npm and serve those in a msgpack bundle to the client. Besides this it always add a really fast resolver that uses the in-memory/on-disk npm registry. 4 | 5 | ## Running the project 6 | 7 | Run the following command: `cargo run` 8 | 9 | ## Environment variables 10 | 11 | ### Port 12 | 13 | Define a custom port to start the server on 14 | 15 | Example: `PORT=1234` - Defaults to 8080 16 | 17 | ### Database 18 | 19 | To run this locally you need to define the location of the rocksdb database, which is where the sandpack cdn stores all the data it needs. 20 | 21 | Example: `NPM_ROCKS_DB=/persisted/npm_rocks_db` 22 | 23 | ### Tracing 24 | 25 | - OpenTelemetry exporter endpoint: `OTEL_EXPORTER_OTLP_ENDPOINT` 26 | - OpenTelemetry metadata headers, prefix with `OTEL_METADATA_`, for example for honeycomb you add: `OTEL_METADATA_X_HONEYCOMB_TEAM=` 27 | -------------------------------------------------------------------------------- /catalog-info.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: backstage.io/v1alpha1 2 | kind: Component 3 | metadata: 4 | name: sandpack-cdn 5 | description: Sandpack CDN microservice 6 | annotations: 7 | github.com/project-slug: codesandbox/sandpack-cdn 8 | backstage.io/kubernetes-namespace: sandpack-cdn-production 9 | backstage.io/kubernetes-label-selector: app=sandpack-cdn,component=sandpack-cdn 10 | codesandbox/deploy-image: europe-docker.pkg.dev/codesandbox-microservices/codesandbox/sandpack-cdn 11 | codesandbox/deploy-gitops-repo: codesandbox/codesandbox-gitops 12 | codesandbox/deploy-gitops-yaml-key: "images.[0].newTag" 13 | codesandbox/deploy-image-tag-regex-production: ".*" 14 | codesandbox/deploy-gitops-file-production: codesandbox-apps/sandpack-cdn/production/kustomization.yaml 15 | codesandbox/deploy-image-tag-regex-staging: ".*" 16 | codesandbox/deploy-gitops-file-staging: codesandbox-apps/sandpack-cdn/staging/kustomization.yaml 17 | codesandbox/deploy-gitops-version-type: tag 18 | spec: 19 | type: service 20 | lifecycle: production 21 | owner: client 22 | -------------------------------------------------------------------------------- /fixtures/pkg-json/parse-test.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react", 3 | "version": "17.0.2", 4 | "jsnext:main": "index.next.js", 5 | "main": "index.cjs", 6 | "module": "index.mjs", 7 | "browser": "index.browser.js", 8 | "exports": { 9 | "something": "src/something.js", 10 | "dev-tools": { 11 | "default": { 12 | "browser": "index.browser.js" 13 | } 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /integration-tests/__snapshots__/file-collection.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`@babel/runtime@7.16.5 1`] = ` 4 | Array [ 5 | Array [ 6 | "helpers/applyDecoratedDescriptor.js", 7 | "object", 8 | ], 9 | Array [ 10 | "helpers/arrayLikeToArray.js", 11 | "object", 12 | ], 13 | Array [ 14 | "helpers/arrayWithHoles.js", 15 | "object", 16 | ], 17 | Array [ 18 | "helpers/arrayWithoutHoles.js", 19 | "object", 20 | ], 21 | Array [ 22 | "helpers/assertThisInitialized.js", 23 | "object", 24 | ], 25 | Array [ 26 | "helpers/AsyncGenerator.js", 27 | "object", 28 | ], 29 | Array [ 30 | "helpers/asyncGeneratorDelegate.js", 31 | "object", 32 | ], 33 | Array [ 34 | "helpers/asyncIterator.js", 35 | "object", 36 | ], 37 | Array [ 38 | "helpers/asyncToGenerator.js", 39 | "object", 40 | ], 41 | Array [ 42 | "helpers/awaitAsyncGenerator.js", 43 | "object", 44 | ], 45 | Array [ 46 | "helpers/AwaitValue.js", 47 | "object", 48 | ], 49 | Array [ 50 | "helpers/checkPrivateRedeclaration.js", 51 | "object", 52 | ], 53 | Array [ 54 | "helpers/classApplyDescriptorDestructureSet.js", 55 | "object", 56 | ], 57 | Array [ 58 | "helpers/classApplyDescriptorGet.js", 59 | "object", 60 | ], 61 | Array [ 62 | "helpers/classApplyDescriptorSet.js", 63 | "object", 64 | ], 65 | Array [ 66 | "helpers/classCallCheck.js", 67 | "object", 68 | ], 69 | Array [ 70 | "helpers/classCheckPrivateStaticAccess.js", 71 | "object", 72 | ], 73 | Array [ 74 | "helpers/classCheckPrivateStaticFieldDescriptor.js", 75 | "object", 76 | ], 77 | Array [ 78 | "helpers/classExtractFieldDescriptor.js", 79 | "object", 80 | ], 81 | Array [ 82 | "helpers/classNameTDZError.js", 83 | "object", 84 | ], 85 | Array [ 86 | "helpers/classPrivateFieldDestructureSet.js", 87 | "object", 88 | ], 89 | Array [ 90 | "helpers/classPrivateFieldGet.js", 91 | "object", 92 | ], 93 | Array [ 94 | "helpers/classPrivateFieldInitSpec.js", 95 | "object", 96 | ], 97 | Array [ 98 | "helpers/classPrivateFieldLooseBase.js", 99 | "object", 100 | ], 101 | Array [ 102 | "helpers/classPrivateFieldLooseKey.js", 103 | "object", 104 | ], 105 | Array [ 106 | "helpers/classPrivateFieldSet.js", 107 | "object", 108 | ], 109 | Array [ 110 | "helpers/classPrivateMethodGet.js", 111 | "object", 112 | ], 113 | Array [ 114 | "helpers/classPrivateMethodInitSpec.js", 115 | "object", 116 | ], 117 | Array [ 118 | "helpers/classPrivateMethodSet.js", 119 | "object", 120 | ], 121 | Array [ 122 | "helpers/classStaticPrivateFieldDestructureSet.js", 123 | "object", 124 | ], 125 | Array [ 126 | "helpers/classStaticPrivateFieldSpecGet.js", 127 | "object", 128 | ], 129 | Array [ 130 | "helpers/classStaticPrivateFieldSpecSet.js", 131 | "object", 132 | ], 133 | Array [ 134 | "helpers/classStaticPrivateMethodGet.js", 135 | "object", 136 | ], 137 | Array [ 138 | "helpers/classStaticPrivateMethodSet.js", 139 | "object", 140 | ], 141 | Array [ 142 | "helpers/construct.js", 143 | "object", 144 | ], 145 | Array [ 146 | "helpers/createClass.js", 147 | "object", 148 | ], 149 | Array [ 150 | "helpers/createForOfIteratorHelper.js", 151 | "object", 152 | ], 153 | Array [ 154 | "helpers/createForOfIteratorHelperLoose.js", 155 | "object", 156 | ], 157 | Array [ 158 | "helpers/createSuper.js", 159 | "object", 160 | ], 161 | Array [ 162 | "helpers/decorate.js", 163 | "object", 164 | ], 165 | Array [ 166 | "helpers/defaults.js", 167 | "object", 168 | ], 169 | Array [ 170 | "helpers/defineEnumerableProperties.js", 171 | "object", 172 | ], 173 | Array [ 174 | "helpers/defineProperty.js", 175 | "object", 176 | ], 177 | Array [ 178 | "helpers/esm/applyDecoratedDescriptor.js", 179 | "object", 180 | ], 181 | Array [ 182 | "helpers/esm/arrayLikeToArray.js", 183 | "object", 184 | ], 185 | Array [ 186 | "helpers/esm/arrayWithHoles.js", 187 | "object", 188 | ], 189 | Array [ 190 | "helpers/esm/arrayWithoutHoles.js", 191 | "object", 192 | ], 193 | Array [ 194 | "helpers/esm/assertThisInitialized.js", 195 | "object", 196 | ], 197 | Array [ 198 | "helpers/esm/AsyncGenerator.js", 199 | "object", 200 | ], 201 | Array [ 202 | "helpers/esm/asyncGeneratorDelegate.js", 203 | "object", 204 | ], 205 | Array [ 206 | "helpers/esm/asyncIterator.js", 207 | "object", 208 | ], 209 | Array [ 210 | "helpers/esm/asyncToGenerator.js", 211 | "object", 212 | ], 213 | Array [ 214 | "helpers/esm/awaitAsyncGenerator.js", 215 | "object", 216 | ], 217 | Array [ 218 | "helpers/esm/AwaitValue.js", 219 | "object", 220 | ], 221 | Array [ 222 | "helpers/esm/checkPrivateRedeclaration.js", 223 | "object", 224 | ], 225 | Array [ 226 | "helpers/esm/classApplyDescriptorDestructureSet.js", 227 | "object", 228 | ], 229 | Array [ 230 | "helpers/esm/classApplyDescriptorGet.js", 231 | "object", 232 | ], 233 | Array [ 234 | "helpers/esm/classApplyDescriptorSet.js", 235 | "object", 236 | ], 237 | Array [ 238 | "helpers/esm/classCallCheck.js", 239 | "object", 240 | ], 241 | Array [ 242 | "helpers/esm/classCheckPrivateStaticAccess.js", 243 | "object", 244 | ], 245 | Array [ 246 | "helpers/esm/classCheckPrivateStaticFieldDescriptor.js", 247 | "object", 248 | ], 249 | Array [ 250 | "helpers/esm/classExtractFieldDescriptor.js", 251 | "object", 252 | ], 253 | Array [ 254 | "helpers/esm/classNameTDZError.js", 255 | "object", 256 | ], 257 | Array [ 258 | "helpers/esm/classPrivateFieldDestructureSet.js", 259 | "object", 260 | ], 261 | Array [ 262 | "helpers/esm/classPrivateFieldGet.js", 263 | "object", 264 | ], 265 | Array [ 266 | "helpers/esm/classPrivateFieldInitSpec.js", 267 | "object", 268 | ], 269 | Array [ 270 | "helpers/esm/classPrivateFieldLooseBase.js", 271 | "object", 272 | ], 273 | Array [ 274 | "helpers/esm/classPrivateFieldLooseKey.js", 275 | "object", 276 | ], 277 | Array [ 278 | "helpers/esm/classPrivateFieldSet.js", 279 | "object", 280 | ], 281 | Array [ 282 | "helpers/esm/classPrivateMethodGet.js", 283 | "object", 284 | ], 285 | Array [ 286 | "helpers/esm/classPrivateMethodInitSpec.js", 287 | "object", 288 | ], 289 | Array [ 290 | "helpers/esm/classPrivateMethodSet.js", 291 | "object", 292 | ], 293 | Array [ 294 | "helpers/esm/classStaticPrivateFieldDestructureSet.js", 295 | "object", 296 | ], 297 | Array [ 298 | "helpers/esm/classStaticPrivateFieldSpecGet.js", 299 | "object", 300 | ], 301 | Array [ 302 | "helpers/esm/classStaticPrivateFieldSpecSet.js", 303 | "object", 304 | ], 305 | Array [ 306 | "helpers/esm/classStaticPrivateMethodGet.js", 307 | "object", 308 | ], 309 | Array [ 310 | "helpers/esm/classStaticPrivateMethodSet.js", 311 | "object", 312 | ], 313 | Array [ 314 | "helpers/esm/construct.js", 315 | "object", 316 | ], 317 | Array [ 318 | "helpers/esm/createClass.js", 319 | "object", 320 | ], 321 | Array [ 322 | "helpers/esm/createForOfIteratorHelper.js", 323 | "object", 324 | ], 325 | Array [ 326 | "helpers/esm/createForOfIteratorHelperLoose.js", 327 | "object", 328 | ], 329 | Array [ 330 | "helpers/esm/createSuper.js", 331 | "object", 332 | ], 333 | Array [ 334 | "helpers/esm/decorate.js", 335 | "object", 336 | ], 337 | Array [ 338 | "helpers/esm/defaults.js", 339 | "object", 340 | ], 341 | Array [ 342 | "helpers/esm/defineEnumerableProperties.js", 343 | "object", 344 | ], 345 | Array [ 346 | "helpers/esm/defineProperty.js", 347 | "object", 348 | ], 349 | Array [ 350 | "helpers/esm/extends.js", 351 | "object", 352 | ], 353 | Array [ 354 | "helpers/esm/get.js", 355 | "object", 356 | ], 357 | Array [ 358 | "helpers/esm/getPrototypeOf.js", 359 | "object", 360 | ], 361 | Array [ 362 | "helpers/esm/inherits.js", 363 | "object", 364 | ], 365 | Array [ 366 | "helpers/esm/inheritsLoose.js", 367 | "object", 368 | ], 369 | Array [ 370 | "helpers/esm/initializerDefineProperty.js", 371 | "object", 372 | ], 373 | Array [ 374 | "helpers/esm/initializerWarningHelper.js", 375 | "object", 376 | ], 377 | Array [ 378 | "helpers/esm/instanceof.js", 379 | "object", 380 | ], 381 | Array [ 382 | "helpers/esm/interopRequireDefault.js", 383 | "object", 384 | ], 385 | Array [ 386 | "helpers/esm/interopRequireWildcard.js", 387 | "object", 388 | ], 389 | Array [ 390 | "helpers/esm/isNativeFunction.js", 391 | "object", 392 | ], 393 | Array [ 394 | "helpers/esm/isNativeReflectConstruct.js", 395 | "object", 396 | ], 397 | Array [ 398 | "helpers/esm/iterableToArray.js", 399 | "object", 400 | ], 401 | Array [ 402 | "helpers/esm/iterableToArrayLimit.js", 403 | "object", 404 | ], 405 | Array [ 406 | "helpers/esm/iterableToArrayLimitLoose.js", 407 | "object", 408 | ], 409 | Array [ 410 | "helpers/esm/jsx.js", 411 | "object", 412 | ], 413 | Array [ 414 | "helpers/esm/maybeArrayLike.js", 415 | "object", 416 | ], 417 | Array [ 418 | "helpers/esm/newArrowCheck.js", 419 | "object", 420 | ], 421 | Array [ 422 | "helpers/esm/nonIterableRest.js", 423 | "object", 424 | ], 425 | Array [ 426 | "helpers/esm/nonIterableSpread.js", 427 | "object", 428 | ], 429 | Array [ 430 | "helpers/esm/objectDestructuringEmpty.js", 431 | "object", 432 | ], 433 | Array [ 434 | "helpers/esm/objectSpread.js", 435 | "object", 436 | ], 437 | Array [ 438 | "helpers/esm/objectSpread2.js", 439 | "object", 440 | ], 441 | Array [ 442 | "helpers/esm/objectWithoutProperties.js", 443 | "object", 444 | ], 445 | Array [ 446 | "helpers/esm/objectWithoutPropertiesLoose.js", 447 | "object", 448 | ], 449 | Array [ 450 | "helpers/esm/package.json", 451 | "number", 452 | ], 453 | Array [ 454 | "helpers/esm/possibleConstructorReturn.js", 455 | "object", 456 | ], 457 | Array [ 458 | "helpers/esm/readOnlyError.js", 459 | "object", 460 | ], 461 | Array [ 462 | "helpers/esm/set.js", 463 | "object", 464 | ], 465 | Array [ 466 | "helpers/esm/setPrototypeOf.js", 467 | "object", 468 | ], 469 | Array [ 470 | "helpers/esm/skipFirstGeneratorNext.js", 471 | "object", 472 | ], 473 | Array [ 474 | "helpers/esm/slicedToArray.js", 475 | "object", 476 | ], 477 | Array [ 478 | "helpers/esm/slicedToArrayLoose.js", 479 | "object", 480 | ], 481 | Array [ 482 | "helpers/esm/superPropBase.js", 483 | "object", 484 | ], 485 | Array [ 486 | "helpers/esm/taggedTemplateLiteral.js", 487 | "object", 488 | ], 489 | Array [ 490 | "helpers/esm/taggedTemplateLiteralLoose.js", 491 | "object", 492 | ], 493 | Array [ 494 | "helpers/esm/tdz.js", 495 | "object", 496 | ], 497 | Array [ 498 | "helpers/esm/temporalRef.js", 499 | "object", 500 | ], 501 | Array [ 502 | "helpers/esm/temporalUndefined.js", 503 | "object", 504 | ], 505 | Array [ 506 | "helpers/esm/toArray.js", 507 | "object", 508 | ], 509 | Array [ 510 | "helpers/esm/toConsumableArray.js", 511 | "object", 512 | ], 513 | Array [ 514 | "helpers/esm/toPrimitive.js", 515 | "object", 516 | ], 517 | Array [ 518 | "helpers/esm/toPropertyKey.js", 519 | "object", 520 | ], 521 | Array [ 522 | "helpers/esm/typeof.js", 523 | "object", 524 | ], 525 | Array [ 526 | "helpers/esm/unsupportedIterableToArray.js", 527 | "object", 528 | ], 529 | Array [ 530 | "helpers/esm/wrapAsyncGenerator.js", 531 | "object", 532 | ], 533 | Array [ 534 | "helpers/esm/wrapNativeSuper.js", 535 | "object", 536 | ], 537 | Array [ 538 | "helpers/esm/wrapRegExp.js", 539 | "object", 540 | ], 541 | Array [ 542 | "helpers/esm/writeOnlyError.js", 543 | "object", 544 | ], 545 | Array [ 546 | "helpers/extends.js", 547 | "object", 548 | ], 549 | Array [ 550 | "helpers/get.js", 551 | "object", 552 | ], 553 | Array [ 554 | "helpers/getPrototypeOf.js", 555 | "object", 556 | ], 557 | Array [ 558 | "helpers/inherits.js", 559 | "object", 560 | ], 561 | Array [ 562 | "helpers/inheritsLoose.js", 563 | "object", 564 | ], 565 | Array [ 566 | "helpers/initializerDefineProperty.js", 567 | "object", 568 | ], 569 | Array [ 570 | "helpers/initializerWarningHelper.js", 571 | "object", 572 | ], 573 | Array [ 574 | "helpers/instanceof.js", 575 | "object", 576 | ], 577 | Array [ 578 | "helpers/interopRequireDefault.js", 579 | "object", 580 | ], 581 | Array [ 582 | "helpers/interopRequireWildcard.js", 583 | "object", 584 | ], 585 | Array [ 586 | "helpers/isNativeFunction.js", 587 | "object", 588 | ], 589 | Array [ 590 | "helpers/isNativeReflectConstruct.js", 591 | "object", 592 | ], 593 | Array [ 594 | "helpers/iterableToArray.js", 595 | "object", 596 | ], 597 | Array [ 598 | "helpers/iterableToArrayLimit.js", 599 | "object", 600 | ], 601 | Array [ 602 | "helpers/iterableToArrayLimitLoose.js", 603 | "object", 604 | ], 605 | Array [ 606 | "helpers/jsx.js", 607 | "object", 608 | ], 609 | Array [ 610 | "helpers/maybeArrayLike.js", 611 | "object", 612 | ], 613 | Array [ 614 | "helpers/newArrowCheck.js", 615 | "object", 616 | ], 617 | Array [ 618 | "helpers/nonIterableRest.js", 619 | "object", 620 | ], 621 | Array [ 622 | "helpers/nonIterableSpread.js", 623 | "object", 624 | ], 625 | Array [ 626 | "helpers/objectDestructuringEmpty.js", 627 | "object", 628 | ], 629 | Array [ 630 | "helpers/objectSpread.js", 631 | "object", 632 | ], 633 | Array [ 634 | "helpers/objectSpread2.js", 635 | "object", 636 | ], 637 | Array [ 638 | "helpers/objectWithoutProperties.js", 639 | "object", 640 | ], 641 | Array [ 642 | "helpers/objectWithoutPropertiesLoose.js", 643 | "object", 644 | ], 645 | Array [ 646 | "helpers/possibleConstructorReturn.js", 647 | "object", 648 | ], 649 | Array [ 650 | "helpers/readOnlyError.js", 651 | "object", 652 | ], 653 | Array [ 654 | "helpers/set.js", 655 | "object", 656 | ], 657 | Array [ 658 | "helpers/setPrototypeOf.js", 659 | "object", 660 | ], 661 | Array [ 662 | "helpers/skipFirstGeneratorNext.js", 663 | "object", 664 | ], 665 | Array [ 666 | "helpers/slicedToArray.js", 667 | "object", 668 | ], 669 | Array [ 670 | "helpers/slicedToArrayLoose.js", 671 | "object", 672 | ], 673 | Array [ 674 | "helpers/superPropBase.js", 675 | "object", 676 | ], 677 | Array [ 678 | "helpers/taggedTemplateLiteral.js", 679 | "object", 680 | ], 681 | Array [ 682 | "helpers/taggedTemplateLiteralLoose.js", 683 | "object", 684 | ], 685 | Array [ 686 | "helpers/tdz.js", 687 | "object", 688 | ], 689 | Array [ 690 | "helpers/temporalRef.js", 691 | "object", 692 | ], 693 | Array [ 694 | "helpers/temporalUndefined.js", 695 | "object", 696 | ], 697 | Array [ 698 | "helpers/toArray.js", 699 | "object", 700 | ], 701 | Array [ 702 | "helpers/toConsumableArray.js", 703 | "object", 704 | ], 705 | Array [ 706 | "helpers/toPrimitive.js", 707 | "object", 708 | ], 709 | Array [ 710 | "helpers/toPropertyKey.js", 711 | "object", 712 | ], 713 | Array [ 714 | "helpers/typeof.js", 715 | "object", 716 | ], 717 | Array [ 718 | "helpers/unsupportedIterableToArray.js", 719 | "object", 720 | ], 721 | Array [ 722 | "helpers/wrapAsyncGenerator.js", 723 | "object", 724 | ], 725 | Array [ 726 | "helpers/wrapNativeSuper.js", 727 | "object", 728 | ], 729 | Array [ 730 | "helpers/wrapRegExp.js", 731 | "object", 732 | ], 733 | Array [ 734 | "helpers/writeOnlyError.js", 735 | "object", 736 | ], 737 | Array [ 738 | "LICENSE", 739 | "number", 740 | ], 741 | Array [ 742 | "package.json", 743 | "object", 744 | ], 745 | Array [ 746 | "README.md", 747 | "number", 748 | ], 749 | Array [ 750 | "regenerator/index.js", 751 | "object", 752 | ], 753 | ] 754 | `; 755 | 756 | exports[`framer@1.3.6 1`] = ` 757 | Array [ 758 | Array [ 759 | "build/esm/framer.debug.js", 760 | "object", 761 | ], 762 | Array [ 763 | "build/esm/framer.debug.js.map", 764 | "number", 765 | ], 766 | Array [ 767 | "build/esm/framer.js", 768 | "number", 769 | ], 770 | Array [ 771 | "build/esm/framer.js.map", 772 | "number", 773 | ], 774 | Array [ 775 | "build/framer.api.json", 776 | "number", 777 | ], 778 | Array [ 779 | "build/framer.d.ts", 780 | "number", 781 | ], 782 | Array [ 783 | "build/framer.debug.js", 784 | "number", 785 | ], 786 | Array [ 787 | "build/framer.debug.js.map", 788 | "number", 789 | ], 790 | Array [ 791 | "build/framer.js", 792 | "number", 793 | ], 794 | Array [ 795 | "build/framer.js.map", 796 | "number", 797 | ], 798 | Array [ 799 | "CHANGELOG.md", 800 | "number", 801 | ], 802 | Array [ 803 | "LICENSE.md", 804 | "number", 805 | ], 806 | Array [ 807 | "package.json", 808 | "object", 809 | ], 810 | Array [ 811 | "README.md", 812 | "number", 813 | ], 814 | Array [ 815 | "resource.d.ts", 816 | "number", 817 | ], 818 | ] 819 | `; 820 | 821 | exports[`framer@2.0.0-beta.13 1`] = ` 822 | Array [ 823 | Array [ 824 | "build/animation/Animatable/Animatable.d.ts", 825 | "number", 826 | ], 827 | Array [ 828 | "build/animation/Animatable/Animatable.d.ts.map", 829 | "number", 830 | ], 831 | Array [ 832 | "build/animation/Animatable/Animatable.js", 833 | "object", 834 | ], 835 | Array [ 836 | "build/animation/Animatable/Animatable.js.map", 837 | "number", 838 | ], 839 | Array [ 840 | "build/animation/Animatable/index.d.ts", 841 | "number", 842 | ], 843 | Array [ 844 | "build/animation/Animatable/index.d.ts.map", 845 | "number", 846 | ], 847 | Array [ 848 | "build/animation/Animatable/index.js", 849 | "object", 850 | ], 851 | Array [ 852 | "build/animation/Animatable/index.js.map", 853 | "number", 854 | ], 855 | Array [ 856 | "build/animation/Animatable/Observers.d.ts", 857 | "number", 858 | ], 859 | Array [ 860 | "build/animation/Animatable/Observers.d.ts.map", 861 | "number", 862 | ], 863 | Array [ 864 | "build/animation/Animatable/Observers.js", 865 | "object", 866 | ], 867 | Array [ 868 | "build/animation/Animatable/Observers.js.map", 869 | "number", 870 | ], 871 | Array [ 872 | "build/animation/Animators/Animator.d.ts", 873 | "number", 874 | ], 875 | Array [ 876 | "build/animation/Animators/Animator.d.ts.map", 877 | "number", 878 | ], 879 | Array [ 880 | "build/animation/Animators/Animator.js", 881 | "object", 882 | ], 883 | Array [ 884 | "build/animation/Animators/Animator.js.map", 885 | "number", 886 | ], 887 | Array [ 888 | "build/animation/Animators/FrictionAnimator.d.ts", 889 | "number", 890 | ], 891 | Array [ 892 | "build/animation/Animators/FrictionAnimator.d.ts.map", 893 | "number", 894 | ], 895 | Array [ 896 | "build/animation/Animators/FrictionAnimator.js", 897 | "object", 898 | ], 899 | Array [ 900 | "build/animation/Animators/FrictionAnimator.js.map", 901 | "number", 902 | ], 903 | Array [ 904 | "build/animation/Animators/InertialScrollAnimator.d.ts", 905 | "number", 906 | ], 907 | Array [ 908 | "build/animation/Animators/InertialScrollAnimator.d.ts.map", 909 | "number", 910 | ], 911 | Array [ 912 | "build/animation/Animators/InertialScrollAnimator.js", 913 | "object", 914 | ], 915 | Array [ 916 | "build/animation/Animators/InertialScrollAnimator.js.map", 917 | "number", 918 | ], 919 | Array [ 920 | "build/animation/Animators/Integrator.d.ts", 921 | "number", 922 | ], 923 | Array [ 924 | "build/animation/Animators/Integrator.d.ts.map", 925 | "number", 926 | ], 927 | Array [ 928 | "build/animation/Animators/Integrator.js", 929 | "object", 930 | ], 931 | Array [ 932 | "build/animation/Animators/Integrator.js.map", 933 | "number", 934 | ], 935 | Array [ 936 | "build/animation/Animators/SpringAnimator.d.ts", 937 | "number", 938 | ], 939 | Array [ 940 | "build/animation/Animators/SpringAnimator.d.ts.map", 941 | "number", 942 | ], 943 | Array [ 944 | "build/animation/Animators/SpringAnimator.js", 945 | "object", 946 | ], 947 | Array [ 948 | "build/animation/Animators/SpringAnimator.js.map", 949 | "number", 950 | ], 951 | Array [ 952 | "build/animation/Animators/SpringCurveValueConverter.d.ts", 953 | "number", 954 | ], 955 | Array [ 956 | "build/animation/Animators/SpringCurveValueConverter.d.ts.map", 957 | "number", 958 | ], 959 | Array [ 960 | "build/animation/Animators/SpringCurveValueConverter.js", 961 | "object", 962 | ], 963 | Array [ 964 | "build/animation/Animators/SpringCurveValueConverter.js.map", 965 | "number", 966 | ], 967 | Array [ 968 | "build/animation/Drivers/AnimationDriver.d.ts", 969 | "number", 970 | ], 971 | Array [ 972 | "build/animation/Drivers/AnimationDriver.d.ts.map", 973 | "number", 974 | ], 975 | Array [ 976 | "build/animation/Drivers/AnimationDriver.js", 977 | "object", 978 | ], 979 | Array [ 980 | "build/animation/Drivers/AnimationDriver.js.map", 981 | "number", 982 | ], 983 | Array [ 984 | "build/animation/Drivers/MainLoopDriver.d.ts", 985 | "number", 986 | ], 987 | Array [ 988 | "build/animation/Drivers/MainLoopDriver.d.ts.map", 989 | "number", 990 | ], 991 | Array [ 992 | "build/animation/Drivers/MainLoopDriver.js", 993 | "object", 994 | ], 995 | Array [ 996 | "build/animation/Drivers/MainLoopDriver.js.map", 997 | "number", 998 | ], 999 | Array [ 1000 | "build/components/AnimateLayout/LayoutIdContext.d.ts", 1001 | "number", 1002 | ], 1003 | Array [ 1004 | "build/components/AnimateLayout/LayoutIdContext.d.ts.map", 1005 | "number", 1006 | ], 1007 | Array [ 1008 | "build/components/AnimateLayout/LayoutIdContext.js", 1009 | "object", 1010 | ], 1011 | Array [ 1012 | "build/components/AnimateLayout/LayoutIdContext.js.map", 1013 | "number", 1014 | ], 1015 | Array [ 1016 | "build/components/AnimateLayout/SharedLayoutRoot.d.ts", 1017 | "number", 1018 | ], 1019 | Array [ 1020 | "build/components/AnimateLayout/SharedLayoutRoot.d.ts.map", 1021 | "number", 1022 | ], 1023 | Array [ 1024 | "build/components/AnimateLayout/SharedLayoutRoot.js", 1025 | "object", 1026 | ], 1027 | Array [ 1028 | "build/components/AnimateLayout/SharedLayoutRoot.js.map", 1029 | "number", 1030 | ], 1031 | Array [ 1032 | "build/components/AnimateLayout/SharedLayoutTree.d.ts", 1033 | "number", 1034 | ], 1035 | Array [ 1036 | "build/components/AnimateLayout/SharedLayoutTree.d.ts.map", 1037 | "number", 1038 | ], 1039 | Array [ 1040 | "build/components/AnimateLayout/SharedLayoutTree.js", 1041 | "object", 1042 | ], 1043 | Array [ 1044 | "build/components/AnimateLayout/SharedLayoutTree.js.map", 1045 | "number", 1046 | ], 1047 | Array [ 1048 | "build/components/EmptyState.d.ts", 1049 | "number", 1050 | ], 1051 | Array [ 1052 | "build/components/EmptyState.d.ts.map", 1053 | "number", 1054 | ], 1055 | Array [ 1056 | "build/components/EmptyState.js", 1057 | "object", 1058 | ], 1059 | Array [ 1060 | "build/components/EmptyState.js.map", 1061 | "number", 1062 | ], 1063 | Array [ 1064 | "build/components/hoc/WithDragging.d.ts", 1065 | "number", 1066 | ], 1067 | Array [ 1068 | "build/components/hoc/WithDragging.d.ts.map", 1069 | "number", 1070 | ], 1071 | Array [ 1072 | "build/components/hoc/WithDragging.js", 1073 | "object", 1074 | ], 1075 | Array [ 1076 | "build/components/hoc/WithDragging.js.map", 1077 | "number", 1078 | ], 1079 | Array [ 1080 | "build/components/hoc/WithEvents.d.ts", 1081 | "number", 1082 | ], 1083 | Array [ 1084 | "build/components/hoc/WithEvents.d.ts.map", 1085 | "number", 1086 | ], 1087 | Array [ 1088 | "build/components/hoc/WithEvents.js", 1089 | "object", 1090 | ], 1091 | Array [ 1092 | "build/components/hoc/WithEvents.js.map", 1093 | "number", 1094 | ], 1095 | Array [ 1096 | "build/components/hoc/withMeasuredSize.d.ts", 1097 | "number", 1098 | ], 1099 | Array [ 1100 | "build/components/hoc/withMeasuredSize.d.ts.map", 1101 | "number", 1102 | ], 1103 | Array [ 1104 | "build/components/hoc/withMeasuredSize.js", 1105 | "object", 1106 | ], 1107 | Array [ 1108 | "build/components/hoc/withMeasuredSize.js.map", 1109 | "number", 1110 | ], 1111 | Array [ 1112 | "build/components/MagicMotionCrossfadeRoot.d.ts", 1113 | "number", 1114 | ], 1115 | Array [ 1116 | "build/components/MagicMotionCrossfadeRoot.d.ts.map", 1117 | "number", 1118 | ], 1119 | Array [ 1120 | "build/components/MagicMotionCrossfadeRoot.js", 1121 | "object", 1122 | ], 1123 | Array [ 1124 | "build/components/MagicMotionCrossfadeRoot.js.map", 1125 | "number", 1126 | ], 1127 | Array [ 1128 | "build/components/Navigation.d.ts", 1129 | "number", 1130 | ], 1131 | Array [ 1132 | "build/components/Navigation.d.ts.map", 1133 | "number", 1134 | ], 1135 | Array [ 1136 | "build/components/Navigation.js", 1137 | "object", 1138 | ], 1139 | Array [ 1140 | "build/components/Navigation.js.map", 1141 | "number", 1142 | ], 1143 | Array [ 1144 | "build/components/NavigationContainer.d.ts", 1145 | "number", 1146 | ], 1147 | Array [ 1148 | "build/components/NavigationContainer.d.ts.map", 1149 | "number", 1150 | ], 1151 | Array [ 1152 | "build/components/NavigationContainer.js", 1153 | "object", 1154 | ], 1155 | Array [ 1156 | "build/components/NavigationContainer.js.map", 1157 | "number", 1158 | ], 1159 | Array [ 1160 | "build/components/NavigationContainerContext.d.ts", 1161 | "number", 1162 | ], 1163 | Array [ 1164 | "build/components/NavigationContainerContext.d.ts.map", 1165 | "number", 1166 | ], 1167 | Array [ 1168 | "build/components/NavigationContainerContext.js", 1169 | "object", 1170 | ], 1171 | Array [ 1172 | "build/components/NavigationContainerContext.js.map", 1173 | "number", 1174 | ], 1175 | Array [ 1176 | "build/components/NavigationContext.d.ts", 1177 | "number", 1178 | ], 1179 | Array [ 1180 | "build/components/NavigationContext.d.ts.map", 1181 | "number", 1182 | ], 1183 | Array [ 1184 | "build/components/NavigationContext.js", 1185 | "object", 1186 | ], 1187 | Array [ 1188 | "build/components/NavigationContext.js.map", 1189 | "number", 1190 | ], 1191 | Array [ 1192 | "build/components/NavigationRouteContext.d.ts", 1193 | "number", 1194 | ], 1195 | Array [ 1196 | "build/components/NavigationRouteContext.d.ts.map", 1197 | "number", 1198 | ], 1199 | Array [ 1200 | "build/components/NavigationRouteContext.js", 1201 | "object", 1202 | ], 1203 | Array [ 1204 | "build/components/NavigationRouteContext.js.map", 1205 | "number", 1206 | ], 1207 | Array [ 1208 | "build/components/NavigationTargetContext.d.ts", 1209 | "number", 1210 | ], 1211 | Array [ 1212 | "build/components/NavigationTargetContext.d.ts.map", 1213 | "number", 1214 | ], 1215 | Array [ 1216 | "build/components/NavigationTargetContext.js", 1217 | "object", 1218 | ], 1219 | Array [ 1220 | "build/components/NavigationTargetContext.js.map", 1221 | "number", 1222 | ], 1223 | Array [ 1224 | "build/components/NavigationTransitions.d.ts", 1225 | "number", 1226 | ], 1227 | Array [ 1228 | "build/components/NavigationTransitions.d.ts.map", 1229 | "number", 1230 | ], 1231 | Array [ 1232 | "build/components/NavigationTransitions.js", 1233 | "object", 1234 | ], 1235 | Array [ 1236 | "build/components/NavigationTransitions.js.map", 1237 | "number", 1238 | ], 1239 | Array [ 1240 | "build/components/NavigatorMock.d.ts", 1241 | "number", 1242 | ], 1243 | Array [ 1244 | "build/components/NavigatorMock.d.ts.map", 1245 | "number", 1246 | ], 1247 | Array [ 1248 | "build/components/NavigatorMock.js", 1249 | "object", 1250 | ], 1251 | Array [ 1252 | "build/components/NavigatorMock.js.map", 1253 | "number", 1254 | ], 1255 | Array [ 1256 | "build/components/Page/EmulatedPage.d.ts", 1257 | "number", 1258 | ], 1259 | Array [ 1260 | "build/components/Page/EmulatedPage.d.ts.map", 1261 | "number", 1262 | ], 1263 | Array [ 1264 | "build/components/Page/EmulatedPage.js", 1265 | "object", 1266 | ], 1267 | Array [ 1268 | "build/components/Page/EmulatedPage.js.map", 1269 | "number", 1270 | ], 1271 | Array [ 1272 | "build/components/Page/PageContainer.d.ts", 1273 | "number", 1274 | ], 1275 | Array [ 1276 | "build/components/Page/PageContainer.d.ts.map", 1277 | "number", 1278 | ], 1279 | Array [ 1280 | "build/components/Page/PageContainer.js", 1281 | "object", 1282 | ], 1283 | Array [ 1284 | "build/components/Page/PageContainer.js.map", 1285 | "number", 1286 | ], 1287 | Array [ 1288 | "build/components/Page/types.d.ts", 1289 | "number", 1290 | ], 1291 | Array [ 1292 | "build/components/Page/types.d.ts.map", 1293 | "number", 1294 | ], 1295 | Array [ 1296 | "build/components/Page/types.js", 1297 | "object", 1298 | ], 1299 | Array [ 1300 | "build/components/Page/types.js.map", 1301 | "number", 1302 | ], 1303 | Array [ 1304 | "build/components/reduceNavigationStateForAction.d.ts", 1305 | "number", 1306 | ], 1307 | Array [ 1308 | "build/components/reduceNavigationStateForAction.d.ts.map", 1309 | "number", 1310 | ], 1311 | Array [ 1312 | "build/components/reduceNavigationStateForAction.js", 1313 | "object", 1314 | ], 1315 | Array [ 1316 | "build/components/reduceNavigationStateForAction.js.map", 1317 | "number", 1318 | ], 1319 | Array [ 1320 | "build/components/Scroll/EmulatedScroll.d.ts", 1321 | "number", 1322 | ], 1323 | Array [ 1324 | "build/components/Scroll/EmulatedScroll.d.ts.map", 1325 | "number", 1326 | ], 1327 | Array [ 1328 | "build/components/Scroll/EmulatedScroll.js", 1329 | "object", 1330 | ], 1331 | Array [ 1332 | "build/components/Scroll/EmulatedScroll.js.map", 1333 | "number", 1334 | ], 1335 | Array [ 1336 | "build/components/Scroll/NativeScroll.d.ts", 1337 | "number", 1338 | ], 1339 | Array [ 1340 | "build/components/Scroll/NativeScroll.d.ts.map", 1341 | "number", 1342 | ], 1343 | Array [ 1344 | "build/components/Scroll/NativeScroll.js", 1345 | "object", 1346 | ], 1347 | Array [ 1348 | "build/components/Scroll/NativeScroll.js.map", 1349 | "number", 1350 | ], 1351 | Array [ 1352 | "build/components/Scroll/Scroll.d.ts", 1353 | "number", 1354 | ], 1355 | Array [ 1356 | "build/components/Scroll/Scroll.d.ts.map", 1357 | "number", 1358 | ], 1359 | Array [ 1360 | "build/components/Scroll/Scroll.js", 1361 | "object", 1362 | ], 1363 | Array [ 1364 | "build/components/Scroll/Scroll.js.map", 1365 | "number", 1366 | ], 1367 | Array [ 1368 | "build/components/Scroll/types.d.ts", 1369 | "number", 1370 | ], 1371 | Array [ 1372 | "build/components/Scroll/types.d.ts.map", 1373 | "number", 1374 | ], 1375 | Array [ 1376 | "build/components/Scroll/types.js", 1377 | "object", 1378 | ], 1379 | Array [ 1380 | "build/components/Scroll/types.js.map", 1381 | "number", 1382 | ], 1383 | Array [ 1384 | "build/components/Scroll/useWheelScroll.d.ts", 1385 | "number", 1386 | ], 1387 | Array [ 1388 | "build/components/Scroll/useWheelScroll.d.ts.map", 1389 | "number", 1390 | ], 1391 | Array [ 1392 | "build/components/Scroll/useWheelScroll.js", 1393 | "object", 1394 | ], 1395 | Array [ 1396 | "build/components/Scroll/useWheelScroll.js.map", 1397 | "number", 1398 | ], 1399 | Array [ 1400 | "build/components/Stack/Stack.d.ts", 1401 | "number", 1402 | ], 1403 | Array [ 1404 | "build/components/Stack/Stack.d.ts.map", 1405 | "number", 1406 | ], 1407 | Array [ 1408 | "build/components/Stack/Stack.js", 1409 | "object", 1410 | ], 1411 | Array [ 1412 | "build/components/Stack/Stack.js.map", 1413 | "number", 1414 | ], 1415 | Array [ 1416 | "build/components/Stack/types.d.ts", 1417 | "number", 1418 | ], 1419 | Array [ 1420 | "build/components/Stack/types.d.ts.map", 1421 | "number", 1422 | ], 1423 | Array [ 1424 | "build/components/Stack/types.js", 1425 | "object", 1426 | ], 1427 | Array [ 1428 | "build/components/Stack/types.js.map", 1429 | "number", 1430 | ], 1431 | Array [ 1432 | "build/components/useNavigation.d.ts", 1433 | "number", 1434 | ], 1435 | Array [ 1436 | "build/components/useNavigation.d.ts.map", 1437 | "number", 1438 | ], 1439 | Array [ 1440 | "build/components/useNavigation.js", 1441 | "object", 1442 | ], 1443 | Array [ 1444 | "build/components/useNavigation.js.map", 1445 | "number", 1446 | ], 1447 | Array [ 1448 | "build/components/utils/animatePointWithInertia.d.ts", 1449 | "number", 1450 | ], 1451 | Array [ 1452 | "build/components/utils/animatePointWithInertia.d.ts.map", 1453 | "number", 1454 | ], 1455 | Array [ 1456 | "build/components/utils/animatePointWithInertia.js", 1457 | "object", 1458 | ], 1459 | Array [ 1460 | "build/components/utils/animatePointWithInertia.js.map", 1461 | "number", 1462 | ], 1463 | Array [ 1464 | "build/components/utils/paddingFromProps.d.ts", 1465 | "number", 1466 | ], 1467 | Array [ 1468 | "build/components/utils/paddingFromProps.d.ts.map", 1469 | "number", 1470 | ], 1471 | Array [ 1472 | "build/components/utils/paddingFromProps.js", 1473 | "object", 1474 | ], 1475 | Array [ 1476 | "build/components/utils/paddingFromProps.js.map", 1477 | "number", 1478 | ], 1479 | Array [ 1480 | "build/components/utils/useConstant.d.ts", 1481 | "number", 1482 | ], 1483 | Array [ 1484 | "build/components/utils/useConstant.d.ts.map", 1485 | "number", 1486 | ], 1487 | Array [ 1488 | "build/components/utils/useConstant.js", 1489 | "object", 1490 | ], 1491 | Array [ 1492 | "build/components/utils/useConstant.js.map", 1493 | "number", 1494 | ], 1495 | Array [ 1496 | "build/components/utils/useEmulatedTouchScroll.d.ts", 1497 | "number", 1498 | ], 1499 | Array [ 1500 | "build/components/utils/useEmulatedTouchScroll.d.ts.map", 1501 | "number", 1502 | ], 1503 | Array [ 1504 | "build/components/utils/useEmulatedTouchScroll.js", 1505 | "object", 1506 | ], 1507 | Array [ 1508 | "build/components/utils/useEmulatedTouchScroll.js.map", 1509 | "number", 1510 | ], 1511 | Array [ 1512 | "build/components/utils/useMap.d.ts", 1513 | "number", 1514 | ], 1515 | Array [ 1516 | "build/components/utils/useMap.d.ts.map", 1517 | "number", 1518 | ], 1519 | Array [ 1520 | "build/components/utils/useMap.js", 1521 | "object", 1522 | ], 1523 | Array [ 1524 | "build/components/utils/useMap.js.map", 1525 | "number", 1526 | ], 1527 | Array [ 1528 | "build/components/utils/useUpdateScrollOffset.d.ts", 1529 | "number", 1530 | ], 1531 | Array [ 1532 | "build/components/utils/useUpdateScrollOffset.d.ts.map", 1533 | "number", 1534 | ], 1535 | Array [ 1536 | "build/components/utils/useUpdateScrollOffset.js", 1537 | "object", 1538 | ], 1539 | Array [ 1540 | "build/components/utils/useUpdateScrollOffset.js.map", 1541 | "number", 1542 | ], 1543 | Array [ 1544 | "build/core/EventEmitter.d.ts", 1545 | "number", 1546 | ], 1547 | Array [ 1548 | "build/core/EventEmitter.d.ts.map", 1549 | "number", 1550 | ], 1551 | Array [ 1552 | "build/core/EventEmitter.js", 1553 | "object", 1554 | ], 1555 | Array [ 1556 | "build/core/EventEmitter.js.map", 1557 | "number", 1558 | ], 1559 | Array [ 1560 | "build/core/Loop.d.ts", 1561 | "number", 1562 | ], 1563 | Array [ 1564 | "build/core/Loop.d.ts.map", 1565 | "number", 1566 | ], 1567 | Array [ 1568 | "build/core/Loop.js", 1569 | "object", 1570 | ], 1571 | Array [ 1572 | "build/core/Loop.js.map", 1573 | "number", 1574 | ], 1575 | Array [ 1576 | "build/core/Time.d.ts", 1577 | "number", 1578 | ], 1579 | Array [ 1580 | "build/core/Time.d.ts.map", 1581 | "number", 1582 | ], 1583 | Array [ 1584 | "build/core/Time.js", 1585 | "object", 1586 | ], 1587 | Array [ 1588 | "build/core/Time.js.map", 1589 | "number", 1590 | ], 1591 | Array [ 1592 | "build/data/Data.d.ts", 1593 | "number", 1594 | ], 1595 | Array [ 1596 | "build/data/Data.d.ts.map", 1597 | "number", 1598 | ], 1599 | Array [ 1600 | "build/data/Data.js", 1601 | "object", 1602 | ], 1603 | Array [ 1604 | "build/data/Data.js.map", 1605 | "number", 1606 | ], 1607 | Array [ 1608 | "build/data/ObservableObject.d.ts", 1609 | "number", 1610 | ], 1611 | Array [ 1612 | "build/data/ObservableObject.d.ts.map", 1613 | "number", 1614 | ], 1615 | Array [ 1616 | "build/data/ObservableObject.js", 1617 | "object", 1618 | ], 1619 | Array [ 1620 | "build/data/ObservableObject.js.map", 1621 | "number", 1622 | ], 1623 | Array [ 1624 | "build/deprecated/convertColorProps.d.ts", 1625 | "number", 1626 | ], 1627 | Array [ 1628 | "build/deprecated/convertColorProps.d.ts.map", 1629 | "number", 1630 | ], 1631 | Array [ 1632 | "build/deprecated/convertColorProps.js", 1633 | "object", 1634 | ], 1635 | Array [ 1636 | "build/deprecated/convertColorProps.js.map", 1637 | "number", 1638 | ], 1639 | Array [ 1640 | "build/deprecated/DataObserver.d.ts", 1641 | "number", 1642 | ], 1643 | Array [ 1644 | "build/deprecated/DataObserver.d.ts.map", 1645 | "number", 1646 | ], 1647 | Array [ 1648 | "build/deprecated/DataObserver.js", 1649 | "object", 1650 | ], 1651 | Array [ 1652 | "build/deprecated/DataObserver.js.map", 1653 | "number", 1654 | ], 1655 | Array [ 1656 | "build/deprecated/WithOverride.d.ts", 1657 | "number", 1658 | ], 1659 | Array [ 1660 | "build/deprecated/WithOverride.d.ts.map", 1661 | "number", 1662 | ], 1663 | Array [ 1664 | "build/deprecated/WithOverride.js", 1665 | "object", 1666 | ], 1667 | Array [ 1668 | "build/deprecated/WithOverride.js.map", 1669 | "number", 1670 | ], 1671 | Array [ 1672 | "build/events/FramerEvent.d.ts", 1673 | "number", 1674 | ], 1675 | Array [ 1676 | "build/events/FramerEvent.d.ts.map", 1677 | "number", 1678 | ], 1679 | Array [ 1680 | "build/events/FramerEvent.js", 1681 | "object", 1682 | ], 1683 | Array [ 1684 | "build/events/FramerEvent.js.map", 1685 | "number", 1686 | ], 1687 | Array [ 1688 | "build/events/FramerEventSession.d.ts", 1689 | "number", 1690 | ], 1691 | Array [ 1692 | "build/events/FramerEventSession.d.ts.map", 1693 | "number", 1694 | ], 1695 | Array [ 1696 | "build/events/FramerEventSession.js", 1697 | "object", 1698 | ], 1699 | Array [ 1700 | "build/events/FramerEventSession.js.map", 1701 | "number", 1702 | ], 1703 | Array [ 1704 | "build/events/recognizer/GestureRecognizer.d.ts", 1705 | "number", 1706 | ], 1707 | Array [ 1708 | "build/events/recognizer/GestureRecognizer.d.ts.map", 1709 | "number", 1710 | ], 1711 | Array [ 1712 | "build/events/recognizer/GestureRecognizer.js", 1713 | "object", 1714 | ], 1715 | Array [ 1716 | "build/events/recognizer/GestureRecognizer.js.map", 1717 | "number", 1718 | ], 1719 | Array [ 1720 | "build/events/recognizer/MouseWheelGestureRecognizer.d.ts", 1721 | "number", 1722 | ], 1723 | Array [ 1724 | "build/events/recognizer/MouseWheelGestureRecognizer.d.ts.map", 1725 | "number", 1726 | ], 1727 | Array [ 1728 | "build/events/recognizer/MouseWheelGestureRecognizer.js", 1729 | "object", 1730 | ], 1731 | Array [ 1732 | "build/events/recognizer/MouseWheelGestureRecognizer.js.map", 1733 | "number", 1734 | ], 1735 | Array [ 1736 | "build/events/recognizer/PanGestureRecognizer.d.ts", 1737 | "number", 1738 | ], 1739 | Array [ 1740 | "build/events/recognizer/PanGestureRecognizer.d.ts.map", 1741 | "number", 1742 | ], 1743 | Array [ 1744 | "build/events/recognizer/PanGestureRecognizer.js", 1745 | "object", 1746 | ], 1747 | Array [ 1748 | "build/events/recognizer/PanGestureRecognizer.js.map", 1749 | "number", 1750 | ], 1751 | Array [ 1752 | "build/events/recognizer/TapGestureRecognizer.d.ts", 1753 | "number", 1754 | ], 1755 | Array [ 1756 | "build/events/recognizer/TapGestureRecognizer.d.ts.map", 1757 | "number", 1758 | ], 1759 | Array [ 1760 | "build/events/recognizer/TapGestureRecognizer.js", 1761 | "object", 1762 | ], 1763 | Array [ 1764 | "build/events/recognizer/TapGestureRecognizer.js.map", 1765 | "number", 1766 | ], 1767 | Array [ 1768 | "build/index.d.ts", 1769 | "number", 1770 | ], 1771 | Array [ 1772 | "build/index.d.ts.map", 1773 | "number", 1774 | ], 1775 | Array [ 1776 | "build/index.js", 1777 | "object", 1778 | ], 1779 | Array [ 1780 | "build/index.js.map", 1781 | "number", 1782 | ], 1783 | Array [ 1784 | "build/interpolation/Interpolation.d.ts", 1785 | "number", 1786 | ], 1787 | Array [ 1788 | "build/interpolation/Interpolation.d.ts.map", 1789 | "number", 1790 | ], 1791 | Array [ 1792 | "build/interpolation/Interpolation.js", 1793 | "object", 1794 | ], 1795 | Array [ 1796 | "build/interpolation/Interpolation.js.map", 1797 | "number", 1798 | ], 1799 | Array [ 1800 | "build/interpolation/NumberInterpolation.d.ts", 1801 | "number", 1802 | ], 1803 | Array [ 1804 | "build/interpolation/NumberInterpolation.d.ts.map", 1805 | "number", 1806 | ], 1807 | Array [ 1808 | "build/interpolation/NumberInterpolation.js", 1809 | "object", 1810 | ], 1811 | Array [ 1812 | "build/interpolation/NumberInterpolation.js.map", 1813 | "number", 1814 | ], 1815 | Array [ 1816 | "build/modules/cx.d.ts", 1817 | "number", 1818 | ], 1819 | Array [ 1820 | "build/modules/cx.d.ts.map", 1821 | "number", 1822 | ], 1823 | Array [ 1824 | "build/modules/cx.js", 1825 | "object", 1826 | ], 1827 | Array [ 1828 | "build/modules/cx.js.map", 1829 | "number", 1830 | ], 1831 | Array [ 1832 | "build/modules/framerPageLink.d.ts", 1833 | "number", 1834 | ], 1835 | Array [ 1836 | "build/modules/framerPageLink.d.ts.map", 1837 | "number", 1838 | ], 1839 | Array [ 1840 | "build/modules/framerPageLink.js", 1841 | "object", 1842 | ], 1843 | Array [ 1844 | "build/modules/framerPageLink.js.map", 1845 | "number", 1846 | ], 1847 | Array [ 1848 | "build/modules/hocOptions.d.ts", 1849 | "number", 1850 | ], 1851 | Array [ 1852 | "build/modules/hocOptions.d.ts.map", 1853 | "number", 1854 | ], 1855 | Array [ 1856 | "build/modules/hocOptions.js", 1857 | "object", 1858 | ], 1859 | Array [ 1860 | "build/modules/hocOptions.js.map", 1861 | "number", 1862 | ], 1863 | Array [ 1864 | "build/modules/useActiveVariantCallback.d.ts", 1865 | "number", 1866 | ], 1867 | Array [ 1868 | "build/modules/useActiveVariantCallback.d.ts.map", 1869 | "number", 1870 | ], 1871 | Array [ 1872 | "build/modules/useActiveVariantCallback.js", 1873 | "object", 1874 | ], 1875 | Array [ 1876 | "build/modules/useActiveVariantCallback.js.map", 1877 | "number", 1878 | ], 1879 | Array [ 1880 | "build/modules/useAddVariantProps.d.ts", 1881 | "number", 1882 | ], 1883 | Array [ 1884 | "build/modules/useAddVariantProps.d.ts.map", 1885 | "number", 1886 | ], 1887 | Array [ 1888 | "build/modules/useAddVariantProps.js", 1889 | "object", 1890 | ], 1891 | Array [ 1892 | "build/modules/useAddVariantProps.js.map", 1893 | "number", 1894 | ], 1895 | Array [ 1896 | "build/modules/useBreakpointVariants.d.ts", 1897 | "number", 1898 | ], 1899 | Array [ 1900 | "build/modules/useBreakpointVariants.d.ts.map", 1901 | "number", 1902 | ], 1903 | Array [ 1904 | "build/modules/useBreakpointVariants.js", 1905 | "object", 1906 | ], 1907 | Array [ 1908 | "build/modules/useBreakpointVariants.js.map", 1909 | "number", 1910 | ], 1911 | Array [ 1912 | "build/modules/useForceUpdate.d.ts", 1913 | "number", 1914 | ], 1915 | Array [ 1916 | "build/modules/useForceUpdate.d.ts.map", 1917 | "number", 1918 | ], 1919 | Array [ 1920 | "build/modules/useForceUpdate.js", 1921 | "object", 1922 | ], 1923 | Array [ 1924 | "build/modules/useForceUpdate.js.map", 1925 | "number", 1926 | ], 1927 | Array [ 1928 | "build/modules/useGamepad.d.ts", 1929 | "number", 1930 | ], 1931 | Array [ 1932 | "build/modules/useGamepad.d.ts.map", 1933 | "number", 1934 | ], 1935 | Array [ 1936 | "build/modules/useGamepad.js", 1937 | "object", 1938 | ], 1939 | Array [ 1940 | "build/modules/useGamepad.js.map", 1941 | "number", 1942 | ], 1943 | Array [ 1944 | "build/modules/useHotkey.d.ts", 1945 | "number", 1946 | ], 1947 | Array [ 1948 | "build/modules/useHotkey.d.ts.map", 1949 | "number", 1950 | ], 1951 | Array [ 1952 | "build/modules/useHotkey.js", 1953 | "object", 1954 | ], 1955 | Array [ 1956 | "build/modules/useHotkey.js.map", 1957 | "number", 1958 | ], 1959 | Array [ 1960 | "build/modules/useIsOnFramerCanvas.d.ts", 1961 | "number", 1962 | ], 1963 | Array [ 1964 | "build/modules/useIsOnFramerCanvas.d.ts.map", 1965 | "number", 1966 | ], 1967 | Array [ 1968 | "build/modules/useIsOnFramerCanvas.js", 1969 | "object", 1970 | ], 1971 | Array [ 1972 | "build/modules/useIsOnFramerCanvas.js.map", 1973 | "number", 1974 | ], 1975 | Array [ 1976 | "build/modules/useNavigate.d.ts", 1977 | "number", 1978 | ], 1979 | Array [ 1980 | "build/modules/useNavigate.d.ts.map", 1981 | "number", 1982 | ], 1983 | Array [ 1984 | "build/modules/useNavigate.js", 1985 | "object", 1986 | ], 1987 | Array [ 1988 | "build/modules/useNavigate.js.map", 1989 | "number", 1990 | ], 1991 | Array [ 1992 | "build/modules/useOnVariantChange.d.ts", 1993 | "number", 1994 | ], 1995 | Array [ 1996 | "build/modules/useOnVariantChange.d.ts.map", 1997 | "number", 1998 | ], 1999 | Array [ 2000 | "build/modules/useOnVariantChange.js", 2001 | "object", 2002 | ], 2003 | Array [ 2004 | "build/modules/useOnVariantChange.js.map", 2005 | "number", 2006 | ], 2007 | Array [ 2008 | "build/modules/useVariantState.d.ts", 2009 | "number", 2010 | ], 2011 | Array [ 2012 | "build/modules/useVariantState.d.ts.map", 2013 | "number", 2014 | ], 2015 | Array [ 2016 | "build/modules/useVariantState.js", 2017 | "object", 2018 | ], 2019 | Array [ 2020 | "build/modules/useVariantState.js.map", 2021 | "number", 2022 | ], 2023 | Array [ 2024 | "build/modules/withCSS.d.ts", 2025 | "number", 2026 | ], 2027 | Array [ 2028 | "build/modules/withCSS.d.ts.map", 2029 | "number", 2030 | ], 2031 | Array [ 2032 | "build/modules/withCSS.js", 2033 | "object", 2034 | ], 2035 | Array [ 2036 | "build/modules/withCSS.js.map", 2037 | "number", 2038 | ], 2039 | Array [ 2040 | "build/modules/withVariantAppearEffect.d.ts", 2041 | "number", 2042 | ], 2043 | Array [ 2044 | "build/modules/withVariantAppearEffect.d.ts.map", 2045 | "number", 2046 | ], 2047 | Array [ 2048 | "build/modules/withVariantAppearEffect.js", 2049 | "object", 2050 | ], 2051 | Array [ 2052 | "build/modules/withVariantAppearEffect.js.map", 2053 | "number", 2054 | ], 2055 | Array [ 2056 | "build/render/componentLoader/definition.d.ts", 2057 | "number", 2058 | ], 2059 | Array [ 2060 | "build/render/componentLoader/definition.d.ts.map", 2061 | "number", 2062 | ], 2063 | Array [ 2064 | "build/render/componentLoader/definition.js", 2065 | "object", 2066 | ], 2067 | Array [ 2068 | "build/render/componentLoader/definition.js.map", 2069 | "number", 2070 | ], 2071 | Array [ 2072 | "build/render/componentLoader/index.d.ts", 2073 | "number", 2074 | ], 2075 | Array [ 2076 | "build/render/componentLoader/index.d.ts.map", 2077 | "number", 2078 | ], 2079 | Array [ 2080 | "build/render/componentLoader/index.js", 2081 | "object", 2082 | ], 2083 | Array [ 2084 | "build/render/componentLoader/index.js.map", 2085 | "number", 2086 | ], 2087 | Array [ 2088 | "build/render/fonts/CustomFontSource.d.ts", 2089 | "number", 2090 | ], 2091 | Array [ 2092 | "build/render/fonts/CustomFontSource.d.ts.map", 2093 | "number", 2094 | ], 2095 | Array [ 2096 | "build/render/fonts/CustomFontSource.js", 2097 | "object", 2098 | ], 2099 | Array [ 2100 | "build/render/fonts/CustomFontSource.js.map", 2101 | "number", 2102 | ], 2103 | Array [ 2104 | "build/render/fonts/fonts.d.ts", 2105 | "number", 2106 | ], 2107 | Array [ 2108 | "build/render/fonts/fonts.d.ts.map", 2109 | "number", 2110 | ], 2111 | Array [ 2112 | "build/render/fonts/fonts.js", 2113 | "object", 2114 | ], 2115 | Array [ 2116 | "build/render/fonts/fonts.js.map", 2117 | "number", 2118 | ], 2119 | Array [ 2120 | "build/render/fonts/fontStore.d.ts", 2121 | "number", 2122 | ], 2123 | Array [ 2124 | "build/render/fonts/fontStore.d.ts.map", 2125 | "number", 2126 | ], 2127 | Array [ 2128 | "build/render/fonts/fontStore.js", 2129 | "object", 2130 | ], 2131 | Array [ 2132 | "build/render/fonts/fontStore.js.map", 2133 | "number", 2134 | ], 2135 | Array [ 2136 | "build/render/fonts/GoogleFontSource.d.ts", 2137 | "number", 2138 | ], 2139 | Array [ 2140 | "build/render/fonts/GoogleFontSource.d.ts.map", 2141 | "number", 2142 | ], 2143 | Array [ 2144 | "build/render/fonts/GoogleFontSource.js", 2145 | "object", 2146 | ], 2147 | Array [ 2148 | "build/render/fonts/GoogleFontSource.js.map", 2149 | "number", 2150 | ], 2151 | Array [ 2152 | "build/render/fonts/loadFont.d.ts", 2153 | "number", 2154 | ], 2155 | Array [ 2156 | "build/render/fonts/loadFont.d.ts.map", 2157 | "number", 2158 | ], 2159 | Array [ 2160 | "build/render/fonts/loadFont.js", 2161 | "object", 2162 | ], 2163 | Array [ 2164 | "build/render/fonts/loadFont.js.map", 2165 | "number", 2166 | ], 2167 | Array [ 2168 | "build/render/fonts/LocalFontSource.d.ts", 2169 | "number", 2170 | ], 2171 | Array [ 2172 | "build/render/fonts/LocalFontSource.d.ts.map", 2173 | "number", 2174 | ], 2175 | Array [ 2176 | "build/render/fonts/LocalFontSource.js", 2177 | "object", 2178 | ], 2179 | Array [ 2180 | "build/render/fonts/LocalFontSource.js.map", 2181 | "number", 2182 | ], 2183 | Array [ 2184 | "build/render/fonts/types.d.ts", 2185 | "number", 2186 | ], 2187 | Array [ 2188 | "build/render/fonts/types.d.ts.map", 2189 | "number", 2190 | ], 2191 | Array [ 2192 | "build/render/fonts/types.js", 2193 | "object", 2194 | ], 2195 | Array [ 2196 | "build/render/fonts/types.js.map", 2197 | "number", 2198 | ], 2199 | Array [ 2200 | "build/render/fonts/useFontLoadStatus.d.ts", 2201 | "number", 2202 | ], 2203 | Array [ 2204 | "build/render/fonts/useFontLoadStatus.d.ts.map", 2205 | "number", 2206 | ], 2207 | Array [ 2208 | "build/render/fonts/useFontLoadStatus.js", 2209 | "object", 2210 | ], 2211 | Array [ 2212 | "build/render/fonts/useFontLoadStatus.js.map", 2213 | "number", 2214 | ], 2215 | Array [ 2216 | "build/render/fonts/utils.d.ts", 2217 | "number", 2218 | ], 2219 | Array [ 2220 | "build/render/fonts/utils.d.ts.map", 2221 | "number", 2222 | ], 2223 | Array [ 2224 | "build/render/fonts/utils.js", 2225 | "object", 2226 | ], 2227 | Array [ 2228 | "build/render/fonts/utils.js.map", 2229 | "number", 2230 | ], 2231 | Array [ 2232 | "build/render/presentation/ComponentContainerContext.d.ts", 2233 | "number", 2234 | ], 2235 | Array [ 2236 | "build/render/presentation/ComponentContainerContext.d.ts.map", 2237 | "number", 2238 | ], 2239 | Array [ 2240 | "build/render/presentation/ComponentContainerContext.js", 2241 | "object", 2242 | ], 2243 | Array [ 2244 | "build/render/presentation/ComponentContainerContext.js.map", 2245 | "number", 2246 | ], 2247 | Array [ 2248 | "build/render/presentation/CustomProperties.d.ts", 2249 | "number", 2250 | ], 2251 | Array [ 2252 | "build/render/presentation/CustomProperties.d.ts.map", 2253 | "number", 2254 | ], 2255 | Array [ 2256 | "build/render/presentation/CustomProperties.js", 2257 | "object", 2258 | ], 2259 | Array [ 2260 | "build/render/presentation/CustomProperties.js.map", 2261 | "number", 2262 | ], 2263 | Array [ 2264 | "build/render/presentation/Frame/DeprecatedFrame.d.ts", 2265 | "number", 2266 | ], 2267 | Array [ 2268 | "build/render/presentation/Frame/DeprecatedFrame.d.ts.map", 2269 | "number", 2270 | ], 2271 | Array [ 2272 | "build/render/presentation/Frame/DeprecatedFrame.js", 2273 | "object", 2274 | ], 2275 | Array [ 2276 | "build/render/presentation/Frame/DeprecatedFrame.js.map", 2277 | "number", 2278 | ], 2279 | Array [ 2280 | "build/render/presentation/Frame/FrameWithMotion.d.ts", 2281 | "number", 2282 | ], 2283 | Array [ 2284 | "build/render/presentation/Frame/FrameWithMotion.d.ts.map", 2285 | "number", 2286 | ], 2287 | Array [ 2288 | "build/render/presentation/Frame/FrameWithMotion.js", 2289 | "object", 2290 | ], 2291 | Array [ 2292 | "build/render/presentation/Frame/FrameWithMotion.js.map", 2293 | "number", 2294 | ], 2295 | Array [ 2296 | "build/render/presentation/Frame/getStyleForFrameProps.d.ts", 2297 | "number", 2298 | ], 2299 | Array [ 2300 | "build/render/presentation/Frame/getStyleForFrameProps.d.ts.map", 2301 | "number", 2302 | ], 2303 | Array [ 2304 | "build/render/presentation/Frame/getStyleForFrameProps.js", 2305 | "object", 2306 | ], 2307 | Array [ 2308 | "build/render/presentation/Frame/getStyleForFrameProps.js.map", 2309 | "number", 2310 | ], 2311 | Array [ 2312 | "build/render/presentation/Frame/index.d.ts", 2313 | "number", 2314 | ], 2315 | Array [ 2316 | "build/render/presentation/Frame/index.d.ts.map", 2317 | "number", 2318 | ], 2319 | Array [ 2320 | "build/render/presentation/Frame/index.js", 2321 | "object", 2322 | ], 2323 | Array [ 2324 | "build/render/presentation/Frame/index.js.map", 2325 | "number", 2326 | ], 2327 | Array [ 2328 | "build/render/presentation/Frame/isDeprecatedFrameProps.d.ts", 2329 | "number", 2330 | ], 2331 | Array [ 2332 | "build/render/presentation/Frame/isDeprecatedFrameProps.d.ts.map", 2333 | "number", 2334 | ], 2335 | Array [ 2336 | "build/render/presentation/Frame/isDeprecatedFrameProps.js", 2337 | "object", 2338 | ], 2339 | Array [ 2340 | "build/render/presentation/Frame/isDeprecatedFrameProps.js.map", 2341 | "number", 2342 | ], 2343 | Array [ 2344 | "build/render/presentation/Frame/types.d.ts", 2345 | "number", 2346 | ], 2347 | Array [ 2348 | "build/render/presentation/Frame/types.d.ts.map", 2349 | "number", 2350 | ], 2351 | Array [ 2352 | "build/render/presentation/Frame/types.js", 2353 | "object", 2354 | ], 2355 | Array [ 2356 | "build/render/presentation/Frame/types.js.map", 2357 | "number", 2358 | ], 2359 | Array [ 2360 | "build/render/presentation/GradientElement.d.ts", 2361 | "number", 2362 | ], 2363 | Array [ 2364 | "build/render/presentation/GradientElement.d.ts.map", 2365 | "number", 2366 | ], 2367 | Array [ 2368 | "build/render/presentation/GradientElement.js", 2369 | "object", 2370 | ], 2371 | Array [ 2372 | "build/render/presentation/GradientElement.js.map", 2373 | "number", 2374 | ], 2375 | Array [ 2376 | "build/render/presentation/Image.d.ts", 2377 | "number", 2378 | ], 2379 | Array [ 2380 | "build/render/presentation/Image.d.ts.map", 2381 | "number", 2382 | ], 2383 | Array [ 2384 | "build/render/presentation/Image.js", 2385 | "object", 2386 | ], 2387 | Array [ 2388 | "build/render/presentation/Image.js.map", 2389 | "number", 2390 | ], 2391 | Array [ 2392 | "build/render/presentation/ImagePatternElement.d.ts", 2393 | "number", 2394 | ], 2395 | Array [ 2396 | "build/render/presentation/ImagePatternElement.d.ts.map", 2397 | "number", 2398 | ], 2399 | Array [ 2400 | "build/render/presentation/ImagePatternElement.js", 2401 | "object", 2402 | ], 2403 | Array [ 2404 | "build/render/presentation/ImagePatternElement.js.map", 2405 | "number", 2406 | ], 2407 | Array [ 2408 | "build/render/presentation/Layer.d.ts", 2409 | "number", 2410 | ], 2411 | Array [ 2412 | "build/render/presentation/Layer.d.ts.map", 2413 | "number", 2414 | ], 2415 | Array [ 2416 | "build/render/presentation/Layer.js", 2417 | "object", 2418 | ], 2419 | Array [ 2420 | "build/render/presentation/Layer.js.map", 2421 | "number", 2422 | ], 2423 | Array [ 2424 | "build/render/presentation/SVG.d.ts", 2425 | "number", 2426 | ], 2427 | Array [ 2428 | "build/render/presentation/SVG.d.ts.map", 2429 | "number", 2430 | ], 2431 | Array [ 2432 | "build/render/presentation/SVG.js", 2433 | "object", 2434 | ], 2435 | Array [ 2436 | "build/render/presentation/SVG.js.map", 2437 | "number", 2438 | ], 2439 | Array [ 2440 | "build/render/presentation/SVGRoot.d.ts", 2441 | "number", 2442 | ], 2443 | Array [ 2444 | "build/render/presentation/SVGRoot.d.ts.map", 2445 | "number", 2446 | ], 2447 | Array [ 2448 | "build/render/presentation/SVGRoot.js", 2449 | "object", 2450 | ], 2451 | Array [ 2452 | "build/render/presentation/SVGRoot.js.map", 2453 | "number", 2454 | ], 2455 | Array [ 2456 | "build/render/presentation/Text.d.ts", 2457 | "number", 2458 | ], 2459 | Array [ 2460 | "build/render/presentation/Text.d.ts.map", 2461 | "number", 2462 | ], 2463 | Array [ 2464 | "build/render/presentation/Text.js", 2465 | "object", 2466 | ], 2467 | Array [ 2468 | "build/render/presentation/Text.js.map", 2469 | "number", 2470 | ], 2471 | Array [ 2472 | "build/render/presentation/Vector.d.ts", 2473 | "number", 2474 | ], 2475 | Array [ 2476 | "build/render/presentation/Vector.d.ts.map", 2477 | "number", 2478 | ], 2479 | Array [ 2480 | "build/render/presentation/Vector.js", 2481 | "object", 2482 | ], 2483 | Array [ 2484 | "build/render/presentation/Vector.js.map", 2485 | "number", 2486 | ], 2487 | Array [ 2488 | "build/render/presentation/VectorGroup.d.ts", 2489 | "number", 2490 | ], 2491 | Array [ 2492 | "build/render/presentation/VectorGroup.d.ts.map", 2493 | "number", 2494 | ], 2495 | Array [ 2496 | "build/render/presentation/VectorGroup.js", 2497 | "object", 2498 | ], 2499 | Array [ 2500 | "build/render/presentation/VectorGroup.js.map", 2501 | "number", 2502 | ], 2503 | Array [ 2504 | "build/render/style/BackgroundImageComponent.d.ts", 2505 | "number", 2506 | ], 2507 | Array [ 2508 | "build/render/style/BackgroundImageComponent.d.ts.map", 2509 | "number", 2510 | ], 2511 | Array [ 2512 | "build/render/style/BackgroundImageComponent.js", 2513 | "object", 2514 | ], 2515 | Array [ 2516 | "build/render/style/BackgroundImageComponent.js.map", 2517 | "number", 2518 | ], 2519 | Array [ 2520 | "build/render/style/backgroundImageFromProps.d.ts", 2521 | "number", 2522 | ], 2523 | Array [ 2524 | "build/render/style/backgroundImageFromProps.d.ts.map", 2525 | "number", 2526 | ], 2527 | Array [ 2528 | "build/render/style/backgroundImageFromProps.js", 2529 | "object", 2530 | ], 2531 | Array [ 2532 | "build/render/style/backgroundImageFromProps.js.map", 2533 | "number", 2534 | ], 2535 | Array [ 2536 | "build/render/style/BorderComponent.d.ts", 2537 | "number", 2538 | ], 2539 | Array [ 2540 | "build/render/style/BorderComponent.d.ts.map", 2541 | "number", 2542 | ], 2543 | Array [ 2544 | "build/render/style/BorderComponent.js", 2545 | "object", 2546 | ], 2547 | Array [ 2548 | "build/render/style/BorderComponent.js.map", 2549 | "number", 2550 | ], 2551 | Array [ 2552 | "build/render/style/collectVisualStyleFromProps.d.ts", 2553 | "number", 2554 | ], 2555 | Array [ 2556 | "build/render/style/collectVisualStyleFromProps.d.ts.map", 2557 | "number", 2558 | ], 2559 | Array [ 2560 | "build/render/style/collectVisualStyleFromProps.js", 2561 | "object", 2562 | ], 2563 | Array [ 2564 | "build/render/style/collectVisualStyleFromProps.js.map", 2565 | "number", 2566 | ], 2567 | Array [ 2568 | "build/render/style/shadow.d.ts", 2569 | "number", 2570 | ], 2571 | Array [ 2572 | "build/render/style/shadow.d.ts.map", 2573 | "number", 2574 | ], 2575 | Array [ 2576 | "build/render/style/shadow.js", 2577 | "object", 2578 | ], 2579 | Array [ 2580 | "build/render/style/shadow.js.map", 2581 | "number", 2582 | ], 2583 | Array [ 2584 | "build/render/StyleSheetContext.d.ts", 2585 | "number", 2586 | ], 2587 | Array [ 2588 | "build/render/StyleSheetContext.d.ts.map", 2589 | "number", 2590 | ], 2591 | Array [ 2592 | "build/render/StyleSheetContext.js", 2593 | "object", 2594 | ], 2595 | Array [ 2596 | "build/render/StyleSheetContext.js.map", 2597 | "number", 2598 | ], 2599 | Array [ 2600 | "build/render/traits/BackdropFilters.d.ts", 2601 | "number", 2602 | ], 2603 | Array [ 2604 | "build/render/traits/BackdropFilters.d.ts.map", 2605 | "number", 2606 | ], 2607 | Array [ 2608 | "build/render/traits/BackdropFilters.js", 2609 | "object", 2610 | ], 2611 | Array [ 2612 | "build/render/traits/BackdropFilters.js.map", 2613 | "number", 2614 | ], 2615 | Array [ 2616 | "build/render/traits/Background.d.ts", 2617 | "number", 2618 | ], 2619 | Array [ 2620 | "build/render/traits/Background.d.ts.map", 2621 | "number", 2622 | ], 2623 | Array [ 2624 | "build/render/traits/Background.js", 2625 | "object", 2626 | ], 2627 | Array [ 2628 | "build/render/traits/Background.js.map", 2629 | "number", 2630 | ], 2631 | Array [ 2632 | "build/render/traits/Blending.d.ts", 2633 | "number", 2634 | ], 2635 | Array [ 2636 | "build/render/traits/Blending.d.ts.map", 2637 | "number", 2638 | ], 2639 | Array [ 2640 | "build/render/traits/Blending.js", 2641 | "object", 2642 | ], 2643 | Array [ 2644 | "build/render/traits/Blending.js.map", 2645 | "number", 2646 | ], 2647 | Array [ 2648 | "build/render/traits/Fill.d.ts", 2649 | "number", 2650 | ], 2651 | Array [ 2652 | "build/render/traits/Fill.d.ts.map", 2653 | "number", 2654 | ], 2655 | Array [ 2656 | "build/render/traits/Fill.js", 2657 | "object", 2658 | ], 2659 | Array [ 2660 | "build/render/traits/Fill.js.map", 2661 | "number", 2662 | ], 2663 | Array [ 2664 | "build/render/traits/Filters.d.ts", 2665 | "number", 2666 | ], 2667 | Array [ 2668 | "build/render/traits/Filters.d.ts.map", 2669 | "number", 2670 | ], 2671 | Array [ 2672 | "build/render/traits/Filters.js", 2673 | "object", 2674 | ], 2675 | Array [ 2676 | "build/render/traits/Filters.js.map", 2677 | "number", 2678 | ], 2679 | Array [ 2680 | "build/render/traits/FreeSpace.d.ts", 2681 | "number", 2682 | ], 2683 | Array [ 2684 | "build/render/traits/FreeSpace.d.ts.map", 2685 | "number", 2686 | ], 2687 | Array [ 2688 | "build/render/traits/FreeSpace.js", 2689 | "object", 2690 | ], 2691 | Array [ 2692 | "build/render/traits/FreeSpace.js.map", 2693 | "number", 2694 | ], 2695 | Array [ 2696 | "build/render/traits/Opacity.d.ts", 2697 | "number", 2698 | ], 2699 | Array [ 2700 | "build/render/traits/Opacity.d.ts.map", 2701 | "number", 2702 | ], 2703 | Array [ 2704 | "build/render/traits/Opacity.js", 2705 | "object", 2706 | ], 2707 | Array [ 2708 | "build/render/traits/Opacity.js.map", 2709 | "number", 2710 | ], 2711 | Array [ 2712 | "build/render/traits/Overflow.d.ts", 2713 | "number", 2714 | ], 2715 | Array [ 2716 | "build/render/traits/Overflow.d.ts.map", 2717 | "number", 2718 | ], 2719 | Array [ 2720 | "build/render/traits/Overflow.js", 2721 | "object", 2722 | ], 2723 | Array [ 2724 | "build/render/traits/Overflow.js.map", 2725 | "number", 2726 | ], 2727 | Array [ 2728 | "build/render/traits/Path.d.ts", 2729 | "number", 2730 | ], 2731 | Array [ 2732 | "build/render/traits/Path.d.ts.map", 2733 | "number", 2734 | ], 2735 | Array [ 2736 | "build/render/traits/Path.js", 2737 | "object", 2738 | ], 2739 | Array [ 2740 | "build/render/traits/Path.js.map", 2741 | "number", 2742 | ], 2743 | Array [ 2744 | "build/render/traits/Radius.d.ts", 2745 | "number", 2746 | ], 2747 | Array [ 2748 | "build/render/traits/Radius.d.ts.map", 2749 | "number", 2750 | ], 2751 | Array [ 2752 | "build/render/traits/Radius.js", 2753 | "object", 2754 | ], 2755 | Array [ 2756 | "build/render/traits/Radius.js.map", 2757 | "number", 2758 | ], 2759 | Array [ 2760 | "build/render/traits/Shadow.d.ts", 2761 | "number", 2762 | ], 2763 | Array [ 2764 | "build/render/traits/Shadow.d.ts.map", 2765 | "number", 2766 | ], 2767 | Array [ 2768 | "build/render/traits/Shadow.js", 2769 | "object", 2770 | ], 2771 | Array [ 2772 | "build/render/traits/Shadow.js.map", 2773 | "number", 2774 | ], 2775 | Array [ 2776 | "build/render/traits/TextColor.d.ts", 2777 | "number", 2778 | ], 2779 | Array [ 2780 | "build/render/traits/TextColor.d.ts.map", 2781 | "number", 2782 | ], 2783 | Array [ 2784 | "build/render/traits/TextColor.js", 2785 | "object", 2786 | ], 2787 | Array [ 2788 | "build/render/traits/TextColor.js.map", 2789 | "number", 2790 | ], 2791 | Array [ 2792 | "build/render/traits/Transform.d.ts", 2793 | "number", 2794 | ], 2795 | Array [ 2796 | "build/render/traits/Transform.d.ts.map", 2797 | "number", 2798 | ], 2799 | Array [ 2800 | "build/render/traits/Transform.js", 2801 | "object", 2802 | ], 2803 | Array [ 2804 | "build/render/traits/Transform.js.map", 2805 | "number", 2806 | ], 2807 | Array [ 2808 | "build/render/types/Action.d.ts", 2809 | "number", 2810 | ], 2811 | Array [ 2812 | "build/render/types/Action.d.ts.map", 2813 | "number", 2814 | ], 2815 | Array [ 2816 | "build/render/types/Action.js", 2817 | "object", 2818 | ], 2819 | Array [ 2820 | "build/render/types/Action.js.map", 2821 | "number", 2822 | ], 2823 | Array [ 2824 | "build/render/types/BackgroundImage.d.ts", 2825 | "number", 2826 | ], 2827 | Array [ 2828 | "build/render/types/BackgroundImage.d.ts.map", 2829 | "number", 2830 | ], 2831 | Array [ 2832 | "build/render/types/BackgroundImage.js", 2833 | "object", 2834 | ], 2835 | Array [ 2836 | "build/render/types/BackgroundImage.js.map", 2837 | "number", 2838 | ], 2839 | Array [ 2840 | "build/render/types/Color/Color.d.ts", 2841 | "number", 2842 | ], 2843 | Array [ 2844 | "build/render/types/Color/Color.d.ts.map", 2845 | "number", 2846 | ], 2847 | Array [ 2848 | "build/render/types/Color/Color.js", 2849 | "object", 2850 | ], 2851 | Array [ 2852 | "build/render/types/Color/Color.js.map", 2853 | "number", 2854 | ], 2855 | Array [ 2856 | "build/render/types/Color/ConvertColor.d.ts", 2857 | "number", 2858 | ], 2859 | Array [ 2860 | "build/render/types/Color/ConvertColor.d.ts.map", 2861 | "number", 2862 | ], 2863 | Array [ 2864 | "build/render/types/Color/ConvertColor.js", 2865 | "object", 2866 | ], 2867 | Array [ 2868 | "build/render/types/Color/ConvertColor.js.map", 2869 | "number", 2870 | ], 2871 | Array [ 2872 | "build/render/types/Color/converters.d.ts", 2873 | "number", 2874 | ], 2875 | Array [ 2876 | "build/render/types/Color/converters.d.ts.map", 2877 | "number", 2878 | ], 2879 | Array [ 2880 | "build/render/types/Color/converters.js", 2881 | "object", 2882 | ], 2883 | Array [ 2884 | "build/render/types/Color/converters.js.map", 2885 | "number", 2886 | ], 2887 | Array [ 2888 | "build/render/types/Color/CSSNames.d.ts", 2889 | "number", 2890 | ], 2891 | Array [ 2892 | "build/render/types/Color/CSSNames.d.ts.map", 2893 | "number", 2894 | ], 2895 | Array [ 2896 | "build/render/types/Color/CSSNames.js", 2897 | "object", 2898 | ], 2899 | Array [ 2900 | "build/render/types/Color/CSSNames.js.map", 2901 | "number", 2902 | ], 2903 | Array [ 2904 | "build/render/types/Color/index.d.ts", 2905 | "number", 2906 | ], 2907 | Array [ 2908 | "build/render/types/Color/index.d.ts.map", 2909 | "number", 2910 | ], 2911 | Array [ 2912 | "build/render/types/Color/index.js", 2913 | "object", 2914 | ], 2915 | Array [ 2916 | "build/render/types/Color/index.js.map", 2917 | "number", 2918 | ], 2919 | Array [ 2920 | "build/render/types/Color/types.d.ts", 2921 | "number", 2922 | ], 2923 | Array [ 2924 | "build/render/types/Color/types.d.ts.map", 2925 | "number", 2926 | ], 2927 | Array [ 2928 | "build/render/types/Color/types.js", 2929 | "object", 2930 | ], 2931 | Array [ 2932 | "build/render/types/Color/types.js.map", 2933 | "number", 2934 | ], 2935 | Array [ 2936 | "build/render/types/Color/Utils.d.ts", 2937 | "number", 2938 | ], 2939 | Array [ 2940 | "build/render/types/Color/Utils.d.ts.map", 2941 | "number", 2942 | ], 2943 | Array [ 2944 | "build/render/types/Color/Utils.js", 2945 | "object", 2946 | ], 2947 | Array [ 2948 | "build/render/types/Color/Utils.js.map", 2949 | "number", 2950 | ], 2951 | Array [ 2952 | "build/render/types/Constraints.d.ts", 2953 | "number", 2954 | ], 2955 | Array [ 2956 | "build/render/types/Constraints.d.ts.map", 2957 | "number", 2958 | ], 2959 | Array [ 2960 | "build/render/types/Constraints.js", 2961 | "object", 2962 | ], 2963 | Array [ 2964 | "build/render/types/Constraints.js.map", 2965 | "number", 2966 | ], 2967 | Array [ 2968 | "build/render/types/Gradient.d.ts", 2969 | "number", 2970 | ], 2971 | Array [ 2972 | "build/render/types/Gradient.d.ts.map", 2973 | "number", 2974 | ], 2975 | Array [ 2976 | "build/render/types/Gradient.js", 2977 | "object", 2978 | ], 2979 | Array [ 2980 | "build/render/types/Gradient.js.map", 2981 | "number", 2982 | ], 2983 | Array [ 2984 | "build/render/types/GradientColorStop.d.ts", 2985 | "number", 2986 | ], 2987 | Array [ 2988 | "build/render/types/GradientColorStop.d.ts.map", 2989 | "number", 2990 | ], 2991 | Array [ 2992 | "build/render/types/GradientColorStop.js", 2993 | "object", 2994 | ], 2995 | Array [ 2996 | "build/render/types/GradientColorStop.js.map", 2997 | "number", 2998 | ], 2999 | Array [ 3000 | "build/render/types/LinearGradient.d.ts", 3001 | "number", 3002 | ], 3003 | Array [ 3004 | "build/render/types/LinearGradient.d.ts.map", 3005 | "number", 3006 | ], 3007 | Array [ 3008 | "build/render/types/LinearGradient.js", 3009 | "object", 3010 | ], 3011 | Array [ 3012 | "build/render/types/LinearGradient.js.map", 3013 | "number", 3014 | ], 3015 | Array [ 3016 | "build/render/types/MultiStopGradient.d.ts", 3017 | "number", 3018 | ], 3019 | Array [ 3020 | "build/render/types/MultiStopGradient.d.ts.map", 3021 | "number", 3022 | ], 3023 | Array [ 3024 | "build/render/types/MultiStopGradient.js", 3025 | "object", 3026 | ], 3027 | Array [ 3028 | "build/render/types/MultiStopGradient.js.map", 3029 | "number", 3030 | ], 3031 | Array [ 3032 | "build/render/types/NewConstraints.d.ts", 3033 | "number", 3034 | ], 3035 | Array [ 3036 | "build/render/types/NewConstraints.d.ts.map", 3037 | "number", 3038 | ], 3039 | Array [ 3040 | "build/render/types/NewConstraints.js", 3041 | "object", 3042 | ], 3043 | Array [ 3044 | "build/render/types/NewConstraints.js.map", 3045 | "number", 3046 | ], 3047 | Array [ 3048 | "build/render/types/PathSegment.d.ts", 3049 | "number", 3050 | ], 3051 | Array [ 3052 | "build/render/types/PathSegment.d.ts.map", 3053 | "number", 3054 | ], 3055 | Array [ 3056 | "build/render/types/PathSegment.js", 3057 | "object", 3058 | ], 3059 | Array [ 3060 | "build/render/types/PathSegment.js.map", 3061 | "number", 3062 | ], 3063 | Array [ 3064 | "build/render/types/Point.d.ts", 3065 | "number", 3066 | ], 3067 | Array [ 3068 | "build/render/types/Point.d.ts.map", 3069 | "number", 3070 | ], 3071 | Array [ 3072 | "build/render/types/Point.js", 3073 | "object", 3074 | ], 3075 | Array [ 3076 | "build/render/types/Point.js.map", 3077 | "number", 3078 | ], 3079 | Array [ 3080 | "build/render/types/PropertyControls.d.ts", 3081 | "number", 3082 | ], 3083 | Array [ 3084 | "build/render/types/PropertyControls.d.ts.map", 3085 | "number", 3086 | ], 3087 | Array [ 3088 | "build/render/types/PropertyControls.js", 3089 | "object", 3090 | ], 3091 | Array [ 3092 | "build/render/types/PropertyControls.js.map", 3093 | "number", 3094 | ], 3095 | Array [ 3096 | "build/render/types/RadialGradient.d.ts", 3097 | "number", 3098 | ], 3099 | Array [ 3100 | "build/render/types/RadialGradient.d.ts.map", 3101 | "number", 3102 | ], 3103 | Array [ 3104 | "build/render/types/RadialGradient.js", 3105 | "object", 3106 | ], 3107 | Array [ 3108 | "build/render/types/RadialGradient.js.map", 3109 | "number", 3110 | ], 3111 | Array [ 3112 | "build/render/types/Rect.d.ts", 3113 | "number", 3114 | ], 3115 | Array [ 3116 | "build/render/types/Rect.d.ts.map", 3117 | "number", 3118 | ], 3119 | Array [ 3120 | "build/render/types/Rect.js", 3121 | "object", 3122 | ], 3123 | Array [ 3124 | "build/render/types/Rect.js.map", 3125 | "number", 3126 | ], 3127 | Array [ 3128 | "build/render/types/RenderEnvironment.d.ts", 3129 | "number", 3130 | ], 3131 | Array [ 3132 | "build/render/types/RenderEnvironment.d.ts.map", 3133 | "number", 3134 | ], 3135 | Array [ 3136 | "build/render/types/RenderEnvironment.js", 3137 | "object", 3138 | ], 3139 | Array [ 3140 | "build/render/types/RenderEnvironment.js.map", 3141 | "number", 3142 | ], 3143 | Array [ 3144 | "build/render/types/Shadow.d.ts", 3145 | "number", 3146 | ], 3147 | Array [ 3148 | "build/render/types/Shadow.d.ts.map", 3149 | "number", 3150 | ], 3151 | Array [ 3152 | "build/render/types/Shadow.js", 3153 | "object", 3154 | ], 3155 | Array [ 3156 | "build/render/types/Shadow.js.map", 3157 | "number", 3158 | ], 3159 | Array [ 3160 | "build/render/types/SimpleGradient.d.ts", 3161 | "number", 3162 | ], 3163 | Array [ 3164 | "build/render/types/SimpleGradient.d.ts.map", 3165 | "number", 3166 | ], 3167 | Array [ 3168 | "build/render/types/SimpleGradient.js", 3169 | "object", 3170 | ], 3171 | Array [ 3172 | "build/render/types/SimpleGradient.js.map", 3173 | "number", 3174 | ], 3175 | Array [ 3176 | "build/render/types/Size.d.ts", 3177 | "number", 3178 | ], 3179 | Array [ 3180 | "build/render/types/Size.d.ts.map", 3181 | "number", 3182 | ], 3183 | Array [ 3184 | "build/render/types/Size.js", 3185 | "object", 3186 | ], 3187 | Array [ 3188 | "build/render/types/Size.js.map", 3189 | "number", 3190 | ], 3191 | Array [ 3192 | "build/render/types/Stroke.d.ts", 3193 | "number", 3194 | ], 3195 | Array [ 3196 | "build/render/types/Stroke.d.ts.map", 3197 | "number", 3198 | ], 3199 | Array [ 3200 | "build/render/types/Stroke.js", 3201 | "object", 3202 | ], 3203 | Array [ 3204 | "build/render/types/Stroke.js.map", 3205 | "number", 3206 | ], 3207 | Array [ 3208 | "build/render/types/StrokeAlignment.d.ts", 3209 | "number", 3210 | ], 3211 | Array [ 3212 | "build/render/types/StrokeAlignment.d.ts.map", 3213 | "number", 3214 | ], 3215 | Array [ 3216 | "build/render/types/StrokeAlignment.js", 3217 | "object", 3218 | ], 3219 | Array [ 3220 | "build/render/types/StrokeAlignment.js.map", 3221 | "number", 3222 | ], 3223 | Array [ 3224 | "build/render/types/svgElementAttributeDefaults.d.ts", 3225 | "number", 3226 | ], 3227 | Array [ 3228 | "build/render/types/svgElementAttributeDefaults.d.ts.map", 3229 | "number", 3230 | ], 3231 | Array [ 3232 | "build/render/types/svgElementAttributeDefaults.js", 3233 | "object", 3234 | ], 3235 | Array [ 3236 | "build/render/types/svgElementAttributeDefaults.js.map", 3237 | "number", 3238 | ], 3239 | Array [ 3240 | "build/render/types/TransformValues.d.ts", 3241 | "number", 3242 | ], 3243 | Array [ 3244 | "build/render/types/TransformValues.d.ts.map", 3245 | "number", 3246 | ], 3247 | Array [ 3248 | "build/render/types/TransformValues.js", 3249 | "object", 3250 | ], 3251 | Array [ 3252 | "build/render/types/TransformValues.js.map", 3253 | "number", 3254 | ], 3255 | Array [ 3256 | "build/render/utils/createTransformValues.d.ts", 3257 | "number", 3258 | ], 3259 | Array [ 3260 | "build/render/utils/createTransformValues.d.ts.map", 3261 | "number", 3262 | ], 3263 | Array [ 3264 | "build/render/utils/createTransformValues.js", 3265 | "object", 3266 | ], 3267 | Array [ 3268 | "build/render/utils/createTransformValues.js.map", 3269 | "number", 3270 | ], 3271 | Array [ 3272 | "build/render/utils/debounce.d.ts", 3273 | "number", 3274 | ], 3275 | Array [ 3276 | "build/render/utils/debounce.d.ts.map", 3277 | "number", 3278 | ], 3279 | Array [ 3280 | "build/render/utils/debounce.js", 3281 | "object", 3282 | ], 3283 | Array [ 3284 | "build/render/utils/debounce.js.map", 3285 | "number", 3286 | ], 3287 | Array [ 3288 | "build/render/utils/elementPropertiesForGradient.d.ts", 3289 | "number", 3290 | ], 3291 | Array [ 3292 | "build/render/utils/elementPropertiesForGradient.d.ts.map", 3293 | "number", 3294 | ], 3295 | Array [ 3296 | "build/render/utils/elementPropertiesForGradient.js", 3297 | "object", 3298 | ], 3299 | Array [ 3300 | "build/render/utils/elementPropertiesForGradient.js.map", 3301 | "number", 3302 | ], 3303 | Array [ 3304 | "build/render/utils/extractStyleFromProps.d.ts", 3305 | "number", 3306 | ], 3307 | Array [ 3308 | "build/render/utils/extractStyleFromProps.d.ts.map", 3309 | "number", 3310 | ], 3311 | Array [ 3312 | "build/render/utils/extractStyleFromProps.js", 3313 | "object", 3314 | ], 3315 | Array [ 3316 | "build/render/utils/extractStyleFromProps.js.map", 3317 | "number", 3318 | ], 3319 | Array [ 3320 | "build/render/utils/filtersForNode.d.ts", 3321 | "number", 3322 | ], 3323 | Array [ 3324 | "build/render/utils/filtersForNode.d.ts.map", 3325 | "number", 3326 | ], 3327 | Array [ 3328 | "build/render/utils/filtersForNode.js", 3329 | "object", 3330 | ], 3331 | Array [ 3332 | "build/render/utils/filtersForNode.js.map", 3333 | "number", 3334 | ], 3335 | Array [ 3336 | "build/render/utils/getMeasurableCodeComponentChildren.d.ts", 3337 | "number", 3338 | ], 3339 | Array [ 3340 | "build/render/utils/getMeasurableCodeComponentChildren.d.ts.map", 3341 | "number", 3342 | ], 3343 | Array [ 3344 | "build/render/utils/getMeasurableCodeComponentChildren.js", 3345 | "object", 3346 | ], 3347 | Array [ 3348 | "build/render/utils/getMeasurableCodeComponentChildren.js.map", 3349 | "number", 3350 | ], 3351 | Array [ 3352 | "build/render/utils/gradientColorStops.d.ts", 3353 | "number", 3354 | ], 3355 | Array [ 3356 | "build/render/utils/gradientColorStops.d.ts.map", 3357 | "number", 3358 | ], 3359 | Array [ 3360 | "build/render/utils/gradientColorStops.js", 3361 | "object", 3362 | ], 3363 | Array [ 3364 | "build/render/utils/gradientColorStops.js.map", 3365 | "number", 3366 | ], 3367 | Array [ 3368 | "build/render/utils/imagePatternPropsForFill.d.ts", 3369 | "number", 3370 | ], 3371 | Array [ 3372 | "build/render/utils/imagePatternPropsForFill.d.ts.map", 3373 | "number", 3374 | ], 3375 | Array [ 3376 | "build/render/utils/imagePatternPropsForFill.js", 3377 | "object", 3378 | ], 3379 | Array [ 3380 | "build/render/utils/imagePatternPropsForFill.js.map", 3381 | "number", 3382 | ], 3383 | Array [ 3384 | "build/render/utils/imageRendering.d.ts", 3385 | "number", 3386 | ], 3387 | Array [ 3388 | "build/render/utils/imageRendering.d.ts.map", 3389 | "number", 3390 | ], 3391 | Array [ 3392 | "build/render/utils/imageRendering.js", 3393 | "object", 3394 | ], 3395 | Array [ 3396 | "build/render/utils/imageRendering.js.map", 3397 | "number", 3398 | ], 3399 | Array [ 3400 | "build/render/utils/imageUrlForAsset.d.ts", 3401 | "number", 3402 | ], 3403 | Array [ 3404 | "build/render/utils/imageUrlForAsset.d.ts.map", 3405 | "number", 3406 | ], 3407 | Array [ 3408 | "build/render/utils/imageUrlForAsset.js", 3409 | "object", 3410 | ], 3411 | Array [ 3412 | "build/render/utils/imageUrlForAsset.js.map", 3413 | "number", 3414 | ], 3415 | Array [ 3416 | "build/render/utils/injectComponentCSSRules.d.ts", 3417 | "number", 3418 | ], 3419 | Array [ 3420 | "build/render/utils/injectComponentCSSRules.d.ts.map", 3421 | "number", 3422 | ], 3423 | Array [ 3424 | "build/render/utils/injectComponentCSSRules.js", 3425 | "object", 3426 | ], 3427 | Array [ 3428 | "build/render/utils/injectComponentCSSRules.js.map", 3429 | "number", 3430 | ], 3431 | Array [ 3432 | "build/render/utils/isEqual.d.ts", 3433 | "number", 3434 | ], 3435 | Array [ 3436 | "build/render/utils/isEqual.d.ts.map", 3437 | "number", 3438 | ], 3439 | Array [ 3440 | "build/render/utils/isEqual.js", 3441 | "object", 3442 | ], 3443 | Array [ 3444 | "build/render/utils/isEqual.js.map", 3445 | "number", 3446 | ], 3447 | Array [ 3448 | "build/render/utils/isFiniteNumber.d.ts", 3449 | "number", 3450 | ], 3451 | Array [ 3452 | "build/render/utils/isFiniteNumber.d.ts.map", 3453 | "number", 3454 | ], 3455 | Array [ 3456 | "build/render/utils/isFiniteNumber.js", 3457 | "object", 3458 | ], 3459 | Array [ 3460 | "build/render/utils/isFiniteNumber.js.map", 3461 | "number", 3462 | ], 3463 | Array [ 3464 | "build/render/utils/isMotionValue.d.ts", 3465 | "number", 3466 | ], 3467 | Array [ 3468 | "build/render/utils/isMotionValue.d.ts.map", 3469 | "number", 3470 | ], 3471 | Array [ 3472 | "build/render/utils/isMotionValue.js", 3473 | "object", 3474 | ], 3475 | Array [ 3476 | "build/render/utils/isMotionValue.js.map", 3477 | "number", 3478 | ], 3479 | Array [ 3480 | "build/render/utils/isShallowEqualArray.d.ts", 3481 | "number", 3482 | ], 3483 | Array [ 3484 | "build/render/utils/isShallowEqualArray.d.ts.map", 3485 | "number", 3486 | ], 3487 | Array [ 3488 | "build/render/utils/isShallowEqualArray.js", 3489 | "object", 3490 | ], 3491 | Array [ 3492 | "build/render/utils/isShallowEqualArray.js.map", 3493 | "number", 3494 | ], 3495 | Array [ 3496 | "build/render/utils/layoutHintDataPropsForCenter.d.ts", 3497 | "number", 3498 | ], 3499 | Array [ 3500 | "build/render/utils/layoutHintDataPropsForCenter.d.ts.map", 3501 | "number", 3502 | ], 3503 | Array [ 3504 | "build/render/utils/layoutHintDataPropsForCenter.js", 3505 | "object", 3506 | ], 3507 | Array [ 3508 | "build/render/utils/layoutHintDataPropsForCenter.js.map", 3509 | "number", 3510 | ], 3511 | Array [ 3512 | "build/render/utils/nodeIdFromString.d.ts", 3513 | "number", 3514 | ], 3515 | Array [ 3516 | "build/render/utils/nodeIdFromString.d.ts.map", 3517 | "number", 3518 | ], 3519 | Array [ 3520 | "build/render/utils/nodeIdFromString.js", 3521 | "object", 3522 | ], 3523 | Array [ 3524 | "build/render/utils/nodeIdFromString.js.map", 3525 | "number", 3526 | ], 3527 | Array [ 3528 | "build/render/utils/processOverrideForwarding.d.ts", 3529 | "number", 3530 | ], 3531 | Array [ 3532 | "build/render/utils/processOverrideForwarding.d.ts.map", 3533 | "number", 3534 | ], 3535 | Array [ 3536 | "build/render/utils/processOverrideForwarding.js", 3537 | "object", 3538 | ], 3539 | Array [ 3540 | "build/render/utils/processOverrideForwarding.js.map", 3541 | "number", 3542 | ], 3543 | Array [ 3544 | "build/render/utils/roundedNumber.d.ts", 3545 | "number", 3546 | ], 3547 | Array [ 3548 | "build/render/utils/roundedNumber.d.ts.map", 3549 | "number", 3550 | ], 3551 | Array [ 3552 | "build/render/utils/roundedNumber.js", 3553 | "object", 3554 | ], 3555 | Array [ 3556 | "build/render/utils/roundedNumber.js.map", 3557 | "number", 3558 | ], 3559 | Array [ 3560 | "build/render/utils/setDocumentStyles.d.ts", 3561 | "number", 3562 | ], 3563 | Array [ 3564 | "build/render/utils/setDocumentStyles.d.ts.map", 3565 | "number", 3566 | ], 3567 | Array [ 3568 | "build/render/utils/setDocumentStyles.js", 3569 | "object", 3570 | ], 3571 | Array [ 3572 | "build/render/utils/setDocumentStyles.js.map", 3573 | "number", 3574 | ], 3575 | Array [ 3576 | "build/render/utils/setLayerBacked.d.ts", 3577 | "number", 3578 | ], 3579 | Array [ 3580 | "build/render/utils/setLayerBacked.d.ts.map", 3581 | "number", 3582 | ], 3583 | Array [ 3584 | "build/render/utils/setLayerBacked.js", 3585 | "object", 3586 | ], 3587 | Array [ 3588 | "build/render/utils/setLayerBacked.js.map", 3589 | "number", 3590 | ], 3591 | Array [ 3592 | "build/render/utils/transformCustomValues.d.ts", 3593 | "number", 3594 | ], 3595 | Array [ 3596 | "build/render/utils/transformCustomValues.d.ts.map", 3597 | "number", 3598 | ], 3599 | Array [ 3600 | "build/render/utils/transformCustomValues.js", 3601 | "object", 3602 | ], 3603 | Array [ 3604 | "build/render/utils/transformCustomValues.js.map", 3605 | "number", 3606 | ], 3607 | Array [ 3608 | "build/render/utils/transformString.d.ts", 3609 | "number", 3610 | ], 3611 | Array [ 3612 | "build/render/utils/transformString.d.ts.map", 3613 | "number", 3614 | ], 3615 | Array [ 3616 | "build/render/utils/transformString.js", 3617 | "object", 3618 | ], 3619 | Array [ 3620 | "build/render/utils/transformString.js.map", 3621 | "number", 3622 | ], 3623 | Array [ 3624 | "build/render/utils/transformTemplate.d.ts", 3625 | "number", 3626 | ], 3627 | Array [ 3628 | "build/render/utils/transformTemplate.d.ts.map", 3629 | "number", 3630 | ], 3631 | Array [ 3632 | "build/render/utils/transformTemplate.js", 3633 | "object", 3634 | ], 3635 | Array [ 3636 | "build/render/utils/transformTemplate.js.map", 3637 | "number", 3638 | ], 3639 | Array [ 3640 | "build/render/utils/useLayoutId.d.ts", 3641 | "number", 3642 | ], 3643 | Array [ 3644 | "build/render/utils/useLayoutId.d.ts.map", 3645 | "number", 3646 | ], 3647 | Array [ 3648 | "build/render/utils/useLayoutId.js", 3649 | "object", 3650 | ], 3651 | Array [ 3652 | "build/render/utils/useLayoutId.js.map", 3653 | "number", 3654 | ], 3655 | Array [ 3656 | "build/render/utils/useMeasureLayout.d.ts", 3657 | "number", 3658 | ], 3659 | Array [ 3660 | "build/render/utils/useMeasureLayout.d.ts.map", 3661 | "number", 3662 | ], 3663 | Array [ 3664 | "build/render/utils/useMeasureLayout.js", 3665 | "object", 3666 | ], 3667 | Array [ 3668 | "build/render/utils/useMeasureLayout.js.map", 3669 | "number", 3670 | ], 3671 | Array [ 3672 | "build/render/utils/useWebkitFixes.d.ts", 3673 | "number", 3674 | ], 3675 | Array [ 3676 | "build/render/utils/useWebkitFixes.d.ts.map", 3677 | "number", 3678 | ], 3679 | Array [ 3680 | "build/render/utils/useWebkitFixes.js", 3681 | "object", 3682 | ], 3683 | Array [ 3684 | "build/render/utils/useWebkitFixes.js.map", 3685 | "number", 3686 | ], 3687 | Array [ 3688 | "build/render/WindowContext.d.ts", 3689 | "number", 3690 | ], 3691 | Array [ 3692 | "build/render/WindowContext.d.ts.map", 3693 | "number", 3694 | ], 3695 | Array [ 3696 | "build/render/WindowContext.js", 3697 | "object", 3698 | ], 3699 | Array [ 3700 | "build/render/WindowContext.js.map", 3701 | "number", 3702 | ], 3703 | Array [ 3704 | "build/useIsomorphicLayoutEffect.d.ts", 3705 | "number", 3706 | ], 3707 | Array [ 3708 | "build/useIsomorphicLayoutEffect.d.ts.map", 3709 | "number", 3710 | ], 3711 | Array [ 3712 | "build/useIsomorphicLayoutEffect.js", 3713 | "object", 3714 | ], 3715 | Array [ 3716 | "build/useIsomorphicLayoutEffect.js.map", 3717 | "number", 3718 | ], 3719 | Array [ 3720 | "build/utils/addFonts.d.ts", 3721 | "number", 3722 | ], 3723 | Array [ 3724 | "build/utils/addFonts.d.ts.map", 3725 | "number", 3726 | ], 3727 | Array [ 3728 | "build/utils/addFonts.js", 3729 | "object", 3730 | ], 3731 | Array [ 3732 | "build/utils/addFonts.js.map", 3733 | "number", 3734 | ], 3735 | Array [ 3736 | "build/utils/addPropertyControls.d.ts", 3737 | "number", 3738 | ], 3739 | Array [ 3740 | "build/utils/addPropertyControls.d.ts.map", 3741 | "number", 3742 | ], 3743 | Array [ 3744 | "build/utils/addPropertyControls.js", 3745 | "object", 3746 | ], 3747 | Array [ 3748 | "build/utils/addPropertyControls.js.map", 3749 | "number", 3750 | ], 3751 | Array [ 3752 | "build/utils/assert.d.ts", 3753 | "number", 3754 | ], 3755 | Array [ 3756 | "build/utils/assert.d.ts.map", 3757 | "number", 3758 | ], 3759 | Array [ 3760 | "build/utils/assert.js", 3761 | "object", 3762 | ], 3763 | Array [ 3764 | "build/utils/assert.js.map", 3765 | "number", 3766 | ], 3767 | Array [ 3768 | "build/utils/deprecation.d.ts", 3769 | "number", 3770 | ], 3771 | Array [ 3772 | "build/utils/deprecation.d.ts.map", 3773 | "number", 3774 | ], 3775 | Array [ 3776 | "build/utils/deprecation.js", 3777 | "object", 3778 | ], 3779 | Array [ 3780 | "build/utils/deprecation.js.map", 3781 | "number", 3782 | ], 3783 | Array [ 3784 | "build/utils/environment.d.ts", 3785 | "number", 3786 | ], 3787 | Array [ 3788 | "build/utils/environment.d.ts.map", 3789 | "number", 3790 | ], 3791 | Array [ 3792 | "build/utils/environment.js", 3793 | "object", 3794 | ], 3795 | Array [ 3796 | "build/utils/environment.js.map", 3797 | "number", 3798 | ], 3799 | Array [ 3800 | "build/utils/events.d.ts", 3801 | "number", 3802 | ], 3803 | Array [ 3804 | "build/utils/events.d.ts.map", 3805 | "number", 3806 | ], 3807 | Array [ 3808 | "build/utils/events.js", 3809 | "object", 3810 | ], 3811 | Array [ 3812 | "build/utils/events.js.map", 3813 | "number", 3814 | ], 3815 | Array [ 3816 | "build/utils/internalId.d.ts", 3817 | "number", 3818 | ], 3819 | Array [ 3820 | "build/utils/internalId.d.ts.map", 3821 | "number", 3822 | ], 3823 | Array [ 3824 | "build/utils/internalId.js", 3825 | "object", 3826 | ], 3827 | Array [ 3828 | "build/utils/internalId.js.map", 3829 | "number", 3830 | ], 3831 | Array [ 3832 | "build/utils/isPropValid.d.ts", 3833 | "number", 3834 | ], 3835 | Array [ 3836 | "build/utils/isPropValid.d.ts.map", 3837 | "number", 3838 | ], 3839 | Array [ 3840 | "build/utils/isPropValid.js", 3841 | "object", 3842 | ], 3843 | Array [ 3844 | "build/utils/isPropValid.js.map", 3845 | "number", 3846 | ], 3847 | Array [ 3848 | "build/utils/math.d.ts", 3849 | "number", 3850 | ], 3851 | Array [ 3852 | "build/utils/math.d.ts.map", 3853 | "number", 3854 | ], 3855 | Array [ 3856 | "build/utils/math.js", 3857 | "object", 3858 | ], 3859 | Array [ 3860 | "build/utils/math.js.map", 3861 | "number", 3862 | ], 3863 | Array [ 3864 | "build/utils/memoize.d.ts", 3865 | "number", 3866 | ], 3867 | Array [ 3868 | "build/utils/memoize.d.ts.map", 3869 | "number", 3870 | ], 3871 | Array [ 3872 | "build/utils/memoize.js", 3873 | "object", 3874 | ], 3875 | Array [ 3876 | "build/utils/memoize.js.map", 3877 | "number", 3878 | ], 3879 | Array [ 3880 | "build/utils/runtimeInjection.d.ts", 3881 | "number", 3882 | ], 3883 | Array [ 3884 | "build/utils/runtimeInjection.d.ts.map", 3885 | "number", 3886 | ], 3887 | Array [ 3888 | "build/utils/runtimeInjection.js", 3889 | "object", 3890 | ], 3891 | Array [ 3892 | "build/utils/runtimeInjection.js.map", 3893 | "number", 3894 | ], 3895 | Array [ 3896 | "build/utils/safeNavigator.d.ts", 3897 | "number", 3898 | ], 3899 | Array [ 3900 | "build/utils/safeNavigator.d.ts.map", 3901 | "number", 3902 | ], 3903 | Array [ 3904 | "build/utils/safeNavigator.js", 3905 | "object", 3906 | ], 3907 | Array [ 3908 | "build/utils/safeNavigator.js.map", 3909 | "number", 3910 | ], 3911 | Array [ 3912 | "build/utils/safeWindow.d.ts", 3913 | "number", 3914 | ], 3915 | Array [ 3916 | "build/utils/safeWindow.d.ts.map", 3917 | "number", 3918 | ], 3919 | Array [ 3920 | "build/utils/safeWindow.js", 3921 | "object", 3922 | ], 3923 | Array [ 3924 | "build/utils/safeWindow.js.map", 3925 | "number", 3926 | ], 3927 | Array [ 3928 | "build/utils/string.d.ts", 3929 | "number", 3930 | ], 3931 | Array [ 3932 | "build/utils/string.d.ts.map", 3933 | "number", 3934 | ], 3935 | Array [ 3936 | "build/utils/string.js", 3937 | "object", 3938 | ], 3939 | Array [ 3940 | "build/utils/string.js.map", 3941 | "number", 3942 | ], 3943 | Array [ 3944 | "build/utils/type-guards.d.ts", 3945 | "number", 3946 | ], 3947 | Array [ 3948 | "build/utils/type-guards.d.ts.map", 3949 | "number", 3950 | ], 3951 | Array [ 3952 | "build/utils/type-guards.js", 3953 | "object", 3954 | ], 3955 | Array [ 3956 | "build/utils/type-guards.js.map", 3957 | "number", 3958 | ], 3959 | Array [ 3960 | "build/utils/utils.d.ts", 3961 | "number", 3962 | ], 3963 | Array [ 3964 | "build/utils/utils.d.ts.map", 3965 | "number", 3966 | ], 3967 | Array [ 3968 | "build/utils/utils.js", 3969 | "object", 3970 | ], 3971 | Array [ 3972 | "build/utils/utils.js.map", 3973 | "number", 3974 | ], 3975 | Array [ 3976 | "build/utils/warnOnce.d.ts", 3977 | "number", 3978 | ], 3979 | Array [ 3980 | "build/utils/warnOnce.d.ts.map", 3981 | "number", 3982 | ], 3983 | Array [ 3984 | "build/utils/warnOnce.js", 3985 | "object", 3986 | ], 3987 | Array [ 3988 | "build/utils/warnOnce.js.map", 3989 | "number", 3990 | ], 3991 | Array [ 3992 | "CHANGELOG.md", 3993 | "number", 3994 | ], 3995 | Array [ 3996 | "LICENSE.md", 3997 | "number", 3998 | ], 3999 | Array [ 4000 | "package.json", 4001 | "object", 4002 | ], 4003 | Array [ 4004 | "postinstall.cjs", 4005 | "number", 4006 | ], 4007 | Array [ 4008 | "README.md", 4009 | "number", 4010 | ], 4011 | ] 4012 | `; 4013 | 4014 | exports[`object-assign@4.1.1 1`] = ` 4015 | Array [ 4016 | Array [ 4017 | "index.js", 4018 | "object", 4019 | ], 4020 | Array [ 4021 | "license", 4022 | "number", 4023 | ], 4024 | Array [ 4025 | "package.json", 4026 | "object", 4027 | ], 4028 | Array [ 4029 | "readme.md", 4030 | "number", 4031 | ], 4032 | ] 4033 | `; 4034 | 4035 | exports[`react@18 1`] = ` 4036 | Array [ 4037 | Array [ 4038 | "cjs/react-jsx-dev-runtime.development.js", 4039 | "object", 4040 | ], 4041 | Array [ 4042 | "cjs/react-jsx-dev-runtime.production.min.js", 4043 | "number", 4044 | ], 4045 | Array [ 4046 | "cjs/react-jsx-dev-runtime.profiling.min.js", 4047 | "number", 4048 | ], 4049 | Array [ 4050 | "cjs/react-jsx-runtime.development.js", 4051 | "object", 4052 | ], 4053 | Array [ 4054 | "cjs/react-jsx-runtime.production.min.js", 4055 | "number", 4056 | ], 4057 | Array [ 4058 | "cjs/react-jsx-runtime.profiling.min.js", 4059 | "number", 4060 | ], 4061 | Array [ 4062 | "cjs/react.development.js", 4063 | "object", 4064 | ], 4065 | Array [ 4066 | "cjs/react.production.min.js", 4067 | "number", 4068 | ], 4069 | Array [ 4070 | "cjs/react.shared-subset.development.js", 4071 | "number", 4072 | ], 4073 | Array [ 4074 | "cjs/react.shared-subset.production.min.js", 4075 | "number", 4076 | ], 4077 | Array [ 4078 | "index.js", 4079 | "object", 4080 | ], 4081 | Array [ 4082 | "jsx-dev-runtime.js", 4083 | "object", 4084 | ], 4085 | Array [ 4086 | "jsx-runtime.js", 4087 | "object", 4088 | ], 4089 | Array [ 4090 | "LICENSE", 4091 | "number", 4092 | ], 4093 | Array [ 4094 | "package.json", 4095 | "object", 4096 | ], 4097 | Array [ 4098 | "react.shared-subset.js", 4099 | "number", 4100 | ], 4101 | Array [ 4102 | "README.md", 4103 | "number", 4104 | ], 4105 | Array [ 4106 | "umd/react.development.js", 4107 | "number", 4108 | ], 4109 | Array [ 4110 | "umd/react.production.min.js", 4111 | "number", 4112 | ], 4113 | Array [ 4114 | "umd/react.profiling.min.js", 4115 | "number", 4116 | ], 4117 | ] 4118 | `; 4119 | -------------------------------------------------------------------------------- /integration-tests/deps.test.ts: -------------------------------------------------------------------------------- 1 | import { fetchV2Deps, V2Deps } from "./utils"; 2 | import { parse as parseSemver } from "semver"; 3 | 4 | function validateContract(data: V2Deps) { 5 | expect(typeof data).toBe("object"); 6 | for (let [key, value] of Object.entries(data)) { 7 | const splitKey = key.split("@"); 8 | const majorVersion = +(splitKey.pop() ?? ""); 9 | expect(isNaN(majorVersion)).toBe(false); 10 | expect(typeof key).toBe("string"); 11 | 12 | const parsed = parseSemver(value); 13 | expect(parsed?.major).toBe(majorVersion); 14 | } 15 | } 16 | 17 | test("react and next", async () => { 18 | const deps = await fetchV2Deps([ 19 | { name: "react", range: "^18.1.0" }, 20 | { name: "next", range: "^12.3.1" }, 21 | ]); 22 | validateContract(deps); 23 | expect(typeof deps["react@18"]).toBe("string"); 24 | expect(typeof deps["next@12"]).toBe("string"); 25 | }); 26 | 27 | test("next@latest", async () => { 28 | const deps = await fetchV2Deps([{ name: "next", range: "latest" }]); 29 | validateContract(deps); 30 | expect(typeof deps["next@13"]).toBe("string"); 31 | }); 32 | 33 | test("react@*", async () => { 34 | const deps = await fetchV2Deps([{ name: "react", range: "*" }]); 35 | validateContract(deps); 36 | expect(typeof deps["react@18"]).toBe("string"); 37 | }); 38 | 39 | test("react without range", async () => { 40 | const deps = await fetchV2Deps([{ name: "react", range: "" }]); 41 | validateContract(deps); 42 | expect(typeof deps["react@18"]).toBe("string"); 43 | }); 44 | -------------------------------------------------------------------------------- /integration-tests/fetch.ts: -------------------------------------------------------------------------------- 1 | import { sleep } from "./utils"; 2 | import fetch from "node-fetch"; 3 | 4 | interface RetryOptions { 5 | maxRetries?: number; 6 | retryDelay?: number; 7 | } 8 | 9 | export type RequestInitWithRetry = RequestInit & RetryOptions; 10 | 11 | // 408 is timeout 12 | // 429 is too many requests 13 | // 424 is failed dependency 14 | // 499 is client closed connection 15 | // 444 is connection closed without response 16 | // 502 is Bad gateway 17 | // 503 is Service Unavailable 18 | // 504 is Gateway Timeout 19 | // 599 is Network Connect Timeout Error 20 | const ERROR_CODES_TO_RETRY = new Set([ 21 | 408, 429, 424, 499, 444, 502, 503, 504, 599, 22 | ]); 23 | function isRetryableStatus(status: number): boolean { 24 | return ERROR_CODES_TO_RETRY.has(status); 25 | } 26 | 27 | /** 28 | * Fetches a resource using the provided config and retries if it fails with a network or server availability error 29 | * 30 | * @param {RequestInfo} input: request info for fetch 31 | * @param {RequestInit} init: request options for fetch 32 | * @param {pRetry.PromiseRetryOptions} retryOptions: Retry configuration 33 | * @returns {Response} 34 | */ 35 | export async function retryFetch( 36 | input: RequestInfo, 37 | init: RequestInitWithRetry = {}, 38 | count = 0 39 | ): Promise { 40 | const { maxRetries = 0, retryDelay = 500 } = init; 41 | if (count > maxRetries) { 42 | throw new Error("Fetch failed, maximum retries exceeded"); 43 | } 44 | const shouldRetry = count < maxRetries; 45 | try { 46 | const result = await fetch(input, init); 47 | if (result.ok) { 48 | return result; 49 | } 50 | // Don't use p-retry it cannot be scope hoisted properly 51 | // See https://github.com/parcel-bundler/parcel/issues/7866 52 | const isRetryable = isRetryableStatus(result.status); 53 | if (!shouldRetry || !isRetryable) { 54 | const text = await result.text().catch(() => ""); 55 | throw new Error(`${result.status}: ${text}`); 56 | } 57 | } catch (err) { 58 | if (!shouldRetry) { 59 | throw err; 60 | } 61 | } 62 | await sleep(retryDelay); 63 | return retryFetch(input, init, count + 1); 64 | } 65 | -------------------------------------------------------------------------------- /integration-tests/mod.test.ts: -------------------------------------------------------------------------------- 1 | import { fetchV2Module, V2Module } from "./utils"; 2 | 3 | function getSnapshot(data: V2Module): Record { 4 | const res = {}; 5 | for (let [key, value] of Object.entries(data)) { 6 | res[key] = value.byteLength; 7 | } 8 | return res; 9 | } 10 | 11 | test("react@18.1.0", async () => { 12 | const module = await fetchV2Module("react", "18.1.0"); 13 | expect(getSnapshot(module)).toMatchSnapshot(); 14 | }); 15 | 16 | test("next@12.3.1", async () => { 17 | const module = await fetchV2Module("next", "12.3.1"); 18 | expect(getSnapshot(module)).toMatchSnapshot(); 19 | }); 20 | -------------------------------------------------------------------------------- /integration-tests/utils.ts: -------------------------------------------------------------------------------- 1 | import urlJoin from "url-join"; 2 | import { decode } from "@msgpack/msgpack"; 3 | import { retryFetch } from "./fetch"; 4 | 5 | const CDN_ROOT: string = process.env.CDN_ROOT || "http://localhost:8080"; 6 | 7 | export function sleep(ms) { 8 | return new Promise((resolve) => setTimeout(resolve, ms)); 9 | } 10 | 11 | export function encodeBase64(payload) { 12 | return Buffer.from(payload).toString("base64"); 13 | } 14 | 15 | export type V2Module = Record; 16 | 17 | export async function fetchV2Module( 18 | name: string, 19 | version: string 20 | ): Promise { 21 | const specifier = `${name}@${version}`; 22 | const encoded_specifier = encodeBase64(specifier); 23 | const result = await retryFetch( 24 | urlJoin(CDN_ROOT, `/v2/mod/${encoded_specifier}`), 25 | { maxRetries: 5 } 26 | ); 27 | // @ts-ignore 28 | const blob = await result.buffer(); 29 | return decode(blob) as V2Module; 30 | } 31 | 32 | export type V2Deps = Record; 33 | 34 | export async function fetchV2Deps( 35 | deps: Array<{name: string, range: string}> 36 | ): Promise { 37 | const specifier = deps.map(v => `${v.name}@${v.range}`).join(';'); 38 | const encoded_specifier = encodeBase64(specifier); 39 | const url = urlJoin(CDN_ROOT, `/v2/deps/${encoded_specifier}`); 40 | const result = await retryFetch( 41 | url, 42 | { maxRetries: 5 } 43 | ); 44 | // @ts-ignore 45 | const blob = await result.buffer(); 46 | return decode(blob) as V2Deps; 47 | } 48 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | const SECOND = 1000; 2 | 3 | module.exports = { 4 | transform: { 5 | "^.+\\.(t|j)sx?$": ["@swc/jest"], 6 | }, 7 | testTimeout: 60 * SECOND, 8 | }; 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sandpack-cdn", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "repository": "git@github.com:DeMoorJasper/sandpack-cdn.git", 6 | "author": "Jasper De Moor ", 7 | "license": "MIT", 8 | "scripts": { 9 | "ci:run_tests": "node ./run_tests", 10 | "test": "jest" 11 | }, 12 | "devDependencies": {}, 13 | "dependencies": { 14 | "@msgpack/msgpack": "^2.8.0", 15 | "@swc/core": "^1.3.95", 16 | "@swc/jest": "^0.2.29", 17 | "@types/jest": "^29.5.6", 18 | "@types/semver": "^7.3.12", 19 | "jest": "^29.7.0", 20 | "node-fetch": "^2.6.7", 21 | "semver": "^7.5.2", 22 | "typescript": "^4.6.4", 23 | "url-join": "^4.0.1" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /run_tests.js: -------------------------------------------------------------------------------- 1 | const { exec, spawn } = require("node:child_process"); 2 | const path = require("node:path"); 3 | const fetch = require("node-fetch"); 4 | const urlJoin = require("url-join"); 5 | 6 | const PORT = +(process.env.PORT | "9000"); 7 | 8 | function sleep(ms) { 9 | return new Promise((resolve) => setTimeout(resolve, ms)); 10 | } 11 | 12 | async function waitForServerStart(serverAddress) { 13 | for (let i = 0; i < 25; i++) { 14 | console.log("Trying to connect to the server..."); 15 | try { 16 | // Fetch react-dom@18.1.0 17 | await fetch( 18 | urlJoin(serverAddress, "/package/MyhyZWFjdC1kb21AMTguMS4wKQ=="), 19 | {} 20 | ); 21 | return true; 22 | } catch (err) { 23 | console.error(err); 24 | } 25 | console.log("Waiting 50ms before retrying..."); 26 | await sleep(50); 27 | } 28 | 29 | throw new Error("Server did not start in time :("); 30 | } 31 | 32 | async function run() { 33 | const buildFile = path.join(__dirname, "/target/release/sandpack-cdn"); 34 | const spawnedServer = spawn(buildFile, { 35 | env: { 36 | ...process.env, 37 | PORT: PORT, 38 | }, 39 | }); 40 | 41 | spawnedServer.on("spawn", () => { 42 | console.log("Started the server process", buildFile); 43 | }); 44 | spawnedServer.on("error", (err) => console.error(err)); 45 | spawnedServer.on("disconnect", () => console.log("Server bus disconnected")); 46 | spawnedServer.on("close", () => console.log("Server closed")); 47 | 48 | spawnedServer.stdout.setEncoding("utf-8"); 49 | spawnedServer.stdout.on("data", (data) => { 50 | console.log(data); 51 | }); 52 | spawnedServer.stderr.setEncoding("utf-8"); 53 | spawnedServer.stderr.on("data", (data) => { 54 | console.error(data); 55 | }); 56 | 57 | const cdnAddress = `http://localhost:${PORT}`; 58 | await waitForServerStart(cdnAddress); 59 | 60 | console.log("Server has responded successfully"); 61 | 62 | const spawnedTest = spawn("jest", ["--forceExit"], { 63 | stdio: "inherit", 64 | env: { 65 | ...process.env, 66 | CDN_ROOT: cdnAddress, 67 | }, 68 | }); 69 | 70 | await new Promise((resolve, reject) => { 71 | spawnedTest.on("close", (code) => { 72 | if (code !== 0) { 73 | reject(new Error("tests failed")); 74 | } else { 75 | resolve(); 76 | } 77 | }); 78 | }).finally(() => { 79 | spawnedServer.kill(); 80 | }); 81 | } 82 | 83 | run().catch((err) => { 84 | process.exitCode = 1; 85 | console.error(err); 86 | }); 87 | -------------------------------------------------------------------------------- /src/app_error.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | use tokio::sync::broadcast; 3 | use warp::{hyper::http, reject}; 4 | 5 | pub type AppResult = Result; 6 | 7 | #[derive(Error, Debug)] 8 | pub enum ServerError { 9 | #[error("invalid semver")] 10 | InvalidSemver(#[from] node_semver::SemverError), 11 | #[error("Failed request")] 12 | FailedRequest(#[from] reqwest_middleware::Error), 13 | #[error(transparent)] 14 | RequestFailed(#[from] reqwest::Error), 15 | #[error("Response has a non-200 status code")] 16 | RequestErrorStatus { status_code: u16 }, 17 | #[error("IO Operation failed")] 18 | IoError(#[from] std::io::Error), 19 | #[error("Could not parse url")] 20 | UrlParseError(#[from] url::ParseError), 21 | #[error("Could not parse json string")] 22 | JSONParseError(#[from] serde_json::Error), 23 | #[error("Package version not found {0}@{1}")] 24 | PackageVersionNotFound(String, String), 25 | #[error("Package {0} not found")] 26 | PackageNotFound(String), 27 | #[error("Infallible error")] 28 | Infallible(#[from] std::convert::Infallible), 29 | #[error("Could not parse module")] 30 | SWCParseError { message: String }, 31 | #[error("Could not download tarball package")] 32 | TarballDownloadError { status_code: u16, url: String }, 33 | #[error("Could not download package metadata")] 34 | PackageMetadataDownloadError { status_code: u16, url: String }, 35 | #[error("Could not download npm package manifest")] 36 | NpmManifestDownloadError { 37 | status_code: u16, 38 | package_name: String, 39 | }, 40 | #[error("Invalid package specifier")] 41 | InvalidPackageSpecifier, 42 | #[error("Invalid byte buffer")] 43 | InvalidString(#[from] std::str::Utf8Error), 44 | #[error("Join error")] 45 | JoinError(#[from] tokio::task::JoinError), 46 | #[error("Invalid CDN version")] 47 | InvalidCDNVersion, 48 | #[error("Could not parse integer")] 49 | IntegerParse(#[from] std::num::ParseIntError), 50 | #[error("Invalid status code")] 51 | InvalidStatusCode(#[from] http::status::InvalidStatusCode), 52 | #[error("Failed to serialize to msgpack")] 53 | SerializeError(), 54 | #[error("Failed to deserialize from msgpack")] 55 | DeserializeError(), 56 | #[error("Failed to decode base64 string")] 57 | Base64DecodingError(), 58 | #[error("Sendable error")] 59 | SendableError(#[from] SendableError), 60 | #[error("Resource hasn't changed")] 61 | NotChanged, 62 | #[error("Invalid query")] 63 | InvalidQuery, 64 | #[error("Unexpected error")] 65 | UnexpectedError { message: String }, 66 | #[error("MessagePack Decode Error")] 67 | MessagePackDecodeError(#[from] rmp_serde::decode::Error), 68 | } 69 | 70 | impl From for std::io::Error { 71 | fn from(err: ServerError) -> Self { 72 | std::io::Error::new(std::io::ErrorKind::Other, format!("{:?}", err)) 73 | } 74 | } 75 | 76 | impl reject::Reject for ServerError {} 77 | 78 | #[derive(Debug, Clone, thiserror::Error)] 79 | #[error("stringified error: {inner}")] 80 | pub struct SendableError { 81 | pub inner: String, 82 | } 83 | 84 | impl SendableError { 85 | pub fn new(e: E) -> Self { 86 | Self { 87 | inner: e.to_string(), 88 | } 89 | } 90 | } 91 | 92 | impl From for SendableError { 93 | fn from(e: broadcast::error::RecvError) -> Self { 94 | SendableError::new(e) 95 | } 96 | } 97 | 98 | impl From for SendableError { 99 | fn from(e: ServerError) -> Self { 100 | SendableError::new(e) 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/cached.rs: -------------------------------------------------------------------------------- 1 | // There's a whole blog article about this: https://fasterthanli.me/articles/request-coalescing-in-async-rust 2 | // Don't need to reinvent the wheel here, but we do iterate further on this 3 | use parking_lot::Mutex; 4 | use std::future::Future; 5 | use std::{ 6 | pin::Pin, 7 | sync::{Arc, Weak}, 8 | time::{Duration, Instant}, 9 | }; 10 | use tokio::sync::broadcast; 11 | use tracing::info; 12 | 13 | use crate::app_error::SendableError; 14 | 15 | pub type BoxFut<'a, O> = Pin + Send + 'a>>; 16 | 17 | #[derive(Clone)] 18 | pub struct Cached 19 | where 20 | T: Clone + Send + Sync + 'static, 21 | { 22 | inner: Arc>>, 23 | refresh_interval: Duration, 24 | } 25 | 26 | type LastFetched = Option<(Instant, T)>; 27 | 28 | struct CachedInner 29 | where 30 | T: Clone + Send + Sync + 'static, 31 | { 32 | last_fetched: LastFetched, 33 | inflight: Option>>>, 34 | } 35 | 36 | impl Default for CachedInner 37 | where 38 | T: Clone + Send + Sync + 'static, 39 | { 40 | fn default() -> Self { 41 | Self { 42 | last_fetched: None, 43 | inflight: None, 44 | } 45 | } 46 | } 47 | 48 | impl Cached 49 | where 50 | T: Clone + Send + Sync + 'static, 51 | { 52 | pub fn new(refresh_interval: Duration) -> Self { 53 | Self { 54 | inner: Default::default(), 55 | refresh_interval, 56 | } 57 | } 58 | 59 | pub async fn get_cached(&self, f: F) -> Result 60 | where 61 | F: FnOnce(Option) -> BoxFut<'static, Result> + Send + 'static, 62 | E: std::fmt::Display + 'static, 63 | { 64 | let mut rx = { 65 | // only sync code in this block 66 | let mut inner = self.inner.lock(); 67 | 68 | if let Some((fetched_at, value)) = inner.last_fetched.as_ref() { 69 | if fetched_at.elapsed() < self.refresh_interval { 70 | return Ok(value.clone()); 71 | } else { 72 | info!("stale, let's refresh"); 73 | } 74 | } 75 | 76 | let last_fetched = inner.last_fetched.clone().map(|v| v.1); 77 | if let Some(inflight) = inner.inflight.as_ref().and_then(Weak::upgrade) { 78 | if let Some(val) = last_fetched { 79 | info!("Returning stale data"); 80 | return Ok(val); 81 | } 82 | 83 | inflight.subscribe() 84 | } else { 85 | // there isn't, let's fetch 86 | let (tx, rx) = broadcast::channel::>(1); 87 | // let's reference-count a single `Sender`: 88 | let tx = Arc::new(tx); 89 | // and only store a weak reference in our state: 90 | inner.inflight = Some(Arc::downgrade(&tx)); 91 | let inner = self.inner.clone(); 92 | 93 | // call the closure first, so we don't send _it_ across threads, 94 | // just the Future it returns 95 | let fut = f(last_fetched.clone()); 96 | 97 | tokio::spawn(async move { 98 | let res = fut.await; 99 | 100 | { 101 | // only sync code in this block 102 | let mut inner = inner.lock(); 103 | inner.inflight = None; 104 | 105 | match res { 106 | Ok(value) => { 107 | inner.last_fetched.replace((Instant::now(), value.clone())); 108 | let _ = tx.send(Ok(value)); 109 | } 110 | Err(e) => { 111 | let _ = tx.send(Err(SendableError { 112 | inner: e.to_string(), 113 | })); 114 | } 115 | }; 116 | } 117 | }); 118 | 119 | if let Some(val) = last_fetched { 120 | info!("Returning stale data"); 121 | return Ok(val); 122 | } 123 | 124 | rx 125 | } 126 | }; 127 | 128 | // if we reached here, we're waiting for an in-flight request (we weren't able to serve from cache) 129 | let received = rx.recv().await??; 130 | Ok(received) 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use crate::npm_replicator::{registry::NpmRocksDB, replication_task}; 2 | use dotenv::dotenv; 3 | use std::env; 4 | use std::net::SocketAddr; 5 | use warp::http::header::{HeaderMap, HeaderValue}; 6 | use warp::Filter; 7 | 8 | mod app_error; 9 | mod cached; 10 | mod npm; 11 | mod npm_replicator; 12 | mod package; 13 | mod router; 14 | mod setup_tracing; 15 | mod utils; 16 | 17 | #[tokio::main] 18 | async fn main() -> Result<(), std::io::Error> { 19 | dotenv().ok(); 20 | setup_tracing::setup_tracing(); 21 | 22 | let port = match env::var("PORT") { 23 | Ok(var) => var, 24 | Err(_) => String::from("8080"), 25 | } 26 | .parse::() 27 | .unwrap(); 28 | 29 | // Setup npm db 30 | let npm_registry_path = 31 | env::var("NPM_ROCKS_DB").expect("NPM_ROCKS_DB env variable should be set"); 32 | let npm_fs_db = NpmRocksDB::new(&npm_registry_path); 33 | 34 | replication_task::spawn_sync_thread(npm_fs_db.clone()); 35 | 36 | // cors headers 37 | let mut headers = HeaderMap::new(); 38 | headers.insert("Access-Control-Allow-Origin", HeaderValue::from_static("*")); 39 | headers.insert( 40 | "Access-Control-Allow-Headers", 41 | HeaderValue::from_static("*"), 42 | ); 43 | headers.insert( 44 | "Access-Control-Allow-Methods", 45 | HeaderValue::from_static("GET, POST, OPTIONS"), 46 | ); 47 | let cors_headers_filter = warp::reply::with::headers(headers); 48 | 49 | let filter = router::routes::routes(npm_fs_db) 50 | .with(warp::trace::request()) 51 | .with(cors_headers_filter) 52 | .with(warp::compression::gzip()); 53 | 54 | let addr: SocketAddr = ([0, 0, 0, 0], port).into(); 55 | println!("Server running on {}", addr); 56 | warp::serve(filter).run(addr).await; 57 | 58 | Ok(()) 59 | } 60 | -------------------------------------------------------------------------------- /src/npm/dep_tree_builder.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::{BTreeMap, HashMap, HashSet}, 3 | fmt, 4 | }; 5 | 6 | use node_semver::{Range, Version}; 7 | use tracing::{error, info}; 8 | 9 | use crate::{ 10 | app_error::ServerError, npm_replicator::registry::NpmRocksDB, 11 | package::process::parse_package_specifier_no_validation, 12 | }; 13 | 14 | #[derive(Clone, Eq, Hash, PartialEq, Debug)] 15 | pub enum DepRange { 16 | Range(Range), 17 | Tag(String), 18 | } 19 | 20 | impl DepRange { 21 | pub fn parse(value: String) -> DepRange { 22 | if value == *"*" || value == *"" { 23 | DepRange::Range(Range::any()) 24 | } else { 25 | match Range::parse(&value) { 26 | Ok(value) => DepRange::Range(value), 27 | Err(_err) => DepRange::Tag(value), 28 | } 29 | } 30 | } 31 | } 32 | 33 | impl fmt::Display for DepRange { 34 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 35 | match self { 36 | DepRange::Range(range) => { 37 | write!(f, "{}", range) 38 | } 39 | DepRange::Tag(tag) => { 40 | write!(f, "{}", tag) 41 | } 42 | } 43 | } 44 | } 45 | 46 | #[derive(Clone, Eq, Hash, PartialEq, Debug)] 47 | pub struct DepRequest { 48 | name: String, 49 | range: DepRange, 50 | } 51 | 52 | impl DepRequest { 53 | fn new(name: String, range: DepRange) -> DepRequest { 54 | DepRequest { name, range } 55 | } 56 | 57 | pub fn from_name_version(name: String, version: String) -> Result { 58 | let parsed_range = DepRange::parse(version); 59 | if let DepRange::Tag(tag) = parsed_range.clone() { 60 | if tag.contains(':') { 61 | // Example: npm:@babel/core@7.12.9 62 | if tag.starts_with("npm:") { 63 | let (actual_name, actual_version) = 64 | parse_package_specifier_no_validation(&tag[4..])?; 65 | let parsed_range = DepRange::parse(actual_version); 66 | return Ok(DepRequest::new(actual_name.to_string(), parsed_range)); 67 | } 68 | } 69 | } 70 | Ok(DepRequest::new(name, parsed_range)) 71 | } 72 | } 73 | 74 | pub type ResolutionsMap = BTreeMap; 75 | pub type AliasesMap = BTreeMap; 76 | 77 | pub struct DepTreeBuilder { 78 | pub resolutions: ResolutionsMap, 79 | pub aliases: AliasesMap, 80 | packages: HashMap>, 81 | npm_db: NpmRocksDB, 82 | } 83 | 84 | impl DepTreeBuilder { 85 | pub fn new(npm_db: NpmRocksDB) -> DepTreeBuilder { 86 | DepTreeBuilder { 87 | resolutions: BTreeMap::new(), 88 | aliases: BTreeMap::new(), 89 | packages: HashMap::new(), 90 | npm_db, 91 | } 92 | } 93 | 94 | fn add_dependency(&mut self, name: &str, version: &Version) { 95 | let mut key = String::from(name); 96 | key.push('@'); 97 | key.push_str(&version.major.to_string()); 98 | if let Some(value) = self.resolutions.get(&key) { 99 | // If value is larger than version we continue 100 | // otherwise we need to add this version to prevent infinite recursion 101 | // We also make the highest version win 102 | if value >= version { 103 | return; 104 | } 105 | } 106 | self.resolutions.insert(key, version.clone()); 107 | if let Some(versions) = self.packages.get_mut(name) { 108 | versions.insert(version.clone()); 109 | } else { 110 | let mut versions = HashSet::new(); 111 | versions.insert(version.clone()); 112 | self.packages.insert(String::from(name), versions); 113 | } 114 | } 115 | 116 | fn has_dependency(&mut self, name: &str, range: &Range) -> bool { 117 | if let Some(versions) = self.packages.get(&String::from(name)) { 118 | for version in versions { 119 | if range.satisfies(version) { 120 | // TODO: Make this only run for dev builds, it slows down requests a lot sometimes... 121 | // println!( 122 | // "{}@{} is already resolved, skipping", 123 | // &request.name, &request.range 124 | // ); 125 | 126 | return true; 127 | } 128 | } 129 | } 130 | false 131 | } 132 | 133 | #[tracing::instrument(name = "resolve_dependency", level = "debug", skip_all, fields(pkg_name = request.name.as_str(), range = request.range.to_string().as_str()))] 134 | fn resolve_dependency( 135 | &mut self, 136 | request: DepRequest, 137 | mut transient_deps: HashSet, 138 | ) -> Result, ServerError> { 139 | let data = self.npm_db.get_package(&request.name)?; 140 | let mut range = Range::any(); 141 | if let DepRange::Tag(tag) = &request.range { 142 | match data.dist_tags.get(tag) { 143 | Some(found_version) => { 144 | range = Range::parse(found_version)?; 145 | let version = Version::parse(found_version)?; 146 | self.aliases.insert( 147 | format!("{}@{}", &request.name, tag), 148 | format!("{}@{}", &request.name, &version.major), 149 | ); 150 | } 151 | None => { 152 | // If it contains a colon, it's a special specifier and we should just ignore those 153 | if tag.contains(':') { 154 | return Ok(transient_deps); 155 | } else { 156 | error!("Invalid package specifier"); 157 | return Err(ServerError::InvalidPackageSpecifier); 158 | } 159 | } 160 | } 161 | } else if let DepRange::Range(original_range) = &request.range { 162 | range = original_range.clone(); 163 | } 164 | 165 | if self.has_dependency(&request.name, &range) { 166 | info!("Dependency already exists, skipping"); 167 | return Ok(transient_deps); 168 | } 169 | 170 | let mut highest_version: Option = None; 171 | for (version, _data) in data.versions.iter().rev() { 172 | let parsed_version = Version::parse(version)?; 173 | if range.satisfies(&parsed_version) { 174 | highest_version = Some(parsed_version); 175 | break; 176 | } 177 | } 178 | 179 | if let Some(resolved_version) = highest_version { 180 | self.add_dependency(&request.name, &resolved_version); 181 | 182 | let data = data.versions.get(&resolved_version.to_string()); 183 | if let Some(data) = data { 184 | for (name, range) in data.dependencies.iter() { 185 | transient_deps 186 | .insert(DepRequest::from_name_version(name.clone(), range.clone())?); 187 | } 188 | } 189 | Ok(transient_deps) 190 | } else { 191 | error!("Package version not found"); 192 | Err(ServerError::PackageVersionNotFound( 193 | request.name, 194 | request.range.to_string(), 195 | )) 196 | } 197 | } 198 | 199 | fn resolve_dependencies( 200 | &mut self, 201 | deps: HashSet, 202 | ) -> Result, ServerError> { 203 | let mut transient_deps: HashSet = HashSet::new(); 204 | for request in deps { 205 | if let DepRange::Range(original_range) = &request.range { 206 | if self.has_dependency(&request.name, original_range) { 207 | continue; 208 | } 209 | } 210 | 211 | transient_deps = self.resolve_dependency(request, transient_deps)?; 212 | } 213 | Ok(transient_deps) 214 | } 215 | 216 | #[tracing::instrument(name = "resolve_dep_tree", skip_all)] 217 | pub fn resolve_tree(&mut self, deps: HashSet) -> Result<(), ServerError> { 218 | let mut deps = deps; 219 | let mut count = 0; 220 | while !deps.is_empty() && count < 200 { 221 | deps = self.resolve_dependencies(deps)?; 222 | count += 1; 223 | } 224 | 225 | info!("Finished resolving in {} ticks", count); 226 | 227 | Ok(()) 228 | } 229 | } 230 | -------------------------------------------------------------------------------- /src/npm/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod package_content; 2 | pub mod dep_tree_builder; 3 | pub mod package_data; 4 | -------------------------------------------------------------------------------- /src/npm/package_content.rs: -------------------------------------------------------------------------------- 1 | use std::io::{Cursor, Read}; 2 | use std::{fmt, sync::Arc, time::Duration}; 3 | 4 | use crate::{app_error::ServerError, cached::Cached, npm_replicator::registry::NpmRocksDB}; 5 | use ::tar::{Archive, EntryType}; 6 | use flate2::read::GzDecoder; 7 | use moka::future::Cache; 8 | use reqwest_middleware::{ClientBuilder, ClientWithMiddleware}; 9 | use reqwest_retry::{policies::ExponentialBackoff, RetryTransientMiddleware}; 10 | use std::collections::HashMap; 11 | 12 | pub type ByteVec = Vec; 13 | pub type FileMap = Arc>; 14 | 15 | #[tracing::instrument(name = "accumulate_files", skip(archive))] 16 | fn accumulate_files( 17 | mut archive: Archive, 18 | ) -> Result, ServerError> { 19 | let mut collected: HashMap = HashMap::new(); 20 | for file in archive.entries()? { 21 | // Make sure there wasn't an I/O error 22 | let mut file = file?; 23 | 24 | if !EntryType::is_file(&file.header().entry_type()) { 25 | continue; 26 | } 27 | 28 | // Read file content 29 | let mut buf: Vec = Vec::new(); 30 | file.read_to_end(&mut buf)?; 31 | 32 | // Read file path 33 | let header_path = file.header().path()?; 34 | let filepath_str = header_path.to_str().unwrap_or("package/unknown"); 35 | let first_slash_position = filepath_str.chars().position(|c| c == '/').unwrap_or(0); 36 | let filepath = String::from(&filepath_str[first_slash_position..]); 37 | 38 | // Insert into collection 39 | collected.insert(filepath, buf); 40 | } 41 | Ok(collected) 42 | } 43 | 44 | #[tracing::instrument(name = "download_tarball", skip(client))] 45 | async fn download_tarball( 46 | client: &ClientWithMiddleware, 47 | url: &str, 48 | ) -> Result { 49 | let response = client.get(url).send().await?; 50 | let response_status = response.status(); 51 | if !response_status.is_success() { 52 | return Err(ServerError::TarballDownloadError { 53 | status_code: response_status.as_u16(), 54 | url: String::from(url), 55 | }); 56 | } 57 | 58 | let content = Cursor::new(response.bytes().await?); 59 | let files = if url.ends_with(".tar") { 60 | let archive = Archive::new(content); 61 | accumulate_files(archive) 62 | } else { 63 | let tar = GzDecoder::new(content); 64 | let archive = Archive::new(tar); 65 | accumulate_files(archive) 66 | }; 67 | Ok(Arc::new(files?)) 68 | } 69 | 70 | #[tracing::instrument(name = "get_tarball", skip(client, cached))] 71 | async fn get_tarball( 72 | url: &str, 73 | client: ClientWithMiddleware, 74 | cached: Cached, 75 | ) -> Result { 76 | let url_string = String::from(url); 77 | let res = cached 78 | .get_cached(|_last_val| { 79 | Box::pin(async move { 80 | let content = download_tarball(&client, url_string.as_str()).await?; 81 | Ok::<_, ServerError>(content) 82 | }) 83 | }) 84 | .await?; 85 | 86 | Ok(res) 87 | } 88 | 89 | fn get_client() -> ClientWithMiddleware { 90 | let retry_policy = ExponentialBackoff::builder().build_with_max_retries(3); 91 | 92 | let client_builder = reqwest::ClientBuilder::new() 93 | .timeout(Duration::from_secs(120)) 94 | .deflate(true) 95 | .gzip(true) 96 | .brotli(true); 97 | let base_client = client_builder 98 | .build() 99 | .expect("reqwest::ClientBuilder::build()"); 100 | 101 | ClientBuilder::new(base_client) 102 | .with(RetryTransientMiddleware::new_with_policy(retry_policy)) 103 | .build() 104 | } 105 | 106 | #[derive(Clone)] 107 | pub struct PackageContentFetcher { 108 | cache: Cache>, 109 | refresh_interval: Duration, 110 | } 111 | 112 | impl PackageContentFetcher { 113 | pub fn new() -> PackageContentFetcher { 114 | let ttl = Duration::from_secs(86400); 115 | let max_capacity = 50; 116 | PackageContentFetcher { 117 | cache: Cache::builder() 118 | .max_capacity(max_capacity) 119 | .time_to_idle(ttl) 120 | .build(), 121 | refresh_interval: Duration::from_secs(604800), 122 | } 123 | } 124 | 125 | #[tracing::instrument(name = "pkg_content_get", skip(self))] 126 | pub async fn get(&self, url: &str) -> Result { 127 | let key = String::from(url); 128 | let client = get_client(); 129 | if let Some(found_value) = self.cache.get(&key).await { 130 | get_tarball(url, client, found_value).await 131 | } else { 132 | let cached: Cached = Cached::new(self.refresh_interval); 133 | self.cache.insert(key, cached.clone()).await; 134 | get_tarball(url, client, cached).await 135 | } 136 | } 137 | } 138 | 139 | impl fmt::Debug for PackageContentFetcher { 140 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 141 | f.write_str("PackageContentFetcher") 142 | } 143 | } 144 | 145 | #[tracing::instrument(name = "download_package_content", skip(npm_db, content_fetcher))] 146 | pub async fn download_package_content( 147 | package_name: &str, 148 | version: &str, 149 | npm_db: &NpmRocksDB, 150 | content_fetcher: &PackageContentFetcher, 151 | ) -> Result { 152 | let manifest = npm_db.get_package(package_name)?; 153 | if let Some(version_data) = manifest.versions.get(version) { 154 | let content = content_fetcher.get(version_data.tarball.as_str()).await?; 155 | Ok(content) 156 | } else { 157 | Err(ServerError::PackageVersionNotFound( 158 | String::from(package_name), 159 | String::from(version), 160 | )) 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /src/npm/package_data.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::BTreeMap, time::Duration}; 2 | 3 | use reqwest_middleware::{ClientBuilder, ClientWithMiddleware}; 4 | use reqwest_retry::{policies::ExponentialBackoff, RetryTransientMiddleware}; 5 | use serde::{Deserialize, Serialize}; 6 | use serde_with::serde_as; 7 | 8 | use crate::app_error::ServerError; 9 | 10 | fn get_client() -> ClientWithMiddleware { 11 | let retry_policy = ExponentialBackoff::builder().build_with_max_retries(3); 12 | 13 | let client_builder = reqwest::ClientBuilder::new() 14 | .timeout(Duration::from_secs(120)) 15 | .deflate(true) 16 | .gzip(true) 17 | .brotli(true); 18 | let base_client = client_builder 19 | .build() 20 | .expect("reqwest::ClientBuilder::build()"); 21 | 22 | ClientBuilder::new(base_client) 23 | .with(RetryTransientMiddleware::new_with_policy(retry_policy)) 24 | .build() 25 | } 26 | 27 | #[serde_as] 28 | #[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone)] 29 | pub struct PackageDist { 30 | pub tarball: String, 31 | } 32 | 33 | #[serde_as] 34 | #[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone)] 35 | pub struct PackageVersion { 36 | pub dist: PackageDist, 37 | #[serde(default)] 38 | pub dependencies: BTreeMap, 39 | } 40 | 41 | #[serde_as] 42 | #[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone)] 43 | pub struct PackageMetadata { 44 | pub name: String, 45 | 46 | #[serde(rename = "dist-tags", default)] 47 | pub dist_tags: BTreeMap, 48 | 49 | #[serde(default)] 50 | pub versions: BTreeMap, 51 | } 52 | 53 | #[tracing::instrument(name = "download_pkg_metadata")] 54 | pub async fn download_pkg_metadata(pkg_name: &str) -> Result { 55 | let url: String = format!("https://registry.npmjs.org/{}", pkg_name); 56 | let client = get_client(); 57 | let response = client 58 | .get(&url) 59 | // Return a minimal version of the package metadata 60 | .header( 61 | "Accept", 62 | "application/vnd.npm.install-v1+json; q=1.0, application/json; q=0.8, */*", 63 | ) 64 | .send() 65 | .await?; 66 | let response_status = response.status(); 67 | if !response_status.is_success() { 68 | return Err(ServerError::PackageMetadataDownloadError { 69 | status_code: response_status.as_u16(), 70 | url: String::from(&url), 71 | }); 72 | } 73 | 74 | let txt = response.text().await?; 75 | let metadata: PackageMetadata = serde_json::from_str(&txt)?; 76 | 77 | Ok(metadata) 78 | } 79 | -------------------------------------------------------------------------------- /src/npm_replicator/changes.rs: -------------------------------------------------------------------------------- 1 | use super::{ 2 | error::{ChangeStreamError, ChangeStreamResult}, 3 | types::changes::ChangesPage, 4 | }; 5 | use reqwest::{Client, Method}; 6 | use std::{collections::HashMap, time::Duration}; 7 | 8 | /// The max timeout value for longpoll/continous HTTP requests 9 | /// that CouchDB supports (see [1]). 10 | /// 11 | /// [1]: https://docs.couchdb.org/en/stable/api/database/changes.html 12 | const COUCH_MAX_TIMEOUT: usize = 60000; 13 | 14 | /// The stream for the `_changes` endpoint. 15 | /// 16 | /// This is returned from [Database::changes]. 17 | pub struct ChangesStream { 18 | client: Client, 19 | last_seq: serde_json::Value, 20 | params: HashMap, 21 | pub limit: usize, 22 | } 23 | 24 | impl ChangesStream { 25 | /// Create a new changes stream. 26 | pub fn new(limit: usize, last_seq: serde_json::Value) -> Self { 27 | let mut params = HashMap::new(); 28 | params.insert("feed".to_string(), "longpoll".to_string()); 29 | params.insert("include_docs".to_string(), "true".to_string()); 30 | params.insert("timeout".to_string(), COUCH_MAX_TIMEOUT.to_string()); 31 | params.insert("limit".to_string(), limit.to_string()); 32 | let client = Client::builder() 33 | .timeout(Duration::from_secs(120)) 34 | .build() 35 | .unwrap(); 36 | Self { 37 | params, 38 | last_seq, 39 | limit, 40 | client, 41 | } 42 | } 43 | 44 | pub fn should_wait(&self, last_result_count: usize) -> bool { 45 | last_result_count < (self.limit / 2) 46 | } 47 | 48 | pub async fn fetch_next(&mut self) -> ChangeStreamResult { 49 | self.params 50 | .insert("since".to_string(), self.last_seq.to_string()); 51 | let request = self 52 | .client 53 | .request(Method::GET, "https://replicate.npmjs.com/registry/_changes") 54 | .query(&self.params); 55 | // println!("{:?}", request); 56 | let res = request.send().await?; 57 | if !res.status().is_success() { 58 | return Err(ChangeStreamError::new( 59 | res.status().into(), 60 | Some(res.text().await.unwrap_or_else(|_| String::from(""))), 61 | )); 62 | } 63 | let page: ChangesPage = res.json().await?; 64 | self.last_seq = page.last_seq.into(); 65 | Ok(page) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/npm_replicator/error.rs: -------------------------------------------------------------------------------- 1 | use reqwest::StatusCode; 2 | 3 | #[derive(Clone, Debug)] 4 | pub struct ChangeStreamError { 5 | pub status: u16, 6 | pub message: Option, 7 | } 8 | 9 | impl ChangeStreamError { 10 | pub fn new(status: u16, message: Option) -> Self { 11 | Self { status, message } 12 | } 13 | } 14 | 15 | impl From for ChangeStreamError { 16 | fn from(e: reqwest::Error) -> Self { 17 | ChangeStreamError::new( 18 | e.status() 19 | .unwrap_or(StatusCode::INTERNAL_SERVER_ERROR) 20 | .into(), 21 | Some(e.to_string()), 22 | ) 23 | } 24 | } 25 | 26 | pub type ChangeStreamResult = Result; 27 | -------------------------------------------------------------------------------- /src/npm_replicator/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod changes; 2 | pub mod types; 3 | pub mod replication_task; 4 | pub mod error; 5 | pub mod registry; 6 | -------------------------------------------------------------------------------- /src/npm_replicator/registry.rs: -------------------------------------------------------------------------------- 1 | use std::{num::NonZeroUsize, path::PathBuf, sync::Arc}; 2 | 3 | use lru::LruCache; 4 | use parking_lot::Mutex; 5 | use rocksdb::DB; 6 | 7 | use crate::{ 8 | app_error::{AppResult, ServerError}, 9 | npm::package_data::download_pkg_metadata, 10 | utils::{msgpack::serialize_msgpack, time::secs_since_epoch}, 11 | }; 12 | 13 | use super::types::document::MinimalPackageData; 14 | 15 | #[derive(Clone, Debug)] 16 | pub struct NpmRocksDB { 17 | pub db_path: PathBuf, 18 | db: Arc>, 19 | cache: Arc>>>, 20 | } 21 | 22 | impl NpmRocksDB { 23 | pub fn new(db_path: &str) -> Self { 24 | let db = DB::open_default(db_path).unwrap(); 25 | let cache = LruCache::new(NonZeroUsize::new(500).unwrap()); 26 | 27 | Self { 28 | db_path: PathBuf::from(db_path), 29 | db: Arc::new(Mutex::new(db)), 30 | cache: Arc::new(Mutex::new(cache)), 31 | } 32 | } 33 | 34 | #[tracing::instrument(name = "npm_db_get_last_seq", level = "debug", skip(self))] 35 | pub fn get_last_seq(&self) -> AppResult { 36 | if let Some(result) = self.db.lock().get(b"#CDN_LAST_SYNC").unwrap() { 37 | Ok(i64::from_le_bytes( 38 | result[..] 39 | .try_into() 40 | .expect("last sync invalid byte length"), 41 | )) 42 | } else { 43 | Ok(0) 44 | } 45 | } 46 | 47 | #[tracing::instrument(name = "npm_db_update_last_seq", level = "debug", skip(self))] 48 | pub fn update_last_seq(&self, next_seq: i64) -> AppResult { 49 | self.db 50 | .lock() 51 | .put(b"#CDN_LAST_SYNC", next_seq.to_le_bytes()) 52 | .unwrap(); 53 | Ok(1) 54 | } 55 | 56 | #[tracing::instrument(name = "npm_db_delete_package", level = "debug", skip(self))] 57 | pub fn delete_package(&self, pkg_name: &str) -> AppResult { 58 | self.db.lock().delete(pkg_name.as_bytes()).unwrap(); 59 | Ok(1) 60 | } 61 | 62 | #[tracing::instrument(name = "npm_db_write_package", level = "debug", skip(self, pkg), fields(pkg_name = pkg.name.as_str()))] 63 | pub fn write_package(&self, pkg: MinimalPackageData) -> AppResult { 64 | if pkg.versions.is_empty() { 65 | println!("Tried to write pkg {}, but has no versions", pkg.name); 66 | return self.delete_package(&pkg.name); 67 | } 68 | 69 | let pkg_name = pkg.name.clone(); 70 | let content = serialize_msgpack(&pkg)?; 71 | 72 | { 73 | self.db.lock().put(pkg_name.as_bytes(), content).unwrap(); 74 | } 75 | 76 | { 77 | let mut cache = self.cache.lock(); 78 | cache.pop(&pkg_name); 79 | } 80 | 81 | Ok(1) 82 | } 83 | 84 | #[tracing::instrument(name = "npm_db_get_package", level = "debug", skip(self))] 85 | pub fn get_package(&self, pkg_name: &str) -> AppResult> { 86 | { 87 | let mut cache = self.cache.lock(); 88 | let cached_value = cache.get(pkg_name); 89 | if let Some(pkg_data) = cached_value { 90 | tracing::debug!("NPM Cache hit"); 91 | return Ok(pkg_data.clone()); 92 | } 93 | }; 94 | 95 | let content_val: Option> = { 96 | let span = tracing::span!(tracing::Level::DEBUG, "db_get_pkg").entered(); 97 | let result = self.db.lock().get(pkg_name.as_bytes()).unwrap(); 98 | span.exit(); 99 | result 100 | }; 101 | 102 | if let Some(pkg_content) = content_val { 103 | let span = tracing::span!(tracing::Level::DEBUG, "parse_pkg").entered(); 104 | let found_pkg: MinimalPackageData = rmp_serde::from_slice(&pkg_content)?; 105 | span.exit(); 106 | 107 | let span = tracing::span!(tracing::Level::DEBUG, "write_cached_pkg").entered(); 108 | let mut cache = self.cache.lock(); 109 | let wrapped_pkg = Arc::new(found_pkg); 110 | cache.put(pkg_name.to_string(), wrapped_pkg.clone()); 111 | span.exit(); 112 | 113 | Ok(wrapped_pkg) 114 | } else { 115 | Err(crate::app_error::ServerError::PackageNotFound( 116 | pkg_name.to_string(), 117 | )) 118 | } 119 | } 120 | 121 | pub async fn fetch_missing_pkg(&mut self, pkg_name: &str) -> Result<(), ServerError> { 122 | let mut should_fetch = false; 123 | match self.get_package(pkg_name) { 124 | Ok(pkg) => { 125 | if pkg.last_updated.is_none() { 126 | should_fetch = true; 127 | } else { 128 | let last_updated = pkg.last_updated.unwrap(); 129 | let now = secs_since_epoch(); 130 | let diff = now - last_updated; 131 | if diff > 60 { 132 | should_fetch = true; 133 | } 134 | } 135 | } 136 | Err(err) => match err { 137 | ServerError::PackageNotFound(_) => { 138 | should_fetch = true; 139 | } 140 | _ => { 141 | return Err(err); 142 | } 143 | }, 144 | } 145 | 146 | if should_fetch { 147 | let metadata = download_pkg_metadata(pkg_name).await?; 148 | let pkg = MinimalPackageData::from_registry_meta(metadata); 149 | self.write_package(pkg)?; 150 | } 151 | 152 | Ok(()) 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/npm_replicator/replication_task.rs: -------------------------------------------------------------------------------- 1 | use super::registry::NpmRocksDB; 2 | use crate::app_error::AppResult; 3 | use crate::npm::package_data::download_pkg_metadata; 4 | use crate::npm_replicator::changes::ChangesStream; 5 | use crate::npm_replicator::types::changes::Event::Change; 6 | use crate::npm_replicator::types::document::MinimalPackageData; 7 | 8 | use std::time::Duration; 9 | use tokio::time::sleep; 10 | 11 | const FINISHED_DEBOUNCE: u64 = 60000; 12 | 13 | async fn sync(db: NpmRocksDB) -> AppResult<()> { 14 | let last_seq: i64 = db.get_last_seq()?; 15 | println!("[NPM-Replication] Last synced sequence {}", last_seq); 16 | let mut stream = ChangesStream::new(50, last_seq.into()); 17 | loop { 18 | match stream.fetch_next().await { 19 | Ok(page) => { 20 | let result_count = { page.results.len() }; 21 | for entry in page.results { 22 | if let Change(evt) = entry { 23 | if evt.deleted { 24 | db.delete_package(&evt.id)?; 25 | println!("[NPM-Replication] Deleted package {}", evt.id); 26 | } else if let Some(doc) = evt.doc { 27 | println!("[NPM-Replication] Fetching package {} from npm", evt.id); 28 | let metadata_result = download_pkg_metadata(&doc.id).await; 29 | match metadata_result { 30 | Ok(metadata) => { 31 | let pkg: MinimalPackageData = 32 | MinimalPackageData::from_registry_meta(metadata); 33 | db.write_package(pkg)?; 34 | println!("[NPM-Replication] Wrote package {} to db", evt.id); 35 | } 36 | Err(_err) => { 37 | db.delete_package(&evt.id)?; 38 | println!("[NPM-Replication] Package {} does not seem to exist, removing it", evt.id); 39 | } 40 | } 41 | } 42 | } 43 | } 44 | 45 | println!("[NPM-Replication] Updated last seq to {}", page.last_seq); 46 | db.update_last_seq(page.last_seq)?; 47 | 48 | if stream.should_wait(result_count) { 49 | sleep(Duration::from_millis(FINISHED_DEBOUNCE)).await; 50 | } 51 | } 52 | Err(err) => { 53 | println!("NPM Registry sync error {:?}", err); 54 | sleep(Duration::from_millis(FINISHED_DEBOUNCE)).await; 55 | } 56 | } 57 | } 58 | } 59 | 60 | pub fn spawn_sync_thread(db: NpmRocksDB) { 61 | println!("[NPM-Replication] Spawning npm sync worker..."); 62 | tokio::task::spawn(async move { 63 | if let Err(err) = sync(db).await { 64 | println!("[NPM-Replication] SYNC WORKER CRASHED {:?}", err); 65 | sleep(Duration::from_millis(500)).await; 66 | } 67 | }); 68 | } 69 | -------------------------------------------------------------------------------- /src/npm_replicator/types/changes.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use serde_with::{serde_as, DefaultOnError}; 3 | 4 | use super::document::RegistryDocument; 5 | 6 | #[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone)] 7 | #[serde(untagged)] 8 | pub enum Event { 9 | Change(ChangeEvent), 10 | Finished(FinishedEvent), 11 | } 12 | 13 | #[serde_as] 14 | #[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone)] 15 | pub struct ChangeEvent { 16 | pub seq: serde_json::Value, 17 | pub id: String, 18 | pub changes: Vec, 19 | 20 | #[serde(default)] 21 | pub deleted: bool, 22 | 23 | #[serde(default)] 24 | #[serde_as(deserialize_as = "DefaultOnError")] 25 | pub doc: Option, 26 | } 27 | 28 | #[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone)] 29 | pub struct Change { 30 | pub rev: String, 31 | } 32 | 33 | // Don't think we actually need this but couch_rs uses it so let's roll with it 34 | #[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone)] 35 | pub struct FinishedEvent { 36 | pub last_seq: serde_json::Value, 37 | pub pending: Option, // not available on CouchDB 1.0 38 | } 39 | 40 | #[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone)] 41 | pub struct ChangesPage { 42 | pub results: Vec, 43 | pub last_seq: i64, 44 | } 45 | -------------------------------------------------------------------------------- /src/npm_replicator/types/document.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use serde_with::{serde_as, DefaultOnError}; 3 | use std::collections::BTreeMap; 4 | 5 | use crate::{npm::package_data::PackageMetadata, utils::time::secs_since_epoch}; 6 | 7 | #[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone)] 8 | pub struct DocumentPackageDist { 9 | pub tarball: String, 10 | } 11 | 12 | #[serde_as] 13 | #[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone)] 14 | pub struct DocumentPackageVersion { 15 | #[serde(default)] 16 | #[serde_as(deserialize_as = "DefaultOnError")] 17 | pub dependencies: Option>, 18 | #[serde(default, rename = "optionalDependencies")] 19 | #[serde_as(deserialize_as = "DefaultOnError")] 20 | pub optional_dependencies: Option>, 21 | pub dist: DocumentPackageDist, 22 | } 23 | 24 | #[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone)] 25 | pub struct RegistryDocument { 26 | #[serde(rename = "_id")] 27 | pub id: String, 28 | 29 | #[serde(default, rename = "_deleted")] 30 | pub deleted: bool, 31 | 32 | #[serde(rename = "dist-tags")] 33 | pub dist_tags: Option>, 34 | 35 | pub versions: Option>, 36 | } 37 | 38 | #[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone)] 39 | pub struct MinimalPackageVersionData { 40 | pub tarball: String, 41 | pub dependencies: BTreeMap, 42 | } 43 | 44 | #[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone, Default)] 45 | pub struct MinimalPackageData { 46 | pub name: String, 47 | pub dist_tags: BTreeMap, 48 | pub versions: BTreeMap, 49 | // Seconds since the epoch 50 | pub last_updated: Option, 51 | } 52 | 53 | impl MinimalPackageData { 54 | pub fn from_registry_meta(raw: PackageMetadata) -> MinimalPackageData { 55 | let mut data = MinimalPackageData { 56 | name: raw.name, 57 | dist_tags: raw.dist_tags, 58 | versions: BTreeMap::new(), 59 | last_updated: Some(secs_since_epoch()), 60 | }; 61 | for (key, value) in raw.versions { 62 | data.versions.insert( 63 | key, 64 | MinimalPackageVersionData { 65 | tarball: value.dist.tarball, 66 | dependencies: value.dependencies, 67 | }, 68 | ); 69 | } 70 | data 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/npm_replicator/types/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod changes; 2 | pub mod document; 3 | -------------------------------------------------------------------------------- /src/package/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod process; 2 | -------------------------------------------------------------------------------- /src/package/process.rs: -------------------------------------------------------------------------------- 1 | use node_semver::Version; 2 | 3 | use crate::app_error::ServerError; 4 | 5 | // Used for parsing specifiers with ranges 6 | pub fn parse_package_specifier_no_validation( 7 | package_specifier: &str, 8 | ) -> Result<(String, String), ServerError> { 9 | let mut parts: Vec<&str> = package_specifier.split('@').collect(); 10 | let package_version_opt = parts.pop(); 11 | if let Some(package_version) = package_version_opt { 12 | if parts.len() > 2 { 13 | return Err(ServerError::InvalidPackageSpecifier); 14 | } 15 | 16 | let package_name = parts.join("@"); 17 | Ok(( 18 | String::from(package_name.trim()), 19 | String::from(package_version.trim()), 20 | )) 21 | } else { 22 | Err(ServerError::InvalidPackageSpecifier) 23 | } 24 | } 25 | 26 | // Used for parsing specifiers that are exact versions, ensures version is valid semver 27 | pub fn parse_package_specifier(package_specifier: &str) -> Result<(String, String), ServerError> { 28 | let (name, version) = parse_package_specifier_no_validation(package_specifier)?; 29 | Version::parse(&version)?; 30 | Ok((name, version)) 31 | } 32 | 33 | #[cfg(test)] 34 | mod tests { 35 | use super::*; 36 | 37 | #[test] 38 | fn simple_version() { 39 | let (pkg_name, pkg_version) = parse_package_specifier("foo@1.2.3").unwrap(); 40 | assert_eq!(pkg_name, "foo"); 41 | assert_eq!(pkg_version, "1.2.3"); 42 | } 43 | 44 | #[test] 45 | fn simple_version_range_scoped() { 46 | let (pkg_name, pkg_version) = 47 | parse_package_specifier_no_validation("@types/react-dom@^1.2.3").unwrap(); 48 | assert_eq!(pkg_name, "@types/react-dom"); 49 | assert_eq!(pkg_version, "^1.2.3"); 50 | } 51 | 52 | #[test] 53 | fn version_range_whitespace() { 54 | let (pkg_name, pkg_version) = 55 | parse_package_specifier_no_validation("@types/dom @ 1 - 2 ").unwrap(); 56 | assert_eq!(pkg_name, "@types/dom"); 57 | assert_eq!(pkg_version, "1 - 2"); 58 | } 59 | 60 | #[test] 61 | fn larger_than_range() { 62 | let (pkg_name, pkg_version) = 63 | parse_package_specifier_no_validation("@code-sandbox_/test@ >=4 ").unwrap(); 64 | assert_eq!(pkg_name, "@code-sandbox_/test"); 65 | assert_eq!(pkg_version, ">=4"); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/router/custom_reply.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use serde::Serialize; 4 | use warp::{ 5 | http::HeaderValue, 6 | hyper::{header::HeaderName, StatusCode}, 7 | reply::Response, 8 | Reply, 9 | }; 10 | 11 | use crate::{app_error::ServerError, utils::msgpack::serialize_msgpack}; 12 | 13 | pub struct CustomReply { 14 | body: Vec, 15 | status: StatusCode, 16 | headers: HashMap, 17 | } 18 | 19 | impl CustomReply { 20 | pub fn json(value: &T) -> Result 21 | where 22 | T: Serialize, 23 | { 24 | let mut reply = CustomReply { 25 | body: serde_json::to_vec(value)?, 26 | status: StatusCode::OK, 27 | headers: HashMap::new(), 28 | }; 29 | reply.add_header("content-type", "application/json"); 30 | Ok(reply) 31 | } 32 | 33 | pub fn msgpack(value: &T) -> Result 34 | where 35 | T: Serialize, 36 | { 37 | let buf = serialize_msgpack(value)?; 38 | let mut reply = CustomReply { 39 | body: buf, 40 | status: StatusCode::OK, 41 | headers: HashMap::new(), 42 | }; 43 | // It should really be application/msgpack but 44 | // this is a hack to get cloudflare to encode it 45 | // using gzip/brotli 46 | reply.add_header("content-type", "application/javascript"); 47 | Ok(reply) 48 | } 49 | 50 | pub fn add_header(&mut self, name: &str, value: &str) { 51 | self.headers.insert(name.to_string(), value.to_string()); 52 | } 53 | 54 | pub fn set_status(&mut self, status: StatusCode) { 55 | self.status = status; 56 | } 57 | } 58 | 59 | impl Reply for CustomReply { 60 | #[inline] 61 | fn into_response(self) -> Response { 62 | let mut response = Response::new(self.body.into()); 63 | *response.status_mut() = self.status; 64 | for (key, value) in self.headers { 65 | response.headers_mut().insert( 66 | HeaderName::try_from(key.as_str()).unwrap(), 67 | HeaderValue::try_from(value.as_str()).unwrap(), 68 | ); 69 | } 70 | response 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/router/error_reply.rs: -------------------------------------------------------------------------------- 1 | use serde::{self, Deserialize, Serialize}; 2 | use warp::hyper::StatusCode; 3 | 4 | use crate::app_error::ServerError; 5 | 6 | use super::custom_reply::CustomReply; 7 | 8 | #[derive(Clone, Serialize, Deserialize)] 9 | pub struct ErrorReply { 10 | status: u16, 11 | message: String, 12 | details: String, 13 | } 14 | 15 | impl ErrorReply { 16 | pub fn new(status: u16, message: String, details: String) -> Self { 17 | ErrorReply { 18 | status, 19 | message, 20 | details, 21 | } 22 | } 23 | 24 | pub fn as_reply(&self, cache_ttl: u32) -> Result { 25 | let mut reply = CustomReply::json(self)?; 26 | reply.set_status(StatusCode::from_u16(self.status)?); 27 | reply.add_header( 28 | "Cache-Control", 29 | format!("public, max-age={}", cache_ttl).as_str(), 30 | ); 31 | reply.add_header( 32 | "CDN-Cache-Control", 33 | format!("max-age={}", cache_ttl).as_str(), 34 | ); 35 | Ok(reply) 36 | } 37 | } 38 | 39 | impl From for ErrorReply { 40 | fn from(err: ServerError) -> Self { 41 | ErrorReply::new(500, format!("{}", err), format!("{:?}", err)) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/router/health.rs: -------------------------------------------------------------------------------- 1 | use warp::{Filter, Rejection, Reply}; 2 | 3 | pub async fn health_route_handler() -> Result { 4 | Ok("ok") 5 | } 6 | 7 | pub fn health_route() -> impl Filter + Clone { 8 | warp::path!("health") 9 | .and(warp::get()) 10 | .and_then(health_route_handler) 11 | } 12 | -------------------------------------------------------------------------------- /src/router/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod routes; 2 | mod custom_reply; 3 | mod error_reply; 4 | mod health; 5 | mod utils; 6 | mod routes_v2; 7 | -------------------------------------------------------------------------------- /src/router/routes.rs: -------------------------------------------------------------------------------- 1 | use warp::{Filter, Rejection, Reply}; 2 | 3 | use crate::npm::package_content::PackageContentFetcher; 4 | use crate::npm_replicator::registry::NpmRocksDB; 5 | 6 | use super::error_reply::ErrorReply; 7 | use super::health::health_route; 8 | use super::routes_v2::route_deps::deps_route; 9 | use super::routes_v2::route_mod::mod_route; 10 | use super::routes_v2::route_npm_status::npm_sync_status_route; 11 | 12 | pub fn routes( 13 | npm_db: NpmRocksDB, 14 | ) -> impl Filter + Clone { 15 | // 15 minutes refresh interval and 1 day ttl 16 | let pkg_content_fetcher = PackageContentFetcher::new(); 17 | 18 | mod_route(npm_db.clone(), pkg_content_fetcher) 19 | .or(deps_route(npm_db.clone())) 20 | .or(npm_sync_status_route(npm_db)) 21 | .or(health_route()) 22 | .or(not_found_route()) 23 | } 24 | 25 | pub fn with_data( 26 | data: T, 27 | ) -> impl Filter + Clone 28 | where 29 | T: Clone + std::marker::Send, 30 | { 31 | warp::any().map(move || data.clone()) 32 | } 33 | 34 | pub async fn not_found_handler() -> Result { 35 | Ok( 36 | ErrorReply::new(404, "Not found".to_string(), "Not found".to_string()) 37 | .as_reply(300) 38 | .unwrap(), 39 | ) 40 | } 41 | 42 | pub fn not_found_route() -> impl Filter + Clone 43 | { 44 | warp::any().and_then(not_found_handler) 45 | } 46 | -------------------------------------------------------------------------------- /src/router/routes_v2/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod route_mod; 2 | pub mod route_deps; 3 | pub mod route_npm_status; 4 | -------------------------------------------------------------------------------- /src/router/routes_v2/route_deps.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | 3 | use warp::{Filter, Rejection, Reply}; 4 | 5 | use crate::app_error::{AppResult, ServerError}; 6 | use crate::npm::dep_tree_builder::{DepRequest, DepTreeBuilder, ResolutionsMap}; 7 | use crate::npm_replicator::registry::NpmRocksDB; 8 | use crate::package::process::parse_package_specifier_no_validation; 9 | use crate::router::utils::decode_base64; 10 | 11 | use super::super::custom_reply::CustomReply; 12 | use super::super::error_reply::ErrorReply; 13 | use super::super::routes::with_data; 14 | 15 | fn parse_query(query: String) -> Result, ServerError> { 16 | let parts = query.split(';'); 17 | let mut dep_requests: HashSet = HashSet::new(); 18 | for part in parts { 19 | let (name, version) = parse_package_specifier_no_validation(part)?; 20 | let versions = version.split(','); 21 | for version in versions { 22 | dep_requests.insert(DepRequest::from_name_version( 23 | name.clone(), 24 | version.to_string(), 25 | )?); 26 | } 27 | } 28 | Ok(dep_requests) 29 | } 30 | 31 | async fn get_reply( 32 | path: String, 33 | npm_db: NpmRocksDB, 34 | is_json: bool, 35 | ) -> Result { 36 | let decoded_query = decode_base64(&path)?; 37 | let dep_requests = parse_query(decoded_query)?; 38 | 39 | let mut res_map: Option = None; 40 | let mut last_failed_pkg_name: Option = None; 41 | for _idx in 0..100 { 42 | let cloned_dep_requests = dep_requests.clone(); 43 | let cloned_npm_db = npm_db.clone(); 44 | let result: AppResult = tokio::task::spawn_blocking(move || { 45 | let mut tree_builder = DepTreeBuilder::new(cloned_npm_db); 46 | tree_builder.resolve_tree(cloned_dep_requests)?; 47 | for (alias_key, alias_value) in tree_builder.aliases { 48 | if let Some(resolved_version) = tree_builder.resolutions.get(&alias_value) { 49 | tree_builder 50 | .resolutions 51 | .insert(alias_key, resolved_version.clone()); 52 | } 53 | } 54 | Ok(tree_builder.resolutions) 55 | }) 56 | .await?; 57 | 58 | match result { 59 | Ok(data) => { 60 | res_map = Some(data); 61 | break; 62 | } 63 | 64 | Err(err) => { 65 | let mut cloned_npm_db = npm_db.clone(); 66 | let new_pkg_name; 67 | match err { 68 | ServerError::PackageVersionNotFound(pkg_name, _) => { 69 | new_pkg_name = pkg_name; 70 | } 71 | ServerError::PackageNotFound(pkg_name) => { 72 | new_pkg_name = pkg_name; 73 | } 74 | err => { 75 | return Err(err); 76 | } 77 | } 78 | 79 | if new_pkg_name.len() > 0 { 80 | if Some(new_pkg_name.clone()) == last_failed_pkg_name { 81 | return Err(ServerError::PackageNotFound(new_pkg_name)); 82 | } 83 | last_failed_pkg_name = Some(new_pkg_name.clone()); 84 | cloned_npm_db.fetch_missing_pkg(&new_pkg_name).await?; 85 | } 86 | } 87 | } 88 | } 89 | 90 | if res_map == None { 91 | return Err(ServerError::PackageNotFound( 92 | last_failed_pkg_name.unwrap_or("unknown".to_string()), 93 | )); 94 | } 95 | 96 | let mut reply = match is_json { 97 | true => CustomReply::json(&res_map)?, 98 | false => CustomReply::msgpack(&res_map)?, 99 | }; 100 | let cache_ttl = 3600; 101 | reply.add_header( 102 | "Cache-Control", 103 | format!("public, max-age={}", cache_ttl).as_str(), 104 | ); 105 | reply.add_header( 106 | "CDN-Cache-Control", 107 | format!("max-age={}", cache_ttl).as_str(), 108 | ); 109 | Ok(reply) 110 | } 111 | 112 | async fn deps_route_handler( 113 | path: String, 114 | npm_db: NpmRocksDB, 115 | is_json: bool, 116 | ) -> Result { 117 | match get_reply(path, npm_db, is_json).await { 118 | Ok(reply) => Ok(reply), 119 | Err(err) => Ok(ErrorReply::from(err).as_reply(300).unwrap()), 120 | } 121 | } 122 | 123 | fn json_route( 124 | npm_db: NpmRocksDB, 125 | ) -> impl Filter + Clone { 126 | warp::path!("v2" / "json" / "deps" / String) 127 | .and(warp::get()) 128 | .and(with_data(npm_db)) 129 | .and(with_data(true)) 130 | .and_then(deps_route_handler) 131 | } 132 | 133 | fn msgpack_route( 134 | npm_db: NpmRocksDB, 135 | ) -> impl Filter + Clone { 136 | warp::path!("v2" / "deps" / String) 137 | .and(warp::get()) 138 | .and(with_data(npm_db)) 139 | .and(with_data(false)) 140 | .and_then(deps_route_handler) 141 | } 142 | 143 | pub fn deps_route( 144 | npm_db: NpmRocksDB, 145 | ) -> impl Filter + Clone { 146 | json_route(npm_db.clone()).or(msgpack_route(npm_db)) 147 | } 148 | -------------------------------------------------------------------------------- /src/router/routes_v2/route_mod.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use serde_bytes::ByteBuf; 4 | use warp::{Filter, Rejection, Reply}; 5 | 6 | use crate::app_error::ServerError; 7 | use crate::npm::package_content::{download_package_content, FileMap, PackageContentFetcher}; 8 | use crate::npm_replicator::registry::NpmRocksDB; 9 | use crate::package::process::parse_package_specifier; 10 | use crate::router::utils::decode_base64; 11 | 12 | use super::super::custom_reply::CustomReply; 13 | use super::super::error_reply::ErrorReply; 14 | use super::super::routes::with_data; 15 | 16 | #[tracing::instrument(name = "get_files", skip(files))] 17 | async fn encode_files(files: FileMap) -> Result, ServerError> { 18 | let mut encoded_files: HashMap = HashMap::new(); 19 | for (filepath, content) in files.iter() { 20 | encoded_files.insert(filepath.clone(), ByteBuf::from(content.clone())); 21 | } 22 | Ok(encoded_files) 23 | } 24 | 25 | #[tracing::instrument(name = "create_files_reply", skip(files))] 26 | async fn create_reply(files: FileMap) -> Result { 27 | let files = encode_files(files).await?; 28 | let mut reply = CustomReply::msgpack(&files)?; 29 | let cache_ttl = 365 * 24 * 3600; 30 | reply.add_header( 31 | "Cache-Control", 32 | format!("public, max-age={}", cache_ttl).as_str(), 33 | ); 34 | reply.add_header( 35 | "CDN-Cache-Control", 36 | format!("max-age={}", cache_ttl).as_str(), 37 | ); 38 | Ok(reply) 39 | } 40 | 41 | pub async fn get_mod_reply( 42 | path: String, 43 | npm_db: NpmRocksDB, 44 | pkg_content_fetcher: PackageContentFetcher, 45 | ) -> Result { 46 | let decoded_specifier = decode_base64(&path)?; 47 | let (pkg_name, pkg_version) = parse_package_specifier(&decoded_specifier)?; 48 | 49 | let content = 50 | download_package_content(&pkg_name, &pkg_version, &npm_db, &pkg_content_fetcher).await?; 51 | 52 | create_reply(content).await 53 | } 54 | 55 | pub async fn mod_route_handler( 56 | path: String, 57 | npm_db: NpmRocksDB, 58 | pkg_content_fetcher: PackageContentFetcher, 59 | ) -> Result { 60 | match get_mod_reply(path, npm_db, pkg_content_fetcher).await { 61 | Ok(reply) => Ok(reply), 62 | Err(err) => Ok(ErrorReply::from(err).as_reply(300).unwrap()), 63 | } 64 | } 65 | 66 | pub fn mod_route( 67 | npm_db: NpmRocksDB, 68 | pkg_content_fetcher: PackageContentFetcher, 69 | ) -> impl Filter + Clone { 70 | warp::path!("v2" / "mod" / String) 71 | .and(warp::get()) 72 | .and(with_data(npm_db)) 73 | .and(with_data(pkg_content_fetcher)) 74 | .and_then(mod_route_handler) 75 | } 76 | -------------------------------------------------------------------------------- /src/router/routes_v2/route_npm_status.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use warp::{Filter, Rejection, Reply}; 3 | 4 | use crate::app_error::{AppResult, ServerError}; 5 | use crate::npm_replicator::registry::NpmRocksDB; 6 | 7 | use super::super::custom_reply::CustomReply; 8 | use super::super::error_reply::ErrorReply; 9 | use super::super::routes::with_data; 10 | 11 | #[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone)] 12 | struct NpmSyncStatus { 13 | last_seq: i64, 14 | } 15 | 16 | async fn get_reply(npm_db: NpmRocksDB) -> Result { 17 | let status: AppResult = tokio::task::spawn_blocking(move || { 18 | let last_seq = npm_db.get_last_seq()?; 19 | 20 | Ok(NpmSyncStatus { last_seq }) 21 | }) 22 | .await?; 23 | 24 | let mut reply = CustomReply::json(&status?)?; 25 | let cache_ttl = 300; 26 | reply.add_header( 27 | "Cache-Control", 28 | format!("public, max-age={}", cache_ttl).as_str(), 29 | ); 30 | Ok(reply) 31 | } 32 | 33 | async fn route_handler(npm_db: NpmRocksDB) -> Result { 34 | match get_reply(npm_db).await { 35 | Ok(reply) => Ok(reply), 36 | Err(err) => Ok(ErrorReply::from(err).as_reply(300).unwrap()), 37 | } 38 | } 39 | 40 | pub fn npm_sync_status_route( 41 | npm_db: NpmRocksDB, 42 | ) -> impl Filter + Clone { 43 | warp::path!("v2" / "npm_sync_status") 44 | .and(warp::get()) 45 | .and(with_data(npm_db)) 46 | .and_then(route_handler) 47 | } 48 | -------------------------------------------------------------------------------- /src/router/utils.rs: -------------------------------------------------------------------------------- 1 | use crate::app_error::ServerError; 2 | 3 | pub fn decode_base64(part: &str) -> Result { 4 | let decoded = base64_simd::STANDARD 5 | .decode_to_vec(part.as_bytes()) 6 | .map_err(|_e| ServerError::Base64DecodingError())?; 7 | let val = String::from_utf8(decoded).map_err(|_e| ServerError::Base64DecodingError())?; 8 | Ok(val) 9 | } 10 | -------------------------------------------------------------------------------- /src/setup_tracing.rs: -------------------------------------------------------------------------------- 1 | use opentelemetry::sdk::{trace as sdktrace, Resource}; 2 | use opentelemetry::KeyValue; 3 | use opentelemetry_otlp::WithExportConfig; 4 | use opentelemetry_semantic_conventions; 5 | use std::collections::HashMap; 6 | use std::env; 7 | use tracing_subscriber::filter::LevelFilter; 8 | use tracing_subscriber::layer::SubscriberExt; 9 | use tracing_subscriber::Registry; 10 | use tracing_tree::HierarchicalLayer; 11 | 12 | const HEADER_PREFIX: &str = "OTEL_METADATA_"; 13 | 14 | // Used environment variables 15 | // OTEL_METADATA_AUTHORIZATION = otel collector basic auth 16 | // OTEL_EXPORTER_OTLP_ENDPOINT = http://otel-collector.csbops.io 17 | fn init_opentelemetry() -> Option { 18 | let mut headers: HashMap = HashMap::new(); 19 | for (key, value) in env::vars() 20 | .filter(|(name, _)| name.starts_with(HEADER_PREFIX)) 21 | .map(|(name, value)| { 22 | let header_name = name 23 | .strip_prefix(HEADER_PREFIX) 24 | .map(|h| h.replace('_', "-")) 25 | .map(|h| h.to_ascii_lowercase()) 26 | .unwrap(); 27 | (header_name, value) 28 | }) 29 | { 30 | println!("Found tracing metadata env variable: {}", key); 31 | headers.insert(key, value.parse().unwrap()); 32 | } 33 | 34 | if let Err(_err) = env::var("OTEL_EXPORTER_OTLP_ENDPOINT") { 35 | println!("env variable OTEL_EXPORTER_OTLP_ENDPOINT has not been set"); 36 | return None; 37 | } 38 | 39 | if let Err(_err) = env::var("OTEL_SERVICE_NAME") { 40 | println!("env variable OTEL_SERVICE_NAME has not been set, falling back to sandpack-cdn as the service name"); 41 | env::set_var("OTEL_SERVICE_NAME", "sandpack-cdn"); 42 | } 43 | 44 | // First, create a OTLP exporter builder. 45 | let exporter = opentelemetry_otlp::new_exporter() 46 | .http() 47 | .with_headers(headers) 48 | .with_endpoint(env::var("OTEL_EXPORTER_OTLP_ENDPOINT").unwrap()) 49 | .with_env(); 50 | 51 | match opentelemetry_otlp::new_pipeline() 52 | .tracing() 53 | .with_exporter(exporter) 54 | .with_trace_config(sdktrace::config().with_resource(Resource::new(vec![ 55 | KeyValue::new( 56 | opentelemetry_semantic_conventions::resource::SERVICE_NAME, 57 | env::var("OTEL_SERVICE_NAME").unwrap(), 58 | ), 59 | KeyValue::new( 60 | opentelemetry_semantic_conventions::resource::DEPLOYMENT_ENVIRONMENT, 61 | env::var("ENVIRONMENT").expect("Please provide the env var ENVIRONMENT"), 62 | ), 63 | ]))) 64 | .install_batch(opentelemetry::runtime::Tokio) 65 | { 66 | Ok(v) => Some(v), 67 | Err(err) => { 68 | println!("Failed to setup tracing {:?}", err); 69 | None 70 | } 71 | } 72 | } 73 | 74 | pub fn setup_tracing() { 75 | // NOTE: the underlying subscriber MUST be the Registry subscriber 76 | let subscriber = Registry::default() // provide underlying span data store 77 | .with(LevelFilter::INFO); // filter out low-level debug tracing (eg tokio executor) 78 | 79 | // Install a new OpenTelemetry trace pipeline 80 | let tracer_res = init_opentelemetry(); 81 | match tracer_res { 82 | Some(tracer) => { 83 | // Create a tracing layer with the configured tracer 84 | let telemetry_layer = tracing_opentelemetry::layer().with_tracer(tracer); 85 | let new_subscriber = subscriber.with(telemetry_layer); 86 | tracing::subscriber::set_global_default(new_subscriber).unwrap(); 87 | } 88 | None => { 89 | println!("Could not setup open telemetry, falling back to tracing_tree"); 90 | let new_subscriber = subscriber.with( 91 | HierarchicalLayer::new(2) 92 | .with_targets(true) 93 | .with_bracketed_fields(true), 94 | ); 95 | tracing::subscriber::set_global_default(new_subscriber).unwrap(); 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/utils/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod test_utils; 2 | pub mod msgpack; 3 | pub mod time; -------------------------------------------------------------------------------- /src/utils/msgpack.rs: -------------------------------------------------------------------------------- 1 | extern crate rmp_serde as rmps; 2 | 3 | use crate::app_error::ServerError; 4 | use serde::Serialize; 5 | 6 | pub fn serialize_msgpack(value: &T) -> Result, ServerError> 7 | where 8 | T: Serialize, 9 | { 10 | let mut buf = Vec::new(); 11 | let serializer = rmps::Serializer::new(&mut buf); 12 | value 13 | .serialize(&mut serializer.with_struct_map()) 14 | .map_err(|_e| ServerError::SerializeError())?; 15 | Ok(buf) 16 | } 17 | -------------------------------------------------------------------------------- /src/utils/test_utils.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::fs; 3 | 4 | use crate::app_error::ServerError; 5 | 6 | #[allow(dead_code)] 7 | pub fn read_fixture(fixture_name: &str) -> Result { 8 | let fixture_path = env::current_dir()?.join(fixture_name); 9 | let fixture_content: String = fs::read_to_string(fixture_path)?; 10 | Ok(fixture_content) 11 | } 12 | -------------------------------------------------------------------------------- /src/utils/time.rs: -------------------------------------------------------------------------------- 1 | use std::time::{SystemTime, UNIX_EPOCH}; 2 | 3 | pub fn secs_since_epoch() -> u64 { 4 | let start = SystemTime::now(); 5 | let since_the_epoch = start 6 | .duration_since(UNIX_EPOCH) 7 | .expect("Time went backwards"); 8 | since_the_epoch.as_secs() 9 | } 10 | --------------------------------------------------------------------------------