From 98c82dd4bccf82b80c1f031b0770f0f4121fc075 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliv=C3=A9r=20Falvai?= Date: Wed, 29 Jul 2026 07:58:07 +0200 Subject: [PATCH] Vendor acquisition-sdk instead of depending on the code-push npm package Adds src/acquisition-sdk/ (vendored TypeScript from the archived microsoft/code-push repo, trimmed types.ts to only what's used, tests ported for future reference but not wired into any runner), a tsconfig.build.json + build:ts script compiling it to lib/ at prepack/setup time, and points CodePush.js at the compiled output instead of the code-push package. Drops the code-push dependency entirely, which was only ever used for this one submodule and pulled in the superagent/proxy-agent/vm2 CLI dependency chain that never actually shipped in the bundled app JS. --- .gitignore | 3 + .npmignore | 11 + CLAUDE.md | 6 +- CodePush.js | 2 +- package-lock.json | 497 +++--------------- package.json | 7 +- request-fetch-adapter.js | 5 +- .../__tests__/acquisition-rest-mock.ts | 124 +++++ .../__tests__/acquisition-sdk.test.ts | 301 +++++++++++ src/acquisition-sdk/acquisition-sdk.ts | 292 ++++++++++ src/acquisition-sdk/code-push-error.ts | 36 ++ src/acquisition-sdk/types.ts | 46 ++ tsconfig.build.json | 24 + tsconfig.json | 9 +- 14 files changed, 925 insertions(+), 438 deletions(-) create mode 100644 src/acquisition-sdk/__tests__/acquisition-rest-mock.ts create mode 100644 src/acquisition-sdk/__tests__/acquisition-sdk.test.ts create mode 100644 src/acquisition-sdk/acquisition-sdk.ts create mode 100644 src/acquisition-sdk/code-push-error.ts create mode 100644 src/acquisition-sdk/types.ts create mode 100644 tsconfig.build.json diff --git a/.gitignore b/.gitignore index ddc58ec..de9ce82 100644 --- a/.gitignore +++ b/.gitignore @@ -77,6 +77,9 @@ build/Release # https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git node_modules +# Compiled output of src/ (built via `npm run build:ts`, generated fresh at prepare time) +lib/ + # Xcode # # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore diff --git a/.npmignore b/.npmignore index d2d8659..47d2643 100644 --- a/.npmignore +++ b/.npmignore @@ -34,6 +34,11 @@ Recipes/ bin/ test/ +# Don't publish TypeScript source, only the compiled lib/ output (built via `npm run build:ts` +# as part of `prepare`) ships +# Anchored to repo root (leading slash): an unanchored `src/` would also match `android/app/src/`. +/src/ + # Remove after this framework is published on NPM code-push-plugin-testing-framework/ @@ -101,3 +106,9 @@ packages/ .watchmanconfig +# Dev tooling, not relevant to consumers +.claude/ +CLAUDE.md +mise.toml +tsconfig.json +tsconfig.build.json diff --git a/CLAUDE.md b/CLAUDE.md index 1219509..1e3d54d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -16,6 +16,8 @@ React Native CodePush is a native module that enables over-the-air updates for R ### Build - `npm run build` - Build TypeScript tests to bin/ directory - `npm run tsc` - TypeScript compilation +- `npm run build:ts` - Compiles `src/` (currently just the vendored `acquisition-sdk`) to `lib/`, which is what ships to consumers instead of raw `.ts`. Wired into `setup` (so local dev/tests have `lib/` available) and `prepare` (so `npm publish`/`npm pack` always ship a freshly built `lib/`). + - This split (a separate `tsconfig.build.json` and `tsconfig.json` for `test/` -> `bin/`) is temporary. Once `CodePush.js` and the rest of this repo's runtime JS are migrated to TypeScript, these should be unified into a single build, and adopting `react-native-builder-bob` (or some other common tool) is worth considering at that point instead of hand-rolled `tsc` + npm script wiring. ### Platform Testing - Tests run on actual emulators/simulators with real React Native apps @@ -28,7 +30,7 @@ React Native CodePush is a native module that enables over-the-air updates for R - **JavaScript Bridge** (`CodePush.js`): Main API layer exposing update methods - **Native Modules**: Platform-specific implementations handling file operations, bundle management - **Update Manager**: Handles download, installation, and rollback logic -- **Acquisition SDK**: Manages server communication and update metadata +- **Acquisition SDK** (`src/acquisition-sdk/`): Manages server communication and update metadata ### Platform Structure - **iOS**: `ios/` - Objective-C implementation with CocoaPods integration @@ -46,6 +48,7 @@ React Native CodePush is a native module that enables over-the-air updates for R - **Custom Test Runner**: TypeScript-based test framework in `test/` - **Real App Testing**: Creates actual React Native apps for integration testing - **Scenario Testing**: Update, rollback, and error scenarios +- **No unit test infra yet**: this repo only has the mocha-based integration suite above. `src/acquisition-sdk/__tests__/` contains tests ported from upstream `microsoft/code-push`, kept for future reference - they are deliberately not wired into `npm test` or any runner. Don't assume they're dead/forgotten code, and don't wire them in without setting up real unit test infra first. - **Templates**: `test/template/` holds native files (Podfile, AppDelegate, Android app files) and JS scenarios copied over top of a freshly generated RN/Expo app during test setup, overwriting its defaults — edit files here, not the generated project, for changes to persist - **`test:ios` vs `test:setup:ios` vs `test:fast:ios`**: `test:ios` is just `test:setup:ios` followed by `test:fast:ios` — the two are meant to be split apart for local iteration. - `test:setup:ios` (mocha `--ios --setup`) boots the simulator and provisions the test app once: copies templates, runs `pod install`, patches Info.plist/AppDelegate. It never builds or runs any test scenario. @@ -61,3 +64,4 @@ React Native CodePush is a native module that enables over-the-air updates for R - **Android Gradle Plugin**: Automatically generates bundle hashes and processes assets - **iOS CocoaPods**: Manages native dependencies and build configuration - **Bundle Processing**: Automated zip creation and hash calculation for OTA updates +- **`.npmignore` is a blocklist, not an allowlist**: `package.json` has no `files` field, so any new top-level file/dir ships to npm by default unless explicitly excluded. When adding new repo tooling/config, check whether it needs a `.npmignore` entry. Verify with `npm pack --dry-run`. diff --git a/CodePush.js b/CodePush.js index 2d1d9de..cb46ea9 100644 --- a/CodePush.js +++ b/CodePush.js @@ -1,4 +1,4 @@ -import { AcquisitionManager as Sdk } from "code-push/script/acquisition-sdk"; +import { AcquisitionManager as Sdk } from "./lib/acquisition-sdk/acquisition-sdk"; import { Alert } from "./AlertAdapter"; import requestFetchAdapter from "./request-fetch-adapter"; import { AppState, Platform } from "react-native"; diff --git a/package-lock.json b/package-lock.json index 2bdd2b5..d58b51f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,14 @@ { "name": "@bitrise/code-push-sdk", - "version": "10.4.1", + "version": "0.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@bitrise/code-push-sdk", - "version": "10.4.1", + "version": "0.0.0", "license": "MIT", "dependencies": { - "code-push": "4.2.3", "glob": "^7.1.7", "hoist-non-react-statics": "^3.3.2", "inquirer": "^8.1.5", @@ -1508,11 +1507,6 @@ "node": ">= 6" } }, - "node_modules/@tootallnate/quickjs-emscripten": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", - "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==" - }, "node_modules/@types/assert": { "version": "1.5.10", "resolved": "https://registry.npmjs.org/@types/assert/-/assert-1.5.10.tgz", @@ -1726,6 +1720,7 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "dev": true, "dependencies": { "debug": "^4.3.4" }, @@ -1737,6 +1732,7 @@ "version": "4.3.7", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dev": true, "dependencies": { "ms": "^2.1.3" }, @@ -1872,88 +1868,6 @@ "node": ">= 8" } }, - "node_modules/appcenter-file-upload-client": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/appcenter-file-upload-client/-/appcenter-file-upload-client-0.1.0.tgz", - "integrity": "sha512-W8lueBBvLuItND2vmvfdIDTbIYHOHXr5ohObhqvBNL3XCOGTqQq1rhWUxBX5Mb5geLBuLDC0HQOtq9pcBgi71w==", - "dependencies": { - "detect-node": "^2.0.4", - "superagent": "5.1.0", - "url-parse": "^1.4.7" - } - }, - "node_modules/appcenter-file-upload-client/node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/appcenter-file-upload-client/node_modules/form-data": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", - "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 0.12" - } - }, - "node_modules/appcenter-file-upload-client/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/appcenter-file-upload-client/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/appcenter-file-upload-client/node_modules/superagent": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/superagent/-/superagent-5.1.0.tgz", - "integrity": "sha512-7V6JVx5N+eTL1MMqRBX0v0bG04UjrjAvvZJTF/VDH/SH2GjSLqlrcYepFlpTrXpm37aSY6h3GGVWGxXl/98TKA==", - "deprecated": "Please upgrade to v9.0.0+ as we have fixed a public vulnerability with formidable dependency. Note that v9.0.0+ requires Node.js v14.18.0+. See https://github.com/ladjs/superagent/pull/1800 for insight. This project is supported and maintained by the team at Forward Email @ https://forwardemail.net", - "dependencies": { - "component-emitter": "^1.3.0", - "cookiejar": "^2.1.2", - "debug": "^4.1.1", - "fast-safe-stringify": "^2.0.6", - "form-data": "^2.3.3", - "formidable": "^1.2.1", - "methods": "^1.1.2", - "mime": "^2.4.4", - "qs": "^6.7.0", - "readable-stream": "^3.4.0", - "semver": "^6.1.1" - }, - "engines": { - "node": ">= 6.4.0" - } - }, "node_modules/archiver": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/archiver/-/archiver-7.0.1.tgz", @@ -2098,12 +2012,14 @@ "node_modules/asap": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==" + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "dev": true }, "node_modules/ast-types": { "version": "0.13.4", "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", + "dev": true, "dependencies": { "tslib": "^2.0.1" }, @@ -2127,7 +2043,8 @@ "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true }, "node_modules/b4a": { "version": "1.6.6", @@ -2291,14 +2208,6 @@ "baseline-browser-mapping": "dist/cli.js" } }, - "node_modules/basic-ftp": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.5.tgz", - "integrity": "sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==", - "engines": { - "node": ">=10.0.0" - } - }, "node_modules/big-integer": { "version": "1.6.52", "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz", @@ -2517,6 +2426,7 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dev": true, "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", @@ -2784,19 +2694,6 @@ "node": ">=0.8" } }, - "node_modules/code-push": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/code-push/-/code-push-4.2.3.tgz", - "integrity": "sha512-FPwcU9/5lgMJH7MfBgkr4nCta513DGx2v4mg2yW860+8sCQTbhHHnduoAjSlZgyZJj3FIxzW2ccD41pVtTJDow==", - "dependencies": { - "appcenter-file-upload-client": "0.1.0", - "proxy-agent": "^6.3.0", - "recursive-fs": "^2.1.0", - "slash": "^3.0.0", - "superagent": "^8.0.0", - "yazl": "^2.5.1" - } - }, "node_modules/code-push-plugin-testing-framework": { "resolved": "code-push-plugin-testing-framework", "link": true @@ -2830,6 +2727,7 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, "dependencies": { "delayed-stream": "~1.0.0" }, @@ -2847,6 +2745,7 @@ "version": "1.3.1", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz", "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==", + "dev": true, "funding": { "url": "https://github.com/sponsors/sindresorhus" } @@ -2976,7 +2875,8 @@ "node_modules/cookiejar": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", - "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==" + "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==", + "dev": true }, "node_modules/core-util-is": { "version": "1.0.3", @@ -3033,14 +2933,6 @@ "node": "*" } }, - "node_modules/data-uri-to-buffer": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", - "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", - "engines": { - "node": ">= 14" - } - }, "node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -3089,6 +2981,7 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", @@ -3101,19 +2994,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/degenerator": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", - "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", - "dependencies": { - "ast-types": "^0.13.4", - "escodegen": "^2.1.0", - "esprima": "^4.0.1" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/del": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/del/-/del-6.0.0.tgz", @@ -3140,6 +3020,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, "engines": { "node": ">=0.4.0" } @@ -3163,15 +3044,12 @@ "npm": "1.2.8000 || >= 1.4.16" } }, - "node_modules/detect-node": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", - "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==" - }, "node_modules/dezalgo": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", + "dev": true, + "peer": true, "dependencies": { "asap": "^2.0.0", "wrappy": "1" @@ -3245,6 +3123,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dev": true, "dependencies": { "get-intrinsic": "^1.2.4" }, @@ -3256,6 +3135,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, "engines": { "node": ">= 0.4" } @@ -3283,30 +3163,11 @@ "node": ">=0.8.0" } }, - "node_modules/escodegen": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", - "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", - "dependencies": { - "esprima": "^4.0.1", - "estraverse": "^5.2.0", - "esutils": "^2.0.2" - }, - "bin": { - "escodegen": "bin/escodegen.js", - "esgenerate": "bin/esgenerate.js" - }, - "engines": { - "node": ">=6.0" - }, - "optionalDependencies": { - "source-map": "~0.6.1" - } - }, "node_modules/esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, "bin": { "esparse": "bin/esparse.js", "esvalidate": "bin/esvalidate.js" @@ -3315,18 +3176,11 @@ "node": ">=4" } }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "engines": { - "node": ">=4.0" - } - }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -3485,7 +3339,8 @@ "node_modules/fast-safe-stringify": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", - "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==" + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", + "dev": true }, "node_modules/fastq": { "version": "1.17.1", @@ -3651,6 +3506,7 @@ "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.6.tgz", "integrity": "sha512-KcpbcpuLNOwrEjnbpMC0gS+X8ciDoZE1kkqzat4a8vrprf+s9pKNQ/QIwWfbfs4ltgmFl3MD177SNTkve3BwGQ==", "deprecated": "Please upgrade to latest, formidable@v2 or formidable@v3! Check these notes: https://bit.ly/2ZEqIau", + "dev": true, "funding": { "url": "https://ko-fi.com/tunnckoCore/commissions" } @@ -3673,19 +3529,6 @@ "node": ">= 0.6" } }, - "node_modules/fs-extra": { - "version": "11.2.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", - "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=14.14" - } - }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -3746,6 +3589,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -3773,6 +3617,7 @@ "version": "1.2.4", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "dev": true, "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2", @@ -3797,36 +3642,6 @@ "node": ">=8.0.0" } }, - "node_modules/get-uri": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.3.tgz", - "integrity": "sha512-BzUrJBS9EcUb4cFol8r4W3v1cPsSyajLSthNkz5BxbpDcHN5tIrM10E2eNvfnvBn3DaT3DUgx0OpsBKkaOpanw==", - "dependencies": { - "basic-ftp": "^5.0.2", - "data-uri-to-buffer": "^6.0.2", - "debug": "^4.3.4", - "fs-extra": "^11.2.0" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/get-uri/node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -3883,6 +3698,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, "dependencies": { "get-intrinsic": "^1.1.3" }, @@ -3893,7 +3709,8 @@ "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true }, "node_modules/growl": { "version": "1.10.5", @@ -3937,6 +3754,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, "dependencies": { "es-define-property": "^1.0.0" }, @@ -3948,6 +3766,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "dev": true, "engines": { "node": ">= 0.4" }, @@ -3959,6 +3778,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, "engines": { "node": ">= 0.4" }, @@ -3970,6 +3790,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, "dependencies": { "function-bind": "^1.1.2" }, @@ -4014,6 +3835,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz", "integrity": "sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==", + "dev": true, + "peer": true, "engines": { "node": ">=8" } @@ -4042,38 +3865,11 @@ "node": ">= 0.8" } }, - "node_modules/http-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", - "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/http-proxy-agent/node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, "node_modules/https-proxy-agent": { "version": "7.0.5", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", + "dev": true, "dependencies": { "agent-base": "^7.0.2", "debug": "4" @@ -4086,6 +3882,7 @@ "version": "4.3.7", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dev": true, "dependencies": { "ms": "^2.1.3" }, @@ -4241,6 +4038,7 @@ "version": "9.0.5", "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", + "dev": true, "dependencies": { "jsbn": "1.1.0", "sprintf-js": "^1.1.3" @@ -4670,7 +4468,8 @@ "node_modules/jsbn": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", - "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==" + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", + "dev": true }, "node_modules/jsc-safe-url": { "version": "0.2.4", @@ -4705,17 +4504,6 @@ "node": ">=6" } }, - "node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, "node_modules/kind-of": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-1.1.0.tgz", @@ -4856,14 +4644,6 @@ "loose-envify": "cli.js" } }, - "node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", - "engines": { - "node": ">=12" - } - }, "node_modules/makeerror": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", @@ -4934,6 +4714,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "dev": true, "engines": { "node": ">= 0.6" } @@ -5395,6 +5176,7 @@ "version": "2.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true, "bin": { "mime": "cli.js" }, @@ -5406,6 +5188,7 @@ "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, "engines": { "node": ">= 0.6" } @@ -5414,6 +5197,7 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, "dependencies": { "mime-db": "1.52.0" }, @@ -5647,7 +5431,8 @@ "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true }, "node_modules/mute-stream": { "version": "0.0.8", @@ -5679,6 +5464,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==", + "dev": true, "engines": { "node": ">= 0.4.0" } @@ -5730,6 +5516,7 @@ "version": "1.13.2", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "dev": true, "engines": { "node": ">= 0.4" }, @@ -5889,52 +5676,6 @@ "node": ">=6" } }, - "node_modules/pac-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.0.2.tgz", - "integrity": "sha512-BFi3vZnO9X5Qt6NRz7ZOaPja3ic0PhlsmCRYLOpN11+mWBCR6XJDqW5RF3j8jm4WGGQZtBA+bTfxYzeKW73eHg==", - "dependencies": { - "@tootallnate/quickjs-emscripten": "^0.23.0", - "agent-base": "^7.0.2", - "debug": "^4.3.4", - "get-uri": "^6.0.1", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.5", - "pac-resolver": "^7.0.1", - "socks-proxy-agent": "^8.0.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/pac-proxy-agent/node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/pac-resolver": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", - "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", - "dependencies": { - "degenerator": "^5.0.0", - "netmask": "^2.0.2" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/package-json-from-dist": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz", @@ -6168,44 +5909,11 @@ "node": ">= 0.10" } }, - "node_modules/proxy-agent": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.4.0.tgz", - "integrity": "sha512-u0piLU+nCOHMgGjRbimiXmA9kM/L9EHh3zL81xCdp7m+Y2pHIsnmbdDoEDoAz5geaonNR6q6+yOPQs6n4T6sBQ==", - "dependencies": { - "agent-base": "^7.0.2", - "debug": "^4.3.4", - "http-proxy-agent": "^7.0.1", - "https-proxy-agent": "^7.0.3", - "lru-cache": "^7.14.1", - "pac-proxy-agent": "^7.0.1", - "proxy-from-env": "^1.1.0", - "socks-proxy-agent": "^8.0.2" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/proxy-agent/node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true }, "node_modules/q": { "version": "1.5.1", @@ -6222,6 +5930,7 @@ "version": "6.11.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dev": true, "dependencies": { "side-channel": "^1.0.4" }, @@ -6232,11 +5941,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/querystringify": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" - }, "node_modules/queue": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz", @@ -6590,18 +6294,6 @@ "node": ">= 0.10" } }, - "node_modules/recursive-fs": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/recursive-fs/-/recursive-fs-2.1.0.tgz", - "integrity": "sha512-oed3YruYsD52Mi16s/07eYblQOLi5dTtxpIJNdfCEJ7S5v8dDgVcycar0pRWf4IBuPMIkoctC8RTqGJzIKMNAQ==", - "bin": { - "recursive-copy": "bin/recursive-copy", - "recursive-delete": "bin/recursive-delete" - }, - "engines": { - "node": ">=10.0.0" - } - }, "node_modules/regenerator-runtime": { "version": "0.13.11", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", @@ -6838,11 +6530,6 @@ "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", "dev": true }, - "node_modules/requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" - }, "node_modules/resolve": { "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", @@ -7138,6 +6825,7 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", @@ -7227,6 +6915,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "dev": true, "dependencies": { "call-bind": "^1.0.7", "es-errors": "^1.3.0", @@ -7259,6 +6948,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, "engines": { "node": ">=8" } @@ -7267,6 +6957,7 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true, "engines": { "node": ">= 6.0.0", "npm": ">= 3.0.0" @@ -7276,6 +6967,7 @@ "version": "2.8.3", "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz", "integrity": "sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==", + "dev": true, "dependencies": { "ip-address": "^9.0.5", "smart-buffer": "^4.2.0" @@ -7285,40 +6977,11 @@ "npm": ">= 3.0.0" } }, - "node_modules/socks-proxy-agent": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.4.tgz", - "integrity": "sha512-GNAq/eg8Udq2x0eNiFkr9gRg5bA7PXEWagQdeRX4cPSG+X/8V38v637gim9bjFptMk1QWsCTr0ttrJEiXbNnRw==", - "dependencies": { - "agent-base": "^7.1.1", - "debug": "^4.3.4", - "socks": "^2.8.3" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/socks-proxy-agent/node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "devOptional": true, + "dev": true, "engines": { "node": ">=0.10.0" } @@ -7337,7 +7000,8 @@ "node_modules/sprintf-js": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", - "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==" + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "dev": true }, "node_modules/stack-utils": { "version": "2.0.6", @@ -7500,6 +7164,8 @@ "resolved": "https://registry.npmjs.org/superagent/-/superagent-8.1.2.tgz", "integrity": "sha512-6WTxW1EB6yCxV5VFOIPQruWGHqc3yI7hEmZK6h+pyk69Lk/Ut7rLUY6W/ONF2MjBuGjvmMiIpsrVJ2vjrHlslA==", "deprecated": "Please upgrade to v9.0.0+ as we have fixed a public vulnerability with formidable dependency. Note that v9.0.0+ requires Node.js v14.18.0+. See https://github.com/ladjs/superagent/pull/1800 for insight. This project is supported and maintained by the team at Forward Email @ https://forwardemail.net", + "dev": true, + "peer": true, "dependencies": { "component-emitter": "^1.3.0", "cookiejar": "^2.1.4", @@ -7772,6 +7438,8 @@ "version": "4.3.7", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dev": true, + "peer": true, "dependencies": { "ms": "^2.1.3" }, @@ -7788,6 +7456,8 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dev": true, + "peer": true, "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -7801,6 +7471,8 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/formidable/-/formidable-2.1.2.tgz", "integrity": "sha512-CM3GuJ57US06mlpQ47YcunuUZ9jpm8Vx+P2CGt2j7HpgkKZO/DJYQ0Bobim8G6PFQmK5lOqOOdUXboU+h73A4g==", + "dev": true, + "peer": true, "dependencies": { "dezalgo": "^1.0.4", "hexoid": "^1.0.0", @@ -8187,14 +7859,6 @@ "node": ">=4.2.0" } }, - "node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "engines": { - "node": ">= 10.0.0" - } - }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -8235,15 +7899,6 @@ "browserslist": ">= 4.21.0" } }, - "node_modules/url-parse": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", - "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", - "dependencies": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -8534,22 +8189,6 @@ "node": ">=10" } }, - "node_modules/yazl": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/yazl/-/yazl-2.5.1.tgz", - "integrity": "sha512-phENi2PLiHnHb6QBVot+dJnaAZ0xosj7p3fWl+znIjBDlnMI2PsZCJZ306BPTFOaHf5qdDEI8x5qFrSOBN5vrw==", - "dependencies": { - "buffer-crc32": "~0.2.3" - } - }, - "node_modules/yazl/node_modules/buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", - "engines": { - "node": "*" - } - }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index 7bab663..114cd5b 100644 --- a/package.json +++ b/package.json @@ -14,8 +14,10 @@ "author": "Bitrise", "license": "MIT", "scripts": { - "clean": "shx rm -rf bin", - "setup": "npm install --quiet --no-progress", + "clean": "shx rm -rf bin lib", + "setup": "npm install --quiet --no-progress && npm run build:ts", + "build:ts": "tsc -p tsconfig.build.json", + "prepare": "npm run build:ts", "prebuild:tests": "npm run clean && npm run tslint", "build:tests": "tsc", "test": "npm run build:tests && npm run test:setup && npm run test:fast", @@ -42,7 +44,6 @@ "url": "https://github.com/bitrise-io/react-native-code-push" }, "dependencies": { - "code-push": "4.2.3", "glob": "^7.1.7", "hoist-non-react-statics": "^3.3.2", "inquirer": "^8.1.5", diff --git a/request-fetch-adapter.js b/request-fetch-adapter.js index a3f2c46..79a0fc4 100644 --- a/request-fetch-adapter.js +++ b/request-fetch-adapter.js @@ -12,7 +12,6 @@ module.exports = { "Content-Type": "application/json", "X-CodePush-Plugin-Name": packageJson.name, "X-CodePush-Plugin-Version": packageJson.version, - "X-CodePush-SDK-Version": packageJson.dependencies["code-push"] }; if (requestBody && typeof requestBody === "object") { @@ -37,7 +36,7 @@ module.exports = { function getHttpMethodName(verb) { // Note: This should stay in sync with the enum definition in - // https://github.com/microsoft/code-push/blob/master/sdk/script/acquisition-sdk.ts#L6 + // src/acquisition-sdk/acquisition-sdk.ts return [ "GET", "HEAD", @@ -49,4 +48,4 @@ function getHttpMethodName(verb) { "CONNECT", "PATCH" ][verb]; -} \ No newline at end of file +} diff --git a/src/acquisition-sdk/__tests__/acquisition-rest-mock.ts b/src/acquisition-sdk/__tests__/acquisition-rest-mock.ts new file mode 100644 index 0000000..4a67cbe --- /dev/null +++ b/src/acquisition-sdk/__tests__/acquisition-rest-mock.ts @@ -0,0 +1,124 @@ +// Ported from https://github.com/microsoft/code-push/blob/master/src/test/acquisition-rest-mock.ts (archived, MIT licensed) +// Not currently wired into any test runner — this repo has no unit test infra yet. + +import * as querystring from "querystring"; + +import * as acquisitionSdk from "../acquisition-sdk"; +import * as types from "../types"; + +export var validDeploymentKey = "Valid Deployment Key"; +export var latestPackage = { + download_url: "http://www.windowsazure.com/blobs/awperoiuqpweru", + description: "Angry flappy birds", + target_binary_range: "1.5.0", + label: "2.4.0", + is_mandatory: false, + is_available: true, + update_app_version: false, + package_hash: "hash240", + package_size: 1024 +}; + +export var serverUrl = "http://myurl.com"; +var publicPrefixUrl = "/v0.1/public/codepush"; +var reportStatusDeployUrl = serverUrl + publicPrefixUrl + "/report_status/deploy"; +var reportStatusDownloadUrl = serverUrl + publicPrefixUrl + "/report_status/download"; +var updateCheckUrl = serverUrl + publicPrefixUrl + "/update_check?"; + +export function updateMockUrl() { + reportStatusDeployUrl = serverUrl + publicPrefixUrl + "/report_status/deploy"; + reportStatusDownloadUrl = serverUrl + publicPrefixUrl + "/report_status/download"; + updateCheckUrl = serverUrl + publicPrefixUrl + "/update_check?"; +} + +export class HttpRequester implements acquisitionSdk.Http.Requester { + private expectedStatusCode: number; + + constructor(expectedStatusCode?: number) { + this.expectedStatusCode = expectedStatusCode; + } + + public request(verb: acquisitionSdk.Http.Verb, url: string, requestBodyOrCallback: string | acquisitionSdk.Callback, callback?: acquisitionSdk.Callback): void { + if (!callback && typeof requestBodyOrCallback === "function") { + callback = >requestBodyOrCallback; + } + + if (verb === acquisitionSdk.Http.Verb.GET && url.indexOf(updateCheckUrl) === 0) { + var params = querystring.parse(url.substring(updateCheckUrl.length)); + Server.onUpdateCheck(params, callback, this.expectedStatusCode); + } else if (verb === acquisitionSdk.Http.Verb.POST && url === reportStatusDeployUrl) { + Server.onReportStatus(callback, this.expectedStatusCode); + } else if (verb === acquisitionSdk.Http.Verb.POST && url === reportStatusDownloadUrl) { + Server.onReportStatus(callback, this.expectedStatusCode); + } else { + throw new Error("Unexpected call"); + } + } +} + +export class CustomResponseHttpRequester implements acquisitionSdk.Http.Requester { + response: acquisitionSdk.Http.Response; + + constructor(response: acquisitionSdk.Http.Response) { + this.response = response; + } + + public request(verb: acquisitionSdk.Http.Verb, url: string, requestBodyOrCallback: string | acquisitionSdk.Callback, callback?: acquisitionSdk.Callback): void { + if (typeof requestBodyOrCallback !== "function") { + throw new Error("Unexpected request body"); + } + + callback = >requestBodyOrCallback; + callback(null, this.response); + } +} + +class Server { + public static onAcquire(params: any, callback: acquisitionSdk.Callback): void { + if (params.deploymentKey !== validDeploymentKey) { + callback(/*error=*/ null, { + statusCode: 200, + body: JSON.stringify({ update_info: { isAvailable: false } }) + }); + } else { + callback(/*error=*/ null, { + statusCode: 200, + body: JSON.stringify({ update_info: latestPackage }) + }); + } + } + + public static onUpdateCheck(params: any, callback: acquisitionSdk.Callback, expectedStatusCode?: number): void { + var updateRequest: types.UpdateCheckRequest = { + deployment_key: params.deployment_key, + app_version: params.app_version, + package_hash: params.package_hash, + is_companion: !!(params.is_companion), + label: params.label + }; + + if (!updateRequest.deployment_key || !updateRequest.app_version) { + callback(/*error=*/ null, { statusCode: 400 }); + } else { + var updateInfo = { is_available: false }; + if (updateRequest.deployment_key === validDeploymentKey) { + if (updateRequest.is_companion || updateRequest.app_version === latestPackage.target_binary_range) { + if (updateRequest.package_hash !== latestPackage.package_hash) { + updateInfo = latestPackage; + } + } else if (updateRequest.app_version < latestPackage.target_binary_range) { + updateInfo = { update_app_version: true, target_binary_range: latestPackage.target_binary_range }; + } + } + + callback(/*error=*/ null, { + statusCode: expectedStatusCode ? expectedStatusCode : 200, + body: JSON.stringify({ update_info: updateInfo }) + }); + } + } + + public static onReportStatus(callback: acquisitionSdk.Callback, expectedStatusCode: number): void { + callback(/*error*/ null, /*response*/ { statusCode: expectedStatusCode ? expectedStatusCode : 200 }); + } +} diff --git a/src/acquisition-sdk/__tests__/acquisition-sdk.test.ts b/src/acquisition-sdk/__tests__/acquisition-sdk.test.ts new file mode 100644 index 0000000..70899c1 --- /dev/null +++ b/src/acquisition-sdk/__tests__/acquisition-sdk.test.ts @@ -0,0 +1,301 @@ +// Ported from https://github.com/microsoft/code-push/blob/master/src/test/acquisition-sdk.ts (archived, MIT licensed) +// Not currently wired into any test runner — this repo has no unit test infra yet. + +import * as assert from "assert"; + +import * as acquisitionSdk from "../acquisition-sdk"; +import * as acquisitionRestMock from "./acquisition-rest-mock"; +import * as types from "../types"; +import { CodePushPackageError } from "../code-push-error" +import { updateMockUrl } from "./acquisition-rest-mock"; + +const mockApi = acquisitionRestMock; +var latestPackage: types.UpdateCheckResponse = clone(mockApi.latestPackage); + +var configuration: acquisitionSdk.Configuration = { + appVersion: "1.5.0", + clientUniqueId: "My iPhone", + deploymentKey: mockApi.validDeploymentKey, + serverUrl: mockApi.serverUrl, +} + +var templateCurrentPackage: acquisitionSdk.Package = { + deploymentKey: mockApi.validDeploymentKey, + description: "Standard description", + label: "v1", + appVersion: latestPackage.target_binary_range, + packageHash: "hash001", + isMandatory: false, + packageSize: 100 +}; + +var scriptUpdateResult: acquisitionSdk.RemotePackage = { + deploymentKey: mockApi.validDeploymentKey, + description: latestPackage.description, + downloadUrl: latestPackage.download_url, + label: latestPackage.label, + appVersion: latestPackage.target_binary_range, + isMandatory: latestPackage.is_mandatory, + packageHash: latestPackage.package_hash, + packageSize: latestPackage.package_size +}; + +var nativeUpdateResult: acquisitionSdk.NativeUpdateNotification = { + updateAppVersion: true, + appVersion: latestPackage.target_binary_range +}; + +describe("Acquisition SDK", () => { + beforeEach(() => { + mockApi.latestPackage = clone(latestPackage); + mockApi.serverUrl = "http://myurl.com"; + updateMockUrl(); + }); + + it("Package with lower label and different package hash gives update", (done: Mocha.Done) => { + var acquisition = new acquisitionSdk.AcquisitionManager(new mockApi.HttpRequester(), configuration); + acquisition.queryUpdateWithCurrentPackage(templateCurrentPackage, (error: Error, returnPackage: acquisitionSdk.RemotePackage | acquisitionSdk.NativeUpdateNotification) => { + assert.equal(null, error); + assert.deepEqual(scriptUpdateResult, returnPackage); + done(); + }); + }); + + it("Package with equal package hash gives no update", (done: Mocha.Done) => { + var equalVersionPackage: acquisitionSdk.Package = clone(templateCurrentPackage); + equalVersionPackage.packageHash = latestPackage.package_hash; + + var acquisition = new acquisitionSdk.AcquisitionManager(new mockApi.HttpRequester(), configuration); + acquisition.queryUpdateWithCurrentPackage(equalVersionPackage, (error: Error, returnPackage: acquisitionSdk.RemotePackage | acquisitionSdk.NativeUpdateNotification) => { + assert.equal(null, error); + assert.equal(null, returnPackage); + done(); + }); + }); + + it("Package with higher different hash and higher label version gives update", (done: Mocha.Done) => { + var higherVersionPackage: acquisitionSdk.Package = clone(templateCurrentPackage); + higherVersionPackage.packageHash = "hash990"; + + var acquisition = new acquisitionSdk.AcquisitionManager(new mockApi.HttpRequester(), configuration); + acquisition.queryUpdateWithCurrentPackage(higherVersionPackage, (error: Error, returnPackage: acquisitionSdk.RemotePackage | acquisitionSdk.NativeUpdateNotification) => { + assert.equal(null, error); + assert.deepEqual(scriptUpdateResult, returnPackage); + done(); + }); + }); + + it("Package with lower native version gives update notification", (done: Mocha.Done) => { + var lowerAppVersionPackage: acquisitionSdk.Package = clone(templateCurrentPackage); + lowerAppVersionPackage.appVersion = "0.0.1"; + + var acquisition = new acquisitionSdk.AcquisitionManager(new mockApi.HttpRequester(), configuration); + acquisition.queryUpdateWithCurrentPackage(lowerAppVersionPackage, (error: Error, returnPackage: acquisitionSdk.RemotePackage | acquisitionSdk.NativeUpdateNotification) => { + assert.equal(null, error); + assert.deepEqual(nativeUpdateResult, returnPackage); + done(); + }); + }); + + it("Package with higher native version gives no update", (done: Mocha.Done) => { + var higherAppVersionPackage: acquisitionSdk.Package = clone(templateCurrentPackage); + higherAppVersionPackage.appVersion = "9.9.0"; + + var acquisition = new acquisitionSdk.AcquisitionManager(new mockApi.HttpRequester(), configuration); + acquisition.queryUpdateWithCurrentPackage(higherAppVersionPackage, (error: Error, returnPackage: acquisitionSdk.RemotePackage | acquisitionSdk.NativeUpdateNotification) => { + assert.equal(null, error); + assert.deepEqual(null, returnPackage); + done(); + }); + }); + + it("An empty response gives no update", (done: Mocha.Done) => { + var lowerAppVersionPackage: acquisitionSdk.Package = clone(templateCurrentPackage); + lowerAppVersionPackage.appVersion = "0.0.1"; + + var emptyResponse: acquisitionSdk.Http.Response = { + statusCode: 200, + body: JSON.stringify({}) + }; + + var acquisition = new acquisitionSdk.AcquisitionManager(new mockApi.CustomResponseHttpRequester(emptyResponse), configuration); + acquisition.queryUpdateWithCurrentPackage(lowerAppVersionPackage, (error: Error, returnPackage: acquisitionSdk.RemotePackage | acquisitionSdk.NativeUpdateNotification) => { + assert.equal(null, error); + done(); + }); + }); + + it("An unexpected (but valid) JSON response gives no update", (done: Mocha.Done) => { + var lowerAppVersionPackage: acquisitionSdk.Package = clone(templateCurrentPackage); + lowerAppVersionPackage.appVersion = "0.0.1"; + + var unexpectedResponse: acquisitionSdk.Http.Response = { + statusCode: 200, + body: JSON.stringify({ unexpected: "response" }) + }; + + var acquisition = new acquisitionSdk.AcquisitionManager(new mockApi.CustomResponseHttpRequester(unexpectedResponse), configuration); + acquisition.queryUpdateWithCurrentPackage(lowerAppVersionPackage, (error: Error, returnPackage: acquisitionSdk.RemotePackage | acquisitionSdk.NativeUpdateNotification) => { + assert.equal(null, error); + done(); + }); + }); + + it("Package for companion app ignores high native version and gives update", (done: Mocha.Done) => { + var higherAppVersionCompanionPackage: acquisitionSdk.Package = clone(templateCurrentPackage); + higherAppVersionCompanionPackage.appVersion = "9.9.0"; + + var companionAppConfiguration = clone(configuration); + configuration.ignoreAppVersion = true; + + var acquisition = new acquisitionSdk.AcquisitionManager(new mockApi.HttpRequester(), configuration); + acquisition.queryUpdateWithCurrentPackage(higherAppVersionCompanionPackage, (error: Error, returnPackage: acquisitionSdk.RemotePackage | acquisitionSdk.NativeUpdateNotification) => { + assert.equal(null, error); + assert.deepEqual(scriptUpdateResult, returnPackage); + done(); + }); + }); + + it("If latest package is mandatory, returned package is mandatory", (done: Mocha.Done) => { + mockApi.latestPackage.is_mandatory = true; + var acquisition = new acquisitionSdk.AcquisitionManager(new mockApi.HttpRequester(), configuration); + acquisition.queryUpdateWithCurrentPackage(templateCurrentPackage, (error: Error, returnPackage: acquisitionSdk.RemotePackage) => { + assert.equal(null, error); + assert.equal(true, returnPackage.isMandatory); + done(); + }); + }); + + it("If invalid arguments are provided, an error is raised", (done: Mocha.Done) => { + var invalidPackage: acquisitionSdk.Package = clone(templateCurrentPackage); + invalidPackage.appVersion = null; + var expectedError = new CodePushPackageError("Calling common acquisition SDK with incorrect package") + + var acquisition = new acquisitionSdk.AcquisitionManager(new mockApi.HttpRequester(), configuration); + try { + acquisition.queryUpdateWithCurrentPackage(invalidPackage, (error: Error, returnPackage: acquisitionSdk.RemotePackage | acquisitionSdk.NativeUpdateNotification) => { + assert.fail("Should throw an error if the native implementation gave an incorrect package"); + done(); + }); + } catch (error) { + assert.deepEqual(error, expectedError); + assert.equal(error instanceof CodePushPackageError, true) + done(); + } + }); + + it("If an invalid JSON response is returned by the server, an error is raised", (done: Mocha.Done) => { + var lowerAppVersionPackage: acquisitionSdk.Package = clone(templateCurrentPackage); + lowerAppVersionPackage.appVersion = "0.0.1"; + + var invalidJsonResponse: acquisitionSdk.Http.Response = { + statusCode: 200, + body: "invalid {{ json" + }; + + var acquisition = new acquisitionSdk.AcquisitionManager(new mockApi.CustomResponseHttpRequester(invalidJsonResponse), configuration); + acquisition.queryUpdateWithCurrentPackage(lowerAppVersionPackage, (error: Error, returnPackage: acquisitionSdk.RemotePackage | acquisitionSdk.NativeUpdateNotification) => { + assert.notEqual(null, error); + done(); + }); + }); + + it("If deploymentKey is not valid...", (done: Mocha.Done) => { + // TODO: behavior is not defined + done(); + }); + + it("reportStatusDeploy(...) signals completion", (done: Mocha.Done): void => { + var acquisition = new acquisitionSdk.AcquisitionManager(new mockApi.HttpRequester(), configuration); + + acquisition.reportStatusDeploy(templateCurrentPackage, acquisitionSdk.AcquisitionStatus.DeploymentFailed, "1.5.0", mockApi.validDeploymentKey, ((error: Error, parameter: void): void => { + if (error) { + throw error; + } + + assert.equal(parameter, /*expected*/ null); + + done(); + })); + }); + + it("reportStatusDownload(...) signals completion", (done: Mocha.Done): void => { + var acquisition = new acquisitionSdk.AcquisitionManager(new mockApi.HttpRequester(), configuration); + + acquisition.reportStatusDownload(templateCurrentPackage, ((error: Error, parameter: void): void => { + if (error) { + throw error; + } + + assert.equal(parameter, /*expected*/ null); + + done(); + })); + }); + + it("disables api calls on unsuccessful response", (done: Mocha.Done): void => { + var invalidJsonResponse: acquisitionSdk.Http.Response = { + statusCode: 404, + body: "Not found" + }; + + mockApi.serverUrl = "https://codepush.appcenter.ms"; + updateMockUrl(); + configuration = { ...configuration, serverUrl: "https://codepush.appcenter.ms" }; + + var acquisition = new acquisitionSdk.AcquisitionManager(new mockApi.CustomResponseHttpRequester(invalidJsonResponse), configuration); + acquisition.queryUpdateWithCurrentPackage(templateCurrentPackage, (error: Error, returnPackage: acquisitionSdk.RemotePackage | acquisitionSdk.NativeUpdateNotification) => { + assert.strictEqual((acquisitionSdk.AcquisitionManager as any)._apiCallsDisabled, true); + (acquisitionSdk.AcquisitionManager as any)._apiCallsDisabled = false; + }); + + acquisition.queryUpdateWithCurrentPackage(templateCurrentPackage, (error: Error, returnPackage: acquisitionSdk.RemotePackage | acquisitionSdk.NativeUpdateNotification) => { + assert.strictEqual(returnPackage, null); + acquisition = new acquisitionSdk.AcquisitionManager(new mockApi.HttpRequester(404), configuration); + (acquisitionSdk.AcquisitionManager as any)._apiCallsDisabled = false; + }); + + acquisition.reportStatusDeploy(templateCurrentPackage, acquisitionSdk.AcquisitionStatus.DeploymentSucceeded, "1.5.0", mockApi.validDeploymentKey, ((error: Error, parameter: void): void => { + assert.strictEqual((acquisitionSdk.AcquisitionManager as any)._apiCallsDisabled, true); + acquisition = new acquisitionSdk.AcquisitionManager(new mockApi.HttpRequester(404), configuration); + (acquisitionSdk.AcquisitionManager as any)._apiCallsDisabled = false; + })); + + acquisition.reportStatusDownload(templateCurrentPackage, ((error: Error, parameter: void): void => { + assert.strictEqual((acquisitionSdk.AcquisitionManager as any)._apiCallsDisabled, true); + acquisition = acquisition = new acquisitionSdk.AcquisitionManager(new mockApi.CustomResponseHttpRequester(invalidJsonResponse), configuration); + (acquisitionSdk.AcquisitionManager as any)._apiCallsDisabled = false; + })); + + done(); + }) + + it("doesn't disable api calls on successful response", (done: Mocha.Done): void => { + var acquisition = new acquisitionSdk.AcquisitionManager(new mockApi.HttpRequester(), configuration); + mockApi.serverUrl = "https://codepush.appcenter.ms"; + updateMockUrl(); + + acquisition.queryUpdateWithCurrentPackage(templateCurrentPackage, (error: Error, returnPackage: acquisitionSdk.RemotePackage | acquisitionSdk.NativeUpdateNotification) => { + assert.strictEqual((acquisitionSdk.AcquisitionManager as any)._apiCallsDisabled, false); + }); + + acquisition.queryUpdateWithCurrentPackage(templateCurrentPackage, (error: Error, returnPackage: acquisitionSdk.RemotePackage | acquisitionSdk.NativeUpdateNotification) => { + assert.notStrictEqual(returnPackage, null); + }); + + acquisition.reportStatusDeploy(templateCurrentPackage, acquisitionSdk.AcquisitionStatus.DeploymentSucceeded, "1.5.0", mockApi.validDeploymentKey, ((error: Error, parameter: void): void => { + assert.strictEqual((acquisitionSdk.AcquisitionManager as any)._apiCallsDisabled, false); + })); + + acquisition.reportStatusDownload(templateCurrentPackage, ((error: Error, parameter: void): void => { + assert.strictEqual((acquisitionSdk.AcquisitionManager as any)._apiCallsDisabled, false); + })); + + done(); + }) + +}); + +function clone(initialObject: T): T { + return JSON.parse(JSON.stringify(initialObject)); +} diff --git a/src/acquisition-sdk/acquisition-sdk.ts b/src/acquisition-sdk/acquisition-sdk.ts new file mode 100644 index 0000000..4d5c006 --- /dev/null +++ b/src/acquisition-sdk/acquisition-sdk.ts @@ -0,0 +1,292 @@ +// Vendored from https://github.com/microsoft/code-push/blob/master/src/script/acquisition-sdk.ts (archived, MIT licensed) + +import { UpdateCheckResponse, UpdateCheckRequest, DeploymentStatusReport, DownloadReport } from "./types"; +import { CodePushHttpError, CodePushDeployStatusError, CodePushPackageError } from "./code-push-error" + +export namespace Http { + export const enum Verb { + GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS, CONNECT, PATCH + } + + export interface Response { + statusCode: number; + body?: string; + } + + export interface Requester { + request(verb: Verb, url: string, callback: Callback): void; + request(verb: Verb, url: string, requestBody: string, callback: Callback): void; + } +} + +// All fields are non-nullable, except when retrieving the currently running package on the first run of the app, +// in which case only the appVersion is compulsory +export interface Package { + deploymentKey: string; + description: string; + label: string; + appVersion: string; + isMandatory: boolean; + packageHash: string; + packageSize: number; +} + +export interface RemotePackage extends Package { + downloadUrl: string; +} + +export interface NativeUpdateNotification { + updateAppVersion: boolean; // Always true + appVersion: string; +} + +export interface LocalPackage extends Package { + localPath: string; +} + +export interface Callback { (error: Error, parameter: T): void; } + +export interface Configuration { + appVersion: string; + clientUniqueId: string; + deploymentKey: string; + serverUrl: string; + ignoreAppVersion?: boolean +} + +export class AcquisitionStatus { + public static DeploymentSucceeded = "DeploymentSucceeded"; + public static DeploymentFailed = "DeploymentFailed"; +} + +export class AcquisitionManager { + private readonly BASE_URL_PART = "appcenter.ms"; + private _appVersion: string; + private _clientUniqueId: string; + private _deploymentKey: string; + private _httpRequester: Http.Requester; + private _ignoreAppVersion: boolean; + private _serverUrl: string; + private _publicPrefixUrl: string = "v0.1/public/codepush/"; + private _statusCode: number; + private static _apiCallsDisabled: boolean = false; + constructor(httpRequester: Http.Requester, configuration: Configuration) { + this._httpRequester = httpRequester; + + this._serverUrl = configuration.serverUrl; + if (this._serverUrl.slice(-1) !== "/") { + this._serverUrl += "/"; + } + + this._appVersion = configuration.appVersion; + this._clientUniqueId = configuration.clientUniqueId; + this._deploymentKey = configuration.deploymentKey; + this._ignoreAppVersion = configuration.ignoreAppVersion; + } + + private isRecoverable = (statusCode: number): boolean => statusCode >= 500 || statusCode === 408 || statusCode === 429; + + private handleRequestFailure() { + if (this._serverUrl.includes(this.BASE_URL_PART) && !this.isRecoverable(this._statusCode)) { + AcquisitionManager._apiCallsDisabled = true; + } + } + + public queryUpdateWithCurrentPackage(currentPackage: Package, callback?: Callback): void { + if (AcquisitionManager._apiCallsDisabled) { + console.log(`[CodePush] Api calls are disabled, skipping API call`); + callback(/*error=*/ null, /*remotePackage=*/ null); + return; + } + + if (!currentPackage || !currentPackage.appVersion) { + throw new CodePushPackageError("Calling common acquisition SDK with incorrect package"); // Unexpected; indicates error in our implementation + } + + var updateRequest: UpdateCheckRequest = { + deployment_key: this._deploymentKey, + app_version: currentPackage.appVersion, + package_hash: currentPackage.packageHash, + is_companion: this._ignoreAppVersion, + label: currentPackage.label, + client_unique_id: this._clientUniqueId + }; + + var requestUrl: string = this._serverUrl + this._publicPrefixUrl + "update_check?" + queryStringify(updateRequest); + + this._httpRequester.request(Http.Verb.GET, requestUrl, (error: Error, response: Http.Response) => { + if (error) { + callback(error, /*remotePackage=*/ null); + return; + } + + if (response.statusCode < 200 || response.statusCode >= 300) { + let errorMessage: any; + this._statusCode = response.statusCode; + this.handleRequestFailure(); + if (response.statusCode === 0) { + errorMessage = `Couldn't send request to ${requestUrl}, xhr.statusCode = 0 was returned. One of the possible reasons for that might be connection problems. Please, check your internet connection.`; + } else { + errorMessage = `${response.statusCode}: ${response.body}`; + } + callback(new CodePushHttpError(errorMessage), /*remotePackage=*/ null); + return; + } + try { + var responseObject = JSON.parse(response.body); + var updateInfo: UpdateCheckResponse = responseObject.update_info; + } catch (error) { + callback(error, /*remotePackage=*/ null); + return; + } + + if (!updateInfo) { + callback(error, /*remotePackage=*/ null); + return; + } else if (updateInfo.update_app_version) { + callback(/*error=*/ null, { updateAppVersion: true, appVersion: updateInfo.target_binary_range }); + return; + } else if (!updateInfo.is_available) { + callback(/*error=*/ null, /*remotePackage=*/ null); + return; + } + + var remotePackage: RemotePackage = { + deploymentKey: this._deploymentKey, + description: updateInfo.description, + label: updateInfo.label, + appVersion: updateInfo.target_binary_range, + isMandatory: updateInfo.is_mandatory, + packageHash: updateInfo.package_hash, + packageSize: updateInfo.package_size, + downloadUrl: updateInfo.download_url + }; + + callback(/*error=*/ null, remotePackage); + }); + } + + public reportStatusDeploy(deployedPackage?: Package, status?: string, previousLabelOrAppVersion?: string, previousDeploymentKey?: string, callback?: Callback): void { + if (AcquisitionManager._apiCallsDisabled) { + console.log(`[CodePush] Api calls are disabled, skipping API call`); + callback(/*error*/ null, /*not used*/ null); + return; + } + + var url: string = this._serverUrl + this._publicPrefixUrl + "report_status/deploy"; + var body: DeploymentStatusReport = { + app_version: this._appVersion, + deployment_key: this._deploymentKey + }; + + if (this._clientUniqueId) { + body.client_unique_id = this._clientUniqueId; + } + + if (deployedPackage) { + body.label = deployedPackage.label; + body.app_version = deployedPackage.appVersion; + + switch (status) { + case AcquisitionStatus.DeploymentSucceeded: + case AcquisitionStatus.DeploymentFailed: + body.status = status; + break; + + default: + if (callback) { + if (!status) { + callback(new CodePushDeployStatusError("Missing status argument."), /*not used*/ null); + } else { + callback(new CodePushDeployStatusError("Unrecognized status \"" + status + "\"."), /*not used*/ null); + } + } + return; + } + } + + if (previousLabelOrAppVersion) { + body.previous_label_or_app_version = previousLabelOrAppVersion; + } + + if (previousDeploymentKey) { + body.previous_deployment_key = previousDeploymentKey; + } + + callback = typeof arguments[arguments.length - 1] === "function" && arguments[arguments.length - 1]; + + this._httpRequester.request(Http.Verb.POST, url, JSON.stringify(body), (error: Error, response: Http.Response): void => { + if (callback) { + if (error) { + callback(error, /*not used*/ null); + return; + } + + if (response.statusCode < 200 || response.statusCode >= 300) { + this._statusCode = response.statusCode; + this.handleRequestFailure(); + callback(new CodePushHttpError(response.statusCode + ": " + response.body), /*not used*/ null); + return; + } + + callback(/*error*/ null, /*not used*/ null); + } + }); + } + + public reportStatusDownload(downloadedPackage: Package, callback?: Callback): void { + if (AcquisitionManager._apiCallsDisabled) { + console.log(`[CodePush] Api calls are disabled, skipping API call`); + callback(/*error*/ null, /*not used*/ null); + return; + } + + var url: string = this._serverUrl + this._publicPrefixUrl + "report_status/download"; + var body: DownloadReport = { + client_unique_id: this._clientUniqueId, + deployment_key: this._deploymentKey, + label: downloadedPackage.label + }; + + this._httpRequester.request(Http.Verb.POST, url, JSON.stringify(body), (error: Error, response: Http.Response): void => { + if (callback) { + if (error) { + callback(error, /*not used*/ null); + return; + } + + if (response.statusCode < 200 || response.statusCode >= 300) { + this._statusCode = response.statusCode; + this.handleRequestFailure(); + callback(new CodePushHttpError(response.statusCode + ": " + response.body), /*not used*/ null); + return; + } + + callback(/*error*/ null, /*not used*/ null); + } + }); + } +} + +function queryStringify(object: Object): string { + var queryString = ""; + var isFirst: boolean = true; + + for (var property in object) { + if (object.hasOwnProperty(property)) { + var value: string = (object)[property]; + if (value !== null && typeof value !== "undefined") { + if (!isFirst) { + queryString += "&"; + } + + queryString += encodeURIComponent(property) + "="; + queryString += encodeURIComponent(value); + } + + isFirst = false; + } + } + + return queryString; +} \ No newline at end of file diff --git a/src/acquisition-sdk/code-push-error.ts b/src/acquisition-sdk/code-push-error.ts new file mode 100644 index 0000000..ab7f192 --- /dev/null +++ b/src/acquisition-sdk/code-push-error.ts @@ -0,0 +1,36 @@ +// Vendored from https://github.com/microsoft/code-push/blob/master/src/script/code-push-error.ts (archived, MIT licensed) + +export class CodePushError extends Error { + constructor(message: string) { + super(message); + Object.setPrototypeOf(this, CodePushError.prototype); + } +} + +export class CodePushHttpError extends CodePushError { + constructor(message: string) { + super(message); + Object.setPrototypeOf(this, CodePushHttpError.prototype); + } +} + +export class CodePushDeployStatusError extends CodePushError { + constructor(message: string) { + super(message); + Object.setPrototypeOf(this, CodePushDeployStatusError.prototype); + } +} + +export class CodePushPackageError extends CodePushError { + constructor(message: string) { + super(message); + Object.setPrototypeOf(this, CodePushPackageError.prototype); + } +} + +export class CodePushUnauthorizedError extends CodePushError { + constructor(message: string) { + super(message); + Object.setPrototypeOf(this, CodePushUnauthorizedError.prototype); + } +} diff --git a/src/acquisition-sdk/types.ts b/src/acquisition-sdk/types.ts new file mode 100644 index 0000000..1ba2a79 --- /dev/null +++ b/src/acquisition-sdk/types.ts @@ -0,0 +1,46 @@ +// Trimmed from https://github.com/microsoft/code-push/blob/master/src/script/types.ts (archived, MIT licensed) +// Upstream's types.ts also contains management/CLI-API types (Account, App, Deployment, ...) +// that are irrelevant to the on-device acquisition client + +/*in*/ +export interface DeploymentStatusReport { + app_version: string; + client_unique_id?: string; + deployment_key: string; + previous_deployment_key?: string; + previous_label_or_app_version?: string; + label?: string; + status?: string; +} + +/*in*/ +export interface DownloadReport { + client_unique_id: string; + deployment_key: string; + label: string; +} + +/*out*/ +export interface UpdateCheckResponse { + download_url?: string; + description?: string; + is_available: boolean; + is_disabled?: boolean; + target_binary_range: string; + /*generated*/ label?: string; + /*generated*/ package_hash?: string; + package_size?: number; + should_run_binary_version?: boolean; + update_app_version?: boolean; + is_mandatory?: boolean; +} + +/*in*/ +export interface UpdateCheckRequest { + app_version: string; + client_unique_id?: string; + deployment_key: string; + is_companion?: boolean; + label?: string; + package_hash?: string; +} diff --git a/tsconfig.build.json b/tsconfig.build.json new file mode 100644 index 0000000..c5458d1 --- /dev/null +++ b/tsconfig.build.json @@ -0,0 +1,24 @@ +// Compiles src/ -> lib/: the actual runtime code this package ships to consumers. Run via `npm run build:ts`. + +// This is a separate config from tsconfig.json because that one's rootDir/outDir (test -> bin) +// and target audience (the mocha test suite) are unrelated to what we publish to consumers. +// +// TODO: once the rest of this repo's runtime JS (CodePush.js, etc.) is migrated to TypeScript, +// this and tsconfig.json should be unified into a single project. +{ + "compilerOptions": { + "target": "ES5", + "module": "commonjs", + "lib": ["es6"], + "declaration": true, + "noImplicitAny": true, + "noEmitOnError": true, + "moduleResolution": "node", + "sourceMap": true, + "rootDir": "src", + "outDir": "lib", + "removeComments": false + }, + "include": ["src/**/*.ts"], + "exclude": ["src/**/__tests__/**"] +} diff --git a/tsconfig.json b/tsconfig.json index cfb67da..4fad394 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,3 +1,9 @@ +// Compiles test/ -> bin/ for the mocha-based integration test suite (see package.json's +// build:tests/test:* scripts). Deliberately excludes src/ - that's a separate compilation unit +// with its own config, see tsconfig.build.json for why. +// +// TODO: once the rest of this repo's runtime JS (CodePush.js, etc.) is migrated to TypeScript, +// this and tsconfig.build.json should be unified into a single project. { "compilerOptions": { "target": "ES5", @@ -12,6 +18,7 @@ "removeComments": true }, "exclude": [ - "Examples" + "Examples", + "src" ] }