Auto build and run a React app within a Docker container without a Dockerfile
0
fork

Configure Feed

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

feat: open sourcing autopack!!

+4828
+77
.github/workflows/ci.yml
··· 1 + name: "CI" 2 + on: 3 + push: 4 + branches: 5 + - main 6 + pull_request: 7 + branches: 8 + - main 9 + 10 + jobs: 11 + ci: 12 + strategy: 13 + fail-fast: false 14 + matrix: 15 + rust_version: [stable] 16 + platform: 17 + - target: x86_64-unknown-linux-gnu 18 + os: ubuntu-latest 19 + - target: x86_64-apple-darwin 20 + os: macos-latest 21 + - target: x86_64-pc-windows-msvc 22 + os: windows-latest 23 + 24 + runs-on: ${{ matrix.platform.os }} 25 + 26 + steps: 27 + - uses: actions/checkout@v2 28 + - name: Install Rust Stable 29 + uses: actions-rs/toolchain@v1 30 + with: 31 + toolchain: stable 32 + target: ${{ matrix.platform.target }} 33 + override: true 34 + - name: Get current date 35 + run: echo "CURRENT_DATE=$(date +'%Y-%m-%d')" >> $GITHUB_ENV 36 + 37 + - name: Cache cargo registry 38 + uses: actions/cache@v2.1.4 39 + with: 40 + path: ~/.cargo/registry 41 + # Add date to the cache to keep it up to date 42 + key: ${{ matrix.platform.os }}-stable-cargo-registry-${{ hashFiles('Cargo.toml') }}-${{ env.CURRENT_DATE }} 43 + # Restore from outdated cache for speed 44 + restore-keys: | 45 + ${{ matrix.platform.os }}-stable-cargo-registry-${{ hashFiles('Cargo.toml') }} 46 + ${{ matrix.platform.os }}-stable-cargo-registry- 47 + 48 + - name: Cache cargo index 49 + uses: actions/cache@v2.1.4 50 + with: 51 + path: ~/.cargo/git 52 + # Add date to the cache to keep it up to date 53 + key: ${{ matrix.platform.os }}-stable-cargo-index-${{ hashFiles('Cargo.toml') }}-${{ env.CURRENT_DATE }} 54 + # Restore from outdated cache for speed 55 + restore-keys: | 56 + ${{ matrix.platform.os }}-stable-cargo-index-${{ hashFiles('Cargo.toml') }} 57 + ${{ matrix.platform.os }}-stable-cargo-index- 58 + 59 + - name: Cache cargo target 60 + uses: actions/cache@v2 61 + with: 62 + path: target 63 + # Add date to the cache to keep it up to date 64 + key: ${{ matrix.platform.os }}-stable-cargo-core-${{ hashFiles('Cargo.toml') }}-${{ env.CURRENT_DATE }} 65 + # Restore from outdated cache for speed 66 + restore-keys: | 67 + ${{ matrix.platform.os }}-stable-cargo-core-${{ hashFiles('Cargo.toml') }} 68 + ${{ matrix.platform.os }}-stable-cargo-core- 69 + 70 + - uses: actions-rs/cargo@v1 71 + with: 72 + command: build 73 + args: --target=${{ matrix.platform.target }} 74 + 75 + - name: Test autopack 76 + run: | 77 + cargo test -r --no-fail-fast --target ${{ matrix.platform.target }}
+88
.github/workflows/create-artefacts.yml
··· 1 + name: "Create Artefacts" 2 + on: 3 + push: 4 + tags: 5 + - "0.[0-9]+.[0-9]+" 6 + 7 + jobs: 8 + create-release: 9 + name: create-release 10 + runs-on: ubuntu-latest 11 + outputs: 12 + upload_url: ${{ steps.release.outputs.upload_url }} 13 + steps: 14 + - name: Create GitHub release 15 + id: release 16 + uses: actions/create-release@v1 17 + env: 18 + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 19 + with: 20 + tag_name: v${{ github.ref_name }} 21 + release_name: v${{ github.ref_name }} 22 + 23 + build-release: 24 + needs: ['create-release'] 25 + strategy: 26 + fail-fast: false 27 + matrix: 28 + rust_version: [stable] 29 + platform: 30 + - target: x86_64-unknown-linux-gnu 31 + os: ubuntu-latest 32 + npm_package_suffix: linux 33 + - target: x86_64-apple-darwin 34 + os: macos-latest 35 + npm_package_suffix: darwin 36 + - target: x86_64-pc-windows-msvc 37 + os: windows-latest 38 + npm_package_suffix: win 39 + 40 + runs-on: ${{ matrix.platform.os }} 41 + 42 + steps: 43 + - uses: actions/checkout@v2 44 + 45 + - name: Install Rust Stable 46 + uses: actions-rs/toolchain@v1 47 + with: 48 + toolchain: ${{ matrix.rust_version }} 49 + 50 + - name: Build 51 + uses: actions-rs/cargo@v1.0.3 52 + with: 53 + command: build 54 + args: --release --locked --target ${{ matrix.platform.target }} 55 + 56 + - name: Copy Artefacts | unix 57 + if: matrix.platform.os != 'windows-latest' 58 + shell: bash 59 + run: | 60 + staging="auto-pack-v${{ github.ref_name }}-${{ matrix.platform.target }}" 61 + mkdir -p "$staging" 62 + strip "target/${{ matrix.platform.target }}/release/auto-pack" || true 63 + cp "target/${{ matrix.platform.target }}/release/auto-pack" "$staging/" 64 + tar czf "$staging.tar.gz" "$staging" 65 + echo "ASSET=$staging.tar.gz" >> $GITHUB_ENV 66 + 67 + 68 + - name: Copy Artefacts | windows 69 + if: matrix.platform.os == 'windows-latest' 70 + shell: bash 71 + run: | 72 + staging="auto-pack-v${{ github.ref_name }}-${{ matrix.platform.target }}" 73 + mkdir -p "$staging" 74 + strip "target/${{ matrix.platform.target }}/release/auto-pack.exe" 75 + cp "target/${{ matrix.platform.target }}/release/auto-pack.exe" "$staging/" 76 + 7z a "$staging.zip" "$staging" 77 + echo "ASSET=$staging.zip" >> $GITHUB_ENV 78 + 79 + - name: Upload release archive 80 + uses: actions/upload-release-asset@v1.0.1 81 + env: 82 + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 83 + with: 84 + upload_url: ${{ needs.create-release.outputs.upload_url }} 85 + asset_path: ${{ env.ASSET }} 86 + asset_name: ${{ env.ASSET }} 87 + asset_content_type: application/octet-stream 88 +
+120
.gitignore
··· 1 + # Logs 2 + logs 3 + *.log 4 + npm-debug.log* 5 + yarn-debug.log* 6 + yarn-error.log* 7 + lerna-debug.log* 8 + 9 + # Diagnostic reports (https://nodejs.org/api/report.html) 10 + report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 + 12 + # Runtime data 13 + pids 14 + *.pid 15 + *.seed 16 + *.pid.lock 17 + 18 + # Directory for instrumented libs generated by jscoverage/JSCover 19 + lib-cov 20 + 21 + # Coverage directory used by tools like istanbul 22 + coverage 23 + *.lcov 24 + 25 + # nyc test coverage 26 + .nyc_output 27 + 28 + # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 + .grunt 30 + 31 + # Bower dependency directory (https://bower.io/) 32 + bower_components 33 + 34 + # node-waf configuration 35 + .lock-wscript 36 + 37 + # Compiled binary addons (https://nodejs.org/api/addons.html) 38 + build/Release 39 + 40 + # Dependency directories 41 + node_modules/ 42 + jspm_packages/ 43 + 44 + # TypeScript v1 declaration files 45 + typings/ 46 + 47 + # TypeScript cache 48 + *.tsbuildinfo 49 + 50 + # Optional npm cache directory 51 + .npm 52 + 53 + # Optional eslint cache 54 + .eslintcache 55 + 56 + # Microbundle cache 57 + .rpt2_cache/ 58 + .rts2_cache_cjs/ 59 + .rts2_cache_es/ 60 + .rts2_cache_umd/ 61 + 62 + # Optional REPL history 63 + .node_repl_history 64 + 65 + # Output of 'npm pack' 66 + *.tgz 67 + 68 + # Yarn Integrity file 69 + .yarn-integrity 70 + 71 + # dotenv environment variables file 72 + .env 73 + .env.test 74 + 75 + # parcel-bundler cache (https://parceljs.org/) 76 + .cache 77 + 78 + # Next.js build output 79 + .next 80 + 81 + # Nuxt.js build / generate output 82 + .nuxt 83 + dist 84 + 85 + # Gatsby files 86 + .cache/ 87 + # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 + # https://nextjs.org/blog/next-9-1#public-directory-support 89 + # public 90 + 91 + # vuepress build output 92 + .vuepress/dist 93 + 94 + # Serverless directories 95 + .serverless/ 96 + 97 + # FuseBox cache 98 + .fusebox/ 99 + 100 + # DynamoDB Local files 101 + .dynamodb/ 102 + 103 + # TernJS port file 104 + .tern-port 105 + 106 + ## RUST Stuff 107 + 108 + # Generated by Cargo 109 + /target 110 + /debug 111 + /artefacts 112 + 113 + # These are backup files generated by rustfmt 114 + **/*.rs.bk 115 + 116 + # MSVC Windows builds of rustc generate these, which store debugging information 117 + *.pdb 118 + 119 + /.autopack 120 + package.json
+1974
Cargo.lock
··· 1 + # This file is automatically @generated by Cargo. 2 + # It is not intended for manual editing. 3 + version = 3 4 + 5 + [[package]] 6 + name = "adler" 7 + version = "1.0.2" 8 + source = "registry+https://github.com/rust-lang/crates.io-index" 9 + checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 10 + 11 + [[package]] 12 + name = "aead" 13 + version = "0.5.1" 14 + source = "registry+https://github.com/rust-lang/crates.io-index" 15 + checksum = "5c192eb8f11fc081b0fe4259ba5af04217d4e0faddd02417310a927911abd7c8" 16 + dependencies = [ 17 + "crypto-common", 18 + "generic-array", 19 + ] 20 + 21 + [[package]] 22 + name = "aes" 23 + version = "0.7.5" 24 + source = "registry+https://github.com/rust-lang/crates.io-index" 25 + checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" 26 + dependencies = [ 27 + "cfg-if", 28 + "cipher 0.3.0", 29 + "cpufeatures", 30 + "opaque-debug", 31 + ] 32 + 33 + [[package]] 34 + name = "aes" 35 + version = "0.8.1" 36 + source = "registry+https://github.com/rust-lang/crates.io-index" 37 + checksum = "bfe0133578c0986e1fe3dfcd4af1cc5b2dd6c3dbf534d69916ce16a2701d40ba" 38 + dependencies = [ 39 + "cfg-if", 40 + "cipher 0.4.3", 41 + "cpufeatures", 42 + ] 43 + 44 + [[package]] 45 + name = "aes-gcm" 46 + version = "0.10.1" 47 + source = "registry+https://github.com/rust-lang/crates.io-index" 48 + checksum = "82e1366e0c69c9f927b1fa5ce2c7bf9eafc8f9268c0b9800729e8b267612447c" 49 + dependencies = [ 50 + "aead", 51 + "aes 0.8.1", 52 + "cipher 0.4.3", 53 + "ctr", 54 + "ghash", 55 + "subtle", 56 + ] 57 + 58 + [[package]] 59 + name = "ansi_term" 60 + version = "0.12.1" 61 + source = "registry+https://github.com/rust-lang/crates.io-index" 62 + checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" 63 + dependencies = [ 64 + "winapi", 65 + ] 66 + 67 + [[package]] 68 + name = "anyhow" 69 + version = "1.0.58" 70 + source = "registry+https://github.com/rust-lang/crates.io-index" 71 + checksum = "bb07d2053ccdbe10e2af2995a2f116c1330396493dc1269f6a91d0ae82e19704" 72 + 73 + [[package]] 74 + name = "atty" 75 + version = "0.2.14" 76 + source = "registry+https://github.com/rust-lang/crates.io-index" 77 + checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 78 + dependencies = [ 79 + "hermit-abi", 80 + "libc", 81 + "winapi", 82 + ] 83 + 84 + [[package]] 85 + name = "autocfg" 86 + version = "1.1.0" 87 + source = "registry+https://github.com/rust-lang/crates.io-index" 88 + checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 89 + 90 + [[package]] 91 + name = "autopack" 92 + version = "0.1.0" 93 + dependencies = [ 94 + "aes-gcm", 95 + "anyhow", 96 + "blake2", 97 + "clap", 98 + "dialoguer", 99 + "flate2", 100 + "futures-util", 101 + "hex", 102 + "indicatif", 103 + "libc", 104 + "npm-package-json", 105 + "open", 106 + "rand", 107 + "reqwest", 108 + "rmp-serde", 109 + "serde", 110 + "serde_json", 111 + "tar", 112 + "tempfile", 113 + "thiserror", 114 + "tokio", 115 + "toml", 116 + "tracing", 117 + "tracing-subscriber", 118 + "zip", 119 + ] 120 + 121 + [[package]] 122 + name = "base64" 123 + version = "0.13.0" 124 + source = "registry+https://github.com/rust-lang/crates.io-index" 125 + checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" 126 + 127 + [[package]] 128 + name = "base64ct" 129 + version = "1.0.1" 130 + source = "registry+https://github.com/rust-lang/crates.io-index" 131 + checksum = "8a32fd6af2b5827bce66c29053ba0e7c42b9dcab01835835058558c10851a46b" 132 + 133 + [[package]] 134 + name = "bitflags" 135 + version = "1.3.2" 136 + source = "registry+https://github.com/rust-lang/crates.io-index" 137 + checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 138 + 139 + [[package]] 140 + name = "blake2" 141 + version = "0.10.4" 142 + source = "registry+https://github.com/rust-lang/crates.io-index" 143 + checksum = "b9cf849ee05b2ee5fba5e36f97ff8ec2533916700fc0758d40d92136a42f3388" 144 + dependencies = [ 145 + "digest", 146 + ] 147 + 148 + [[package]] 149 + name = "block-buffer" 150 + version = "0.10.2" 151 + source = "registry+https://github.com/rust-lang/crates.io-index" 152 + checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" 153 + dependencies = [ 154 + "generic-array", 155 + ] 156 + 157 + [[package]] 158 + name = "bumpalo" 159 + version = "3.10.0" 160 + source = "registry+https://github.com/rust-lang/crates.io-index" 161 + checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3" 162 + 163 + [[package]] 164 + name = "byteorder" 165 + version = "1.4.3" 166 + source = "registry+https://github.com/rust-lang/crates.io-index" 167 + checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" 168 + 169 + [[package]] 170 + name = "bytes" 171 + version = "1.2.0" 172 + source = "registry+https://github.com/rust-lang/crates.io-index" 173 + checksum = "f0b3de4a0c5e67e16066a0715723abd91edc2f9001d09c46e1dca929351e130e" 174 + 175 + [[package]] 176 + name = "bzip2" 177 + version = "0.4.3" 178 + source = "registry+https://github.com/rust-lang/crates.io-index" 179 + checksum = "6afcd980b5f3a45017c57e57a2fcccbb351cc43a356ce117ef760ef8052b89b0" 180 + dependencies = [ 181 + "bzip2-sys", 182 + "libc", 183 + ] 184 + 185 + [[package]] 186 + name = "bzip2-sys" 187 + version = "0.1.11+1.0.8" 188 + source = "registry+https://github.com/rust-lang/crates.io-index" 189 + checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" 190 + dependencies = [ 191 + "cc", 192 + "libc", 193 + "pkg-config", 194 + ] 195 + 196 + [[package]] 197 + name = "cc" 198 + version = "1.0.73" 199 + source = "registry+https://github.com/rust-lang/crates.io-index" 200 + checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" 201 + dependencies = [ 202 + "jobserver", 203 + ] 204 + 205 + [[package]] 206 + name = "cfg-if" 207 + version = "1.0.0" 208 + source = "registry+https://github.com/rust-lang/crates.io-index" 209 + checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 210 + 211 + [[package]] 212 + name = "cipher" 213 + version = "0.3.0" 214 + source = "registry+https://github.com/rust-lang/crates.io-index" 215 + checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" 216 + dependencies = [ 217 + "generic-array", 218 + ] 219 + 220 + [[package]] 221 + name = "cipher" 222 + version = "0.4.3" 223 + source = "registry+https://github.com/rust-lang/crates.io-index" 224 + checksum = "d1873270f8f7942c191139cb8a40fd228da6c3fd2fc376d7e92d47aa14aeb59e" 225 + dependencies = [ 226 + "crypto-common", 227 + "inout", 228 + ] 229 + 230 + [[package]] 231 + name = "clap" 232 + version = "3.2.12" 233 + source = "registry+https://github.com/rust-lang/crates.io-index" 234 + checksum = "ab8b79fe3946ceb4a0b1c080b4018992b8d27e9ff363644c1c9b6387c854614d" 235 + dependencies = [ 236 + "atty", 237 + "bitflags", 238 + "clap_derive", 239 + "clap_lex", 240 + "indexmap", 241 + "once_cell", 242 + "strsim", 243 + "termcolor", 244 + "textwrap", 245 + ] 246 + 247 + [[package]] 248 + name = "clap_derive" 249 + version = "3.2.7" 250 + source = "registry+https://github.com/rust-lang/crates.io-index" 251 + checksum = "759bf187376e1afa7b85b959e6a664a3e7a95203415dba952ad19139e798f902" 252 + dependencies = [ 253 + "heck", 254 + "proc-macro-error", 255 + "proc-macro2", 256 + "quote", 257 + "syn", 258 + ] 259 + 260 + [[package]] 261 + name = "clap_lex" 262 + version = "0.2.4" 263 + source = "registry+https://github.com/rust-lang/crates.io-index" 264 + checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" 265 + dependencies = [ 266 + "os_str_bytes", 267 + ] 268 + 269 + [[package]] 270 + name = "console" 271 + version = "0.15.1" 272 + source = "registry+https://github.com/rust-lang/crates.io-index" 273 + checksum = "89eab4d20ce20cea182308bca13088fecea9c05f6776cf287205d41a0ed3c847" 274 + dependencies = [ 275 + "encode_unicode", 276 + "libc", 277 + "once_cell", 278 + "terminal_size", 279 + "unicode-width", 280 + "winapi", 281 + ] 282 + 283 + [[package]] 284 + name = "constant_time_eq" 285 + version = "0.1.5" 286 + source = "registry+https://github.com/rust-lang/crates.io-index" 287 + checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" 288 + 289 + [[package]] 290 + name = "core-foundation" 291 + version = "0.9.3" 292 + source = "registry+https://github.com/rust-lang/crates.io-index" 293 + checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" 294 + dependencies = [ 295 + "core-foundation-sys", 296 + "libc", 297 + ] 298 + 299 + [[package]] 300 + name = "core-foundation-sys" 301 + version = "0.8.3" 302 + source = "registry+https://github.com/rust-lang/crates.io-index" 303 + checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" 304 + 305 + [[package]] 306 + name = "cpufeatures" 307 + version = "0.2.2" 308 + source = "registry+https://github.com/rust-lang/crates.io-index" 309 + checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b" 310 + dependencies = [ 311 + "libc", 312 + ] 313 + 314 + [[package]] 315 + name = "crc32fast" 316 + version = "1.3.2" 317 + source = "registry+https://github.com/rust-lang/crates.io-index" 318 + checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" 319 + dependencies = [ 320 + "cfg-if", 321 + ] 322 + 323 + [[package]] 324 + name = "crossbeam-utils" 325 + version = "0.8.11" 326 + source = "registry+https://github.com/rust-lang/crates.io-index" 327 + checksum = "51887d4adc7b564537b15adcfb307936f8075dfcd5f00dde9a9f1d29383682bc" 328 + dependencies = [ 329 + "cfg-if", 330 + "once_cell", 331 + ] 332 + 333 + [[package]] 334 + name = "crypto-common" 335 + version = "0.1.6" 336 + source = "registry+https://github.com/rust-lang/crates.io-index" 337 + checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 338 + dependencies = [ 339 + "generic-array", 340 + "rand_core", 341 + "typenum", 342 + ] 343 + 344 + [[package]] 345 + name = "ctr" 346 + version = "0.9.1" 347 + source = "registry+https://github.com/rust-lang/crates.io-index" 348 + checksum = "0d14f329cfbaf5d0e06b5e87fff7e265d2673c5ea7d2c27691a2c107db1442a0" 349 + dependencies = [ 350 + "cipher 0.4.3", 351 + ] 352 + 353 + [[package]] 354 + name = "dialoguer" 355 + version = "0.10.1" 356 + source = "registry+https://github.com/rust-lang/crates.io-index" 357 + checksum = "d8c8ae48e400addc32a8710c8d62d55cb84249a7d58ac4cd959daecfbaddc545" 358 + dependencies = [ 359 + "console", 360 + "tempfile", 361 + "zeroize", 362 + ] 363 + 364 + [[package]] 365 + name = "digest" 366 + version = "0.10.3" 367 + source = "registry+https://github.com/rust-lang/crates.io-index" 368 + checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" 369 + dependencies = [ 370 + "block-buffer", 371 + "crypto-common", 372 + "subtle", 373 + ] 374 + 375 + [[package]] 376 + name = "encode_unicode" 377 + version = "0.3.6" 378 + source = "registry+https://github.com/rust-lang/crates.io-index" 379 + checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" 380 + 381 + [[package]] 382 + name = "encoding_rs" 383 + version = "0.8.31" 384 + source = "registry+https://github.com/rust-lang/crates.io-index" 385 + checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b" 386 + dependencies = [ 387 + "cfg-if", 388 + ] 389 + 390 + [[package]] 391 + name = "fastrand" 392 + version = "1.8.0" 393 + source = "registry+https://github.com/rust-lang/crates.io-index" 394 + checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" 395 + dependencies = [ 396 + "instant", 397 + ] 398 + 399 + [[package]] 400 + name = "filetime" 401 + version = "0.2.17" 402 + source = "registry+https://github.com/rust-lang/crates.io-index" 403 + checksum = "e94a7bbaa59354bc20dd75b67f23e2797b4490e9d6928203fb105c79e448c86c" 404 + dependencies = [ 405 + "cfg-if", 406 + "libc", 407 + "redox_syscall", 408 + "windows-sys", 409 + ] 410 + 411 + [[package]] 412 + name = "flate2" 413 + version = "1.0.24" 414 + source = "registry+https://github.com/rust-lang/crates.io-index" 415 + checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6" 416 + dependencies = [ 417 + "crc32fast", 418 + "miniz_oxide", 419 + ] 420 + 421 + [[package]] 422 + name = "fnv" 423 + version = "1.0.7" 424 + source = "registry+https://github.com/rust-lang/crates.io-index" 425 + checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 426 + 427 + [[package]] 428 + name = "foreign-types" 429 + version = "0.3.2" 430 + source = "registry+https://github.com/rust-lang/crates.io-index" 431 + checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" 432 + dependencies = [ 433 + "foreign-types-shared", 434 + ] 435 + 436 + [[package]] 437 + name = "foreign-types-shared" 438 + version = "0.1.1" 439 + source = "registry+https://github.com/rust-lang/crates.io-index" 440 + checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" 441 + 442 + [[package]] 443 + name = "form_urlencoded" 444 + version = "1.0.1" 445 + source = "registry+https://github.com/rust-lang/crates.io-index" 446 + checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" 447 + dependencies = [ 448 + "matches", 449 + "percent-encoding", 450 + ] 451 + 452 + [[package]] 453 + name = "futures-channel" 454 + version = "0.3.21" 455 + source = "registry+https://github.com/rust-lang/crates.io-index" 456 + checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" 457 + dependencies = [ 458 + "futures-core", 459 + ] 460 + 461 + [[package]] 462 + name = "futures-core" 463 + version = "0.3.21" 464 + source = "registry+https://github.com/rust-lang/crates.io-index" 465 + checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" 466 + 467 + [[package]] 468 + name = "futures-macro" 469 + version = "0.3.21" 470 + source = "registry+https://github.com/rust-lang/crates.io-index" 471 + checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512" 472 + dependencies = [ 473 + "proc-macro2", 474 + "quote", 475 + "syn", 476 + ] 477 + 478 + [[package]] 479 + name = "futures-sink" 480 + version = "0.3.21" 481 + source = "registry+https://github.com/rust-lang/crates.io-index" 482 + checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" 483 + 484 + [[package]] 485 + name = "futures-task" 486 + version = "0.3.21" 487 + source = "registry+https://github.com/rust-lang/crates.io-index" 488 + checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" 489 + 490 + [[package]] 491 + name = "futures-util" 492 + version = "0.3.21" 493 + source = "registry+https://github.com/rust-lang/crates.io-index" 494 + checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" 495 + dependencies = [ 496 + "futures-core", 497 + "futures-macro", 498 + "futures-task", 499 + "pin-project-lite", 500 + "pin-utils", 501 + "slab", 502 + ] 503 + 504 + [[package]] 505 + name = "generic-array" 506 + version = "0.14.6" 507 + source = "registry+https://github.com/rust-lang/crates.io-index" 508 + checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" 509 + dependencies = [ 510 + "typenum", 511 + "version_check", 512 + ] 513 + 514 + [[package]] 515 + name = "getrandom" 516 + version = "0.2.7" 517 + source = "registry+https://github.com/rust-lang/crates.io-index" 518 + checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" 519 + dependencies = [ 520 + "cfg-if", 521 + "libc", 522 + "wasi", 523 + ] 524 + 525 + [[package]] 526 + name = "ghash" 527 + version = "0.5.0" 528 + source = "registry+https://github.com/rust-lang/crates.io-index" 529 + checksum = "d930750de5717d2dd0b8c0d42c076c0e884c81a73e6cab859bbd2339c71e3e40" 530 + dependencies = [ 531 + "opaque-debug", 532 + "polyval", 533 + ] 534 + 535 + [[package]] 536 + name = "h2" 537 + version = "0.3.13" 538 + source = "registry+https://github.com/rust-lang/crates.io-index" 539 + checksum = "37a82c6d637fc9515a4694bbf1cb2457b79d81ce52b3108bdeea58b07dd34a57" 540 + dependencies = [ 541 + "bytes", 542 + "fnv", 543 + "futures-core", 544 + "futures-sink", 545 + "futures-util", 546 + "http", 547 + "indexmap", 548 + "slab", 549 + "tokio", 550 + "tokio-util", 551 + "tracing", 552 + ] 553 + 554 + [[package]] 555 + name = "hashbrown" 556 + version = "0.12.2" 557 + source = "registry+https://github.com/rust-lang/crates.io-index" 558 + checksum = "607c8a29735385251a339424dd462993c0fed8fa09d378f259377df08c126022" 559 + 560 + [[package]] 561 + name = "heck" 562 + version = "0.4.0" 563 + source = "registry+https://github.com/rust-lang/crates.io-index" 564 + checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" 565 + 566 + [[package]] 567 + name = "hermit-abi" 568 + version = "0.1.19" 569 + source = "registry+https://github.com/rust-lang/crates.io-index" 570 + checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 571 + dependencies = [ 572 + "libc", 573 + ] 574 + 575 + [[package]] 576 + name = "hex" 577 + version = "0.4.3" 578 + source = "registry+https://github.com/rust-lang/crates.io-index" 579 + checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" 580 + 581 + [[package]] 582 + name = "hmac" 583 + version = "0.12.1" 584 + source = "registry+https://github.com/rust-lang/crates.io-index" 585 + checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" 586 + dependencies = [ 587 + "digest", 588 + ] 589 + 590 + [[package]] 591 + name = "http" 592 + version = "0.2.8" 593 + source = "registry+https://github.com/rust-lang/crates.io-index" 594 + checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" 595 + dependencies = [ 596 + "bytes", 597 + "fnv", 598 + "itoa", 599 + ] 600 + 601 + [[package]] 602 + name = "http-body" 603 + version = "0.4.5" 604 + source = "registry+https://github.com/rust-lang/crates.io-index" 605 + checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" 606 + dependencies = [ 607 + "bytes", 608 + "http", 609 + "pin-project-lite", 610 + ] 611 + 612 + [[package]] 613 + name = "httparse" 614 + version = "1.7.1" 615 + source = "registry+https://github.com/rust-lang/crates.io-index" 616 + checksum = "496ce29bb5a52785b44e0f7ca2847ae0bb839c9bd28f69acac9b99d461c0c04c" 617 + 618 + [[package]] 619 + name = "httpdate" 620 + version = "1.0.2" 621 + source = "registry+https://github.com/rust-lang/crates.io-index" 622 + checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" 623 + 624 + [[package]] 625 + name = "hyper" 626 + version = "0.14.20" 627 + source = "registry+https://github.com/rust-lang/crates.io-index" 628 + checksum = "02c929dc5c39e335a03c405292728118860721b10190d98c2a0f0efd5baafbac" 629 + dependencies = [ 630 + "bytes", 631 + "futures-channel", 632 + "futures-core", 633 + "futures-util", 634 + "h2", 635 + "http", 636 + "http-body", 637 + "httparse", 638 + "httpdate", 639 + "itoa", 640 + "pin-project-lite", 641 + "socket2", 642 + "tokio", 643 + "tower-service", 644 + "tracing", 645 + "want", 646 + ] 647 + 648 + [[package]] 649 + name = "hyper-tls" 650 + version = "0.5.0" 651 + source = "registry+https://github.com/rust-lang/crates.io-index" 652 + checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" 653 + dependencies = [ 654 + "bytes", 655 + "hyper", 656 + "native-tls", 657 + "tokio", 658 + "tokio-native-tls", 659 + ] 660 + 661 + [[package]] 662 + name = "idna" 663 + version = "0.2.3" 664 + source = "registry+https://github.com/rust-lang/crates.io-index" 665 + checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" 666 + dependencies = [ 667 + "matches", 668 + "unicode-bidi", 669 + "unicode-normalization", 670 + ] 671 + 672 + [[package]] 673 + name = "indexmap" 674 + version = "1.9.1" 675 + source = "registry+https://github.com/rust-lang/crates.io-index" 676 + checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" 677 + dependencies = [ 678 + "autocfg", 679 + "hashbrown", 680 + ] 681 + 682 + [[package]] 683 + name = "indicatif" 684 + version = "0.17.0" 685 + source = "registry+https://github.com/rust-lang/crates.io-index" 686 + checksum = "fcc42b206e70d86ec03285b123e65a5458c92027d1fb2ae3555878b8113b3ddf" 687 + dependencies = [ 688 + "console", 689 + "number_prefix", 690 + "unicode-segmentation", 691 + "unicode-width", 692 + ] 693 + 694 + [[package]] 695 + name = "inout" 696 + version = "0.1.3" 697 + source = "registry+https://github.com/rust-lang/crates.io-index" 698 + checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" 699 + dependencies = [ 700 + "generic-array", 701 + ] 702 + 703 + [[package]] 704 + name = "instant" 705 + version = "0.1.12" 706 + source = "registry+https://github.com/rust-lang/crates.io-index" 707 + checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" 708 + dependencies = [ 709 + "cfg-if", 710 + ] 711 + 712 + [[package]] 713 + name = "ipnet" 714 + version = "2.5.0" 715 + source = "registry+https://github.com/rust-lang/crates.io-index" 716 + checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b" 717 + 718 + [[package]] 719 + name = "itoa" 720 + version = "1.0.2" 721 + source = "registry+https://github.com/rust-lang/crates.io-index" 722 + checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" 723 + 724 + [[package]] 725 + name = "jobserver" 726 + version = "0.1.24" 727 + source = "registry+https://github.com/rust-lang/crates.io-index" 728 + checksum = "af25a77299a7f711a01975c35a6a424eb6862092cc2d6c72c4ed6cbc56dfc1fa" 729 + dependencies = [ 730 + "libc", 731 + ] 732 + 733 + [[package]] 734 + name = "js-sys" 735 + version = "0.3.59" 736 + source = "registry+https://github.com/rust-lang/crates.io-index" 737 + checksum = "258451ab10b34f8af53416d1fdab72c22e805f0c92a1136d59470ec0b11138b2" 738 + dependencies = [ 739 + "wasm-bindgen", 740 + ] 741 + 742 + [[package]] 743 + name = "lazy_static" 744 + version = "1.4.0" 745 + source = "registry+https://github.com/rust-lang/crates.io-index" 746 + checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 747 + 748 + [[package]] 749 + name = "libc" 750 + version = "0.2.132" 751 + source = "registry+https://github.com/rust-lang/crates.io-index" 752 + checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5" 753 + 754 + [[package]] 755 + name = "lock_api" 756 + version = "0.4.7" 757 + source = "registry+https://github.com/rust-lang/crates.io-index" 758 + checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" 759 + dependencies = [ 760 + "autocfg", 761 + "scopeguard", 762 + ] 763 + 764 + [[package]] 765 + name = "log" 766 + version = "0.4.17" 767 + source = "registry+https://github.com/rust-lang/crates.io-index" 768 + checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" 769 + dependencies = [ 770 + "cfg-if", 771 + ] 772 + 773 + [[package]] 774 + name = "matchers" 775 + version = "0.1.0" 776 + source = "registry+https://github.com/rust-lang/crates.io-index" 777 + checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" 778 + dependencies = [ 779 + "regex-automata", 780 + ] 781 + 782 + [[package]] 783 + name = "matches" 784 + version = "0.1.9" 785 + source = "registry+https://github.com/rust-lang/crates.io-index" 786 + checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" 787 + 788 + [[package]] 789 + name = "memchr" 790 + version = "2.5.0" 791 + source = "registry+https://github.com/rust-lang/crates.io-index" 792 + checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 793 + 794 + [[package]] 795 + name = "mime" 796 + version = "0.3.16" 797 + source = "registry+https://github.com/rust-lang/crates.io-index" 798 + checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" 799 + 800 + [[package]] 801 + name = "miniz_oxide" 802 + version = "0.5.3" 803 + source = "registry+https://github.com/rust-lang/crates.io-index" 804 + checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc" 805 + dependencies = [ 806 + "adler", 807 + ] 808 + 809 + [[package]] 810 + name = "mio" 811 + version = "0.8.4" 812 + source = "registry+https://github.com/rust-lang/crates.io-index" 813 + checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" 814 + dependencies = [ 815 + "libc", 816 + "log", 817 + "wasi", 818 + "windows-sys", 819 + ] 820 + 821 + [[package]] 822 + name = "native-tls" 823 + version = "0.2.10" 824 + source = "registry+https://github.com/rust-lang/crates.io-index" 825 + checksum = "fd7e2f3618557f980e0b17e8856252eee3c97fa12c54dff0ca290fb6266ca4a9" 826 + dependencies = [ 827 + "lazy_static", 828 + "libc", 829 + "log", 830 + "openssl", 831 + "openssl-probe", 832 + "openssl-sys", 833 + "schannel", 834 + "security-framework", 835 + "security-framework-sys", 836 + "tempfile", 837 + ] 838 + 839 + [[package]] 840 + name = "npm-package-json" 841 + version = "0.1.3" 842 + source = "registry+https://github.com/rust-lang/crates.io-index" 843 + checksum = "df163f89771bd6654d5e116952b9ab2d0b952cd8e3315ddaa615f6d2bfbb1fde" 844 + dependencies = [ 845 + "serde", 846 + "serde_json", 847 + "thiserror", 848 + ] 849 + 850 + [[package]] 851 + name = "num-traits" 852 + version = "0.2.15" 853 + source = "registry+https://github.com/rust-lang/crates.io-index" 854 + checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" 855 + dependencies = [ 856 + "autocfg", 857 + ] 858 + 859 + [[package]] 860 + name = "num_cpus" 861 + version = "1.13.1" 862 + source = "registry+https://github.com/rust-lang/crates.io-index" 863 + checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" 864 + dependencies = [ 865 + "hermit-abi", 866 + "libc", 867 + ] 868 + 869 + [[package]] 870 + name = "num_threads" 871 + version = "0.1.6" 872 + source = "registry+https://github.com/rust-lang/crates.io-index" 873 + checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" 874 + dependencies = [ 875 + "libc", 876 + ] 877 + 878 + [[package]] 879 + name = "number_prefix" 880 + version = "0.4.0" 881 + source = "registry+https://github.com/rust-lang/crates.io-index" 882 + checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" 883 + 884 + [[package]] 885 + name = "once_cell" 886 + version = "1.13.0" 887 + source = "registry+https://github.com/rust-lang/crates.io-index" 888 + checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1" 889 + 890 + [[package]] 891 + name = "opaque-debug" 892 + version = "0.3.0" 893 + source = "registry+https://github.com/rust-lang/crates.io-index" 894 + checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" 895 + 896 + [[package]] 897 + name = "open" 898 + version = "3.0.2" 899 + source = "registry+https://github.com/rust-lang/crates.io-index" 900 + checksum = "f23a407004a1033f53e93f9b45580d14de23928faad187384f891507c9b0c045" 901 + dependencies = [ 902 + "pathdiff", 903 + "windows-sys", 904 + ] 905 + 906 + [[package]] 907 + name = "openssl" 908 + version = "0.10.41" 909 + source = "registry+https://github.com/rust-lang/crates.io-index" 910 + checksum = "618febf65336490dfcf20b73f885f5651a0c89c64c2d4a8c3662585a70bf5bd0" 911 + dependencies = [ 912 + "bitflags", 913 + "cfg-if", 914 + "foreign-types", 915 + "libc", 916 + "once_cell", 917 + "openssl-macros", 918 + "openssl-sys", 919 + ] 920 + 921 + [[package]] 922 + name = "openssl-macros" 923 + version = "0.1.0" 924 + source = "registry+https://github.com/rust-lang/crates.io-index" 925 + checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" 926 + dependencies = [ 927 + "proc-macro2", 928 + "quote", 929 + "syn", 930 + ] 931 + 932 + [[package]] 933 + name = "openssl-probe" 934 + version = "0.1.5" 935 + source = "registry+https://github.com/rust-lang/crates.io-index" 936 + checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" 937 + 938 + [[package]] 939 + name = "openssl-sys" 940 + version = "0.9.75" 941 + source = "registry+https://github.com/rust-lang/crates.io-index" 942 + checksum = "e5f9bd0c2710541a3cda73d6f9ac4f1b240de4ae261065d309dbe73d9dceb42f" 943 + dependencies = [ 944 + "autocfg", 945 + "cc", 946 + "libc", 947 + "pkg-config", 948 + "vcpkg", 949 + ] 950 + 951 + [[package]] 952 + name = "os_str_bytes" 953 + version = "6.1.0" 954 + source = "registry+https://github.com/rust-lang/crates.io-index" 955 + checksum = "21326818e99cfe6ce1e524c2a805c189a99b5ae555a35d19f9a284b427d86afa" 956 + 957 + [[package]] 958 + name = "parking_lot" 959 + version = "0.12.1" 960 + source = "registry+https://github.com/rust-lang/crates.io-index" 961 + checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" 962 + dependencies = [ 963 + "lock_api", 964 + "parking_lot_core", 965 + ] 966 + 967 + [[package]] 968 + name = "parking_lot_core" 969 + version = "0.9.3" 970 + source = "registry+https://github.com/rust-lang/crates.io-index" 971 + checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" 972 + dependencies = [ 973 + "cfg-if", 974 + "libc", 975 + "redox_syscall", 976 + "smallvec", 977 + "windows-sys", 978 + ] 979 + 980 + [[package]] 981 + name = "password-hash" 982 + version = "0.3.2" 983 + source = "registry+https://github.com/rust-lang/crates.io-index" 984 + checksum = "1d791538a6dcc1e7cb7fe6f6b58aca40e7f79403c45b2bc274008b5e647af1d8" 985 + dependencies = [ 986 + "base64ct", 987 + "rand_core", 988 + "subtle", 989 + ] 990 + 991 + [[package]] 992 + name = "paste" 993 + version = "1.0.8" 994 + source = "registry+https://github.com/rust-lang/crates.io-index" 995 + checksum = "9423e2b32f7a043629287a536f21951e8c6a82482d0acb1eeebfc90bc2225b22" 996 + 997 + [[package]] 998 + name = "pathdiff" 999 + version = "0.2.1" 1000 + source = "registry+https://github.com/rust-lang/crates.io-index" 1001 + checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" 1002 + 1003 + [[package]] 1004 + name = "pbkdf2" 1005 + version = "0.10.1" 1006 + source = "registry+https://github.com/rust-lang/crates.io-index" 1007 + checksum = "271779f35b581956db91a3e55737327a03aa051e90b1c47aeb189508533adfd7" 1008 + dependencies = [ 1009 + "digest", 1010 + "hmac", 1011 + "password-hash", 1012 + "sha2", 1013 + ] 1014 + 1015 + [[package]] 1016 + name = "percent-encoding" 1017 + version = "2.1.0" 1018 + source = "registry+https://github.com/rust-lang/crates.io-index" 1019 + checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" 1020 + 1021 + [[package]] 1022 + name = "pin-project-lite" 1023 + version = "0.2.9" 1024 + source = "registry+https://github.com/rust-lang/crates.io-index" 1025 + checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" 1026 + 1027 + [[package]] 1028 + name = "pin-utils" 1029 + version = "0.1.0" 1030 + source = "registry+https://github.com/rust-lang/crates.io-index" 1031 + checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 1032 + 1033 + [[package]] 1034 + name = "pkg-config" 1035 + version = "0.3.25" 1036 + source = "registry+https://github.com/rust-lang/crates.io-index" 1037 + checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" 1038 + 1039 + [[package]] 1040 + name = "polyval" 1041 + version = "0.6.0" 1042 + source = "registry+https://github.com/rust-lang/crates.io-index" 1043 + checksum = "7ef234e08c11dfcb2e56f79fd70f6f2eb7f025c0ce2333e82f4f0518ecad30c6" 1044 + dependencies = [ 1045 + "cfg-if", 1046 + "cpufeatures", 1047 + "opaque-debug", 1048 + "universal-hash", 1049 + ] 1050 + 1051 + [[package]] 1052 + name = "ppv-lite86" 1053 + version = "0.2.16" 1054 + source = "registry+https://github.com/rust-lang/crates.io-index" 1055 + checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" 1056 + 1057 + [[package]] 1058 + name = "proc-macro-error" 1059 + version = "1.0.4" 1060 + source = "registry+https://github.com/rust-lang/crates.io-index" 1061 + checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 1062 + dependencies = [ 1063 + "proc-macro-error-attr", 1064 + "proc-macro2", 1065 + "quote", 1066 + "syn", 1067 + "version_check", 1068 + ] 1069 + 1070 + [[package]] 1071 + name = "proc-macro-error-attr" 1072 + version = "1.0.4" 1073 + source = "registry+https://github.com/rust-lang/crates.io-index" 1074 + checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 1075 + dependencies = [ 1076 + "proc-macro2", 1077 + "quote", 1078 + "version_check", 1079 + ] 1080 + 1081 + [[package]] 1082 + name = "proc-macro2" 1083 + version = "1.0.40" 1084 + source = "registry+https://github.com/rust-lang/crates.io-index" 1085 + checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7" 1086 + dependencies = [ 1087 + "unicode-ident", 1088 + ] 1089 + 1090 + [[package]] 1091 + name = "quote" 1092 + version = "1.0.20" 1093 + source = "registry+https://github.com/rust-lang/crates.io-index" 1094 + checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804" 1095 + dependencies = [ 1096 + "proc-macro2", 1097 + ] 1098 + 1099 + [[package]] 1100 + name = "rand" 1101 + version = "0.8.5" 1102 + source = "registry+https://github.com/rust-lang/crates.io-index" 1103 + checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 1104 + dependencies = [ 1105 + "libc", 1106 + "rand_chacha", 1107 + "rand_core", 1108 + ] 1109 + 1110 + [[package]] 1111 + name = "rand_chacha" 1112 + version = "0.3.1" 1113 + source = "registry+https://github.com/rust-lang/crates.io-index" 1114 + checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 1115 + dependencies = [ 1116 + "ppv-lite86", 1117 + "rand_core", 1118 + ] 1119 + 1120 + [[package]] 1121 + name = "rand_core" 1122 + version = "0.6.3" 1123 + source = "registry+https://github.com/rust-lang/crates.io-index" 1124 + checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" 1125 + dependencies = [ 1126 + "getrandom", 1127 + ] 1128 + 1129 + [[package]] 1130 + name = "redox_syscall" 1131 + version = "0.2.16" 1132 + source = "registry+https://github.com/rust-lang/crates.io-index" 1133 + checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" 1134 + dependencies = [ 1135 + "bitflags", 1136 + ] 1137 + 1138 + [[package]] 1139 + name = "regex" 1140 + version = "1.6.0" 1141 + source = "registry+https://github.com/rust-lang/crates.io-index" 1142 + checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" 1143 + dependencies = [ 1144 + "regex-syntax", 1145 + ] 1146 + 1147 + [[package]] 1148 + name = "regex-automata" 1149 + version = "0.1.10" 1150 + source = "registry+https://github.com/rust-lang/crates.io-index" 1151 + checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" 1152 + dependencies = [ 1153 + "regex-syntax", 1154 + ] 1155 + 1156 + [[package]] 1157 + name = "regex-syntax" 1158 + version = "0.6.27" 1159 + source = "registry+https://github.com/rust-lang/crates.io-index" 1160 + checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" 1161 + 1162 + [[package]] 1163 + name = "remove_dir_all" 1164 + version = "0.5.3" 1165 + source = "registry+https://github.com/rust-lang/crates.io-index" 1166 + checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" 1167 + dependencies = [ 1168 + "winapi", 1169 + ] 1170 + 1171 + [[package]] 1172 + name = "reqwest" 1173 + version = "0.11.11" 1174 + source = "registry+https://github.com/rust-lang/crates.io-index" 1175 + checksum = "b75aa69a3f06bbcc66ede33af2af253c6f7a86b1ca0033f60c580a27074fbf92" 1176 + dependencies = [ 1177 + "base64", 1178 + "bytes", 1179 + "encoding_rs", 1180 + "futures-core", 1181 + "futures-util", 1182 + "h2", 1183 + "http", 1184 + "http-body", 1185 + "hyper", 1186 + "hyper-tls", 1187 + "ipnet", 1188 + "js-sys", 1189 + "lazy_static", 1190 + "log", 1191 + "mime", 1192 + "native-tls", 1193 + "percent-encoding", 1194 + "pin-project-lite", 1195 + "serde", 1196 + "serde_json", 1197 + "serde_urlencoded", 1198 + "tokio", 1199 + "tokio-native-tls", 1200 + "tokio-util", 1201 + "tower-service", 1202 + "url", 1203 + "wasm-bindgen", 1204 + "wasm-bindgen-futures", 1205 + "web-sys", 1206 + "winreg", 1207 + ] 1208 + 1209 + [[package]] 1210 + name = "rmp" 1211 + version = "0.8.11" 1212 + source = "registry+https://github.com/rust-lang/crates.io-index" 1213 + checksum = "44519172358fd6d58656c86ab8e7fbc9e1490c3e8f14d35ed78ca0dd07403c9f" 1214 + dependencies = [ 1215 + "byteorder", 1216 + "num-traits", 1217 + "paste", 1218 + ] 1219 + 1220 + [[package]] 1221 + name = "rmp-serde" 1222 + version = "1.1.0" 1223 + source = "registry+https://github.com/rust-lang/crates.io-index" 1224 + checksum = "25786b0d276110195fa3d6f3f31299900cf71dfbd6c28450f3f58a0e7f7a347e" 1225 + dependencies = [ 1226 + "byteorder", 1227 + "rmp", 1228 + "serde", 1229 + ] 1230 + 1231 + [[package]] 1232 + name = "ryu" 1233 + version = "1.0.11" 1234 + source = "registry+https://github.com/rust-lang/crates.io-index" 1235 + checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" 1236 + 1237 + [[package]] 1238 + name = "schannel" 1239 + version = "0.1.20" 1240 + source = "registry+https://github.com/rust-lang/crates.io-index" 1241 + checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2" 1242 + dependencies = [ 1243 + "lazy_static", 1244 + "windows-sys", 1245 + ] 1246 + 1247 + [[package]] 1248 + name = "scopeguard" 1249 + version = "1.1.0" 1250 + source = "registry+https://github.com/rust-lang/crates.io-index" 1251 + checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 1252 + 1253 + [[package]] 1254 + name = "security-framework" 1255 + version = "2.6.1" 1256 + source = "registry+https://github.com/rust-lang/crates.io-index" 1257 + checksum = "2dc14f172faf8a0194a3aded622712b0de276821addc574fa54fc0a1167e10dc" 1258 + dependencies = [ 1259 + "bitflags", 1260 + "core-foundation", 1261 + "core-foundation-sys", 1262 + "libc", 1263 + "security-framework-sys", 1264 + ] 1265 + 1266 + [[package]] 1267 + name = "security-framework-sys" 1268 + version = "2.6.1" 1269 + source = "registry+https://github.com/rust-lang/crates.io-index" 1270 + checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" 1271 + dependencies = [ 1272 + "core-foundation-sys", 1273 + "libc", 1274 + ] 1275 + 1276 + [[package]] 1277 + name = "serde" 1278 + version = "1.0.141" 1279 + source = "registry+https://github.com/rust-lang/crates.io-index" 1280 + checksum = "7af873f2c95b99fcb0bd0fe622a43e29514658873c8ceba88c4cb88833a22500" 1281 + dependencies = [ 1282 + "serde_derive", 1283 + ] 1284 + 1285 + [[package]] 1286 + name = "serde_derive" 1287 + version = "1.0.141" 1288 + source = "registry+https://github.com/rust-lang/crates.io-index" 1289 + checksum = "75743a150d003dd863b51dc809bcad0d73f2102c53632f1e954e738192a3413f" 1290 + dependencies = [ 1291 + "proc-macro2", 1292 + "quote", 1293 + "syn", 1294 + ] 1295 + 1296 + [[package]] 1297 + name = "serde_json" 1298 + version = "1.0.82" 1299 + source = "registry+https://github.com/rust-lang/crates.io-index" 1300 + checksum = "82c2c1fdcd807d1098552c5b9a36e425e42e9fbd7c6a37a8425f390f781f7fa7" 1301 + dependencies = [ 1302 + "itoa", 1303 + "ryu", 1304 + "serde", 1305 + ] 1306 + 1307 + [[package]] 1308 + name = "serde_urlencoded" 1309 + version = "0.7.1" 1310 + source = "registry+https://github.com/rust-lang/crates.io-index" 1311 + checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" 1312 + dependencies = [ 1313 + "form_urlencoded", 1314 + "itoa", 1315 + "ryu", 1316 + "serde", 1317 + ] 1318 + 1319 + [[package]] 1320 + name = "sha1" 1321 + version = "0.10.1" 1322 + source = "registry+https://github.com/rust-lang/crates.io-index" 1323 + checksum = "c77f4e7f65455545c2153c1253d25056825e77ee2533f0e41deb65a93a34852f" 1324 + dependencies = [ 1325 + "cfg-if", 1326 + "cpufeatures", 1327 + "digest", 1328 + ] 1329 + 1330 + [[package]] 1331 + name = "sha2" 1332 + version = "0.10.2" 1333 + source = "registry+https://github.com/rust-lang/crates.io-index" 1334 + checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676" 1335 + dependencies = [ 1336 + "cfg-if", 1337 + "cpufeatures", 1338 + "digest", 1339 + ] 1340 + 1341 + [[package]] 1342 + name = "sharded-slab" 1343 + version = "0.1.4" 1344 + source = "registry+https://github.com/rust-lang/crates.io-index" 1345 + checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" 1346 + dependencies = [ 1347 + "lazy_static", 1348 + ] 1349 + 1350 + [[package]] 1351 + name = "signal-hook-registry" 1352 + version = "1.4.0" 1353 + source = "registry+https://github.com/rust-lang/crates.io-index" 1354 + checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" 1355 + dependencies = [ 1356 + "libc", 1357 + ] 1358 + 1359 + [[package]] 1360 + name = "slab" 1361 + version = "0.4.7" 1362 + source = "registry+https://github.com/rust-lang/crates.io-index" 1363 + checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" 1364 + dependencies = [ 1365 + "autocfg", 1366 + ] 1367 + 1368 + [[package]] 1369 + name = "smallvec" 1370 + version = "1.9.0" 1371 + source = "registry+https://github.com/rust-lang/crates.io-index" 1372 + checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" 1373 + 1374 + [[package]] 1375 + name = "socket2" 1376 + version = "0.4.4" 1377 + source = "registry+https://github.com/rust-lang/crates.io-index" 1378 + checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" 1379 + dependencies = [ 1380 + "libc", 1381 + "winapi", 1382 + ] 1383 + 1384 + [[package]] 1385 + name = "strsim" 1386 + version = "0.10.0" 1387 + source = "registry+https://github.com/rust-lang/crates.io-index" 1388 + checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 1389 + 1390 + [[package]] 1391 + name = "subtle" 1392 + version = "2.4.1" 1393 + source = "registry+https://github.com/rust-lang/crates.io-index" 1394 + checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" 1395 + 1396 + [[package]] 1397 + name = "syn" 1398 + version = "1.0.98" 1399 + source = "registry+https://github.com/rust-lang/crates.io-index" 1400 + checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" 1401 + dependencies = [ 1402 + "proc-macro2", 1403 + "quote", 1404 + "unicode-ident", 1405 + ] 1406 + 1407 + [[package]] 1408 + name = "tar" 1409 + version = "0.4.38" 1410 + source = "registry+https://github.com/rust-lang/crates.io-index" 1411 + checksum = "4b55807c0344e1e6c04d7c965f5289c39a8d94ae23ed5c0b57aabac549f871c6" 1412 + dependencies = [ 1413 + "filetime", 1414 + "libc", 1415 + "xattr", 1416 + ] 1417 + 1418 + [[package]] 1419 + name = "tempfile" 1420 + version = "3.3.0" 1421 + source = "registry+https://github.com/rust-lang/crates.io-index" 1422 + checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" 1423 + dependencies = [ 1424 + "cfg-if", 1425 + "fastrand", 1426 + "libc", 1427 + "redox_syscall", 1428 + "remove_dir_all", 1429 + "winapi", 1430 + ] 1431 + 1432 + [[package]] 1433 + name = "termcolor" 1434 + version = "1.1.3" 1435 + source = "registry+https://github.com/rust-lang/crates.io-index" 1436 + checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" 1437 + dependencies = [ 1438 + "winapi-util", 1439 + ] 1440 + 1441 + [[package]] 1442 + name = "terminal_size" 1443 + version = "0.1.17" 1444 + source = "registry+https://github.com/rust-lang/crates.io-index" 1445 + checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df" 1446 + dependencies = [ 1447 + "libc", 1448 + "winapi", 1449 + ] 1450 + 1451 + [[package]] 1452 + name = "textwrap" 1453 + version = "0.15.0" 1454 + source = "registry+https://github.com/rust-lang/crates.io-index" 1455 + checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" 1456 + 1457 + [[package]] 1458 + name = "thiserror" 1459 + version = "1.0.32" 1460 + source = "registry+https://github.com/rust-lang/crates.io-index" 1461 + checksum = "f5f6586b7f764adc0231f4c79be7b920e766bb2f3e51b3661cdb263828f19994" 1462 + dependencies = [ 1463 + "thiserror-impl", 1464 + ] 1465 + 1466 + [[package]] 1467 + name = "thiserror-impl" 1468 + version = "1.0.32" 1469 + source = "registry+https://github.com/rust-lang/crates.io-index" 1470 + checksum = "12bafc5b54507e0149cdf1b145a5d80ab80a90bcd9275df43d4fff68460f6c21" 1471 + dependencies = [ 1472 + "proc-macro2", 1473 + "quote", 1474 + "syn", 1475 + ] 1476 + 1477 + [[package]] 1478 + name = "thread_local" 1479 + version = "1.1.4" 1480 + source = "registry+https://github.com/rust-lang/crates.io-index" 1481 + checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" 1482 + dependencies = [ 1483 + "once_cell", 1484 + ] 1485 + 1486 + [[package]] 1487 + name = "time" 1488 + version = "0.3.13" 1489 + source = "registry+https://github.com/rust-lang/crates.io-index" 1490 + checksum = "db76ff9fa4b1458b3c7f077f3ff9887394058460d21e634355b273aaf11eea45" 1491 + dependencies = [ 1492 + "itoa", 1493 + "libc", 1494 + "num_threads", 1495 + "time-macros", 1496 + ] 1497 + 1498 + [[package]] 1499 + name = "time-macros" 1500 + version = "0.2.4" 1501 + source = "registry+https://github.com/rust-lang/crates.io-index" 1502 + checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792" 1503 + 1504 + [[package]] 1505 + name = "tinyvec" 1506 + version = "1.6.0" 1507 + source = "registry+https://github.com/rust-lang/crates.io-index" 1508 + checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" 1509 + dependencies = [ 1510 + "tinyvec_macros", 1511 + ] 1512 + 1513 + [[package]] 1514 + name = "tinyvec_macros" 1515 + version = "0.1.0" 1516 + source = "registry+https://github.com/rust-lang/crates.io-index" 1517 + checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" 1518 + 1519 + [[package]] 1520 + name = "tokio" 1521 + version = "1.20.1" 1522 + source = "registry+https://github.com/rust-lang/crates.io-index" 1523 + checksum = "7a8325f63a7d4774dd041e363b2409ed1c5cbbd0f867795e661df066b2b0a581" 1524 + dependencies = [ 1525 + "autocfg", 1526 + "bytes", 1527 + "libc", 1528 + "memchr", 1529 + "mio", 1530 + "num_cpus", 1531 + "once_cell", 1532 + "parking_lot", 1533 + "pin-project-lite", 1534 + "signal-hook-registry", 1535 + "socket2", 1536 + "tokio-macros", 1537 + "winapi", 1538 + ] 1539 + 1540 + [[package]] 1541 + name = "tokio-macros" 1542 + version = "1.8.0" 1543 + source = "registry+https://github.com/rust-lang/crates.io-index" 1544 + checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" 1545 + dependencies = [ 1546 + "proc-macro2", 1547 + "quote", 1548 + "syn", 1549 + ] 1550 + 1551 + [[package]] 1552 + name = "tokio-native-tls" 1553 + version = "0.3.0" 1554 + source = "registry+https://github.com/rust-lang/crates.io-index" 1555 + checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" 1556 + dependencies = [ 1557 + "native-tls", 1558 + "tokio", 1559 + ] 1560 + 1561 + [[package]] 1562 + name = "tokio-util" 1563 + version = "0.7.3" 1564 + source = "registry+https://github.com/rust-lang/crates.io-index" 1565 + checksum = "cc463cd8deddc3770d20f9852143d50bf6094e640b485cb2e189a2099085ff45" 1566 + dependencies = [ 1567 + "bytes", 1568 + "futures-core", 1569 + "futures-sink", 1570 + "pin-project-lite", 1571 + "tokio", 1572 + "tracing", 1573 + ] 1574 + 1575 + [[package]] 1576 + name = "toml" 1577 + version = "0.5.9" 1578 + source = "registry+https://github.com/rust-lang/crates.io-index" 1579 + checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" 1580 + dependencies = [ 1581 + "serde", 1582 + ] 1583 + 1584 + [[package]] 1585 + name = "tower-service" 1586 + version = "0.3.2" 1587 + source = "registry+https://github.com/rust-lang/crates.io-index" 1588 + checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" 1589 + 1590 + [[package]] 1591 + name = "tracing" 1592 + version = "0.1.35" 1593 + source = "registry+https://github.com/rust-lang/crates.io-index" 1594 + checksum = "a400e31aa60b9d44a52a8ee0343b5b18566b03a8321e0d321f695cf56e940160" 1595 + dependencies = [ 1596 + "cfg-if", 1597 + "pin-project-lite", 1598 + "tracing-attributes", 1599 + "tracing-core", 1600 + ] 1601 + 1602 + [[package]] 1603 + name = "tracing-attributes" 1604 + version = "0.1.22" 1605 + source = "registry+https://github.com/rust-lang/crates.io-index" 1606 + checksum = "11c75893af559bc8e10716548bdef5cb2b983f8e637db9d0e15126b61b484ee2" 1607 + dependencies = [ 1608 + "proc-macro2", 1609 + "quote", 1610 + "syn", 1611 + ] 1612 + 1613 + [[package]] 1614 + name = "tracing-core" 1615 + version = "0.1.28" 1616 + source = "registry+https://github.com/rust-lang/crates.io-index" 1617 + checksum = "7b7358be39f2f274f322d2aaed611acc57f382e8eb1e5b48cb9ae30933495ce7" 1618 + dependencies = [ 1619 + "once_cell", 1620 + "valuable", 1621 + ] 1622 + 1623 + [[package]] 1624 + name = "tracing-log" 1625 + version = "0.1.3" 1626 + source = "registry+https://github.com/rust-lang/crates.io-index" 1627 + checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" 1628 + dependencies = [ 1629 + "lazy_static", 1630 + "log", 1631 + "tracing-core", 1632 + ] 1633 + 1634 + [[package]] 1635 + name = "tracing-subscriber" 1636 + version = "0.3.15" 1637 + source = "registry+https://github.com/rust-lang/crates.io-index" 1638 + checksum = "60db860322da191b40952ad9affe65ea23e7dd6a5c442c2c42865810c6ab8e6b" 1639 + dependencies = [ 1640 + "ansi_term", 1641 + "matchers", 1642 + "once_cell", 1643 + "regex", 1644 + "sharded-slab", 1645 + "smallvec", 1646 + "thread_local", 1647 + "tracing", 1648 + "tracing-core", 1649 + "tracing-log", 1650 + ] 1651 + 1652 + [[package]] 1653 + name = "try-lock" 1654 + version = "0.2.3" 1655 + source = "registry+https://github.com/rust-lang/crates.io-index" 1656 + checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" 1657 + 1658 + [[package]] 1659 + name = "typenum" 1660 + version = "1.15.0" 1661 + source = "registry+https://github.com/rust-lang/crates.io-index" 1662 + checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" 1663 + 1664 + [[package]] 1665 + name = "unicode-bidi" 1666 + version = "0.3.8" 1667 + source = "registry+https://github.com/rust-lang/crates.io-index" 1668 + checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" 1669 + 1670 + [[package]] 1671 + name = "unicode-ident" 1672 + version = "1.0.1" 1673 + source = "registry+https://github.com/rust-lang/crates.io-index" 1674 + checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c" 1675 + 1676 + [[package]] 1677 + name = "unicode-normalization" 1678 + version = "0.1.21" 1679 + source = "registry+https://github.com/rust-lang/crates.io-index" 1680 + checksum = "854cbdc4f7bc6ae19c820d44abdc3277ac3e1b2b93db20a636825d9322fb60e6" 1681 + dependencies = [ 1682 + "tinyvec", 1683 + ] 1684 + 1685 + [[package]] 1686 + name = "unicode-segmentation" 1687 + version = "1.9.0" 1688 + source = "registry+https://github.com/rust-lang/crates.io-index" 1689 + checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99" 1690 + 1691 + [[package]] 1692 + name = "unicode-width" 1693 + version = "0.1.9" 1694 + source = "registry+https://github.com/rust-lang/crates.io-index" 1695 + checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" 1696 + 1697 + [[package]] 1698 + name = "universal-hash" 1699 + version = "0.5.0" 1700 + source = "registry+https://github.com/rust-lang/crates.io-index" 1701 + checksum = "7d3160b73c9a19f7e2939a2fdad446c57c1bbbbf4d919d3213ff1267a580d8b5" 1702 + dependencies = [ 1703 + "crypto-common", 1704 + "subtle", 1705 + ] 1706 + 1707 + [[package]] 1708 + name = "url" 1709 + version = "2.2.2" 1710 + source = "registry+https://github.com/rust-lang/crates.io-index" 1711 + checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" 1712 + dependencies = [ 1713 + "form_urlencoded", 1714 + "idna", 1715 + "matches", 1716 + "percent-encoding", 1717 + ] 1718 + 1719 + [[package]] 1720 + name = "valuable" 1721 + version = "0.1.0" 1722 + source = "registry+https://github.com/rust-lang/crates.io-index" 1723 + checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" 1724 + 1725 + [[package]] 1726 + name = "vcpkg" 1727 + version = "0.2.15" 1728 + source = "registry+https://github.com/rust-lang/crates.io-index" 1729 + checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" 1730 + 1731 + [[package]] 1732 + name = "version_check" 1733 + version = "0.9.4" 1734 + source = "registry+https://github.com/rust-lang/crates.io-index" 1735 + checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 1736 + 1737 + [[package]] 1738 + name = "want" 1739 + version = "0.3.0" 1740 + source = "registry+https://github.com/rust-lang/crates.io-index" 1741 + checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" 1742 + dependencies = [ 1743 + "log", 1744 + "try-lock", 1745 + ] 1746 + 1747 + [[package]] 1748 + name = "wasi" 1749 + version = "0.11.0+wasi-snapshot-preview1" 1750 + source = "registry+https://github.com/rust-lang/crates.io-index" 1751 + checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1752 + 1753 + [[package]] 1754 + name = "wasm-bindgen" 1755 + version = "0.2.82" 1756 + source = "registry+https://github.com/rust-lang/crates.io-index" 1757 + checksum = "fc7652e3f6c4706c8d9cd54832c4a4ccb9b5336e2c3bd154d5cccfbf1c1f5f7d" 1758 + dependencies = [ 1759 + "cfg-if", 1760 + "wasm-bindgen-macro", 1761 + ] 1762 + 1763 + [[package]] 1764 + name = "wasm-bindgen-backend" 1765 + version = "0.2.82" 1766 + source = "registry+https://github.com/rust-lang/crates.io-index" 1767 + checksum = "662cd44805586bd52971b9586b1df85cdbbd9112e4ef4d8f41559c334dc6ac3f" 1768 + dependencies = [ 1769 + "bumpalo", 1770 + "log", 1771 + "once_cell", 1772 + "proc-macro2", 1773 + "quote", 1774 + "syn", 1775 + "wasm-bindgen-shared", 1776 + ] 1777 + 1778 + [[package]] 1779 + name = "wasm-bindgen-futures" 1780 + version = "0.4.32" 1781 + source = "registry+https://github.com/rust-lang/crates.io-index" 1782 + checksum = "fa76fb221a1f8acddf5b54ace85912606980ad661ac7a503b4570ffd3a624dad" 1783 + dependencies = [ 1784 + "cfg-if", 1785 + "js-sys", 1786 + "wasm-bindgen", 1787 + "web-sys", 1788 + ] 1789 + 1790 + [[package]] 1791 + name = "wasm-bindgen-macro" 1792 + version = "0.2.82" 1793 + source = "registry+https://github.com/rust-lang/crates.io-index" 1794 + checksum = "b260f13d3012071dfb1512849c033b1925038373aea48ced3012c09df952c602" 1795 + dependencies = [ 1796 + "quote", 1797 + "wasm-bindgen-macro-support", 1798 + ] 1799 + 1800 + [[package]] 1801 + name = "wasm-bindgen-macro-support" 1802 + version = "0.2.82" 1803 + source = "registry+https://github.com/rust-lang/crates.io-index" 1804 + checksum = "5be8e654bdd9b79216c2929ab90721aa82faf65c48cdf08bdc4e7f51357b80da" 1805 + dependencies = [ 1806 + "proc-macro2", 1807 + "quote", 1808 + "syn", 1809 + "wasm-bindgen-backend", 1810 + "wasm-bindgen-shared", 1811 + ] 1812 + 1813 + [[package]] 1814 + name = "wasm-bindgen-shared" 1815 + version = "0.2.82" 1816 + source = "registry+https://github.com/rust-lang/crates.io-index" 1817 + checksum = "6598dd0bd3c7d51095ff6531a5b23e02acdc81804e30d8f07afb77b7215a140a" 1818 + 1819 + [[package]] 1820 + name = "web-sys" 1821 + version = "0.3.59" 1822 + source = "registry+https://github.com/rust-lang/crates.io-index" 1823 + checksum = "ed055ab27f941423197eb86b2035720b1a3ce40504df082cac2ecc6ed73335a1" 1824 + dependencies = [ 1825 + "js-sys", 1826 + "wasm-bindgen", 1827 + ] 1828 + 1829 + [[package]] 1830 + name = "winapi" 1831 + version = "0.3.9" 1832 + source = "registry+https://github.com/rust-lang/crates.io-index" 1833 + checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1834 + dependencies = [ 1835 + "winapi-i686-pc-windows-gnu", 1836 + "winapi-x86_64-pc-windows-gnu", 1837 + ] 1838 + 1839 + [[package]] 1840 + name = "winapi-i686-pc-windows-gnu" 1841 + version = "0.4.0" 1842 + source = "registry+https://github.com/rust-lang/crates.io-index" 1843 + checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1844 + 1845 + [[package]] 1846 + name = "winapi-util" 1847 + version = "0.1.5" 1848 + source = "registry+https://github.com/rust-lang/crates.io-index" 1849 + checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 1850 + dependencies = [ 1851 + "winapi", 1852 + ] 1853 + 1854 + [[package]] 1855 + name = "winapi-x86_64-pc-windows-gnu" 1856 + version = "0.4.0" 1857 + source = "registry+https://github.com/rust-lang/crates.io-index" 1858 + checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1859 + 1860 + [[package]] 1861 + name = "windows-sys" 1862 + version = "0.36.1" 1863 + source = "registry+https://github.com/rust-lang/crates.io-index" 1864 + checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" 1865 + dependencies = [ 1866 + "windows_aarch64_msvc", 1867 + "windows_i686_gnu", 1868 + "windows_i686_msvc", 1869 + "windows_x86_64_gnu", 1870 + "windows_x86_64_msvc", 1871 + ] 1872 + 1873 + [[package]] 1874 + name = "windows_aarch64_msvc" 1875 + version = "0.36.1" 1876 + source = "registry+https://github.com/rust-lang/crates.io-index" 1877 + checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" 1878 + 1879 + [[package]] 1880 + name = "windows_i686_gnu" 1881 + version = "0.36.1" 1882 + source = "registry+https://github.com/rust-lang/crates.io-index" 1883 + checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" 1884 + 1885 + [[package]] 1886 + name = "windows_i686_msvc" 1887 + version = "0.36.1" 1888 + source = "registry+https://github.com/rust-lang/crates.io-index" 1889 + checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" 1890 + 1891 + [[package]] 1892 + name = "windows_x86_64_gnu" 1893 + version = "0.36.1" 1894 + source = "registry+https://github.com/rust-lang/crates.io-index" 1895 + checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" 1896 + 1897 + [[package]] 1898 + name = "windows_x86_64_msvc" 1899 + version = "0.36.1" 1900 + source = "registry+https://github.com/rust-lang/crates.io-index" 1901 + checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" 1902 + 1903 + [[package]] 1904 + name = "winreg" 1905 + version = "0.10.1" 1906 + source = "registry+https://github.com/rust-lang/crates.io-index" 1907 + checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" 1908 + dependencies = [ 1909 + "winapi", 1910 + ] 1911 + 1912 + [[package]] 1913 + name = "xattr" 1914 + version = "0.2.3" 1915 + source = "registry+https://github.com/rust-lang/crates.io-index" 1916 + checksum = "6d1526bbe5aaeb5eb06885f4d987bcdfa5e23187055de9b83fe00156a821fabc" 1917 + dependencies = [ 1918 + "libc", 1919 + ] 1920 + 1921 + [[package]] 1922 + name = "zeroize" 1923 + version = "1.5.7" 1924 + source = "registry+https://github.com/rust-lang/crates.io-index" 1925 + checksum = "c394b5bd0c6f669e7275d9c20aa90ae064cb22e75a1cad54e1b34088034b149f" 1926 + 1927 + [[package]] 1928 + name = "zip" 1929 + version = "0.6.2" 1930 + source = "registry+https://github.com/rust-lang/crates.io-index" 1931 + checksum = "bf225bcf73bb52cbb496e70475c7bd7a3f769df699c0020f6c7bd9a96dcf0b8d" 1932 + dependencies = [ 1933 + "aes 0.7.5", 1934 + "byteorder", 1935 + "bzip2", 1936 + "constant_time_eq", 1937 + "crc32fast", 1938 + "crossbeam-utils", 1939 + "flate2", 1940 + "hmac", 1941 + "pbkdf2", 1942 + "sha1", 1943 + "time", 1944 + "zstd", 1945 + ] 1946 + 1947 + [[package]] 1948 + name = "zstd" 1949 + version = "0.10.2+zstd.1.5.2" 1950 + source = "registry+https://github.com/rust-lang/crates.io-index" 1951 + checksum = "5f4a6bd64f22b5e3e94b4e238669ff9f10815c27a5180108b849d24174a83847" 1952 + dependencies = [ 1953 + "zstd-safe", 1954 + ] 1955 + 1956 + [[package]] 1957 + name = "zstd-safe" 1958 + version = "4.1.6+zstd.1.5.2" 1959 + source = "registry+https://github.com/rust-lang/crates.io-index" 1960 + checksum = "94b61c51bb270702d6167b8ce67340d2754b088d0c091b06e593aa772c3ee9bb" 1961 + dependencies = [ 1962 + "libc", 1963 + "zstd-sys", 1964 + ] 1965 + 1966 + [[package]] 1967 + name = "zstd-sys" 1968 + version = "1.6.3+zstd.1.5.2" 1969 + source = "registry+https://github.com/rust-lang/crates.io-index" 1970 + checksum = "fc49afa5c8d634e75761feda8c592051e7eeb4683ba827211eb0d731d3402ea8" 1971 + dependencies = [ 1972 + "cc", 1973 + "libc", 1974 + ]
+44
Cargo.toml
··· 1 + [package] 2 + name = "autopack" 3 + version = "0.1.0" 4 + edition = "2021" 5 + license = "Apache-2.0" 6 + license-file = "LICENSE" 7 + categories = ["command-line-utilities", "development-tools"] 8 + keywords = ["buildpacks", "cnb"] 9 + 10 + [[bin]] 11 + name = "auto-pack" 12 + test = false 13 + 14 + 15 + [dependencies] 16 + clap = { version = "3.2.11", features = ["derive", "env"] } 17 + tracing = "0.1" 18 + tracing-subscriber = { version = "0.3.15", features = ["env-filter"] } 19 + anyhow = "1.0" 20 + tokio = { version = "1", features = ["full"] } 21 + dialoguer = "0.10.1" 22 + serde = { version = "1.0", features = ["derive"] } 23 + serde_json = "1.0.82" 24 + npm-package-json = "0.1.3" 25 + open = "3.0.2" 26 + toml = "0.5" 27 + reqwest = { version = "0.11.11", features = ["stream"] } 28 + tempfile = "3.3.0" 29 + indicatif = { version = "0.17.0", features = ["improved_unicode"] } 30 + futures-util = "0.3.14" 31 + thiserror = "1.0.32" 32 + rmp-serde = "1.1.0" 33 + blake2 = "0.10.4" 34 + aes-gcm = "0.10.1" 35 + rand = "0.8.5" 36 + hex = "0.4.3" 37 + libc = "0.2.132" 38 + 39 + [target.'cfg(any(target_os="linux", target_os="macos"))'.dependencies] 40 + flate2 = "1.0.24" 41 + tar = "0.4.38" 42 + 43 + [target.'cfg(target_os="windows")'.dependencies] 44 + zip = "0.6.2"
+67
README.md
··· 1 + # `autopack` 2 + 3 + ## About 4 + `autopack` creates Docker/OCI images of a React web application built using Create React App (CRA) tool. All that without any Dockerfile or any Docker mastery. It is available as a command line application (CLI) that run across Windows, Linux, and macOS. 5 + 6 + For motivation and other details, check out the [autopack RFC](doc/autopack-rfc.md) 7 + 8 + ## Installation 9 + 10 + Autopack is distributed via the pre-built binaries that one could install from the [Releases page](https://github.com/kaychaks/autopack/releases). Download, unzip, move the unzipped executable to have it available in your terminal's path, and then use the `auto-pack` command from your React project root folder. 11 + 12 + ## Pre-requisties 13 + 14 + `autopack` relies on Docker to run the built OCI images. So a relevant Docker runtime is required to be installed. 15 + 16 + Currently, `autopack` only works for React projects built using CRA. It requires at least the `build` command to be present in the `package.json`. 17 + 18 + ## Usage 19 + 20 + ```bash 21 + > auto-pack --help 22 + autopack 0.1.0 23 + Auto Pack CLI 24 + 25 + USAGE: 26 + auto-pack <SUBCOMMAND> 27 + 28 + OPTIONS: 29 + -h, --help Print help information 30 + -V, --version Print version information 31 + 32 + SUBCOMMANDS: 33 + build Build auto-pack 34 + help Print this message or the help of the given subcommand(s) 35 + init Initializes auto-pack 36 + run Runs auto-pack 37 + 38 + ``` 39 + ### Initialization 40 + 41 + `autopack` initializes itself on the first run of this command. It will also download and install the required underlying tools. 42 + 43 + ![](doc/assets/init.gif) 44 + 45 + ### Build 46 + 47 + The build command generates the docker image. 48 + 49 + ![](doc/assets/build.gif) 50 + 51 + ### Run 52 + 53 + Run the container 54 + 55 + ![](doc/assets/run.gif) 56 + 57 + ## Build from source 58 + 59 + `autopack` is a Rust application. Follow the process of quickly setup the Rust development environment [as mentioned here](https://www.rust-lang.org/learn/get-started). And then 60 + 61 + ```bash 62 + # build 63 + $ cargo build 64 + # run 65 + $ cargo run init 66 + ``` 67 +
doc/assets/build.gif

This is a binary file and will not be displayed.

doc/assets/init.gif

This is a binary file and will not be displayed.

doc/assets/run.gif

This is a binary file and will not be displayed.

+187
doc/autopack-rfc.md
··· 1 + - Feature Name: auto-pack 2 + - Start Date: 2022-04-21 3 + 4 + # Summary 5 + 6 + A tool enabling web developers to use their favorite boilerplate-creating zero-config framework like CRA and but run the app in a setup similar as prod without - sacrificing the charm of the expected local development workflow or/and expecting them to get a DevOps certification. A tool to end the issue of *it runs fine in local but fails in the deployment pipeline*. 7 + 8 + # Motivation 9 + 10 + Modern enterprise web developers use boilerplate-creating tools like CRA that enhances their productivity when it comes to local development and also to produce a production-ready build artifact(s). However, the same developer is on their own when it comes to taking the built artifact(s) and packaging it in a way that it runs everywhere. 11 + 12 + There is a gap both from technical knowledge as well tooling perspective when it comes for the web developers to have the necessary softwares installed and configurations set to make their application deployable in foreign environments with the same level of sophistication / automation that they get for their local development. 13 + 14 + Most of the time web developers delegate these tasks to a different team (DevOps). And only would realize the issues with their package if/when some form of a continuous deployment pipeline fails to run the same package. This often becomes a contentious issue between developers and DevOps that impacts normal release workflow leading to impacting the overall project delivery. 15 + 16 + Containers today solves the issues of the past when it comes to have reproducible software deployment across various host platforms. However, to create container images from application's source code require decent amount of domain knowledge of unix shells, unix networking / disk I/O, and some general virtualization concepts. Containerizing web applications has its own challenges thanks to the complexity of writing a modern web application itself. So someone has to always create the container images post-facto looking/understanding the semantics of overall application design. 17 + 18 + What if we automate this whole process where developers don't have to change much in their local development workflow and not only get a production-ready build artifact but also a distributable artifact (container image) which takes care of cross-platform deployment issues. 19 + 20 + # Guide-level explanation 21 + 22 + As a web developer today working with modern front-end frameworks like React I have a standard development workflow - 23 + - I use my favorite boilerplate-creating tool like CRA to build a fresh application scaffolding for my new awesome project 24 + - open the project folder in my favorite IDE like VSCode 25 + - run the local development server using `npm run start`, 26 + - start making changes to the source code. 27 + 28 + In the above workflow one of the important gain that I get is the instant feedback in the form of auto-reloading browser. I as a web developer only concern myself to write application specific code and the boilerplate-creating tool takes care of the rest to finally show it in the browser either some error in my code or the actual web application. 29 + 30 + I want the tool here to behave in such a manner. I want to just keep on writing the same application code and I expect the tool to create the relevant deployment artifact. Better use the deployment artifact to deploy in some locally installed runtime. And much cooler would be if the application that today runs locally in the browser from some adhoc web server located within the node_modules folder (this is something [Webpack Dev server](https://webpack.js.org/configuration/dev-server/) provides) would actually gets served from the runtime after loading the deployment artifact ! 31 + 32 + ## Pre-Requisite 33 + 34 + Developers today need to have Node and VSCode installed for them to start creating any web application. For the time being, they would need one more software for this tool to work - Docker. 35 + 36 + ## Installation 37 + 38 + This tool will be a cross-platform CLI executable. Usually it means that for this tool to work it need to be in the system path. For the rest of discussion we will use the name of the tool to be `auto-pack` 39 + 40 + ## Usage 41 + 42 + `auto-pack` is a command-line utility. It generally performs tasks in the background but also have some minimum set of commands 43 + 44 + ```bash 45 + $ auto-pack init 46 + ``` 47 + 48 + `init` command initializes the project by doing some cursory checks to see if the relevant software is already installed. Right now it will check for Docker's availability in the system and whether it's already running. It will also take necessary steps to start any pre-requisites if not started already. 49 + 50 + Along with that it will also take care to install and configure any external dependencies required. Right now it might need to install Buildpack's CLI. 51 + 52 + Once the dependencies are installed and configured, this command will try to prime the project thereby creating and caching some default layers that might future processing faster. 53 + 54 + `init` might also change relevant `npm` scripts in project's `package.json` so that `auto-pack` could channel the usual project build and run commands. 55 + 56 + Finally, `init` would ensure that `auto-pack` runs as a daemon in the background. 57 + 58 + ```bash 59 + $ auto-pack run 60 + ``` 61 + 62 + `run` command would try to launch the packed image as a container and also do the necessary steps to wire the host system hardware ports & launch the relevant browser to render the app. Its job is to kind of mimic the similar experience that `npm run start` provides for a CRA built app. 63 + 64 + ```bash 65 + $ auto-pack export 66 + ``` 67 + 68 + `export` command would try to export the image & other relevant artifacts either directly to some registry or to some distributable format. Right now it might try to publish the image to some docker registry 69 + 70 + ```bash 71 + $ auto-pack show 72 + ``` 73 + 74 + `show` command would try to provide information about the images and running containers (if any). It would also provide information about the tool itself. 75 + 76 + ```bash 77 + $ auto-pack stop 78 + ``` 79 + 80 + `stop` command would try to stop the background process 81 + 82 + ```bash 83 + $ auto-pack clean 84 + ``` 85 + 86 + `clean` command would clean up any stalled processes, intermediate logs, and other temporary files. It will also try to clean up images & running containers. 87 + 88 + ## Expected workflow 89 + 90 + The intent of the tool is not to change developer's current workflow drastically. Once the developer has setup their project using a boilerplate-creating tool they install `auto-pack` executable and run `auto-pack init`. From that point - `auto-pack` should run in the background and developers should have no change in the way they interact with their application during their usual development process. 91 + 92 + Developers should have option to run their application using the standard `npm` scripts provided as part of their boilerplate creating tool or use the new scripts that will leverage `auto-pack` to run the same application but this time by serving the distributable artifact from within the container which `auto-pack` should have generated & launched while it was running in the background. 93 + 94 + ## CI/CD Pipeline Usage 95 + 96 + # Reference-level explanation 97 + 98 + The idea of `auto-pack` is to leverage the concept of creating container images without a Dockerfile. Tools like [Buildpack](https://buildpacks.io/) & [source-to-image (s2i)](https://github.com/openshift/source-to-image) takes this concept to the point where they could automatically generate container images from source code. Such tools work as per this sequence of tasks 99 + 100 + ``` 101 + Detect --> Build --> Export 102 + ``` 103 + 104 + - **Detect**: detects from source code to pick the right base image 105 + - **Build**: install dependencies and run the build command 106 + - **Export**: create OCI image 107 + 108 + `auto-pack` apart from the above tasks will also be doing 109 + 110 + - **SPA Server**: `auto-pack` will create an automatic Node server to serve the SPA which will provide few functionality by default like dynamic configuration management & efficient static file caching. In case a project already has some server `auto-pack` will have a way to leverage the same in place of it's own server. 111 + - **Launch**: once exported the image will be used to launch within a container runtime and also render the web application by (re)launching a relevant browser 112 + - **Watch**: watch for file changes &initiate Build, Export, and Launch 113 + 114 + ``` 115 + Detect --> SPA Server --> Build --> Export --> Launch 116 + ^ | 117 + | v 118 + +----------Watch------+ 119 + ``` 120 + 121 + ## Extendable Design 122 + 123 + `auto-pack` will use Buildpack internally to create OCI images from source code. However, the implementation of the same will be made following a [Bridge](https://en.wikipedia.org/wiki/Bridge_pattern) design pattern so that the relevant tasks are abstracted from the actual implementation using some tool. That tool might be Buildpack right now but that also could change in future. 124 + 125 + ## Rust as the implementation language 126 + 127 + Implementing a tool like `auto-pack` would require system level tasks such as running processes in background mostly as a daemon, doing efficient disk I/O, working with OS level concurrency when it comes to file watching, and finally working efficiently as a CLI across platforms. It is necessary for implementation of `auto-pack` to happen in a system language and not in a high-level programming language so that we don't have to deal with system-level optimizations later. Moreover, we also want the implementation of `auto-pack` to be following a typed functional programming design so that we have solid guarantees from the type system during construction and have other efficiencies that a functional programming thinking provides. 128 + 129 + Rust is the only systems-programing language that matches our desired criteria. It has a sound and highly sophisticated type system that will not only guide us during implementation but also keep our implementation safe. It's system-level support via language primitives & supporting libraries will help having an efficient `auto-pack`' implementation. Moreover, today [Rust is considered to be the future of JS infrastructure](https://leerob.io/blog/rust) given the kind of adoption Rust is having across JS community. 130 + 131 + 132 + ## Custom Buildpack 133 + 134 + `auto-pack` will be leveraging Buildpack to create OCI images without Dockerfiles. Buildpack today provides a nice design via which source code of any language are converted into OCI images. There are already efficient base build packs available for JS projects especially from [Paketo](https://paketo.io/) which `auto-pack` will leverage. 135 + 136 + However, for `auto-pack` we will create a custom buildpack which will have its own detection & building routine. The initial design will focus on some common patterns that today's boilerplate-creating frameworks like CRA, Vite are adopting when they are building the dev server for local development. `auto-pack` will hook into those places to have an efficient build routine. 137 + 138 + Moreover, `auto-pack` will be having its own custom process of layer caching based on our understanding of different types of files that are generated for a modern web application. Every source code change won't generate all new files especially the static assets and hence such could be well cached. A custom buildpack would contain such instructions & more. 139 + 140 + ## Background Process 141 + 142 + `auto-pack` will be working as a directory specific daemon. Core reason for this design decision is because of the nature of tasks that `auto-pack` will do. The tasks of generating images from source code might take a long time (initially) which will surely impact developer productivity. We don't want developers to change their normal workflow but still get the advantage of a local containerized deployment and execution of their web application. And hence doing the resource consuming tasks in the background without impacting developer's main workflow will be beneficial. 143 + 144 + `auto-pack` will be doing following tasks in the background 145 + - **Build**: generating the production build from the current source code of the project 146 + - **Creating Image**: using the build to create the OCI image 147 + - **Creating Container**: using the image to create a container in the available runtime (for now it would be docker) 148 + - **File Watching**: watching for file changes to do incremental building and re-creating images & containers 149 + 150 + ## Incremental builds 151 + 152 + `auto-pack` will be optimizing the build routine on top of the build artifacts produced by the boilerplate-creating tools like CRA. it is going to help `auto-pack` provide a faster image creation & execution feedback. The build produced by most boilerplate-creating tools are in the form of JS bundles (this might change in future once native ES modules are widely used for production build). `auto-pack` will try to use a content-hashing based approach to identify and replace built artifacts in running containers leveraging Buildpack's layer rebasing strategies. 153 + 154 + # Drawbacks 155 + 156 + Generating images without Dockerfile is not a mainstream approach. And when things happen in the background it becomes difficult to diagnose any issues. `auto-pack` will try to smoothen all this with efficient logging, helpful info messages, and proper CLI command options. 157 + 158 + Tools that try to leverage OS processes to do background file monitoring and disk I/O at the same time might suffer from standard issues like stalled processes, zombie daemons, and resource draining threads. `auto-task` will be leveraging Rust's powerful [Tokio](https://tokio.rs/) suite of APIs to safely manage concurrent I/O processes and will use a Supervision Trees based approach to monitor processes. 159 + 160 + Generating & executing images without Dockerfile would surely help developers achieve productivity when it comes to running & testing their application in a production environment locally. However, it might be an issue for DevOps teams who are generally tasked to containerize the application via CI/CD pipelines. And there Dockerfiles are still the preferred way to create images. Even though there are [CI/CD platforms](https://buildpacks.io/docs/tools/) that support Cloud Native Buildpacks (CNB) to create images from source code but in enterprises that process is not as prevalent as it should be. `auto-pack` being a cross platform CLI executable would ensure that it works in CI/CD platforms (which are mostly linux based). Once the CI/CD pipeline environment has docker engine available then `auto-pack` would gather all its relevant dependencies during initialization hence for DevOps it would as good as replacing some variation of `docker build` command with `auto-pack init && auto-pack export` - these series of command would first initialize and then do the necessary steps to export an image out of the source code. 161 + 162 + # Rationale and alternatives 163 + 164 + There are tools today which either provide some way to build CNI images without the requirement of Docker to be available as a deamon or Dockerfile to be available. `auto-pack` is mostly concerned with the later use case. 165 + 166 + Cloud Native Buildpacks are the most preferred way to generate images from source code today. However, it comes with its own learning curve which might not be as daunting as Docker itself but still it requires a standard web developer proficient in React to learn decent amount of lingo from world of containers. And similarly there are other tools to generate images directly without Dockerfile like [creating Docker images with Nix package manager](https://nix.dev/tutorials/building-and-running-docker-images) and [Jib](https://github.com/GoogleContainerTools/jib). Jib comes close to something what CNBs are doing - i.e. directly producing CNI images from source code. However, where CNB is a generic specification that can be operationalized with applications built in any language / platform, Jib is only specific to Java based applications. Creating docker images with Nix is as generic as writing Dockerfiles but in a more sophisticated & expressive programming language (unlike the Dockerfile syntax which is an adhoc configuration language syntax lacking expressiveness and usual developer experience). However, it comes with additional requirements of a presence of a system level tool (i.e. Nix itself) and/or managing tool specific configurations along with application source code. 167 + 168 + Intent of `auto-pack` is to make it easier for web developers (the target users) to venture the world of containers and hence it does not have to bother about requirements of other set of users. That constraint also enables `auto-pack` to have specific customizations that will be relevant and beneficial for modern web developers using tools like CRA. And again it can be safely used as a local only tool while CI/CD platforms could use known tools like Dockerfiles. 169 + 170 + Apropos the above current state of tools to build CNI images without Dockerfiles and the unique mission of `auto-pack`, Cloud Native Buildpacks based backend to create CNI images has been considered to be the choice of underlying technology to create CNI images directly from code. Since the focus is to balance between providing similar developer experience that modern web-developers expect from CLI tools and also to not re-inventing the wheel of creating something from scratch, `auto-pack` is an attempt to create a simple automaton over Cloud Native Buildpacks for a very specific set of users i.e. modern web developers using React specific tools like CRA to develop their applications. 171 + # Prior art 172 + 173 + `auto-pack` will leverage ideas from various tools. The basic idea of generating container images from source code was first seen in Nix and then popularized as CNB. The idea of running processes in the background to improve the efficiency of the activities happening in the foreground is something that Unix daemons & Kernel level processes generally do. There are many designs captured in [The Architecture of Open Source Applications](http://aosabook.org/en/index.html) which kind of elaborates how to do the same efficiently for a reduced scope tool like `auto-pack`. Idea of doing incremental builds via content hashing is something that build tools like [Bazel](https://bazel.build/docs/build#correct-incremental-rebuilds does pretty efficiently. `auto-pack` will leverage some patterns from those implementations. 174 + 175 + # Unresolved questions 176 + 177 + - How can `auto-pack` hook its logs into the UX of `npm run start` provided by CRA ? Or will it be fine if it works via a different npm script like `npm run start:pack` ? The later would reduce its usage though. 178 + - Should `auto-pack` start the background process as soon as users goes inside project's directory like how tools like [direnv](https://direnv.net/) works ? Or it should only start on users action ? We can also have a configuration to decide. 179 + - How to manage the proliferation of docker images which would get created for every change the developer would make ? 180 + 181 + # Future possibilities 182 + 183 + `auto-pack` will start with limited scope to support specific types of projects (React projects built using CRA) with some fixed conventions. Reason for the same is to have the initial version properly tested. However, future versions should have ways to support more type of projects and should have a way to support configurations. 184 + 185 + The requirement of the presence of Docker as engine / daemon with root level privileges for `auto-pack` to work would change once native OCI builders & runtimes like [Buildah](https://github.com/containers/buildah/blob/main/README.md) and [Podman](https://github.com/containers/podman) respectively are widely available across platforms especially in Windows. For `auto-pack` to work in Windows which is the dominant platform of web developers in enterprises Docker is an unfortunate dependency. The future of containerized distribution of applications should be to create OCI images directly from code and running it in some rootless OCI runtime. 186 + 187 + Although `auto-pack` would always be a CLI tool it would be beneficial if we could also have it as an editor plugin. That way we can provide much better UX for developers. Modern IDEs like VSCode provide tools like [Language Server Protocol](https://microsoft.github.io/language-server-protocol/) which can also eliminate `auto-pack` to run as daemon and rather have a nice interface similar to how other Language Server Protocol servers work.
+100
src/autopack.rs
··· 1 + mod crypto; 2 + mod filesystem; 3 + #[cfg(test)] 4 + mod tests; 5 + 6 + use self::filesystem::StateFiles; 7 + use super::{pack::Pack, runtime::Runtime}; 8 + use crate::{ 9 + buildpack::BuildPackProject, 10 + docker::Docker, 11 + error::AppError, 12 + log::{banner, trying}, 13 + package_json::Project, 14 + }; 15 + use serde::{Deserialize, Serialize}; 16 + use std::path::Path; 17 + use tracing::debug; 18 + 19 + #[derive(Debug, Serialize, Deserialize, Default, PartialEq, Clone)] 20 + pub(crate) struct AutoPack { 21 + pub(crate) runtime: Runtime, 22 + pub(crate) pack_cli: Pack, 23 + pub(crate) docker: Docker, 24 + pub(crate) buildpack: BuildPackProject, 25 + pub(crate) client_project: Project, 26 + } 27 + 28 + impl AutoPack { 29 + #[cfg(test)] 30 + pub fn new(runtime: Option<Runtime>) -> AutoPack { 31 + AutoPack { 32 + runtime: runtime.unwrap_or_default(), 33 + pack_cli: Pack::default(), 34 + docker: Docker::default(), 35 + buildpack: BuildPackProject::default(), 36 + client_project: Project::default(), 37 + } 38 + } 39 + pub(crate) fn save(&self, state_path_dir: Option<&Path>) -> anyhow::Result<StateFiles> { 40 + StateFiles::save(self, state_path_dir) 41 + } 42 + 43 + pub(crate) fn load(runtime_dir: Option<&Path>) -> anyhow::Result<AutoPack> { 44 + StateFiles::new(runtime_dir)?.load_autopack() 45 + } 46 + 47 + pub(crate) fn validate(&self) -> anyhow::Result<bool> { 48 + debug!("validating autopack"); 49 + 50 + self.docker.is_running()?; 51 + 52 + self.pack_cli.is_present()?; 53 + 54 + Ok(true) 55 + } 56 + 57 + pub(crate) fn load_validate(runtime_dir: Option<&Path>) -> Result<AutoPack, AppError> { 58 + let autopack = AutoPack::load(runtime_dir).map_err(|e| { 59 + crate::log::error("Please run `auto-pack init` to re-initialize autopack."); 60 + AppError::RunError("Failed loading autopack state", e) 61 + })?; 62 + 63 + autopack 64 + .validate() 65 + .map_err(|e| AppError::RunError("Failed validating autopack", e))?; 66 + 67 + Ok(autopack) 68 + } 69 + 70 + pub(crate) fn build(&self, clear_cache: bool) -> anyhow::Result<()> { 71 + trying("Auto packing project"); 72 + 73 + self.validate()?; 74 + 75 + self.pack_cli.build_image( 76 + self.runtime.project_toml().as_str(), 77 + &self.runtime.proc_file().proc_default_command(), 78 + &self.runtime.proc_file().proc_file_path(), 79 + &self.runtime.proc_file().container_bindings_path(), 80 + &self.client_project.image_name, 81 + clear_cache, 82 + )?; 83 + 84 + Ok(()) 85 + } 86 + 87 + pub(crate) async fn run(self, port: usize) -> anyhow::Result<()> { 88 + banner("Running autopack(ed) project"); 89 + 90 + self.docker 91 + .run( 92 + self.client_project.image_name, 93 + &self.client_project.path, 94 + port, 95 + ) 96 + .await?; 97 + 98 + Ok(()) 99 + } 100 + }
+141
src/autopack/crypto.rs
··· 1 + use aes_gcm::aead::consts::{U12, U32}; 2 + use aes_gcm::aead::generic_array::GenericArray; 3 + use aes_gcm::{aead::Aead, KeyInit}; 4 + use blake2::{Blake2b512, Digest}; 5 + use rand::Rng; 6 + use std::fs; 7 + use tracing::debug; 8 + 9 + use crate::autopack::filesystem::StateFiles; 10 + 11 + pub(super) struct APCrypto { 12 + cipher_key: GenericArray<u8, U32>, 13 + nonce: GenericArray<u8, U12>, 14 + content_hash: Option<Vec<u8>>, 15 + } 16 + 17 + pub(super) struct APEncryptVal { 18 + pub(super) cipher_text: Vec<u8>, 19 + pub(super) nonce: GenericArray<u8, U12>, 20 + } 21 + 22 + impl APEncryptVal { 23 + pub(super) fn new(ct: &str, n: &[u8; 12]) -> Self { 24 + APEncryptVal { 25 + cipher_text: hex::decode(ct).unwrap(), 26 + nonce: *GenericArray::from_slice(n), 27 + } 28 + } 29 + } 30 + 31 + pub(super) struct APCryptoBuilder { 32 + cipher_key: GenericArray<u8, U32>, 33 + // nonce_key: Option<GenericArray<u8, U12>>, 34 + content_hash: Option<Vec<u8>>, 35 + } 36 + 37 + impl APCrypto { 38 + pub(super) fn builder() -> APCryptoBuilder { 39 + let cipher_key = 40 + &hex::decode("b52c505a37d78eda5dd34f20c22540ea1b58963cf8e5bf8ffa85f9f2492505b4") 41 + .unwrap(); 42 + let cipher_key = *GenericArray::from_slice(cipher_key); 43 + 44 + APCryptoBuilder { 45 + cipher_key, 46 + // nonce_key: None, 47 + content_hash: None, 48 + } 49 + } 50 + 51 + pub(super) fn encrypt(&self) -> anyhow::Result<APEncryptVal> { 52 + let content_hash = self 53 + .content_hash 54 + .clone() 55 + .ok_or_else(|| anyhow::anyhow!("no content to encrypt"))?; 56 + 57 + let cipher = aes_gcm::Aes256Gcm::new(&self.cipher_key); 58 + 59 + let cipher_text = cipher 60 + .encrypt(&self.nonce, content_hash.as_slice()) 61 + .map_err(|e| anyhow::anyhow!("encryption error :: {:?}", e))?; 62 + 63 + Ok(APEncryptVal { 64 + cipher_text, 65 + nonce: self.nonce, 66 + }) 67 + } 68 + 69 + pub(super) fn validate_hashes(state_files: &StateFiles) -> anyhow::Result<&StateFiles> { 70 + debug!("Validating content hashes"); 71 + 72 + let StateFiles { 73 + state_content, 74 + checksum, 75 + .. 76 + } = state_files; 77 + 78 + let reference_crypto_val = { 79 + let check_content = fs::read_to_string(checksum.clone())?; 80 + let lines: Vec<_> = check_content.lines().map(|l| l.trim()).collect(); 81 + let nonce = hex::decode(lines[1]).unwrap(); 82 + let mut x = [0; 12]; 83 + x[..=11].clone_from_slice(&nonce); 84 + 85 + APEncryptVal::new(lines[0], &x) 86 + }; 87 + 88 + let decrypted = APCrypto::builder().decrypt(reference_crypto_val)?; 89 + 90 + let existing = { 91 + let state_content = fs::read(state_content.clone())?; 92 + APCrypto::builder().content_hash(state_content).build() 93 + }; 94 + 95 + if decrypted.content_hash == existing.content_hash { 96 + Ok(state_files) 97 + } else { 98 + anyhow::bail!("Hashes don't match") 99 + } 100 + } 101 + } 102 + 103 + impl APCryptoBuilder { 104 + pub(super) fn content_hash(&mut self, contents: Vec<u8>) -> &mut Self { 105 + self.content_hash = Some( 106 + Blake2b512::new_with_prefix(contents) 107 + .finalize() 108 + .as_slice() 109 + .into(), 110 + ); 111 + self 112 + } 113 + 114 + pub(super) fn build(&self) -> APCrypto { 115 + let rand_gen = rand::thread_rng().gen::<[u8; 12]>(); 116 + 117 + let nonce = *GenericArray::from_slice(&rand_gen); 118 + 119 + APCrypto { 120 + cipher_key: self.cipher_key, 121 + content_hash: self.content_hash.clone(), 122 + nonce, 123 + } 124 + } 125 + 126 + pub(super) fn decrypt(&mut self, val: APEncryptVal) -> anyhow::Result<APCrypto> { 127 + let cipher = aes_gcm::Aes256Gcm::new(&self.cipher_key); 128 + 129 + let content_hash = cipher 130 + .decrypt(&val.nonce, val.cipher_text.as_slice()) 131 + .map_err(|e| anyhow::anyhow!("Decryption error :: {:?}", e.to_string()))?; 132 + 133 + self.content_hash = Some(content_hash); 134 + 135 + Ok(APCrypto { 136 + cipher_key: self.cipher_key, 137 + content_hash: self.content_hash.clone(), 138 + nonce: val.nonce, 139 + }) 140 + } 141 + }
+133
src/autopack/filesystem.rs
··· 1 + use super::{ 2 + crypto::{APCrypto, APEncryptVal}, 3 + AutoPack, 4 + }; 5 + use crate::{log::error, runtime::Runtime}; 6 + use rmp_serde::{decode, encode}; 7 + use std::{ 8 + ffi::OsStr, 9 + fs::{self, File}, 10 + path::{Path, PathBuf}, 11 + time, 12 + }; 13 + 14 + pub(crate) struct StateFiles { 15 + pub(crate) state_content: PathBuf, 16 + pub(crate) checksum: PathBuf, 17 + // pub(crate) state_dir: PathBuf, 18 + } 19 + 20 + impl StateFiles { 21 + pub(super) fn new(runtime_dir: Option<&Path>) -> anyhow::Result<StateFiles> { 22 + let runtime_dir = runtime_dir 23 + .map(|e| e.to_path_buf()) 24 + .unwrap_or_else(|| Runtime::default().dir()); 25 + 26 + let state_dir = runtime_dir.join(".state"); 27 + 28 + let fs = state_dir 29 + .read_dir() 30 + .map_err(|e| { 31 + error("Failed loading autopack state"); 32 + anyhow::anyhow!( 33 + "State path {} does not exist :: {:?}", 34 + state_dir.display(), 35 + e.to_string() 36 + ) 37 + })? 38 + .fold((None, None), |mut acc, f| match f { 39 + Ok(x) => { 40 + let ext = x.path(); 41 + let ext = ext.extension().and_then(OsStr::to_str).unwrap_or_default(); 42 + if ext == "checksum" { 43 + acc.1 = Some(x.path()); 44 + } else { 45 + acc.0 = Some(x.path()); 46 + } 47 + acc 48 + } 49 + _ => acc, 50 + }); 51 + 52 + let state_content = 53 + fs.0.ok_or_else(|| anyhow::anyhow!("State content path not found"))?; 54 + let checksum = 55 + fs.1.ok_or_else(|| anyhow::anyhow!("State content checksum path not found"))?; 56 + 57 + Ok(StateFiles { 58 + state_content, 59 + checksum, 60 + // state_dir, 61 + }) 62 + } 63 + pub(super) fn save( 64 + autopack: &AutoPack, 65 + save_path: Option<&Path>, 66 + ) -> anyhow::Result<StateFiles> { 67 + let default_path = autopack.runtime.dir().join(".state"); 68 + let save_path = save_path.unwrap_or(&default_path); 69 + 70 + if !save_path.exists() { 71 + fs::create_dir(save_path)?; 72 + } else { 73 + fs::remove_dir_all(save_path)?; 74 + fs::create_dir(save_path)?; 75 + } 76 + 77 + let version = autopack.client_project.package_json.version.clone(); 78 + 79 + let state_file_name = format!( 80 + "{}_{}_{}", 81 + autopack.client_project.package_json.name, 82 + version, 83 + time::SystemTime::now() 84 + .duration_since(time::UNIX_EPOCH) 85 + .map_err(|e| anyhow::anyhow!("could not get current time :: {:?}", e))? 86 + .as_millis() 87 + ); 88 + let state_file = save_path.join(state_file_name.clone()); 89 + 90 + let save_file = save_path.join(state_file); 91 + let checksum_file = save_path.join(format!("{}.checksum", state_file_name)); 92 + let contents = encode::to_vec(&autopack)?; 93 + 94 + let a = APCrypto::builder().content_hash(contents.clone()).build(); 95 + 96 + let APEncryptVal { cipher_text, nonce } = a.encrypt()?; 97 + 98 + fs::write(save_file.clone(), contents) 99 + .map_err(|e| anyhow::anyhow!("error writing state file :: {:?}", e))?; 100 + 101 + fs::write( 102 + checksum_file.clone(), 103 + format!( 104 + r"{} 105 + {}", 106 + hex::encode(cipher_text), 107 + hex::encode(nonce) 108 + ), 109 + ) 110 + .map_err(|e| anyhow::anyhow!("error writing checksum :: {:?}", e))?; 111 + 112 + Ok(StateFiles { 113 + checksum: checksum_file, 114 + state_content: save_file, 115 + // state_dir: save_path.to_path_buf(), 116 + }) 117 + } 118 + pub(super) fn load_autopack(&self) -> anyhow::Result<AutoPack> { 119 + APCrypto::validate_hashes(self)?; 120 + 121 + let autpack = decode::from_read(File::open(self.state_content.clone()).map_err(|e| { 122 + anyhow::anyhow!("Failed to open file at {:?} :: {:?}", self.state_content, e) 123 + })?) 124 + .map_err(|e| { 125 + anyhow::anyhow!( 126 + "Fail to deserialize application state from file at {:?} :: {:?}", 127 + self.state_content, 128 + e 129 + ) 130 + })?; 131 + Ok(autpack) 132 + } 133 + }
+103
src/autopack/tests.rs
··· 1 + use crate::{autopack::AutoPack, runtime::Runtime}; 2 + use std::fs::{self, OpenOptions}; 3 + use tempfile::Builder; 4 + 5 + #[test] 6 + fn app_state_save_load() { 7 + let runtime_dir = Builder::new() 8 + .tempdir() 9 + .expect("expecting a temp file to be created"); 10 + let runtime = Runtime::builder(runtime_dir.path()) 11 + .dir(false) 12 + .expect("") 13 + .build(); 14 + let app = AutoPack::new(Some(runtime.clone())); 15 + 16 + app.save(None).expect("failed save"); 17 + let saved_app = AutoPack::load(Some(&runtime.dir())).expect("failed load"); 18 + 19 + assert_eq!(app, saved_app); 20 + } 21 + 22 + #[test] 23 + #[should_panic] 24 + fn app_state_bad_content() { 25 + use std::io::Write; 26 + 27 + let app = AutoPack::default(); 28 + let save_path = Builder::new() 29 + .tempdir() 30 + .expect("expecting a temp file to be created"); 31 + 32 + let s = app.save(Some(save_path.path())).expect("failed save"); 33 + // let st = StateFiles::new(&s.state_content).expect("failed to parse the state files"); 34 + let mut f = OpenOptions::new() 35 + .append(true) 36 + .open(s.state_content.clone()) 37 + .expect("failed opening state file"); 38 + writeln!(f, "A new line").expect("failed to write to state file"); 39 + 40 + AutoPack::load(Some(s.state_content.as_path())).expect("failed load"); 41 + } 42 + 43 + #[test] 44 + #[should_panic] 45 + fn app_state_bad_hash() { 46 + let app = AutoPack::default(); 47 + let save_path = Builder::new() 48 + .tempdir() 49 + .expect("expecting a temp file to be created"); 50 + 51 + let s = app.save(Some(save_path.path())).expect("failed save"); 52 + // let st = StateFiles::new(&s).expect("failed to parse the state files"); 53 + 54 + let lines = fs::read_to_string(s.checksum.clone()).expect("failed reading checksum"); 55 + let ls = lines.lines().collect::<Vec<_>>(); 56 + let ls1 = ls[0]; 57 + let mut ls11: Vec<_> = ls1.chars().take(ls1.len() - 2).collect(); 58 + ls11.push('0'); 59 + ls11.push('1'); 60 + 61 + let x = ls11.into_iter().map(|x| x.to_string()).collect::<Vec<_>>(); 62 + 63 + let lss = format!( 64 + r"{} 65 + {}", 66 + &x.join(""), 67 + ls[1] 68 + ); 69 + fs::write(s.checksum, lss).expect("failed to write checksum"); 70 + 71 + AutoPack::load(Some(s.state_content.as_path())).expect("failed load"); 72 + } 73 + 74 + #[test] 75 + #[should_panic] 76 + fn app_state_bad_nonce() { 77 + let app = AutoPack::default(); 78 + let save_path = Builder::new() 79 + .tempdir() 80 + .expect("expecting a temp file to be created"); 81 + 82 + let s = app.save(Some(save_path.path())).expect("failed save"); 83 + // let st = StateFiles::new(&s).expect("failed to parse the state files"); 84 + 85 + let lines = fs::read_to_string(s.checksum.clone()).expect("failed reading checksum"); 86 + let ls = lines.lines().collect::<Vec<_>>(); 87 + let ls1 = ls[1]; 88 + let mut ls11: Vec<_> = ls1.chars().take(ls1.len() - 2).collect(); 89 + ls11.push('0'); 90 + ls11.push('0'); 91 + 92 + let x = ls11.into_iter().map(|x| x.to_string()).collect::<Vec<_>>(); 93 + 94 + let lss = format!( 95 + r"{} 96 + {}", 97 + ls[0], 98 + &x.join("") 99 + ); 100 + fs::write(s.checksum, lss).expect("failed to write checksum"); 101 + 102 + AutoPack::load(Some(s.state_content.as_path())).expect("failed load"); 103 + }
+16
src/bin/auto-pack.rs
··· 1 + use autopack::cli::Cli; 2 + use dialoguer::console::Term; 3 + use std::process; 4 + 5 + #[tokio::main(flavor = "current_thread")] 6 + async fn main() { 7 + tracing_subscriber::fmt::try_init().expect("tracing sub init failed"); 8 + Term::stdout().set_title("auto-pack"); 9 + match Cli::new().run().await { 10 + Ok(_) => process::exit(0), 11 + Err(e) => { 12 + e.handle(); 13 + process::exit(1) 14 + } 15 + } 16 + }
+167
src/buildpack.rs
··· 1 + mod spec; 2 + #[cfg(test)] 3 + mod tests; 4 + 5 + use super::log::{success, trying}; 6 + use crate::error::AppError; 7 + use npm_package_json::{Package, RepositoryReference}; 8 + use std::{env, fs, path::Path, vec}; 9 + use toml::Value; 10 + 11 + pub(crate) use self::spec::*; 12 + 13 + fn node_version_from_engine(pkg_json: Package) -> Option<String> { 14 + pkg_json.engines.get("node").cloned() 15 + } 16 + 17 + fn node_version_from_env() -> Option<String> { 18 + env::var("NODE_VERSION").ok() 19 + } 20 + 21 + fn node_version_from_nvmrc() -> Option<String> { 22 + fs::read_to_string({ 23 + let this = env::current_dir().map(|mut p| { 24 + p.push(".nvmrc"); 25 + p 26 + }); 27 + let default = Path::new(".nvmrc").to_path_buf(); 28 + match this { 29 + Ok(t) => t, 30 + Err(_) => default, 31 + } 32 + }) 33 + .ok() 34 + } 35 + 36 + impl Default for BuildPackProject { 37 + fn default() -> Self { 38 + BuildPackProject::node_cra_template() 39 + } 40 + } 41 + 42 + impl BuildPackProject { 43 + fn node_cra_template() -> Self { 44 + let build = Some(Build { 45 + env: Some(vec![ 46 + Env { 47 + name: Some("BP_DISABLE_SBOM".to_string()), 48 + value: Some("true".to_string()), 49 + }, 50 + // Env { 51 + // name: Some("NODE_ENV".to_string()), 52 + // value: Some("development".to_string()), 53 + // }, 54 + Env { 55 + name: Some("BP_NODE_RUN_SCRIPTS".into()), 56 + value: Some("build".into()), 57 + }, 58 + ]), 59 + file_list: Some(FileList::Exclude( 60 + vec![ 61 + ".devcontainer", 62 + ".husky", 63 + ".editorconfig", 64 + ".gitignore", 65 + ".prettierrc", 66 + "LICENSE.md", 67 + "project-logo.png", 68 + "README.md", 69 + "node_modules", 70 + "build", 71 + "dist", 72 + ] 73 + .iter() 74 + .map(|&x| x.to_string()) 75 + .collect(), 76 + )), 77 + buildpacks: Some(vec![BuildPack { 78 + id: None, 79 + buildpack_field: BuildPackField::Uri(Some("paketo-buildpacks/nodejs".to_string())), 80 + }]), 81 + }); 82 + 83 + BuildPackProject { 84 + project: None, 85 + build, 86 + metadata: Some(Metadata::Meta( 87 + vec![( 88 + "source".to_string(), 89 + Value::String("auto-pack-0.0.1".to_string()), 90 + )] 91 + .into_iter() 92 + .collect(), 93 + )), 94 + } 95 + } 96 + 97 + pub(crate) fn enable_live_reload(&mut self) -> &mut Self { 98 + self.build.as_mut().map(|b| { 99 + b.env.as_mut().map(|e| { 100 + e.push(Env { 101 + name: Some("BP_LIVE_RELOAD_ENABLED".to_string()), 102 + value: Some("true".to_string()), // TODO: configure it 103 + }) 104 + }) 105 + }); 106 + self 107 + } 108 + 109 + pub(crate) fn setup(pkg_json: &Package, live_reload: bool) -> Self { 110 + trying("Configuring Cloud Native Buildpack configuration"); 111 + let mut base = BuildPackProject::node_cra_template(); 112 + 113 + if live_reload { 114 + base.enable_live_reload(); 115 + } 116 + 117 + let pkg_json_cloned = pkg_json.clone(); 118 + base.project = Some(Project { 119 + id: Some(pkg_json_cloned.name), 120 + name: pkg_json_cloned.description, 121 + version: Some(pkg_json_cloned.version), 122 + authors: None, 123 + documentation_url: None, 124 + source_url: pkg_json_cloned.repository.map(|r| match r { 125 + RepositoryReference::Short(s) => s, 126 + RepositoryReference::Full(f) => f.url, 127 + }), 128 + licenses: pkg_json_cloned.license.map(|s| { 129 + vec![License { 130 + licence_type: Some(s), 131 + uri: None, 132 + }] 133 + }), 134 + }); 135 + 136 + match base.build.as_mut() { 137 + Some(b) => match b.env.as_mut() { 138 + Some(e) => e.push(Env { 139 + name: Some("BP_NODE_VERSION".to_string()), 140 + value: node_version_from_env() 141 + .or_else(node_version_from_nvmrc) 142 + .or_else(|| node_version_from_engine(pkg_json.clone())) 143 + .or_else(|| Some("^16.0.0".to_string())), 144 + }), 145 + None => {} 146 + }, 147 + None => {} 148 + } 149 + 150 + success("Finished configuring Cloud Native Buildpacks"); 151 + base 152 + } 153 + 154 + pub(crate) fn export_toml(&self, export_path: &Path) -> Result<(), AppError> { 155 + toml::to_string_pretty(self) 156 + .and_then(|s| { 157 + fs::write(export_path, s).map_err(|e| toml::ser::Error::Custom(e.to_string()))?; 158 + Ok(()) 159 + }) 160 + .map_err(|e| { 161 + AppError::PostConfigureError( 162 + "Failed to export Buildpack project toml", 163 + anyhow::anyhow!(e), 164 + ) 165 + }) 166 + } 167 + }
+102
src/buildpack/spec.rs
··· 1 + mod serde_macro; 2 + 3 + use crate::ser_deser_str_with_def; 4 + use serde::{Deserialize, Serialize}; 5 + use toml::value::Table; 6 + 7 + pub(crate) fn default_version() -> String { 8 + "latest".to_string() 9 + } 10 + 11 + pub(crate) fn default_uri() -> String { 12 + "urn::buildpack::<id>".to_string() 13 + } 14 + 15 + pub(crate) fn default_shell() -> String { 16 + "/bin/sh".to_string() 17 + } 18 + 19 + ser_deser_str_with_def!(VersionSerDeser, default_version); 20 + ser_deser_str_with_def!(UriSerDeser, default_uri); 21 + ser_deser_str_with_def!(ShellSerDeser, default_shell); 22 + 23 + /// Specification following the BuildPack project descriptor 24 + /// https://buildpacks.io/docs/reference/config/project-descriptor/ 25 + #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] 26 + pub struct BuildPackProject { 27 + pub(super) project: Option<Project>, 28 + pub(super) build: Option<Build>, 29 + pub(super) metadata: Option<Metadata>, 30 + } 31 + 32 + #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] 33 + #[serde(rename_all = "kebab-case")] 34 + pub(super) struct Project { 35 + pub(super) id: Option<String>, 36 + pub(super) name: Option<String>, 37 + pub(super) version: Option<String>, 38 + pub(super) source_url: Option<String>, 39 + pub(super) documentation_url: Option<String>, 40 + pub(super) authors: Option<Vec<String>>, 41 + pub(super) licenses: Option<Vec<License>>, 42 + } 43 + 44 + #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] 45 + pub(super) struct Build { 46 + #[serde(flatten)] 47 + pub(super) file_list: Option<FileList>, 48 + pub(super) env: Option<Vec<Env>>, 49 + pub(super) buildpacks: Option<Vec<BuildPack>>, 50 + } 51 + 52 + #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] 53 + #[serde(untagged)] 54 + pub(super) enum Metadata { 55 + #[serde(serialize_with = "toml::ser::tables_last")] 56 + Meta(Table), 57 + } 58 + 59 + #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] 60 + pub(super) struct License { 61 + #[serde(rename = "type")] 62 + pub(super) licence_type: Option<String>, 63 + pub(super) uri: Option<String>, 64 + } 65 + 66 + #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] 67 + pub(super) struct BuildPack { 68 + pub(super) id: Option<String>, 69 + #[serde(flatten)] 70 + pub(super) buildpack_field: BuildPackField, 71 + } 72 + 73 + #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] 74 + pub(super) enum BuildPackField { 75 + #[serde(rename = "version", with = "VersionSerDeser")] 76 + Version(Option<String>), 77 + #[serde(rename = "uri", with = "UriSerDeser")] 78 + Uri(Option<String>), 79 + #[serde(rename = "script")] 80 + Script(Option<Vec<Script>>), 81 + } 82 + 83 + #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] 84 + pub(super) struct Script { 85 + pub(super) api: String, 86 + pub(super) inline: String, 87 + #[serde(with = "ShellSerDeser")] 88 + pub(super) shell: Option<String>, 89 + } 90 + 91 + #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] 92 + pub(super) struct Env { 93 + pub(super) name: Option<String>, 94 + pub(super) value: Option<String>, 95 + } 96 + 97 + #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] 98 + #[serde(rename_all = "lowercase")] 99 + pub(super) enum FileList { 100 + Include(Vec<String>), 101 + Exclude(Vec<String>), 102 + }
+45
src/buildpack/spec/serde_macro.rs
··· 1 + /// Macro to create a module to custom (de)serialization of a field of type `Option<String>` 2 + /// where a `None` is serialized using the function `def_fn` 3 + /// which must have the type signature `() -> String`. 4 + /// 5 + /// During deserialization `def_fn` will be used to convert a value 6 + /// mathching the return of `def_fn` to a `None`. 7 + /// 8 + /// All this to ensure that reflexivity propery 9 + /// of encode and decode holds. 10 + /// 11 + /// i.e. `deserialize . serialize = id` 12 + #[macro_export] 13 + macro_rules! ser_deser_str_with_def { 14 + ($module: ident, $def_fn: ident) => { 15 + struct $module {} 16 + 17 + impl $module { 18 + fn serialize<S>(x: &Option<String>, s: S) -> Result<S::Ok, S::Error> 19 + where 20 + S: serde::Serializer, 21 + { 22 + match x { 23 + Some(str) => s.serialize_str(str), 24 + None => { 25 + let def_str = $def_fn(); 26 + s.serialize_str(&def_str) 27 + } 28 + } 29 + } 30 + 31 + fn deserialize<'de, D>(d: D) -> Result<Option<String>, D::Error> 32 + where 33 + D: serde::Deserializer<'de>, 34 + { 35 + Ok(Option::<String>::deserialize(d)?.and_then(|f| { 36 + if f == $def_fn() { 37 + None 38 + } else { 39 + Some(f) 40 + } 41 + })) 42 + } 43 + } 44 + }; 45 + }
+75
src/buildpack/tests.rs
··· 1 + use super::{Build, BuildPack}; 2 + use crate::buildpack::{BuildPackProject, Env}; 3 + use std::{fs, vec}; 4 + use tempfile::NamedTempFile; 5 + 6 + #[test] 7 + fn parse_test() { 8 + let ref_toml = r#" 9 + [project] 10 + id = "io.buildpacks.my-app" 11 + version = "0.1" 12 + 13 + [build] 14 + include = [ 15 + "cmd/", 16 + "go.mod", 17 + "go.sum", 18 + "*.go" 19 + ] 20 + 21 + [[build.env]] 22 + name = "JAVA_OPTS" 23 + value = "-Xmx1g" 24 + 25 + [[build.buildpacks]] 26 + id = "io.buildpacks/java" 27 + version = "1.0" 28 + 29 + [[build.buildpacks]] 30 + id = "io.buildpacks/nodejs" 31 + version = "1.0" 32 + 33 + [metadata] 34 + foo = "bar" 35 + 36 + [metadata.fizz] 37 + buzz = ["a", "b", "c"] 38 + 39 + "#; 40 + let toml: BuildPackProject = toml::from_str(ref_toml).unwrap(); 41 + let toml2: BuildPackProject = toml::from_str(&toml::to_string(&toml).unwrap()).unwrap(); 42 + 43 + assert_eq!(toml, toml2); 44 + } 45 + 46 + #[test] 47 + fn field_test() { 48 + let bp = BuildPackProject { 49 + project: None, 50 + metadata: None, 51 + build: Some(Build { 52 + env: Some(vec![Env { 53 + name: None, 54 + value: None, 55 + }]), 56 + file_list: None, 57 + buildpacks: Some(vec![BuildPack { 58 + id: None, 59 + buildpack_field: super::BuildPackField::Version(None), 60 + }]), 61 + }), 62 + }; 63 + 64 + let temp = NamedTempFile::new().expect("failed creating a new temp file"); 65 + 66 + let temp = temp.path(); 67 + 68 + bp.export_toml(temp).expect("failed exporting toml"); 69 + 70 + let str = fs::read_to_string(temp).expect("failed to read toml"); 71 + 72 + let ret: BuildPackProject = toml::from_str(&str).expect("failed to deserialize"); 73 + 74 + assert_eq!(bp, ret); 75 + }
+139
src/cli.rs
··· 1 + use self::init::Init; 2 + use crate::{ 3 + autopack::AutoPack, 4 + error::AppError, 5 + log::{banner, error}, 6 + }; 7 + use clap::{self, Parser, Subcommand}; 8 + use std::path::PathBuf; 9 + 10 + pub(crate) mod init; 11 + 12 + #[derive(Parser)] 13 + #[clap(version, about)] 14 + #[clap(propagate_version = true)] 15 + /// Auto Pack CLI 16 + pub struct Cli { 17 + #[clap(subcommand)] 18 + command: Commands, 19 + } 20 + 21 + #[derive(Subcommand)] 22 + enum Commands { 23 + /// Initializes auto-pack 24 + Init { 25 + /// Path to the project (default: present working directory) 26 + #[clap(short = 'p', long = "project")] 27 + client_project_path: Option<PathBuf>, 28 + /// Enable live reload (default: false) 29 + #[clap(short = 'l', long = "live-reload", action)] 30 + live_reload: bool, 31 + /// Don't build after init (default: false) 32 + #[clap(long = "no-build", action)] 33 + no_build: bool, 34 + /// Create runtime directory anyway (default: false) 35 + #[clap(short = 'f', long = "force-create-runtime", action)] 36 + force_create_runtime: bool, 37 + }, 38 + 39 + /// Build auto-pack 40 + Build { 41 + /// Clears cache when building 42 + #[clap(long = "clear-cache", action)] 43 + clear_cache: bool, 44 + }, 45 + 46 + /// Runs auto-pack 47 + Run { 48 + /// Re-build auto-pack 49 + #[clap(short = 'b', long = "build", action)] 50 + build: bool, 51 + 52 + /// Clears cache when re-building 53 + #[clap(long = "clear-cache", action)] 54 + clear_cache: bool, 55 + 56 + /// Port to run the application on 57 + #[clap(long = "port", default_value_t = 8080)] 58 + port: usize, 59 + }, 60 + } 61 + 62 + impl Cli { 63 + pub fn new() -> Cli { 64 + Cli::parse() 65 + } 66 + 67 + fn build(&self, clear_cache: bool) -> Result<(), AppError> { 68 + banner("Building project using autopack"); 69 + let autopack = AutoPack::load_validate(None).map_err(|e| { 70 + error("Failed validating autopack. Please run `auto-pack init` again."); 71 + e 72 + })?; 73 + 74 + autopack.build(clear_cache).map_err(|e| { 75 + error("Autopack build failure. Exiting."); 76 + AppError::BuildError("Build failure", e) 77 + })?; 78 + Ok(()) 79 + } 80 + pub async fn run(self) -> Result<(), AppError> { 81 + // let cli = Cli::parse(); 82 + match self.command { 83 + Commands::Init { 84 + ref client_project_path, 85 + live_reload, 86 + no_build, 87 + force_create_runtime, 88 + } => { 89 + banner("Initializing autopack"); 90 + let ap = Init::pre_configure(client_project_path.clone())? 91 + .configure(live_reload)? 92 + .post_configure(force_create_runtime, live_reload) 93 + .await? 94 + .install(); 95 + 96 + ap.save(None).map_err(|e| { 97 + AppError::PostConfigureError("Failed to serialize autopack state", e) 98 + })?; 99 + 100 + banner("Initialized autopack"); 101 + 102 + if !no_build { 103 + self.build(false)?; 104 + } 105 + 106 + Ok(()) 107 + } 108 + 109 + Commands::Build { clear_cache } => self.build(clear_cache), 110 + 111 + Commands::Run { 112 + build, 113 + clear_cache, 114 + port, 115 + } => { 116 + let autopack = AutoPack::load_validate(None).map_err(|e| { 117 + error("Failed validating autopack. Please run `auto-pack init` again."); 118 + e 119 + })?; 120 + 121 + if build { 122 + self.build(clear_cache)?; 123 + } 124 + 125 + autopack.run(port).await.map_err(|e| { 126 + AppError::RunError("Failed running autopack project", anyhow::anyhow!(e)) 127 + })?; 128 + 129 + Ok(()) 130 + } 131 + } 132 + } 133 + } 134 + 135 + impl Default for Cli { 136 + fn default() -> Self { 137 + Self::new() 138 + } 139 + }
+102
src/cli/init.rs
··· 1 + use crate::{ 2 + autopack::AutoPack, 3 + buildpack::BuildPackProject, 4 + docker::Docker, 5 + error::AppError, 6 + pack::Pack, 7 + package_json::{CreatePackageJson, Project}, 8 + runtime::Runtime, 9 + }; 10 + use std::{env, io, path::PathBuf}; 11 + use tracing::{debug, warn}; 12 + 13 + /// Builder for [`crate::app::autopack::AutoPack`] 14 + pub struct Init { 15 + package_json: Project, 16 + docker: Docker, 17 + buildpack: Option<BuildPackProject>, 18 + runtime: Option<Runtime>, 19 + pack_cli: Option<Pack>, 20 + } 21 + 22 + impl Init { 23 + pub(crate) fn pre_configure(client_project_path: Option<PathBuf>) -> Result<Self, AppError> { 24 + debug!("Begin pre-configure"); 25 + let path = client_project_path 26 + .ok_or_else(|| io::Error::from(io::ErrorKind::NotFound)) 27 + .or_else(|_| env::current_dir()) 28 + .map_err(|e| { 29 + let msg = "No client project path given, falling back on current directory"; 30 + warn!(msg); 31 + AppError::PreconfigureError(msg, anyhow::anyhow!(e)) 32 + })?; 33 + 34 + let docker = Docker::check()?; 35 + let package_json = CreatePackageJson::new(&path).build()?; 36 + 37 + debug!("End pre-configure"); 38 + Ok(Init { 39 + package_json, 40 + docker, 41 + buildpack: None, 42 + runtime: None, 43 + pack_cli: None, 44 + }) 45 + } 46 + 47 + pub(crate) fn configure(&mut self, live_reload: bool) -> Result<&mut Self, AppError> { 48 + debug!("Begin configure"); 49 + let buildpack = BuildPackProject::setup(&self.package_json.package_json, live_reload); 50 + self.buildpack = Some(buildpack); 51 + 52 + debug!("End configure"); 53 + Ok(self) 54 + } 55 + 56 + pub(crate) async fn post_configure( 57 + &mut self, 58 + force_create_runtime: bool, 59 + live_reload: bool 60 + ) -> Result<&mut Self, AppError> { 61 + debug!("Begin post-configure"); 62 + // create runtime 63 + let runtime = Runtime::builder(self.package_json.path.clone().as_path()) 64 + .dir(force_create_runtime)? 65 + .proc_file(live_reload)? 66 + .build(); 67 + 68 + let filename = "project.toml"; 69 + let mut path = runtime.dir().clone(); 70 + path.push(filename); 71 + 72 + // export BP to toml 73 + self.buildpack 74 + .as_ref() 75 + .ok_or_else(|| { 76 + AppError::PostConfigureError( 77 + "Buildpack project not available to export", 78 + anyhow::anyhow!(""), 79 + ) 80 + }) 81 + .and_then(|bp| bp.export_toml(&path))?; 82 + 83 + // Install pack cli 84 + let mut pack = Pack::builder(&runtime.dir()).build(); 85 + let pack = pack.install().await?; 86 + 87 + self.runtime = Some(runtime); 88 + self.pack_cli = Some(pack.to_owned()); 89 + debug!("End post-configure"); 90 + Ok(self) 91 + } 92 + 93 + pub(crate) fn install(&mut self) -> AutoPack { 94 + AutoPack { 95 + runtime: self.runtime.clone().unwrap_or_default(), 96 + pack_cli: self.pack_cli.clone().unwrap_or_default(), 97 + buildpack: self.buildpack.clone().unwrap_or_default(), 98 + docker: self.docker.clone(), 99 + client_project: self.package_json.clone(), 100 + } 101 + } 102 + }
+303
src/docker.rs
··· 1 + use crate::{ 2 + error::AppError, 3 + log::{command_err, command_out, error, instruct, success, trying}, 4 + }; 5 + use futures_util::TryFutureExt; 6 + use serde::{Deserialize, Serialize}; 7 + use std::{ 8 + path::Path, 9 + process::{Command, Stdio}, 10 + }; 11 + use tokio::io::AsyncBufReadExt; 12 + 13 + use tracing::{debug, error}; 14 + 15 + #[cfg(target_os = "macos")] 16 + fn docker_install_path() -> &'static str { 17 + "https://docs.docker.com/desktop/install/mac-install/" 18 + } 19 + 20 + #[cfg(target_os = "linux")] 21 + fn docker_install_path() -> &'static str { 22 + "https://docs.docker.com/desktop/install/linux-install/" 23 + } 24 + 25 + #[cfg(target_os = "windows")] 26 + fn docker_install_path() -> &'static str { 27 + "https://docs.docker.com/desktop/install/windows-install/" 28 + } 29 + 30 + fn ask_install_docker() -> Result<(), AppError> { 31 + debug!("asking user to open docker install page"); 32 + instruct("Please install Docker and run init again."); 33 + let do_open = dialoguer::Confirm::new() 34 + .with_prompt("Press y or enter to open the webpage to install Docker.") 35 + .default(true) 36 + .interact() 37 + .map_err(|e| { 38 + error!( 39 + message = "Error in ask to install docker prompt", 40 + error = format!("{}", e) 41 + ); 42 + AppError::IOError("Failed to launch the docker install page", e) 43 + })?; 44 + if do_open { 45 + debug!(message = "User gave consent to open docker install webpage"); 46 + open::that(docker_install_path()) 47 + .map(|_| { 48 + debug!(message = "Opened webpage", path = docker_install_path()); 49 + }) 50 + .map_err(|e| { 51 + error!( 52 + message = "Error opening docker install path", 53 + path = docker_install_path(), 54 + error = format!("{}", e) 55 + ); 56 + e 57 + }) 58 + .unwrap(); 59 + } else { 60 + debug!(message = "User did not gave consent to open docker install webpage"); 61 + } 62 + 63 + Ok(()) 64 + } 65 + 66 + #[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)] 67 + pub(crate) struct Docker { 68 + version: String, 69 + } 70 + 71 + impl Docker { 72 + pub(crate) fn build(version: &str) -> Docker { 73 + Docker { 74 + version: version.to_string(), 75 + } 76 + } 77 + 78 + // pub(crate) fn version(&self) -> String { 79 + // self.version.clone() 80 + // } 81 + 82 + pub(crate) fn check() -> Result<Docker, AppError> { 83 + debug!("Locating docker..."); 84 + trying("Locating docker"); 85 + 86 + Command::new("docker") 87 + .arg("--version") 88 + .output() 89 + .map_err(|e| anyhow::anyhow!(e)) 90 + .and_then(|x| { 91 + if x.status.success() { 92 + let docker = std::str::from_utf8(&x.stdout) 93 + .map(Docker::build) 94 + .map_err(|e| anyhow::anyhow!(e))?; 95 + debug!("{} is installed", docker.version.trim_end()); 96 + success("Docker is installed"); 97 + Ok(docker) 98 + } else { 99 + error!( 100 + message = "Docker not found", 101 + error = format!("{}", x.status) 102 + ); 103 + error("Docker not found"); 104 + anyhow::bail!("Docker not found") 105 + } 106 + }) 107 + .or_else(|_| { 108 + ask_install_docker()?; 109 + Err(AppError::PreconfigureError( 110 + "Docker not found", 111 + anyhow::anyhow!(""), 112 + )) 113 + }) 114 + } 115 + 116 + pub(crate) fn is_running(&self) -> anyhow::Result<bool> { 117 + debug!("checking if docker is running"); 118 + let cmd = Command::new("docker").arg("ps").arg("--quiet").output()?; 119 + cmd.status 120 + .success() 121 + .then(|| { 122 + debug!("docker is running"); 123 + true 124 + }) 125 + .ok_or_else(|| { 126 + error( 127 + "Docker daemon is not running. Please start docker and run autopack build again.", 128 + ); 129 + anyhow::anyhow!("Docker is not running") 130 + }) 131 + } 132 + 133 + pub(crate) async fn stop_container(&self, image_name: &str) -> anyhow::Result<()> { 134 + let mut search_container_cmd = tokio::process::Command::new("docker"); 135 + 136 + search_container_cmd 137 + .arg("ps") 138 + .args(["--filter", &format!(r#"name={}"#, image_name)]) 139 + .args(["--filter", &format!(r#"status={}"#, "running")]) 140 + .args(["--format", r#"{{.ID}}"#]); 141 + 142 + let search_container_child = search_container_cmd 143 + .stdout(Stdio::piped()) 144 + .stdin(Stdio::null()) 145 + .output() 146 + .await 147 + .map_err(|e| { 148 + anyhow::anyhow!("Failed creating docker container search command :: {}", e) 149 + })?; 150 + 151 + if search_container_child.status.success() { 152 + let container_id = std::str::from_utf8(&search_container_child.stdout) 153 + .map_err(|e| anyhow::anyhow!("Failed converting docker id to str :: {:?}", e))? 154 + .trim(); 155 + 156 + if !container_id.is_empty() { 157 + debug!("trying to stop the container {}", container_id); 158 + 159 + let mut container_stop_cmd = tokio::process::Command::new("docker"); 160 + container_stop_cmd 161 + .args(["container", "stop", container_id]) 162 + .stdout(Stdio::piped()) 163 + .stderr(Stdio::piped()) 164 + .stdin(Stdio::null()); 165 + 166 + loop { 167 + let container_stop_child = container_stop_cmd 168 + .output() 169 + .and_then(|out| async move { 170 + if out.status.success() { 171 + Ok(out.stdout) 172 + } else { 173 + debug!( 174 + "error stopping, retrying... :: {}", 175 + std::str::from_utf8(&out.stderr).expect("could not get stderr") 176 + ); 177 + Err(std::io::Error::new(std::io::ErrorKind::Other, "")) 178 + } 179 + }) 180 + .await; 181 + 182 + if let Ok(out) = container_stop_child { 183 + debug!( 184 + "docker container {} stopped", 185 + std::str::from_utf8(&out).expect("could not get stdout") 186 + ); 187 + break; 188 + } 189 + } 190 + } else { 191 + debug!("Docker container having name {} is not found", image_name); 192 + } 193 + } 194 + 195 + Ok(()) 196 + } 197 + 198 + pub(crate) async fn run( 199 + self, 200 + image_name: String, 201 + project_dir: &Path, 202 + port: usize, 203 + ) -> anyhow::Result<()> { 204 + if let Err(err) = self.stop_container(&image_name).await { 205 + debug!("Failed stopping docker container :: {:?}", err) 206 + } 207 + 208 + let src_dir = project_dir 209 + .canonicalize() 210 + .or_else(|_| std::env::current_dir()) 211 + .map_err(|e| anyhow::anyhow!("Failed getting the project dir :: {:?}", e))?; 212 + 213 + let mut docker_run_cmd = tokio::process::Command::new("docker"); 214 + 215 + docker_run_cmd 216 + .arg("run") 217 + .arg("--interactive") 218 + .arg("--init") 219 + .arg("--rm") 220 + .args([ 221 + "--mount", 222 + &format!( 223 + r"type=bind,source={:#}/src,target=/workspace/src", 224 + src_dir.display() 225 + ), 226 + ]) 227 + .args(["-p", &format!("{}:8080", port)]) 228 + .args(["--name", &image_name]) 229 + .arg(image_name.clone()); 230 + 231 + debug!("docker command :: {:?}", docker_run_cmd); 232 + 233 + let mut child = docker_run_cmd 234 + .stdin(Stdio::piped()) 235 + .stdout(Stdio::piped()) 236 + .stderr(Stdio::piped()) 237 + .kill_on_drop(true) 238 + .spawn() 239 + .map_err(|e| anyhow::anyhow!("Failed spawning docker run command :: {:?}", e))?; 240 + 241 + let stdout = child 242 + .stdout 243 + .take() 244 + .ok_or_else(|| anyhow::anyhow!("child does not have handle to stdout"))?; 245 + 246 + let stderr = child 247 + .stderr 248 + .take() 249 + .ok_or_else(|| anyhow::anyhow!("child does not have handle to stderr"))?; 250 + 251 + let mut out_reader = tokio::io::BufReader::new(stdout).lines(); 252 + let mut err_reader = tokio::io::BufReader::new(stderr).lines(); 253 + 254 + #[cfg(not(target_os = "windows"))] 255 + let mut ctrlc_event = 256 + tokio::signal::unix::signal(tokio::signal::unix::SignalKind::interrupt())?; 257 + 258 + #[cfg(target_os = "windows")] 259 + let mut ctrlc_event = tokio::signal::windows::ctrl_c()?; 260 + 261 + let handle = tokio::spawn(async move { 262 + tokio::select! { 263 + st = child.wait() => { 264 + debug!("Failed running docker container"); 265 + if st.is_err() { 266 + anyhow::bail!(st.unwrap_err()) 267 + } 268 + Ok::<(), anyhow::Error>(()) 269 + } 270 + , 271 + _ = ctrlc_event.recv() => { 272 + if let Err(err) = self.stop_container(&image_name).await { 273 + debug!("Failed stopping docker container :: {:?}", err); 274 + } 275 + 276 + child.kill().map_err(|e| anyhow::anyhow!(e)).await?; 277 + 278 + Ok::<(),anyhow::Error>(()) 279 + } 280 + } 281 + }); 282 + 283 + loop { 284 + tokio::select! { 285 + Ok(Some(line)) = out_reader.next_line() => { 286 + command_out(&line); 287 + } 288 + Ok(Some(line)) = err_reader.next_line() => { 289 + command_err(&line); 290 + } 291 + else => { 292 + break; 293 + } 294 + } 295 + } 296 + 297 + handle 298 + .map_err(|e| anyhow::anyhow!("Error in the spawned task :: {:?}", e)) 299 + .await??; 300 + 301 + Ok(()) 302 + } 303 + }
+54
src/error.rs
··· 1 + use std::ffi::OsString; 2 + use thiserror::Error; 3 + use tracing::error; 4 + 5 + #[derive(Debug, Error)] 6 + pub enum AppError { 7 + #[error("Preconfigure error :: {0} :: {:?}", .1)] 8 + PreconfigureError(&'static str, #[source] anyhow::Error), 9 + #[error("Package JSON not found")] 10 + PackageJSONNotFound, 11 + #[error("React scripts not found")] 12 + ReactScriptsNotFound, 13 + #[error("Some OS specific error :: {:?}", .0.to_str().unwrap_or(""))] 14 + OSPathError(OsString), 15 + #[error("Some IO error :: {0} :: {:?}", .1)] 16 + IOError(&'static str, #[source] std::io::Error), 17 + #[error("Postconfigure error :: {0} :: {:?}", .1)] 18 + PostConfigureError(&'static str, #[source] anyhow::Error), 19 + #[error("Build error :: {0} :: {:?}", .1)] 20 + BuildError(&'static str, #[source] anyhow::Error), 21 + #[error("Run error :: {0} :: {:?}", .1)] 22 + RunError(&'static str, #[source] anyhow::Error), 23 + } 24 + 25 + impl AppError { 26 + pub fn handle(&self) { 27 + match &self { 28 + AppError::IOError(context, e) => { 29 + error!("IO Error :: {} :: {:?}", context, e); 30 + } 31 + AppError::OSPathError(context) => { 32 + error!("OS Specific error :: {:?}", context); 33 + } 34 + AppError::PackageJSONNotFound => { 35 + error!("Package JSON not found"); 36 + } 37 + AppError::PostConfigureError(context, e) => { 38 + error!("Post configure error :: {} :: {:?}", context, e); 39 + } 40 + AppError::PreconfigureError(context, e) => { 41 + error!("Pre configure error :: {} :: {:?}", context, e); 42 + } 43 + AppError::ReactScriptsNotFound => { 44 + error!("React scripts not found"); 45 + } 46 + AppError::BuildError(context, e) => { 47 + error!("Build failure :: {} :: {:?}", context, e) 48 + } 49 + AppError::RunError(context, e) => { 50 + error!("Run failure :: {} :: {:?}", context, e) 51 + } 52 + }; 53 + } 54 + }
+9
src/lib.rs
··· 1 + pub(crate) mod autopack; 2 + pub(crate) mod buildpack; 3 + pub mod cli; 4 + mod docker; 5 + mod error; 6 + pub(crate) mod log; 7 + pub(crate) mod pack; 8 + mod package_json; 9 + pub(crate) mod runtime;
+62
src/log.rs
··· 1 + use dialoguer::console::{Emoji, Style, Term}; 2 + use tracing::error; 3 + 4 + fn handle_err(r: std::io::Result<()>, original_message: &str) { 5 + match r { 6 + Ok(_) => (), 7 + Err(e) => { 8 + error!( 9 + message = "Error writing to console", 10 + original_message, 11 + error = format!("{}", e) 12 + ) 13 + } 14 + } 15 + } 16 + pub fn success(msg: &str) { 17 + let str = format!("{} {}", Emoji("👍", ""), Style::new().blue().apply_to(msg)); 18 + handle_err(Term::stdout().write_line(&str), &str) 19 + } 20 + 21 + pub fn trying(msg: &str) { 22 + let str = format!( 23 + "{} {}...", 24 + Emoji("🕛", ">>"), 25 + Style::new().yellow().apply_to(msg) 26 + ); 27 + handle_err(Term::stderr().write_line(&str), &str) 28 + } 29 + 30 + pub fn error(msg: &str) { 31 + let str = format!("{} {}", Emoji("💣", "!!"), Style::new().red().apply_to(msg)); 32 + handle_err(Term::stderr().write_line(&str), &str) 33 + } 34 + 35 + pub fn instruct(msg: &str) { 36 + let str = format!("{} {}", Emoji("ℹ️", ""), Style::new().cyan().apply_to(msg)); 37 + handle_err(Term::stdout().write_line(&str), &str) 38 + } 39 + 40 + pub fn banner(msg: &str) { 41 + let width = Term::stdout().size().1.into(); 42 + let wd = width - 10; 43 + let str = format!( 44 + "{:-^width$}\n{:^wd$}\n{:-^width$}", 45 + "-", 46 + Style::new().green().apply_to(format!("✨ {} ✨", msg)), 47 + "-", 48 + width = width, 49 + wd = wd 50 + ); 51 + handle_err(Term::stdout().write_line(&str), &str) 52 + } 53 + 54 + pub(crate) fn command_out(msg: &str) { 55 + let str = format!("{:<2}{:>2}", ">", Style::new().green().for_stdout().apply_to(msg)); 56 + handle_err(Term::stderr().write_line(&str), &str) 57 + } 58 + 59 + pub(crate) fn command_err(msg: &str) { 60 + let str = format!("{:<2}{:>2}", ">", Style::new().red().for_stdout().apply_to(msg)); 61 + handle_err(Term::stderr().write_line(&str), &str) 62 + }
+260
src/pack.rs
··· 1 + mod image; 2 + 3 + use super::log::{success, trying}; 4 + use crate::error::AppError; 5 + use anyhow::anyhow; 6 + use futures_util::StreamExt; 7 + use indicatif::{ProgressBar, ProgressStyle}; 8 + use serde::{Deserialize, Serialize}; 9 + use std::{ 10 + fs::File, 11 + io::Write, 12 + path::{Path, PathBuf}, 13 + }; 14 + use tracing::debug; 15 + 16 + #[cfg(target_os = "macos")] 17 + fn release_url(pack_version: &str) -> String { 18 + format!( 19 + "https://github.com/buildpacks/pack/releases/download/v{}/pack-v{}-macos.tgz", 20 + pack_version, pack_version 21 + ) 22 + } 23 + 24 + #[cfg(all(target_os = "linux", target_arch = "arm"))] 25 + fn release_url(pack_version: &str) -> String { 26 + format!( 27 + "https://github.com/buildpacks/pack/releases/download/v{}/pack-v{}-linux-arm64.tgz", 28 + pack_version, pack_version 29 + ) 30 + } 31 + 32 + #[cfg(all(target_os = "linux", target_arch = "x86_64"))] 33 + fn release_url(pack_version: &str) -> String { 34 + format!( 35 + "https://github.com/buildpacks/pack/releases/download/v{}/pack-v{}-linux.tgz", 36 + pack_version, pack_version 37 + ) 38 + } 39 + 40 + #[cfg(target_os = "windows")] 41 + fn release_url(pack_version: &str) -> String { 42 + format!( 43 + "https://github.com/buildpacks/pack/releases/download/v{}/pack-v{}-windows.zip", 44 + pack_version, pack_version 45 + ) 46 + } 47 + 48 + /// Internal binary to be used to create the CNBs 49 + /// https://buildpacks.io/docs/tools/pack/ 50 + #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] 51 + pub(crate) struct Pack { 52 + /// directory within `.autopack` holding the binary. default to `.bin` 53 + pub(crate) bin_dir_path: PathBuf, 54 + /// OS specific full file path to the pack cli binary. default to `.autopack/.bin/pack` in unix and `.autopack/.bin/pack.exe` in windows 55 + pub(crate) bin_file_path: PathBuf, 56 + /// OS specific download URL for pack cli 57 + pub(crate) release_url: String, 58 + /// pack cli version 59 + pub(crate) release_version: String, 60 + } 61 + 62 + impl Default for Pack { 63 + fn default() -> Self { 64 + Pack { 65 + bin_dir_path: PathBuf::new().join(".autopack").join(".bin"), 66 + bin_file_path: PathBuf::new().join(".autopack").join(".bin").join("pack"), 67 + release_url: "".to_string(), 68 + release_version: "".into(), 69 + } 70 + } 71 + } 72 + 73 + impl Pack { 74 + pub(crate) fn builder(runtime_dir: &Path) -> PackBuilder { 75 + PackBuilder::new(runtime_dir) 76 + } 77 + 78 + pub(crate) fn is_present(&self) -> anyhow::Result<bool> { 79 + debug!("Checking if pack-cli is present"); 80 + 81 + self.bin_file_path 82 + .exists() 83 + .then(|| true) 84 + .ok_or_else(|| anyhow::anyhow!("Pack cli not available")) 85 + } 86 + 87 + pub(crate) async fn install(&mut self) -> Result<&Self, AppError> { 88 + trying("Installing pack cli"); 89 + let x = self.is_present().unwrap_or(false); 90 + if x { 91 + debug!("Pack cli present, skipping download and install"); 92 + success("Pack cli already present"); 93 + Ok(self) 94 + } else { 95 + // download release binary 96 + self.download_and_extract().await.map_err(|e| { 97 + AppError::PostConfigureError("Failed to download and extract Pack cli", anyhow!(e)) 98 + })?; 99 + 100 + // check if the installation is fine 101 + let version = self.cli_version().map_err(|e| { 102 + AppError::PostConfigureError("Failed to get the pack cli version", e) 103 + })?; 104 + 105 + debug!("Installed pack cli version {}", version); 106 + success("Installed pack cli"); 107 + Ok(self) 108 + } 109 + } 110 + 111 + #[cfg(any(target_os = "macos", target_os = "linux"))] 112 + fn decompress<P: AsRef<Path>>(&self, path: P, dest: P) -> anyhow::Result<File> { 113 + use flate2::read::GzDecoder; 114 + use tar::Archive; 115 + 116 + let tgz = File::open(path)?; 117 + let tar = GzDecoder::new(tgz); 118 + let mut archive = Archive::new(tar); 119 + archive.unpack(dest.as_ref())?; 120 + File::open(&self.bin_file_path).map_err(|e| anyhow!("Error during decompress. Error {}", e)) 121 + } 122 + 123 + #[cfg(target_os = "windows")] 124 + fn decompress<P: AsRef<Path>>(&self, path: P, dest: P) -> anyhow::Result<File> { 125 + let mut archive = zip::ZipArchive::new(File::open(path)?)?; 126 + archive.extract(dest.as_ref())?; 127 + File::open(&self.bin_file_path).map_err(|e| anyhow!("Error during decompress. Error {}", e)) 128 + } 129 + 130 + async fn download_and_extract(&mut self) -> anyhow::Result<&Self> { 131 + std::fs::create_dir(&self.bin_dir_path)?; 132 + 133 + let mut temp_file = tempfile::Builder::new().tempfile_in(&self.bin_dir_path)?; 134 + 135 + let client = reqwest::Client::new(); 136 + let response = client.get(self.release_url.as_str()).send().await?; 137 + let bin_size = response 138 + .content_length() 139 + .ok_or_else(|| anyhow::Error::msg("Error getting content length"))?; 140 + 141 + let mut start = 0_u64; 142 + let end = bin_size - 1; 143 + 144 + let bar = ProgressBar::new(end); 145 + bar.set_style( 146 + ProgressStyle::default_bar() 147 + .template( 148 + "[{elapsed_precise}] |{bar:40.cyan/blue}| {bytes:>7}/{total_bytes:7} {msg} [{eta}]", 149 + ) 150 + .unwrap() 151 + .progress_chars("█▛▌▖ "), 152 + ); 153 + bar.set_message("Downloading pack cli"); 154 + 155 + let mut bin_stream = response.bytes_stream(); 156 + while let Some(bs) = bin_stream.next().await { 157 + let chunk = bs?; 158 + let chunk_size: u64 = chunk.len() as u64; 159 + temp_file.write(&chunk).map_err(|e| { 160 + anyhow::Error::msg(format!("Error while writing content to file. Error {}", e)) 161 + })?; 162 + let progress = std::cmp::min(chunk_size, end - start + 1); 163 + start += progress; 164 + bar.inc(progress); 165 + } 166 + 167 + bar.finish_with_message("Downloaded pack cli"); 168 + bar.finish_and_clear(); 169 + 170 + let f = self.decompress(temp_file.path(), &self.bin_dir_path)?; 171 + set_permissions(f)?; 172 + Ok(self) 173 + } 174 + 175 + pub(crate) fn cli_version(&self) -> anyhow::Result<String> { 176 + let mut cmd = std::process::Command::new(&self.bin_file_path); 177 + let out = cmd.arg("--version").output()?; 178 + if out.status.success() { 179 + std::str::from_utf8(&out.stdout) 180 + .map(|x| x.to_string()) 181 + .map_err(|e| anyhow::anyhow!(e)) 182 + } else { 183 + anyhow::bail!("Error getting pack output") 184 + } 185 + } 186 + } 187 + pub(crate) struct PackBuilder { 188 + /// directory within `.autopack` holding the binary. default to `.bin` 189 + pub(crate) bin_dir_path: PathBuf, 190 + /// OS specific full file path to the pack cli binary. default to `.autopack/.bin/pack` in unix and `.autopack/.bin/pack.exe` in windows 191 + pub(crate) bin_file_path: PathBuf, 192 + /// OS specific download URL for pack cli 193 + pub(crate) release_url: String, 194 + /// pack cli version 195 + pub(crate) release_version: String, 196 + } 197 + 198 + impl PackBuilder { 199 + pub(crate) fn new(runtime_dir: &Path) -> Self { 200 + let release_version = "0.27.0".to_string(); 201 + let bin_dir_path = PathBuf::new().join(runtime_dir).join(".bin"); 202 + PackBuilder { 203 + bin_dir_path: bin_dir_path.clone(), 204 + release_version: release_version.clone(), 205 + release_url: release_url(&release_version), 206 + 207 + #[cfg(any(target_os = "linux", target_os = "macos"))] 208 + bin_file_path: bin_dir_path.join("pack"), 209 + 210 + #[cfg(target_os = "windows")] 211 + bin_file_path: bin_dir_path.join("pack.exe"), 212 + } 213 + } 214 + 215 + pub(crate) fn build(&mut self) -> Pack { 216 + Pack { 217 + bin_dir_path: self.bin_dir_path.clone(), 218 + bin_file_path: self.bin_file_path.clone(), 219 + release_url: self.release_url.clone(), 220 + release_version: self.release_version.clone(), 221 + } 222 + } 223 + } 224 + 225 + #[cfg(any(target_os = "macos", target_os = "linux"))] 226 + fn set_permissions(f: File) -> anyhow::Result<File> { 227 + use std::os::unix::prelude::PermissionsExt; 228 + 229 + let mut perms = f.metadata()?.permissions(); 230 + perms.set_mode(0o700); 231 + f.set_permissions(perms)?; 232 + Ok(f) 233 + } 234 + 235 + #[cfg(target_os = "windows")] 236 + fn set_permissions(f: File) -> anyhow::Result<File> { 237 + Ok(f) 238 + } 239 + 240 + #[cfg(test)] 241 + mod tests { 242 + use std::env; 243 + 244 + use crate::runtime::Runtime; 245 + 246 + use super::*; 247 + 248 + #[tokio::test] 249 + async fn pack_version_test() { 250 + let rt = Runtime::builder(env::current_dir().unwrap().as_path()) 251 + .dir(false) 252 + .unwrap() 253 + .build(); 254 + let mut pack_builder = Pack::builder(rt.dir().as_path()); 255 + let mut pack = pack_builder.build(); 256 + let pack = pack.install().await.unwrap(); 257 + 258 + assert!(pack.cli_version().unwrap().contains(&pack.release_version)); 259 + } 260 + }
+108
src/pack/image.rs
··· 1 + use super::Pack; 2 + use crate::log::error; 3 + use crate::log::{success, trying}; 4 + use std::{ 5 + io::{BufRead, BufReader}, 6 + process::{Command, Stdio}, 7 + }; 8 + use tracing::{debug, error}; 9 + 10 + impl Pack { 11 + pub(crate) fn build_image( 12 + &self, 13 + project_toml: &str, 14 + start_cmd: &str, 15 + proc_file_path: &str, 16 + container_bindings_path: &str, 17 + image_name: &str, 18 + clear_cache: bool, 19 + ) -> anyhow::Result<()> { 20 + let mut cmd = Command::new(self.bin_file_path.clone()); 21 + let cmd = cmd 22 + .arg("build") 23 + .args(["-d", project_toml]) 24 + .args(["-D", start_cmd]) 25 + .arg("--volume") 26 + .arg(&format!( 27 + r#"{}:{}"#, 28 + proc_file_path, container_bindings_path 29 + )) 30 + .arg(image_name); 31 + 32 + if clear_cache { 33 + cmd.arg("--clear-cache"); 34 + } 35 + 36 + debug!("Executing {:?}", cmd); 37 + 38 + let mut child = cmd 39 + .stdout(Stdio::piped()) 40 + .stderr(Stdio::piped()) 41 + .spawn() 42 + .map_err(|e| anyhow::anyhow!("Failed running pack cli :: {:?}", e))?; 43 + let stdout = child 44 + .stdout 45 + .take() 46 + .ok_or("Cound not capture stdout") 47 + .unwrap(); 48 + let stderr = child 49 + .stderr 50 + .take() 51 + .ok_or("Cound not capture stderr") 52 + .unwrap(); 53 + 54 + let out_reader = BufReader::new(stdout); 55 + let err_reader = BufReader::new(stderr); 56 + 57 + let mut p_install_start_marker = false; 58 + 59 + out_reader.lines().filter_map(|l| l.ok()).for_each(|l| { 60 + let container_installing = l.contains("Pulling from paketobuildpacks/builder"); 61 + let container_installed = 62 + l.contains("Status: Image is up to date for paketobuildpacks/run:base-cnb"); 63 + 64 + let packages_installing = l.contains("Executing build environment install process"); 65 + let completed = l.contains("Completed in"); 66 + let image_built = l.contains("Successfully built image"); 67 + 68 + if container_installing { 69 + trying("Downloading docker images"); 70 + } 71 + if container_installed { 72 + success("Docker images downloaded"); 73 + trying("Setting up buildpacks"); 74 + } 75 + 76 + if packages_installing { 77 + success("Buildpacks setup done"); 78 + trying("Installing NPM packages"); 79 + p_install_start_marker = true; 80 + } 81 + 82 + if completed && p_install_start_marker { 83 + p_install_start_marker = false; 84 + success("Installed NPM packages"); 85 + trying("Finalizing image"); 86 + } 87 + 88 + if image_built { 89 + success(&format!("Image {} built successfully", image_name)); 90 + } 91 + 92 + debug!("{}", l); 93 + }); 94 + err_reader.lines().for_each(|l| error!("{:?}", l)); 95 + 96 + let out = child.wait_with_output()?; 97 + if out.status.success() { 98 + debug!("Auto packing project complete"); 99 + success("Auto packing project complete"); 100 + } else { 101 + error!("Auto packing project failed"); 102 + error("Auto packing project complete"); 103 + anyhow::bail!("Failed auto packing project") 104 + } 105 + 106 + Ok(()) 107 + } 108 + }
+87
src/package_json.rs
··· 1 + use crate::{ 2 + error::AppError, 3 + log::{error, success, trying}, 4 + }; 5 + use npm_package_json::Package; 6 + use serde::{Deserialize, Serialize}; 7 + use std::path::{Path, PathBuf}; 8 + use tracing::{error, info}; 9 + 10 + fn check_package_json(mod_path: &Path) -> Result<String, AppError> { 11 + trying("Locating package.json"); 12 + mod_path 13 + .canonicalize() 14 + .map_err(|e| AppError::IOError("Failed to locate package.json", e)) 15 + .and_then(|mut path| { 16 + path.push("package.json"); 17 + let p = path 18 + .clone() 19 + .into_os_string() 20 + .into_string() 21 + .map_err(AppError::OSPathError); 22 + 23 + if path.exists() { 24 + info!(message = "Package JSON found", package_json_path = ?p); 25 + success("Package JSON found"); 26 + p 27 + } else { 28 + error("Package JSON not found"); 29 + error!(message = "Package JSON not found", package_json_path = ?p); 30 + Err(AppError::PackageJSONNotFound) 31 + } 32 + }) 33 + } 34 + 35 + fn get_pkg_json(path: &str) -> Result<Package, AppError> { 36 + Package::from_path(path).map_err(|e| match e { 37 + npm_package_json::Error::Io(e) => AppError::IOError("Failed to parse package.json", e), 38 + npm_package_json::Error::Parse(_) => AppError::ReactScriptsNotFound, 39 + }) 40 + } 41 + 42 + fn check_react_script(package_json_path: &str) -> Result<Package, AppError> { 43 + trying("Checking react-scripts within the project"); 44 + let pkg = get_pkg_json(package_json_path)?; 45 + 46 + if pkg.dev_dependencies.contains_key("react-scripts") 47 + || pkg.dependencies.contains_key("react-scripts") 48 + { 49 + success("react-scripts found"); 50 + Ok(pkg) 51 + } else { 52 + error("react-scripts not found"); 53 + Err(AppError::ReactScriptsNotFound) 54 + } 55 + } 56 + 57 + #[derive(Debug, Clone, Deserialize, Serialize, Default, PartialEq)] 58 + pub(crate) struct Project { 59 + pub(crate) path: PathBuf, 60 + pub(crate) package_json: Package, 61 + pub(crate) image_name: String, 62 + } 63 + 64 + pub(crate) struct CreatePackageJson { 65 + path: PathBuf, 66 + } 67 + 68 + impl CreatePackageJson { 69 + pub(crate) fn new(p: &Path) -> CreatePackageJson { 70 + CreatePackageJson { 71 + path: p.to_path_buf(), 72 + } 73 + } 74 + 75 + pub(crate) fn build(&self) -> Result<Project, AppError> { 76 + check_package_json(&self.path) 77 + .map(|s| check_react_script(&s))? 78 + .map(|p| { 79 + let image_name = p.name.trim().replace(' ', "_"); 80 + Project { 81 + package_json: p, 82 + path: self.path.clone(), 83 + image_name, 84 + } 85 + }) 86 + } 87 + }
+128
src/runtime.rs
··· 1 + mod proc_file; 2 + 3 + use super::log::trying; 4 + use crate::{error::AppError, log::success}; 5 + use anyhow::Result; 6 + use serde::{Deserialize, Serialize}; 7 + use std::{ 8 + fs, io, 9 + path::{Path, PathBuf}, 10 + }; 11 + use tracing::{debug, warn}; 12 + 13 + pub(crate) use proc_file::ProcFile; 14 + 15 + /// Runtime information of autopack 16 + #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] 17 + pub(crate) struct Runtime { 18 + /// `.autopack` directory path 19 + dir: PathBuf, 20 + /// proc file 21 + proc_file: ProcFile, 22 + } 23 + 24 + pub(crate) struct RuntimeBuilder { 25 + dir: PathBuf, 26 + proc_file: Option<ProcFile>, 27 + } 28 + 29 + impl Default for Runtime { 30 + fn default() -> Self { 31 + Runtime { 32 + dir: PathBuf::new().join(".autopack"), 33 + proc_file: ProcFile::default(), 34 + } 35 + } 36 + } 37 + 38 + fn create_runtime_dir(path: &Path, force_create: bool) -> Result<PathBuf, io::Error> { 39 + let rtp = |created: bool| { 40 + let p = path.canonicalize().unwrap_or_else(|_| path.to_path_buf()); 41 + if created { 42 + debug!("Created runtime folder at {:?}", p); 43 + success("Created runtime folder"); 44 + }else { 45 + debug!("Runtime folder already present at {:?}", p); 46 + success("Runtime folder already present"); 47 + } 48 + 49 + p 50 + }; 51 + if !path.exists() { 52 + fs::create_dir(path)?; 53 + Ok(rtp(true)) 54 + } else if force_create { 55 + let full_path = path.canonicalize()?; 56 + warn!( 57 + "runtime folder present at {:?}; removing the same", 58 + full_path 59 + ); 60 + fs::remove_dir_all(path)?; 61 + create_runtime_dir(path, false) 62 + } else { 63 + Ok(rtp(false)) 64 + } 65 + } 66 + 67 + impl Runtime { 68 + pub(crate) fn builder(mod_path: &Path) -> RuntimeBuilder { 69 + trying("Generating auto-pack runtime"); 70 + RuntimeBuilder { 71 + dir: mod_path.join(".autopack"), 72 + proc_file: None, 73 + } 74 + } 75 + 76 + pub(crate) fn dir(&self) -> PathBuf { 77 + self.dir.clone() 78 + } 79 + 80 + pub(crate) fn proc_file(&self) -> ProcFile { 81 + self.proc_file.clone() 82 + } 83 + 84 + pub(crate) fn project_toml(&self) -> String { 85 + self.dir 86 + .join("project.toml") 87 + .to_str() 88 + .expect("Expected a valid project.toml file") 89 + .to_string() 90 + } 91 + } 92 + 93 + impl RuntimeBuilder { 94 + pub(crate) fn dir(&mut self, force_create_runtime: bool) -> Result<&mut Self, AppError> { 95 + trying("Creating runtime folder"); 96 + let runtime_full_path = create_runtime_dir(self.dir.as_path(), force_create_runtime) 97 + .map_err(|e| AppError::IOError("Failed to create runtime dir", e))?; 98 + 99 + self.dir = runtime_full_path; 100 + Ok(self) 101 + } 102 + 103 + pub(crate) fn proc_file(&mut self, live_reload: bool) -> Result<&mut Self, AppError> { 104 + trying("Creating proc file"); 105 + 106 + let proc_file = ProcFile::builder() 107 + .override_start_entry(live_reload) 108 + .export(&self.dir) 109 + .map_err(|e| AppError::PostConfigureError("Failed creating Procfile", e))? 110 + .build(); 111 + 112 + self.proc_file = Some(proc_file.clone()); 113 + debug!("Created proc file at {:?}", proc_file.proc_file_path()); 114 + success("Created proc file"); 115 + Ok(self) 116 + } 117 + 118 + pub(crate) fn build(&mut self) -> Runtime { 119 + success("Generated auto-pack runtime"); 120 + Runtime { 121 + dir: self.dir.clone(), 122 + proc_file: self 123 + .proc_file 124 + .clone() 125 + .unwrap_or(Runtime::default().proc_file), 126 + } 127 + } 128 + }
+137
src/runtime/proc_file.rs
··· 1 + use serde::{Deserialize, Serialize}; 2 + use std::{ 3 + collections::HashMap, 4 + fs, 5 + path::{Path, PathBuf}, 6 + }; 7 + 8 + /// Procfile is required to override the default start command in the container. 9 + /// 10 + /// [Paketo's Procfile Buildpack](https://github.com/paketo-buildpacks/procfile) supports Procfiles which are either made available via a normal file or via a file organization following the [K8S Service Binding Spec](https://github.com/servicebinding/spec). 11 + /// 12 + /// We are following the Service Binding approach so that there are minimum changes incurred in the application folder structure. 13 + #[derive(Debug, Clone, Deserialize, Serialize, PartialEq)] 14 + pub(crate) struct ProcFile { 15 + command_entries: HashMap<String, String>, 16 + default_command: String, 17 + binding_provider: String, 18 + binding_type: String, 19 + file_path: Option<PathBuf>, 20 + } 21 + 22 + impl Default for ProcFile { 23 + fn default() -> Self { 24 + ProcFile { 25 + command_entries: HashMap::new(), 26 + binding_provider: "autopack".to_string(), 27 + binding_type: "Procfile".to_string(), 28 + default_command: "override-start".to_string(), 29 + file_path: Some(Path::new(".autopack").to_path_buf().join("Procfile")), 30 + } 31 + } 32 + } 33 + 34 + impl ProcFile { 35 + pub(crate) fn builder() -> ProcFileBuilder { 36 + let pf = ProcFile::default(); 37 + ProcFileBuilder { 38 + command_entries: pf.command_entries, 39 + default_command: pf.default_command, 40 + binding_provider: pf.binding_provider, 41 + binding_type: pf.binding_type, 42 + file_path: pf.file_path, 43 + } 44 + } 45 + 46 + pub(crate) fn proc_file_path(&self) -> String { 47 + self.file_path 48 + .clone() 49 + .unwrap_or_else(|| ProcFile::default().file_path.unwrap()) 50 + .to_str() 51 + .expect("expected a valid procfile folder") 52 + .to_string() 53 + } 54 + 55 + pub(crate) fn proc_default_command(&self) -> String { 56 + self.default_command.clone() 57 + } 58 + 59 + pub(crate) fn container_bindings_path(&self) -> String { 60 + "/platform/bindings/Procfile".to_string() 61 + } 62 + } 63 + 64 + pub(crate) struct ProcFileBuilder { 65 + command_entries: HashMap<String, String>, 66 + default_command: String, 67 + binding_provider: String, 68 + binding_type: String, 69 + file_path: Option<PathBuf>, 70 + } 71 + 72 + impl ProcFileBuilder { 73 + pub(crate) fn command_entry( 74 + &mut self, 75 + command_type: &str, 76 + command: &str, 77 + ) -> &mut ProcFileBuilder { 78 + self.command_entries 79 + .insert(command_type.to_string(), command.to_string()); 80 + self 81 + } 82 + 83 + pub(crate) fn override_start_entry(&mut self, live_reload: bool) -> &mut ProcFileBuilder { 84 + let mut watch_cmd = None; 85 + let serve_cmd = "serve -s build -l 8080".to_string(); 86 + if live_reload { 87 + let build_cmd = "npm run build".to_string(); 88 + watch_cmd = Some(format!( 89 + r#"watchexec --restart --shell none --watch /workspace/src -- bash -c "{} && {}""#, 90 + build_cmd, 91 + serve_cmd 92 + )); 93 + } 94 + 95 + self.command_entry( 96 + "override-start", 97 + &format!( 98 + r#"npm install -g serve && {}"#, 99 + watch_cmd.unwrap_or(serve_cmd) 100 + ), 101 + ); 102 + self 103 + } 104 + 105 + pub(crate) fn export(&mut self, export_path: &Path) -> anyhow::Result<&mut ProcFileBuilder> { 106 + let dir = export_path.to_path_buf().join("Procfile"); 107 + 108 + if !dir.exists() { 109 + fs::create_dir(dir.clone())?; 110 + } 111 + 112 + fs::write(dir.join("type"), self.binding_type.clone())?; 113 + fs::write(dir.join("provider"), self.binding_provider.clone())?; 114 + fs::write( 115 + dir.join("Procfile"), 116 + self.command_entries 117 + .clone() 118 + .into_iter() 119 + .fold("".to_string(), |acc, x| { 120 + format!("{}: {}\n{}", x.0, x.1, acc) 121 + }), 122 + )?; 123 + 124 + self.file_path = Some(dir.canonicalize().map_err(|e| anyhow::anyhow!(e))?); 125 + Ok(self) 126 + } 127 + 128 + pub(crate) fn build(&mut self) -> ProcFile { 129 + ProcFile { 130 + command_entries: self.command_entries.clone(), 131 + default_command: self.default_command.clone(), 132 + binding_provider: self.binding_provider.clone(), 133 + binding_type: self.binding_type.clone(), 134 + file_path: self.file_path.clone(), 135 + } 136 + } 137 + }