diff --git a/.azurepipelines/build-code-push-1es.yml b/.azurepipelines/build-code-push-1es.yml new file mode 100644 index 00000000..b5051b82 --- /dev/null +++ b/.azurepipelines/build-code-push-1es.yml @@ -0,0 +1,102 @@ +trigger: +- master + +pr: +- master + +resources: + repositories: + - repository: 1ESPipelineTemplates + type: git + name: 1ESPipelineTemplates/1ESPipelineTemplates + ref: refs/tags/release +name: $(Build.SourceBranchName)_$(date:yyyyMMdd)$(rev:.r) + +extends: + ${{ if eq(variables['Build.SourceBranch'], 'refs/heads/master') }}: + template: v1/1ES.Official.PipelineTemplate.yml@1ESPipelineTemplates + ${{ else }}: + template: v1/1ES.Unofficial.PipelineTemplate.yml@1ESPipelineTemplates + parameters: + pool: + name: 1ES-PT-CBL-Mariner-2.0-Gen2 + os: linux + customBuildTags: + - ES365AIMigrationTooling-BulkMigrated + sdl: + sourceAnalysisPool: 1ES-PT-Windows-2022 + stages: + - stage: Stage + jobs: + - job: HostJob + templateContext: + outputs: + - output: pipelineArtifact + displayName: "Publish Artifact: artifacts" + path: '$(Build.ArtifactStagingDirectory)/npm' + artifactName: npm + + steps: + - task: NodeTool@0 + inputs: + versionSpec: '14.x' + displayName: 'Install Node.js' + + - script: | + npm pack + npm install -g code-push*.tgz + displayName: 'Package code-push' + workingDirectory: $(Build.SourcesDirectory) + + - task: DeleteFiles@1 + inputs: + contents: node_modules + displayName: 'Delete node_modules' + + - task: ArchiveFiles@2 + inputs: + rootFolderOrFile: '$(Build.SourcesDirectory)' + includeRootFolder: false + archiveType: 'tar' + archiveFile: '$(Build.ArtifactStagingDirectory)/npm/$(Build.BuildId).tgz' + replaceExistingArchive: true + verbose: true + displayName: 'Prepare npm artifact' + + - stage: APIScan + dependsOn: Stage + pool: + name: 1ES-PT-Windows-2022 + os: windows + variables: + "agent.source.skip": true + jobs: + - job: APIScan + steps: + - task: DownloadPipelineArtifact@2 + displayName: Download Pipeline Artifacts for APIScan + inputs: + artifactName: npm + targetPath: '$(Agent.BuildDirectory)/npm' + - task: ExtractFiles@1 + inputs: + archiveFilePatterns: '$(Agent.BuildDirectory)/npm/*.tgz' + destinationFolder: '$(Agent.BuildDirectory)/npm_extracted' + - task: AzureKeyVault@2 + inputs: + azureSubscription: 'AC - Dev Infra & Build Pool' + KeyVaultName: 'mobile-center-sdk' + SecretsFilter: 'appcenter-sdk-managed-identity-clientid' + RunAsPreJob: false + - task: APIScan@2 + displayName: 'Run APIScan' + inputs: + softwareFolder: '$(Agent.BuildDirectory)\npm_extracted' + softwareName: 'code-push' + softwareVersionNum: '$(Build.BuildId)' + isLargeApp: false + toolVersion: 'Latest' + verbosityLevel: verbose + condition: and(succeeded(), ne(variables['DisableAPIScan'], 'true')) + env: + AzureServicesAuthConnectionString: 'runAs=App;AppId=$(appcenter-sdk-managed-identity-clientid)' \ No newline at end of file diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 00000000..ba411809 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @microsoft/appcenter-fte diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 00000000..fa9037ba --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,5 @@ +Thanks so much for filing an issue or feature request! We will address it as soon as we can. Please follow these guidelines: + +1. This repository is for the CodePush CLI and management SDK. For issues relating to the CodePush client SDK's, please see: + * react-native-code-push: https://github.com/Microsoft/react-native-code-push +2. In your description, please include the version of `code-push-cli` or `code-push` that you are using. diff --git a/.github/policies/resourceManagement.yml b/.github/policies/resourceManagement.yml new file mode 100644 index 00000000..9fc1c5e2 --- /dev/null +++ b/.github/policies/resourceManagement.yml @@ -0,0 +1,64 @@ +id: +name: GitOps.PullRequestIssueManagement +description: GitOps.PullRequestIssueManagement primitive +owner: +resource: repository +disabled: false +where: +configuration: + resourceManagementConfiguration: + scheduledSearches: + - description: + frequencies: + - hourly: + hour: 4 + filters: + - isOpen + - isNotLabeledWith: + label: bug + - isNotLabeledWith: + label: security + - isNotLabeledWith: + label: Stale + - isNotLabeledWith: + label: do not close + - noActivitySince: + days: 60 + - isIssue + - isNotAssigned + actions: + - addLabel: + label: Stale + - addReply: + reply: This issue has been automatically marked as stale because it has not had any activity for 60 days. It will be closed if no further activity occurs within 15 days of this comment. + - description: + frequencies: + - hourly: + hour: 6 + filters: + - isOpen + - isIssue + - hasLabel: + label: Stale + - isNotLabeledWith: + label: bug + - isNotLabeledWith: + label: do not close + - isNotAssigned + - noActivitySince: + days: 15 + actions: + - addReply: + reply: This issue will now be closed because it hasn't had any activity for 15 days after stale. Please feel free to open a new issue if you still have a question/issue or suggestion. + - closeIssue + eventResponderTasks: + - if: + - payloadType: Issue_Comment + - hasLabel: + label: Stale + then: + - removeLabel: + label: Stale + description: +onFailure: +onSuccess: diff --git a/.github/scripts/check-for-declaration.ts b/.github/scripts/check-for-declaration.ts new file mode 100755 index 00000000..46c34a93 --- /dev/null +++ b/.github/scripts/check-for-declaration.ts @@ -0,0 +1,45 @@ +import fs from "fs"; +import path from "path"; + +type ResultType = { + js : Record + ts : Record +} + +const result: ResultType = {js:{} , ts:{}} + +const readThroughDirectory = (directory: string): void => { + const __directoryPath = directory + const files = fs.readdirSync(__directoryPath); + files.forEach((file) => { + const filePath = path.join(__directoryPath, file); + const stats = fs.statSync(filePath); + if (stats.isDirectory()) { + readThroughDirectory(filePath); + return + } + + if(filePath.endsWith('.js')){ + const name = filePath.split('.') + name.pop() + result.js[name.join('.')] = true + } + + if(filePath.endsWith('.d.ts')){ + const name = filePath.split('.') + name.pop() + name.pop() + result.ts[name.join('.')] = true + } + + }); + + Object.keys(result.js).forEach(file => { + if(!result.ts[file]){ + throw new Error(`Declaration File Missing for ${file}.js`) + } + }) + +}; + +readThroughDirectory(path.join(process.env.INIT_CWD ?? '', './bin')) \ No newline at end of file diff --git a/.github/workflows/code-push-ci.yml b/.github/workflows/code-push-ci.yml new file mode 100644 index 00000000..aef064ce --- /dev/null +++ b/.github/workflows/code-push-ci.yml @@ -0,0 +1,24 @@ +name: Сode-push CI + +on: + pull_request: + branches: + - master + +jobs: + Run-tests: + name: Test code-push-sdk + runs-on: macos-13 + steps: + - name: Checkout + uses: actions/checkout@v1 + - name: Setup NodeJs + uses: actions/setup-node@v1 + with: + node-version: "14.x" + - name: Setup dependencies + run: npm run setup + - name: Build + run: npm run build + - name: Run tests + run: npm run test diff --git a/.gitignore b/.gitignore index 34e3b5db..cbd13c11 100644 --- a/.gitignore +++ b/.gitignore @@ -29,8 +29,6 @@ node_modules # Build specific exclusions bin/ -definitions/external/ -definitions/generated/ # Environment variables *.env @@ -43,3 +41,4 @@ definitions/generated/ # JSON Storage persisted file JsonStorage.json +.idea/ diff --git a/sdk/.npmignore b/.npmignore similarity index 72% rename from sdk/.npmignore rename to .npmignore index c4a04633..9a646a1e 100644 --- a/sdk/.npmignore +++ b/.npmignore @@ -1,4 +1,4 @@ .npmignore .gitignore node_modules/* -definitions/* +test/* diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..6e6f1f86 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,23 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Tests", + "type": "node", + "request": "launch", + "preLaunchTask": "Build", + "runtimeExecutable": "npm", + "runtimeArgs": [ + "run", + "test:debugger" + ], + "port": 9229, + "stopOnEntry": false, + "sourceMaps": true, + "console": "internalConsole", + "internalConsoleOptions": "openOnSessionStart", + "autoAttachChildProcesses": true, + "timeout": 100000 + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..9bbf37f4 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,4 @@ +// Place your settings in this file to overwrite default and user settings. +{ + "typescript.tsdk": "node_modules/typescript/lib" +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 00000000..242cc3e3 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,21 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "type": "shell", + "label": "Build", + "command": "npm", + "args": [ + "run", + "build" + ], + "presentation": { + "echo": false, + "focus": false + }, + "problemMatcher": [ + "$tsc" + ] + } + ] +} diff --git a/Gulpfile.js b/Gulpfile.js deleted file mode 100644 index 3e7558e3..00000000 --- a/Gulpfile.js +++ /dev/null @@ -1,6 +0,0 @@ -'use strict'; - -var gulp = require("gulp"); -var plugins = require("gulp-load-plugins")(); - -require("require-all")(__dirname + "/gulp"); diff --git a/README.md b/README.md index d3310cf4..8c839a73 100644 --- a/README.md +++ b/README.md @@ -1,31 +1,155 @@ -# CodePush +# Archiving this repository -[CodePush](https://microsoft.github.io/code-push) is a cloud service that enables Cordova and React Native developers to deploy mobile app updates directly to their users' devices. It works by acting as a central repository that developers can publish updates to (JS, HTML, CSS and images), and that apps can query for updates from (using provided client SDKs for [Cordova](http://github.com/cordova-plugin-code-push) and [React Native](http://github.com/react-native-code-push)). This allows you to have a more deterministic and direct engagement model with your userbase, when addressing bugs and/or adding small features that don't require you to re-build a binary and re-distribute it through the respective app stores. +Visual Studio App Center was retired on March 31, 2025, except for its Analytics and Diagnostics features. You can learn more about the retirement and the Analytics and Diagnostics extension [here](https://aka.ms/appcenter/retire). CodePush, along with other App Center features, was also retired on March 31, 2025. Consequently, we are archiving this repository. -This repo includes the [management CLI](http://microsoft.github.io/code-push/docs/cli.html) as well as some shared code that is used by both the Cordova and React Native client SDKs. To get started using CodePush, refer to our [documentation](http://microsoft.github.io/code-push/index.html#getting_started), otherwise, read the following steps if you'd like to build/contribute to the project from source. +--- + +[![appcenterbanner](https://user-images.githubusercontent.com/31293287/32969262-3cc5d48a-cb99-11e7-91bf-fa57c67a371c.png)](http://microsoft.github.io/code-push/) + +#### [Sign up With App Center](https://appcenter.ms/signup?utm_source=CodePush&utm_medium=Azure) to use CodePush + +## CodePush SDK + +CodePush SDK enables seamless in-app updates and serves as a core component of the [CodePush React Native SDK](https://github.com/Microsoft/react-native-code-push). + +To start integrating CodePush into your project, visit our [documentation](https://docs.microsoft.com/en-us/appcenter/distribution/codepush/). If you're interested in contributing or building the SDK from source, follow the steps below. + +## Visual Studio App Center CodePush Standalone Version + +For teams or organizations looking to self-host CodePush, we now offer the [CodePush Standalone Version](https://github.com/microsoft/code-push-server) which is compatible with this SDK. It allows you to set up and manage CodePush as a self-hosted service, giving you more control over your infrastructure and data. Visit the repository for installation instructions, usage guides, and more. ## Dev Setup * Install [Node.js](https://nodejs.org/) * Install [Git](http://www.git-scm.com/) -* Install Gulp: `npm install -g gulp` * Clone the Repository: `git clone https://github.com/Microsoft/code-push.git` ### Building -* Run `npm install` from the root of the repository. -* Run `gulp install` to install the NPM dependencies of each module within the project. -* Run `gulp link` to link CLI and SDK for local development. It is advisable to do this step if you are making changes to the SDK and want the CLI to pick those changes. -* Run `gulp build` to build all of the modules. To build just one of the modules (e.g. cli or sdk), run `gulp build-cli` or `gulp build-sdk`. +* Run `npm run setup` to install the NPM dependencies of management SDK. +* Run `npm run build` to build the management SDK for testing. +* Run `npm run build:release` to build the release version of management SDK. ### Running Tests -To run all tests, run `gulp test` script from the root of the project. - -To test just one of the projects (e.g. cli or sdk), run `gulp test-cli` or `gulp test-sdk` +* To run tests, run `npm run test` from the root of the project. +* You can use debug mode for tests with `.vscode/launch.json` file. ### Coding Conventions * Use double quotes for strings * Use four space tabs -* Use `camelCase` for local variables and imported modules, `PascalCase` for types, and `dash-case` for file names \ No newline at end of file +* Use `camelCase` for local variables and imported modules, `PascalCase` for types, and `dash-case` for file names + +This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. + +# CodePush Management SDK (Node.js) + +A JavaScript library for programmatically managing your CodePush account (e.g. creating apps, promoting releases), which allows authoring Node.js-based build and/or deployment scripts, without needing to shell out to the [App Center CLI](https://github.com/microsoft/appcenter-cli). + +## Getting Started + +1. Create a token to authenticate with the CodePush server using the following [App Center CLI](https://github.com/microsoft/appcenter-cli) command: + + ```shell + appcenter tokens create -d "DESCRIPTION_OF_THE_TOKEN" + ``` + + Please copy your `API Token` and keep it secret. You won't be able to see it again. + +2. Install the management SDK by running `npm install code-push --save` + +3. Import it using one of the following statement: (using ES6 syntax as applicable): + * On commonjs environments: + + ```javascript + const CodePush = require("code-push"); + ``` + + * Using ES6 syntax with tsconfig.json: + + ```javascript + import CodePush from "code-push"; + ``` + +4. Create an instance of the `CodePush` class, passing it the `API Token` you created or retrieved in step #1: + + ```javascript + const codePush = new CodePush("YOUR_API_TOKEN"); + ``` + +5. Begin automating the management of your account! For more details on what you can do with this `codePush` object, refer to the API reference section below. + +## API Reference + +The `code-push` module exports a single class (typically referred to as `CodePush`), which represents a proxy to the CodePush account management REST API. This class has a single constructor for authenticating with the CodePush service, and a collection of instance methods that correspond to the commands in the [App Center CLI](https://github.com/microsoft/appcenter-cli), which allow you to programmatically control every aspect of your CodePush account. + +### Constructors + +* __CodePush(accessKey: string)__ - Creates a new instance of the CodePush management SDK, using the specified access key to authenticated with the server. + +### Methods + +*Note: `access key` here refers to an AppCenter API Token.* + +* __addAccessKey(description: string): Promise<AccessKey>__ - Creates a new access key with the specified description (e.g. "VSTS CI"). + +* __addApp(name: string, os: string, platform: string, manuallyProvisionDeployments: boolean = false): Promise<App>__ - Creates a new CodePush app with the specified name, os, and platform. If the default deployments of "Staging" and "Production" are not desired, pass a value of true for the manuallyProvisionDeployments parameter. + +* __addCollaborator(appName: string, email: string): Promise<void>__ - Adds the specified CodePush user as a collaborator to the specified CodePush app. + +* __addDeployment(appName: string, deploymentName: string): Promise<Deployment>__ - Creates a new deployment with the specified name, and associated with the specified app. + +* __clearDeploymentHistory(appName: string, deploymentName: string): Promise<void>__ - Clears the release history associated with the specified app deployment. + +* __getAccessKey(accessKey: string): Promise<AccessKey>__ - Retrieves the metadata about the specific access key. + +* __getAccessKeys(): Promise<AccessKey[]>__ - Retrieves the list of access keys associated with your CodePush account. + +* __getApp(appName: string): Promise<App>__ - Retrieves the metadata about the specified app. + +* __getApps(): Promise<App[]>__ - Retrieves the list of apps associated with your CodePush account. + +* __getCollaborators(appName: string): Promise<CollaboratorMap>__ - Retrieves the list of collaborators associated with the specified app. + +* __getDeployment(appName: string, deploymentName: string): Promise<Deployment>__ - Retrieves the metadata for the specified app deployment. + +* __getDeploymentHistory(appName: string, deploymentName: string): Promise<Package[]>__ - Retrieves the list of releases that have been made to the specified app deployment. + +* __getDeploymentMetrics(appName: string, deploymentName: string): Promise<DeploymentMetrics>__ - Retrieves the installation metrics for the specified app deployment. + +* __getDeployments(appName: string): Promise<Deployment[]>__ - Retrieves the list of deployments associated with the specified app. + +* __patchRelease(appName: string, deploymentName: string, label: string, updateMetadata: PackageInfo): Promise<void>__ - Updates the specified release's metadata with the given information. + +* __promote(appName: string, sourceDeploymentName: string, destinationDeploymentName: string, updateMetadata: PackageInfo): Promise<Package>__ - Promotes the latest release from one deployment to another for the specified app and updates the release with the given metadata. + +* __release(appName: string, deploymentName: string, updateContentsPath: string, targetBinaryVersion: string, updateMetadata: PackageInfo): Promise<Package>__ - Releases a new update to the specified deployment with the given metadata. + +* __removeAccessKey(accessKey: string): Promise<void>__ - Removes the specified access key from your CodePush account. + +* __removeApp(appName: string): Promise<void>__ - Deletes the specified CodePush app from your account. + +* __removeCollaborator(appName: string, email: string): Promise<void>__ - Removes the specified account as a collaborator from the specified app. + +* __removeDeployment(appName: string, deploymentName: string): Promise<void>__ - Removes the specified deployment from the specified app. + +* __renameApp(oldAppName: string, newAppName: string): Promise<void>__ - Renames an existing app. + +* __renameDeployment(appName: string, oldDeploymentName: string, newDeploymentName: string): Promise<void>__ - Renames an existing deployment within the specified app. + +* __rollback(appName: string, deploymentName: string, targetRelease?: string): Promise<void>__ - Rolls back the latest release within the specified deployment. Optionally allows you to target a specific release in the deployment's history, as opposed to rolling to the previous release. + +* __transferApp(appName: string, email: string): Promise<void>__ - Transfers the ownership of the specified app to the specified account. + +### Error Handling + +When an error occurs in any of the methods, the promise will be rejected with a CodePushError object with the following properties: + +* __message__: A user-friendly message that describes the error. +* __statusCode__: An HTTP response code that identifies the category of error: + * __CodePush.ERROR_GATEWAY_TIMEOUT__: A network error prevented you from connecting to the CodePush server. + * __CodePush.ERROR_INTERNAL_SERVER__: An error occurred internally on the CodePush server. + * __CodePush.ERROR_NOT_FOUND__: The resource you are attempting to retrieve does not exist. + * __CodePush.ERROR_CONFLICT__: The resource you are attempting to create already exists. + * __CodePush.ERROR_UNAUTHORIZED__: The access key you configured is invalid or expired. diff --git a/sdk/SDK.njsproj b/SDK.njsproj similarity index 100% rename from sdk/SDK.njsproj rename to SDK.njsproj diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000..46bf9ec0 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,41 @@ + + +## Security + +Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). + +If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://docs.microsoft.com/en-us/previous-versions/tn-archive/cc751383(v=technet.10)), please report it to us as described below. + +## Reporting Security Issues + +**Please do not report security vulnerabilities through public GitHub issues.** + +Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://msrc.microsoft.com/create-report). + +If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://www.microsoft.com/en-us/msrc/pgp-key-msrc). + +You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc). + +Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: + +* Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) +* Full paths of source file(s) related to the manifestation of the issue +* The location of the affected source code (tag/branch/commit or direct URL) +* Any special configuration required to reproduce the issue +* Step-by-step instructions to reproduce the issue +* Proof-of-concept or exploit code (if possible) +* Impact of the issue, including how an attacker might exploit the issue + +This information will help us triage your report more quickly. + +If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://microsoft.com/msrc/bounty) page for more details about our active programs. + +## Preferred Languages + +We prefer all communications to be in English. + +## Policy + +Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://www.microsoft.com/en-us/msrc/cvd). + + \ No newline at end of file diff --git a/cli/.npmignore b/cli/.npmignore deleted file mode 100644 index a57287c2..00000000 --- a/cli/.npmignore +++ /dev/null @@ -1,5 +0,0 @@ -.npmignore -.gitignore -node_modules/* -definitions/* -!cli.js diff --git a/cli/README.md b/cli/README.md deleted file mode 100644 index 1bfed7f9..00000000 --- a/cli/README.md +++ /dev/null @@ -1,131 +0,0 @@ -# CodePush management CLI - -CodePush is a cloud service that enables Cordova and React Native developers to deploy mobile app updates directly to their users' devices. It works by acting as a central repository that developers can publish updates to (JS, HTML, CSS and images), and that apps can query for updates from (using provided client SDKs for -[Cordova](http://github.com/cordova-plugin-code-push) and [React Native](http://github.com/react-native-code-push)). This allows you to have a more deterministic and direct engagement model with your userbase, when addressing bugs and/or adding small features that don't require you to re-build a binary and re-distribute it through the respective app stores. - -![CodePush CLI](https://cloud.githubusercontent.com/assets/1646506/10623441/e355f1ec-7743-11e5-93f7-0223b5baeddc.png) - -## Installation - -* Install [Node.js](https://nodejs.org/) -* Install the CodePush CLI: `npm install -g code-push-cli` - -## Usage - -1. While the service is in beta, you need to [request access](https://microsoft.github.io/code-push) -2. Once your request has been accepted, you can [authenticate](#authentication) using the CodePush CLI -3. Register your [app](#app-management) with the service, and optionally create any additional [deployments](#deployment-management) -4. CodePush-ify your app and point it at the deployment you wish to use ([Cordova](http://github.com/Microsoft/cordova-plugin-code-push) and [React Native](http://github.com/Microsoft/react-native-code-push)) -5. [Deploy](#update-deployment) an update for your registered app -6. Live long and prosper! ([details](https://en.wikipedia.org/wiki/Vulcan_salute)) - -### Authentication - -Every command within the CodePush CLI requires authentication, and therefore, before you can begin managing your account, you need to login using the Github or Microsoft account you used when requesting access to the service. You can do this by running the following command: - -``` -code-push login -``` - -This will launch a browser, asking you to authenticate with the appropriate identity provider. This will generate an access token that you need to copy/paste into the CLI (it will prompt you for it). You are now succesfully authenticated and can safely close your browser window. - -When you login from the CLI, your access token (kind of like a cookie) is persisted to disk so that you don't have to login everytime you attempt to access your account. In order to delete this file from your computer, simply run the following command: - -``` -code-push logout -``` - -If you forget to logout from a machine you'd prefer not to leave a running session on (e.g. your friend's laptop), you can use the following commands to list and remove any "live" access tokens. - -``` -code-push access-key ls -code-push access-key rm -``` - -The list of access keys will display the name of the machine the token was created on, as well as the time the login occured. This should make it easy to spot keys you don't want to keep around. - -### App management -Before you can deploy any updates, you need to register an app with the CodePush service -using the following command: - -``` -code-push app add -``` - -All new apps automatically come with two deployments (Staging and Production) so that you can begin distributing updates to multiple channels without needing to do anything extra (see deployment instructions below). After you create an app, the CLI will output the deployment keys for the Staging and Production channels, which you can begin using to configure your clients via their respective SDKs (details for [Cordova](http://github.com/cordova-plugin-code-push) and [React Native](http://github.com/react-native-code-push)). - -If you don't like the name you gave an app, you can rename it using the following command: - -``` -code-push app rename -``` - -The app's name is only meant to be recognizeable from the management side, and therefore, you can feel free to rename it as neccessary. It won't actually impact the running app, since update queries are made via deployment keys. - -If at some point you no longer need an app, you can remove it from the server using the following command: - -``` -code-push app rm -``` - -Do this with caution since any apps that have been configured to use it will obviously stop receiving updates. - -Finally, if you want to list all apps that you've registered with the CodePush server, -you can run the following command: - -``` -code-push app ls -``` - -### Deployment management -As mentioned above, every created app automatically includes two deployments: **Staging** and **Production**. This allows you to have multiple versions of your app in flight at any given time, while still using the CodePush server to distribute the updates. If having a staging and production version of your app is enough to meet your needs, then you don't need to do anything else. However, if you want an alpha, dev, etc. deployment, you can easily create them using the following command: - -``` -code-push deployment add -``` - -Just like with apps, you can remove, rename and list deployments as well, using the following commands respectively: - -``` -code-push deployment rename -code-push deployment rm -code-push deployment ls -``` - -### Update deployment - -Once your app has been configured to query for updates against the CodePush service--using your desired deployment--you can begin pushing updates to it using the following command: - -``` -code-push release -[--deploymentName ] -[--description ] -[--mandatory ] -``` - -#### Package parameter - -You can specify either a single file (e.g. a JS bundle for a React Native app), or a path to a directory (e.g. the /platforms/ios/www folder for a Cordova app). You don't need to zip up multiple files or directories in order to deploy those changes. The CLI will automatically zip them for you. - -#### App store version parameter - -This specifies what the minimum app store version the code you are pushing depends on. This is important if your JavaScript/etc. takes a dependency on a new capabilitiy of the native side of your app (e.g. a Cordova plugin), and therefore, requires the user to update to the latest version from the app store. - -#### Deployment name parameter - -This specifies which deployment you want to deploy the update. This defaults to "Staging", but when you're ready to deploy to "Production", or one of your own custom deployments, just explicitly set this argument. - -*NOTE: The parameter can be set using either "--deploymentName" or "-d".* - -#### Description parameter - -This provides an optional "change log" for the deployment. The value is simply roundtripped to the client so that when the update is detected, your app can choose to display it to the end-user. - -*NOTE: This parameter can be set using either "--description" or "-desc"* - -#### Mandatory parameter - -This specifies whether the update is mandatory or not (**true** or **false**). The value is simply roundtripped to the client, -who can decide to actually enforce it or not. The default value is **false** - -*NOTE: This parameter can be set using either "--mandatory" or "-m"* diff --git a/cli/definitions/cli.ts b/cli/definitions/cli.ts deleted file mode 100644 index 197ce6bb..00000000 --- a/cli/definitions/cli.ts +++ /dev/null @@ -1,84 +0,0 @@ -export enum CommandType { - accessKeyList, - accessKeyRemove, - appAdd, - appList, - appRemove, - appRename, - deploymentAdd, - deploymentList, - deploymentRemove, - deploymentRename, - login, - logout, - register, - release -} - -export interface ICommand { - type: CommandType; -} - -export interface IAccessKeyListCommand extends ICommand { - format: string; -} - -export interface IAccessKeyRemoveCommand extends ICommand { - accessKeyName: string; -} - -export interface IAppAddCommand extends ICommand { - appName: string; -} - -export interface IAppListCommand extends ICommand { - format: string; -} - -export interface IAppRemoveCommand extends ICommand { - appName: string; -} - -export interface IAppRenameCommand extends ICommand { - currentAppName: string; - newAppName: string; -} - -export interface IDeploymentAddCommand extends ICommand { - appName: string; - deploymentName: string; -} - -export interface IDeploymentListCommand extends ICommand { - appName: string; - format: string; - verbose?: boolean; -} - -export interface IDeploymentRemoveCommand extends ICommand { - appName: string; - deploymentName: string; -} - -export interface IDeploymentRenameCommand extends ICommand { - appName: string; - currentDeploymentName: string; - newDeploymentName: string; -} - -export interface ILoginCommand extends ICommand { - serverUrl: string; -} - -export interface IRegisterCommand extends ICommand { - serverUrl: string; -} - -export interface IReleaseCommand extends ICommand { - appName: string; - deploymentName: string; - description: string; - mandatory: boolean; - appStoreVersion: string; - package: string; -} \ No newline at end of file diff --git a/cli/definitions/slash.d.ts b/cli/definitions/slash.d.ts deleted file mode 100644 index dbce08b1..00000000 --- a/cli/definitions/slash.d.ts +++ /dev/null @@ -1,5 +0,0 @@ -declare module "slash" { - function slash(str: string): string; - - export = slash; -} \ No newline at end of file diff --git a/cli/definitions/wordwrap.d.ts b/cli/definitions/wordwrap.d.ts deleted file mode 100644 index 82f18242..00000000 --- a/cli/definitions/wordwrap.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -declare module "wordwrap"{ - function wordwrap(width: number): (text: string) => string; - export = wordwrap; -} \ No newline at end of file diff --git a/cli/package.json b/cli/package.json deleted file mode 100644 index e7245aa8..00000000 --- a/cli/package.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "name": "code-push-cli", - "version": "1.0.4-beta", - "description": "Management CLI for the CodePush service", - "main": "script/cli.js", - "scripts": { - "test": "gulp test" - }, - "bin": { - "code-push": "script/cli.js" - }, - "repository": { - "type": "git", - "url": "https://github.com/Microsoft/code-push/" - }, - "keywords": [ - "code", - "push", - "cordova", - "react-native", - "react" - ], - "author": "Microsoft Corporation", - "license": "MIT", - "dependencies": { - "code-push": "1.0.0-beta", - "base-64": "^0.1.0", - "chalk": "^1.1.0", - "cli-table": "^0.3.1", - "fs": "0.0.2", - "opener": "^1.4.1", - "prompt": "^0.2.14", - "q": "~1.4.1", - "recursive-fs": "0.1.4", - "semver": "4.3.6", - "slash": "1.0.0", - "try-json": "^1.0.0", - "wordwrap": "1.0.0", - "yargs": "^3.15.0", - "yazl": "2.2.2" - } -} diff --git a/cli/script/cli.ts b/cli/script/cli.ts deleted file mode 100644 index ecb8babb..00000000 --- a/cli/script/cli.ts +++ /dev/null @@ -1,18 +0,0 @@ -/// - -import { Promise } from "q"; -import { command } from "./command-parser"; -import { execute } from "./command-executor"; -import * as chalk from "chalk"; - -function run(): void { - if (!command) { - return; - } - - execute(command) - .catch((error: any): void => console.error(chalk.red("[Error] " + error.message))) - .done(); -} - -run(); \ No newline at end of file diff --git a/cli/script/command-executor.ts b/cli/script/command-executor.ts deleted file mode 100644 index cbe8222f..00000000 --- a/cli/script/command-executor.ts +++ /dev/null @@ -1,733 +0,0 @@ -/// - -import * as base64 from "base-64"; -import * as chalk from "chalk"; -import * as fs from "fs"; -var opener = require("opener"); -import * as os from "os"; -import * as path from "path"; -var prompt = require("prompt"); -import * as Q from "q"; -import * as recursiveFs from "recursive-fs"; -import slash = require("slash"); -import tryJSON = require("try-json"); -var Table = require("cli-table"); -import * as yazl from "yazl"; -import wordwrap = require("wordwrap"); - -import * as cli from "../definitions/cli"; -import { AccessKey, AccountManager, App, Deployment, DeploymentKey } from "code-push"; -import Promise = Q.Promise; - -var configFilePath: string = path.join(process.env.LOCALAPPDATA || process.env.HOME, ".code-push.config"); - -interface IConnectionInfo { - accessKeyName: string; - providerName: string; - providerUniqueId: string; - serverUrl: string; -} - -interface IPackageFile { - isTemporary: boolean; - path: string; -} - -// Exported variables for unit testing. -export var sdk: AccountManager; -export var log = (message: string | Chalk.ChalkChain): void => console.log(message); - -export var loginWithAccessToken = (): Promise => { - if (!connectionInfo) { - return Q.fcall(() => { throw new Error("You are not logged in."); }); - } - - sdk = new AccountManager(connectionInfo.serverUrl); - - var accessToken: string = base64.encode(JSON.stringify({ accessKeyName: connectionInfo.accessKeyName, providerName: connectionInfo.providerName, providerUniqueId: connectionInfo.providerUniqueId })); - - return sdk.loginWithAccessToken(accessToken); -} - -export var confirm = (): Promise => { - return Promise((resolve, reject, notify): void => { - prompt.message = ""; - prompt.delimiter = ""; - - prompt.start(); - - prompt.get({ - properties: { - response: { - description: chalk.cyan("Are you sure? (Y/n):") - } - } - }, (err: any, result: any): void => { - if (!result.response || result.response === "" || result.response === "Y") { - resolve(true); - } else { - if (result.response !== "n") console.log("Invalid response: \"" + result.response + "\""); - resolve(false); - } - }); - }); -} - -var connectionInfo: IConnectionInfo; - -function accessKeyList(command: cli.IAccessKeyListCommand): Promise { - throwForInvalidOutputFormat(command.format); - - return sdk.getAccessKeys() - .then((accessKeys: AccessKey[]): void => { - printAccessKeys(command.format, accessKeys); - }); -} - -function removeLocalAccessKey(): Promise { - return Promise((resolve, reject, notify): void => { - log(chalk.red("[Error] Cannot remove the access key for the current session. Please run 'code-push logout' if you would like to remove this access key.")); - }); -} - -function accessKeyRemove(command: cli.IAccessKeyRemoveCommand): Promise { - if (command.accessKeyName === connectionInfo.accessKeyName) { - return removeLocalAccessKey(); - } else { - return getAccessKeyId(command.accessKeyName) - .then((accessKeyId: string): Promise => { - throwForInvalidAccessKeyId(accessKeyId, command.accessKeyName); - - return confirm() - .then((wasConfirmed: boolean): Promise => { - if (wasConfirmed) { - return sdk.removeAccessKey(accessKeyId) - .then((): void => { - log("Removed access key \"" + command.accessKeyName + "\"."); - }); - } - - log("Remove cancelled."); - }); - }); - } -} - -function appAdd(command: cli.IAppAddCommand): Promise { - return sdk.addApp(command.appName, /*description*/ null) - .then((app: App): Promise => { - log("Successfully added app \"" + command.appName + "\".\nCreated two default deployments:"); - var deploymentListCommand: cli.IDeploymentListCommand = { - type: cli.CommandType.deploymentList, - appName: app.name, - format: "table" - }; - return deploymentList(deploymentListCommand); - }); -} - -function appList(command: cli.IAppListCommand): Promise { - throwForInvalidOutputFormat(command.format); - - return sdk.getApps() - .then((apps: App[]): void => { - printList(command.format, apps); - }); -} - -function appRemove(command: cli.IAppRemoveCommand): Promise { - return getAppId(command.appName) - .then((appId: string): Promise => { - throwForInvalidAppId(appId, command.appName); - - return confirm() - .then((wasConfirmed: boolean): Promise => { - if (wasConfirmed) { - return sdk.removeApp(appId) - .then((): void => { - log("Removed app \"" + command.appName + "\"."); - }); - } - - log("Remove cancelled."); - }); - }); -} - -function appRename(command: cli.IAppRenameCommand): Promise { - return getApp(command.currentAppName) - .then((app: App): Promise => { - throwForInvalidApp(app, command.currentAppName); - - app.name = command.newAppName; - - return sdk.updateApp(app); - }) - .then((): void => { - log("Renamed app \"" + command.currentAppName + "\" to \"" + command.newAppName + "\"."); - }); -} - -function deleteConnectionInfoCache(): void { - try { - fs.unlinkSync(configFilePath); - - log("Deleted configuration file at '" + configFilePath + "'."); - } catch (ex) { - } -} - -function deploymentAdd(command: cli.IDeploymentAddCommand): Promise { - return getAppId(command.appName) - .then((appId: string): Promise => { - throwForInvalidAppId(appId, command.appName); - - return sdk.addDeployment(appId, command.deploymentName, /*description*/ null) - .then((deployment: Deployment): Promise => { - return sdk.getDeploymentKeys(appId, deployment.id); - }).then((deploymentKeys: DeploymentKey[]) => { - log("Added deployment \"" + command.deploymentName + "\" with key \"" + deploymentKeys[0].key + "\" to app \"" + command.appName + "\"."); - }); - }) -} - -export var deploymentList = (command: cli.IDeploymentListCommand): Promise => { - throwForInvalidOutputFormat(command.format); - var theAppId: string; - - return getAppId(command.appName) - .then((appId: string): Promise => { - throwForInvalidAppId(appId, command.appName); - theAppId = appId; - - return sdk.getDeployments(appId); - }) - .then((deployments: Deployment[]): Promise => { - var deploymentKeyList: Array = []; - var deploymentKeyPromises: Array> = []; - deployments.forEach((deployment: Deployment, index: number) => { - deploymentKeyPromises.push(sdk.getDeploymentKeys(theAppId, deployment.id).then((deploymentKeys: DeploymentKey[]): void => { - deploymentKeyList[index] = deploymentKeys[0].key; - })); - }); - return Q.all(deploymentKeyPromises).then(() => { - printDeploymentList(command, deployments, deploymentKeyList); - }); - }); -} - -function deploymentRemove(command: cli.IDeploymentRemoveCommand): Promise { - return getAppId(command.appName) - .then((appId: string): Promise => { - throwForInvalidAppId(appId, command.appName); - - return getDeploymentId(appId, command.deploymentName) - .then((deploymentId: string): Promise => { - throwForInvalidDeploymentId(deploymentId, command.deploymentName, command.appName); - - return confirm() - .then((wasConfirmed: boolean): Promise => { - if (wasConfirmed) { - return sdk.removeDeployment(appId, deploymentId) - .then((): void => { - log("Removed deployment \"" + command.deploymentName + "\" from app \"" + command.appName + "\"."); - }) - } - - log("Remove cancelled."); - }); - }); - }); -} - -function deploymentRename(command: cli.IDeploymentRenameCommand): Promise { - return getAppId(command.appName) - .then((appId: string): Promise => { - throwForInvalidAppId(appId, command.appName); - - return getDeployment(appId, command.currentDeploymentName) - .then((deployment: Deployment): Promise => { - throwForInvalidDeployment(deployment, command.currentDeploymentName, command.appName); - - deployment.name = command.newDeploymentName; - - return sdk.updateDeployment(appId, deployment); - }) - .then((): void => { - log("Renamed deployment \"" + command.currentDeploymentName + "\" to \"" + command.newDeploymentName + "\" for app \"" + command.appName + "\"."); - }); - }); -} - -function deserializeConnectionInfo(): IConnectionInfo { - var json: string; - - try { - json = fs.readFileSync(configFilePath, { encoding: "utf8" }); - } catch (ex) { - return; - } - - return tryJSON(json); -} - -function notifyAlreadyLoggedIn(): Promise { - return Promise((resolve, reject, notify): void => { - log("You are already logged in from this machine."); - }); -} - -export function execute(command: cli.ICommand): Promise { - connectionInfo = deserializeConnectionInfo(); - - switch (command.type) { - case cli.CommandType.login: - if (connectionInfo) { - return notifyAlreadyLoggedIn(); - } - - return login(command); - - case cli.CommandType.logout: - return logout(); - - case cli.CommandType.register: - return register(command); - } - - return loginWithAccessToken() - .then((): Promise => { - switch (command.type) { - case cli.CommandType.accessKeyList: - return accessKeyList(command); - - case cli.CommandType.accessKeyRemove: - return accessKeyRemove(command); - - case cli.CommandType.appAdd: - return appAdd(command); - - case cli.CommandType.appList: - return appList(command); - - case cli.CommandType.appRemove: - return appRemove(command); - - case cli.CommandType.appRename: - return appRename(command); - - case cli.CommandType.deploymentAdd: - return deploymentAdd(command); - - case cli.CommandType.deploymentList: - return deploymentList(command); - - case cli.CommandType.deploymentRemove: - return deploymentRemove(command); - - case cli.CommandType.deploymentRename: - return deploymentRename(command); - - case cli.CommandType.release: - return release(command); - - default: - // We should never see this message as invalid commands should be caught by the argument parser. - log("Invalid command: " + JSON.stringify(command)); - } - }); -} - -function generateRandomFilename(length: number): string { - var filename: string = ""; - var validChar: string = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; - - for (var i = 0; i < length; i++) { - filename += validChar.charAt(Math.floor(Math.random() * validChar.length)); - } - - return filename; -} - -function getAccessKey(accessKeyName: string): Promise { - return sdk.getAccessKeys() - .then((accessKeys: AccessKey[]): AccessKey => { - for (var i = 0; i < accessKeys.length; ++i) { - var accessKey: AccessKey = accessKeys[i]; - - if (accessKey.name === accessKeyName) { - return accessKey; - } - } - }); -} - -function getAccessKeyId(accessKeyName: string): Promise { - return getAccessKey(accessKeyName) - .then((accessKey: AccessKey): string => { - if (accessKey) { - return accessKey.id; - } - - return null; - }); -} - -function getApp(appName: string): Promise { - return sdk.getApps() - .then((apps: App[]): App => { - for (var i = 0; i < apps.length; ++i) { - var app: App = apps[i]; - - if (app.name === appName) { - return app; - } - } - }); -} - -function getAppId(appName: string): Promise { - return getApp(appName) - .then((app: App): string => { - if (app) { - return app.id; - } - - return null; - }); -} - -function getDeployment(appId: string, deploymentName: string): Promise { - return sdk.getDeployments(appId) - .then((deployments: Deployment[]): Deployment => { - for (var i = 0; i < deployments.length; ++i) { - var deployment: Deployment = deployments[i]; - - if (deployment.name === deploymentName) { - return deployment; - } - } - }); -} - -function getDeploymentId(appId: string, deploymentName: string): Promise { - return getDeployment(appId, deploymentName) - .then((deployment: Deployment): string => { - if (deployment) { - return deployment.id; - } - - return null; - }); -} - -function initiateExternalAuthenticationAsync(serverUrl: string, action: string): void { - var message: string = "An internet browser will now launch to authenticate your identity.\r\n\r\n" - + "After completing in-browser authentication, please enter your access token to log in or use [CTRL]+[C] to exit."; - - log(message); - var hostname: string = os.hostname(); - var url: string = serverUrl + "/auth/" + action + "?hostname=" + hostname; - - log("\r\nLaunching browser for " + url); - - opener(url); -} - -function login(command: cli.ILoginCommand): Promise { - initiateExternalAuthenticationAsync(command.serverUrl, "login"); - - return loginWithAccessTokenInternal(command.serverUrl); -} - -function loginWithAccessTokenInternal(serverUrl: string): Promise { - return requestAccessToken() - .then((accessToken: string): Promise => { - if (accessToken === null) { - // The user has aborted the synchronous prompt (e.g.: via [CTRL]+[C]). - return; - } - - if (!accessToken) { - log("Invalid access token."); - - return; - } - - sdk = new AccountManager(serverUrl); - - return sdk.loginWithAccessToken(accessToken) - .then((): void => { - log("Log in successful."); - - // The access token is valid. - serializeConnectionInfo(serverUrl, accessToken); - }); - }); -} - -function logout(): Promise { - if (connectionInfo) { - return loginWithAccessToken() - .then((): Promise => { - return getAccessKeyId(connectionInfo.accessKeyName); - }) - .then((accessKeyId: string): Promise => { - return sdk.removeAccessKey(accessKeyId); - }) - .then((): Promise => sdk.logout(), (): Promise => sdk.logout()) - .then((): void => deleteConnectionInfoCache(), (): void => deleteConnectionInfoCache()) - .then((): void => { - log("Log out successful."); - }); - } - - return Q(null); -} - -function printDeploymentList(command: cli.IDeploymentListCommand, deployments: Deployment[], deploymentKeys: Array): void { - if (command.format === "json") { - var dataSource: any[] = []; - deployments.forEach((deployment: Deployment, index: number) => { - var strippedDeployment: any = { "name": deployment.name, "deploymentKey": deploymentKeys[index] }; - if (command.verbose) { - if (deployment.package) { - strippedDeployment["package"] = { - "appVersion": deployment.package.appVersion, - "isMandatory": deployment.package.isMandatory, - "packageHash": deployment.package.packageHash, - "uploadTime": new Date(+deployment.package.uploadTime) - }; - if (deployment.package.description) strippedDeployment["package"]["description"] = deployment.package.description; - } - } - dataSource.push(strippedDeployment); - }); - log(JSON.stringify(dataSource)); - } else if (command.format === "table") { - var headers = ["Name", "Deployment Key"]; - if (command.verbose) headers.push("Package Metadata"); - printTable(headers, - (dataSource: any[]): void => { - deployments.forEach((deployment: Deployment, index: number): void => { - var row = [deployment.name, deploymentKeys[index]]; - if (command.verbose) { - var packageString: string = ""; - if (deployment.package) { - packageString = - (deployment.package.description ? wordwrap(30)("Description: " + deployment.package.description) + "\n" : "") + - "Minimum App Store Version: " + deployment.package.appVersion + "\n" + - "Mandatory: " + (deployment.package.isMandatory ? "Yes" : "No") + "\n" + - "Hash: " + deployment.package.packageHash + "\n" + - "Uploaded On: " + new Date(+deployment.package.uploadTime); - } - row.push(packageString); - } - dataSource.push(row); - }); - } - ); - } -} - -function printList(format: string, items: T[]): void { - if (format === "json") { - var dataSource: any[] = []; - - items.forEach((item: T): void => { - dataSource.push({ "name": item.name, "id": item.id }); - }); - - log(JSON.stringify(dataSource)); - } else if (format === "table") { - printTable(["Name", "ID"], (dataSource: any[]): void => { - items.forEach((item: T): void => { - dataSource.push([item.name, item.id]); - }); - }); - } -} - -function printAccessKeys(format: string, items: T[]): void { - if (format === "json") { - var dataSource: any[] = []; - - items.forEach((item: T): void => { - dataSource.push({ "name": item.name, "id": item.id, "description": item.description }); - }); - - log(JSON.stringify(dataSource)); - } else if (format === "table") { - printTable(["Key", "Description"], (dataSource: any[]): void => { - items.forEach((item: T): void => { - dataSource.push([item.name, item.description]); - }); - }); - } -} - -function printTable(columnNames: string[], readData: (dataSource: any[]) => void): void { - var table = new Table({ - head: columnNames, - style: { head: ["cyan"] } - }); - - readData(table); - - log(table.toString()); -} - -function register(command: cli.IRegisterCommand): Promise { - initiateExternalAuthenticationAsync(command.serverUrl, "register"); - - return loginWithAccessTokenInternal(command.serverUrl); -} - -function release(command: cli.IReleaseCommand): Promise { - return getAppId(command.appName) - .then((appId: string): Promise => { - throwForInvalidAppId(appId, command.appName); - - return getDeploymentId(appId, command.deploymentName) - .then((deploymentId: string): Promise => { - throwForInvalidDeploymentId(deploymentId, command.deploymentName, command.appName); - - var filePath: string = command.package; - var getPackageFilePromise: Promise; - var isSingleFilePackage: boolean = true; - - if (fs.lstatSync(filePath).isDirectory()) { - isSingleFilePackage = false; - getPackageFilePromise = Promise((resolve: (file: IPackageFile) => void, reject: (reason: Error) => void): void => { - var directoryPath: string = filePath; - - recursiveFs.readdirr(directoryPath, (error?: any, directories?: string[], files?: string[]): void => { - if (error) { - reject(error); - return; - } - - var baseDirectoryPath = path.dirname(directoryPath); - var fileName: string = generateRandomFilename(15) + ".zip"; - var zipFile = new yazl.ZipFile(); - var writeStream: fs.WriteStream = fs.createWriteStream(fileName); - - zipFile.outputStream.pipe(writeStream) - .on("error", (error: Error): void => { - reject(error); - }) - .on("close", (): void => { - filePath = path.join(process.cwd(), fileName); - - resolve({ isTemporary: true, path: filePath }); - }); - - for (var i = 0; i < files.length; ++i) { - var file: string = files[i]; - var relativePath: string = path.relative(baseDirectoryPath, file); - - // yazl does not like backslash (\) in the metadata path. - relativePath = slash(relativePath); - - zipFile.addFile(file, relativePath); - } - - zipFile.end(); - }); - }); - } else { - getPackageFilePromise = Q({ isTemporary: false, path: filePath }); - } - - return getPackageFilePromise - .then((file: IPackageFile): Promise => { - return sdk.addPackage(appId, deploymentId, file.path, command.description, /*label*/ null, command.appStoreVersion, command.mandatory) - .then((): void => { - log("Released a new package containing the \"" + command.package + "\" " + (isSingleFilePackage ? "file" : "directory") + " to the \"" + command.deploymentName + "\" deployment for \"" + command.appName + "\"."); - - if (file.isTemporary) { - fs.unlinkSync(filePath); - } - }); - }); - }); - }); -} - -function requestAccessToken(): Promise { - return Promise((resolve, reject, notify): void => { - prompt.message = ""; - prompt.delimiter = ""; - - prompt.start(); - - prompt.get({ - properties: { - response: { - description: chalk.cyan("Enter your access token: ") - } - } - }, (err: any, result: any): void => { - if (err) { - resolve(null); - } else { - resolve(result.response.trim()); - } - }); - }); -} - -function serializeConnectionInfo(serverUrl: string, accessToken: string): void { - // The access token should have been validated already (i.e.: logging in). - var json: string = base64.decode(accessToken); - var info: IConnectionInfo = JSON.parse(json); - - info.serverUrl = serverUrl; - - json = JSON.stringify(info); - - fs.writeFileSync(configFilePath, json, { encoding: "utf8" }); - - log("Login token persisted to file '" + configFilePath + "'. Run 'code-push logout' to remove the file."); -} - -function throwForInvalidAccessKeyId(accessKeyId: string, accessKeyName: string): void { - if (!accessKeyId) { - throw new Error("Access key \"" + accessKeyName + "\" does not exist."); - } -} - -function throwForInvalidApp(app: App, appName: string): void { - if (!app) { - throw new Error("App \"" + appName + "\" does not exist."); - } -} - -function throwForInvalidAppId(appId: string, appName: string): void { - if (!appId) { - throw new Error("App \"" + appName + "\" does not exist."); - } -} - -function throwForInvalidDeployment(deployment: Deployment, deploymentName: string, appName: string): void { - if (!deployment) { - throw new Error("Deployment \"" + deploymentName + "\" does not exist for app \"" + appName + "\"."); - } -} - -function throwForInvalidDeploymentId(deploymentId: string, deploymentName: string, appName: string): void { - if (!deploymentId) { - throw new Error("Deployment \"" + deploymentName + "\" does not exist for app \"" + appName + "\"."); - } -} - -function throwForInvalidOutputFormat(format: string): void { - switch (format) { - case "json": - case "table": - break; - - default: - throw new Error("Invalid format: " + format + "."); - } -} diff --git a/cli/script/command-parser.ts b/cli/script/command-parser.ts deleted file mode 100644 index 5c93147d..00000000 --- a/cli/script/command-parser.ts +++ /dev/null @@ -1,391 +0,0 @@ -import * as yargs from "yargs"; -import * as cli from "../definitions/cli"; -import * as semver from "semver"; -import * as chalk from "chalk"; - -const USAGE_PREFIX = "Usage: code-push"; -const CODE_PUSH_URL = "https://codepush.azurewebsites.net"; - -// Command categories are: access-key, app, release, deployment, deployment-key, login, logout, register -var isValidCommandCategory = false; -// Commands are the verb following the command category (e.g.: "add" in "app add"). -var isValidCommand = false; -var wasHelpShown = false; - -function showHelp(showRootDescription?: boolean): void { - if (!wasHelpShown) { - if (showRootDescription) { - console.log(chalk.cyan(" _____ __ " + chalk.green(" ___ __ "))); - console.log(chalk.cyan(" / ___/__ ___/ /__" + chalk.green(" / _ \\__ _____ / / "))); - console.log(chalk.cyan("/ /__/ _ \\/ _ / -_)" + chalk.green(" ___/ // (_-]") - .demand(/*count*/ 2, /*max*/ 2) // Require exactly two non-option arguments. - .example("access-key " + commandName, "Lists access keys in tabular format") - .example("access-key " + commandName + " --format json", "Lists apps in JSON format") - .option("format", { default: "table", demand: false, description: "The output format (\"json\" or \"table\")", type: "string" }); - - addCommonConfiguration(yargs); -} - -function accessKeyRemove(commandName: string, yargs: yargs.Argv): void { - isValidCommand = true; - yargs.usage(USAGE_PREFIX + " access-key " + commandName + " ") - .demand(/*count*/ 3, /*max*/ 3) // Require exactly three non-option arguments. - .example("access-key " + commandName + " 8d6513de-050c-4788-96f7-b2a50dd9684v", "Removes the \"8d6513de-050c-4788-96f7-b2a50dd9684v\" access key"); - - addCommonConfiguration(yargs); -} - -function addCommonConfiguration(yargs: yargs.Argv): void { - yargs.wrap(/*columnLimit*/ null) - .strict() // Validate hyphenated (named) arguments. - .fail((msg: string) => showHelp()); // Suppress the default error message. -} - -function appList(commandName: string, yargs: yargs.Argv): void { - isValidCommand = true; - yargs.usage(USAGE_PREFIX + " app " + commandName + " [--format ]") - .demand(/*count*/ 2, /*max*/ 2) // Require exactly two non-option arguments. - .example("app " + commandName, "Lists apps in tabular format") - .example("app " + commandName + " --format json", "Lists apps in JSON format") - .option("format", { default: "table", demand: false, description: "The output format (\"json\" or \"table\")", type: "string" }); - - addCommonConfiguration(yargs); -} - -function appRemove(commandName: string, yargs: yargs.Argv): void { - isValidCommand = true; - yargs.usage(USAGE_PREFIX + " app " + commandName + " ") - .demand(/*count*/ 3, /*max*/ 3) // Require exactly three non-option arguments. - .example("app " + commandName + " MyApp", "Removes app \"MyApp\""); - - addCommonConfiguration(yargs); -} - -function deploymentList(commandName: string, yargs: yargs.Argv): void { - isValidCommand = true; - yargs.usage(USAGE_PREFIX + " deployment " + commandName + " [--format ] [--verbose ]") - .demand(/*count*/ 3, /*max*/ 3) // Require exactly three non-option arguments. - .example("deployment " + commandName + " MyApp", "Lists deployments for app \"MyApp\" in tabular format") - .example("deployment " + commandName + " MyApp --format json", "Lists deployments for app \"MyApp\" in JSON format") - .option("format", { default: "table", demand: false, description: "The output format (\"json\" or \"table\")", type: "string" }) - .option("verbose", { alias: "v", demand: false, description: "Show deployment description and package metadata", type: "boolean" }) - addCommonConfiguration(yargs); -} - -function deploymentRemove(commandName: string, yargs: yargs.Argv): void { - isValidCommand = true; - yargs.usage(USAGE_PREFIX + " deployment " + commandName + " ") - .demand(/*count*/ 4, /*max*/ 4) // Require exactly four non-option arguments. - .example("deployment " + commandName + " MyApp MyDeployment", "Removes deployment \"MyDeployment\" from app \"MyApp\""); - - addCommonConfiguration(yargs); -} - -var argv = yargs.usage(USAGE_PREFIX + " ") - .demand(/*count*/ 1, /*max*/ 1) // Require exactly one non-option argument. - .command("access-key", "View and delete active user sessions", (yargs: yargs.Argv) => { - isValidCommandCategory = true; - yargs.usage(USAGE_PREFIX + " access-key ") - .demand(/*count*/ 2, /*max*/ 2) // Require exactly two non-option arguments. - .command("list", "List the access keys associated with your account", (yargs: yargs.Argv) => accessKeyList("list", yargs)) - .command("ls", "List the access keys associated with your account", (yargs: yargs.Argv) => accessKeyList("ls", yargs)) - .command("remove", "Remove an existing access key", (yargs: yargs.Argv) => accessKeyRemove("remove", yargs)) - .command("rm", "Remove an existing access key", (yargs: yargs.Argv) => accessKeyRemove("rm", yargs)) - .check((argv: any, aliases: { [aliases: string]: string }): any => isValidCommand); // Report unrecognized, non-hyphenated command category. - - addCommonConfiguration(yargs); - }) - .command("app", "View and manage your CodePush-enabled apps", (yargs: yargs.Argv) => { - isValidCommandCategory = true; - yargs.usage(USAGE_PREFIX + " app ") - .demand(/*count*/ 2, /*max*/ 2) // Require exactly two non-option arguments. - .command("add", "Add a new app to your account", (yargs: yargs.Argv): void => { - isValidCommand = true; - yargs.usage(USAGE_PREFIX + " app add ") - .demand(/*count*/ 3, /*max*/ 3) // Require exactly three non-option arguments. - .example("app add MyApp", "Adds app \"MyApp\""); - - addCommonConfiguration(yargs); - }) - .command("list", "List the apps associated with your account", (yargs: yargs.Argv) => appList("list", yargs)) - .command("ls", "List the apps associated with your account", (yargs: yargs.Argv) => appList("ls", yargs)) - .command("remove", "Remove an app from your account", (yargs: yargs.Argv) => appRemove("remove", yargs)) - .command("rename", "Rename an existing app", (yargs: yargs.Argv) => { - isValidCommand = true; - yargs.usage(USAGE_PREFIX + " app rename ") - .demand(/*count*/ 4, /*max*/ 4) // Require exactly four non-option arguments. - .example("app rename CurrentName NewName", "Renames app \"CurrentName\" to \"NewName\""); - - addCommonConfiguration(yargs); - }) - .command("rm", "Remove an app from your account", (yargs: yargs.Argv) => appRemove("rm", yargs)) - .check((argv: any, aliases: { [aliases: string]: string }): any => isValidCommand); // Report unrecognized, non-hyphenated command category. - - addCommonConfiguration(yargs); - }) - .command("release", "Release a new version of your app to a specific deployment", (yargs: yargs.Argv) => { - yargs.usage(USAGE_PREFIX + " release [--deploymentName ] [--description ] [--mandatory ]") - .demand(/*count*/ 4, /*max*/ 4) // Require exactly four non-option arguments. - .example("release MyApp app.js 1.0.3", "Upload app.js to the default deployment for app \"MyApp\" with the minimum required semver compliant app store version of 1.0.3") - .example("release MyApp ./platforms/ios/www 1.0.3 -d Production", "Upload the \"./platforms/ios/www\" folder and all its contents to the \"Production\" deployment for app \"MyApp\" with the minimum required semver compliant app store version of 1.0.3") - .option("deploymentName", { alias: "d", default: "Staging", demand: false, description: "The deployment to publish the update to", type: "string" }) - .option("description", { alias: "des", default: null, demand: false, description: "The description of changes made to the app with this update", type: "string" }) - .option("mandatory", { alias: "m", default: false, demand: false, description: "Whether this update should be considered mandatory to the client", type: "boolean" }) - .check((argv: any, aliases: { [alias: string]: string }) => { - var appStoreVersion: string = argv._[3]; - return semver.valid(appStoreVersion) !== null; - }); - - addCommonConfiguration(yargs); - }) - .command("deployment", "View and manage the deployments for your apps", (yargs: yargs.Argv) => { - isValidCommandCategory = true; - yargs.usage(USAGE_PREFIX + " deployment ") - .demand(/*count*/ 2, /*max*/ 2) // Require exactly two non-option arguments. - .command("add", "Add a new deployment to an existing app", (yargs: yargs.Argv): void => { - isValidCommand = true; - yargs.usage(USAGE_PREFIX + " deployment add ") - .demand(/*count*/ 4, /*max*/ 4) // Require exactly four non-option arguments. - .example("deployment add MyApp MyDeployment", "Adds deployment \"MyDeployment\" to app \"MyApp\""); - - addCommonConfiguration(yargs); - }) - .command("list", "List the deployments associated with an app", (yargs: yargs.Argv) => deploymentList("list", yargs)) - .command("ls", "List the deployments associated with an app", (yargs: yargs.Argv) => deploymentList("ls", yargs)) - .command("remove", "Remove a deployment from an app", (yargs: yargs.Argv) => deploymentRemove("remove", yargs)) - .command("rename", "Rename an existing deployment", (yargs: yargs.Argv) => { - isValidCommand = true; - yargs.usage(USAGE_PREFIX + " deployment rename ") - .demand(/*count*/ 5, /*max*/ 5) // Require exactly five non-option arguments. - .example("deployment rename MyApp CurrentDeploymentName NewDeploymentName", "Renames deployment \"CurrentDeploymentName\" to \"NewDeploymentName\""); - - addCommonConfiguration(yargs); - }) - .command("rm", "Remove a deployment from an app", (yargs: yargs.Argv) => deploymentRemove("rm", yargs)) - .check((argv: any, aliases: { [aliases: string]: string }): any => isValidCommand); // Report unrecognized, non-hyphenated command category. - - addCommonConfiguration(yargs); - }) - .command("login", "Authenticate with the CodePush server in order to begin managing your apps", (yargs: yargs.Argv) => { - isValidCommandCategory = true; - isValidCommand = true; - yargs.usage(USAGE_PREFIX + " login [serverUrl]") - .demand(/*count*/ 1, /*max*/ 2) // Require one non-optional and one optional argument. - .example("login", "Logs in to " + CODE_PUSH_URL) - .check((argv: any, aliases: { [aliases: string]: string }): any => isValidCommand); // Report unrecognized, non-hyphenated command category. - - addCommonConfiguration(yargs); - }) - .command("logout", "Log out of the current session", (yargs: yargs.Argv) => { - isValidCommandCategory = true; - isValidCommand = true; - }) - // Disabling this for closed beta - //.command("register", "Register a new account with a specific CodePush server", (yargs: yargs.Argv) => { - //isValidCommandCategory = true; - //isValidCommand = true; - //yargs.usage(USAGE_PREFIX + " register [serverUrl]") - //.demand(/*count*/ 1, /*max*/ 2) // Require one non-optional and one optional argument. - //.example("register", "Creates a new user account with " + CODE_PUSH_URL) - //.check((argv: any, aliases: { [aliases: string]: string }): any => isValidCommand); // Report unrecognized, non-hyphenated command category. - - //addCommonConfiguration(yargs); - //}) - .alias("v", "version") - .version(require("../package.json").version) - .wrap(/*columnLimit*/ null) - .strict() // Validate hyphenated (named) arguments. - .check((argv: any, aliases: { [aliases: string]: string }): any => isValidCommandCategory) // Report unrecognized, non-hyphenated command category. - .fail((msg: string) => showHelp(/*showRootDescription*/ true)) // Suppress the default error message. - .argv; - -function createCommand(): cli.ICommand { - var cmd: cli.ICommand; - - if (!wasHelpShown && argv._ && argv._.length > 0) { - // Create a command object - var arg0: any = argv._[0]; - var arg1: any = argv._[1]; - var arg2: any = argv._[2]; - var arg3: any = argv._[3]; - var arg4: any = argv._[4]; - - switch (arg0) { - case "access-key": - switch (arg1) { - case "list": - case "ls": - cmd = { type: cli.CommandType.accessKeyList }; - - (cmd).format = argv["format"]; - break; - - case "remove": - case "rm": - if (arg2) { - cmd = { type: cli.CommandType.accessKeyRemove }; - - (cmd).accessKeyName = arg2; - } - break; - } - break; - - case "app": - switch (arg1) { - case "add": - if (arg2) { - cmd = { type: cli.CommandType.appAdd }; - - (cmd).appName = arg2; - } - break; - - case "list": - case "ls": - cmd = { type: cli.CommandType.appList }; - - (cmd).format = argv["format"]; - break; - - case "remove": - case "rm": - if (arg2) { - cmd = { type: cli.CommandType.appRemove }; - - (cmd).appName = arg2; - } - break; - - case "rename": - if (arg2 && arg3) { - cmd = { type: cli.CommandType.appRename }; - - var appRenameCommand = cmd; - - appRenameCommand.currentAppName = arg2; - appRenameCommand.newAppName = arg3; - } - break; - } - break; - - case "deployment": - switch (arg1) { - case "add": - if (arg2 && arg3) { - cmd = { type: cli.CommandType.deploymentAdd }; - - var deploymentAddCommand = cmd; - - deploymentAddCommand.appName = arg2; - deploymentAddCommand.deploymentName = arg3; - } - break; - - case "list": - case "ls": - if (arg2) { - cmd = { type: cli.CommandType.deploymentList }; - - var deploymentListCommand = cmd; - - deploymentListCommand.appName = arg2; - deploymentListCommand.format = argv["format"]; - deploymentListCommand.verbose = argv["verbose"]; - } - break; - - case "remove": - case "rm": - if (arg2 && arg3) { - cmd = { type: cli.CommandType.deploymentRemove }; - - var deploymentRemoveCommand = cmd; - - deploymentRemoveCommand.appName = arg2; - deploymentRemoveCommand.deploymentName = arg3; - } - break; - - case "rename": - if (arg2 && arg3 && arg4) { - cmd = { type: cli.CommandType.deploymentRename }; - - var deploymentRenameCommand = cmd; - - deploymentRenameCommand.appName = arg2; - deploymentRenameCommand.currentDeploymentName = arg3; - deploymentRenameCommand.newDeploymentName = arg4; - } - break; - } - break; - - case "login": - cmd = { type: cli.CommandType.login }; - - var loginCommand = cmd; - - loginCommand.serverUrl = getServerUrl(arg1); - break; - - case "logout": - cmd = { type: cli.CommandType.logout }; - break; - - case "register": - cmd = { type: cli.CommandType.register }; - - var registerCommand = cmd; - - registerCommand.serverUrl = getServerUrl(arg1); - break; - - case "release": - if (arg1 && arg2 && arg3) { - cmd = { type: cli.CommandType.release }; - - var releaseCommand = cmd; - - releaseCommand.appName = arg1; - releaseCommand.package = arg2; - releaseCommand.appStoreVersion = arg3; - releaseCommand.deploymentName = argv["deploymentName"]; - releaseCommand.description = argv["description"]; - releaseCommand.mandatory = argv["mandatory"]; - } - break; - } - - return cmd; - } -} - -function getServerUrl(customUrl: string): string { - var url: string = customUrl || CODE_PUSH_URL; - - // Trim whitespace and a trailing slash (/) character. - url = url.trim(); - if (url[url.length - 1] === "/") { - url = url.substring(0, url.length - 1); - } - - return url; -} - -export var command = createCommand(); \ No newline at end of file diff --git a/cli/test/cli.ts b/cli/test/cli.ts deleted file mode 100644 index 485f84ab..00000000 --- a/cli/test/cli.ts +++ /dev/null @@ -1,396 +0,0 @@ -import * as assert from "assert"; -import * as sinon from "sinon"; -import Q = require("q"); -import Promise = Q.Promise; -import * as codePush from "code-push"; -import * as cli from "../definitions/cli"; -import * as cmdexec from "../script/command-executor"; - -export class SdkStub { - public addApp(name: string, description?: string): Promise { - return Q({ - description: description, - id: "appId", - name: name - }); - } - - public addDeployment(appId: string, name: string, description?: string): Promise { - return Q({ - description: description, - id: "deploymentId", - name: name - }); - } - - public getAccessKeys(): Promise { - return Q([{ - id: "7", - name: "8" - }]); - } - - public getApps(): Promise { - return Q([{ - id: "1", - name: "a" - }, { - id: "2", - name: "b" - }]); - } - - public getDeploymentKeys(appId: string, deploymentId: string): Promise { - return Q([{ - id: "5", - key: "6", - name: "Primary" - }]); - } - - public getDeployments(appId: string): Promise { - return Q([{ - id: "3", - name: "Production" - }, { - id: "4", - name: "Staging", - description: "cde", - package: { - appVersion: "1.0.0", - description: "fgh", - label: "ghi", - packageHash: "jkl", - isMandatory: true, - size: 10, - blobUrl: "http://mno.pqr", - uploadTime: +1000 - } - }]); - } - - public removeAccessKey(accessKeyId: string): Promise { - return Q(null); - } - - public removeApp(appId: string): Promise { - return Q(null); - } - - public removeDeployment(appId: string, deployment: string): Promise { - return Q(null); - } - - public updateApp(app: codePush.App): Promise { - return Q(null); - } - - public updateDeployment(appId: string, deployment: codePush.Deployment): Promise { - return Q(null); - } -} - -describe("CLI", () => { - var log: Sinon.SinonStub; - var sandbox: Sinon.SinonSandbox; - var wasConfirmed = true; - - beforeEach((): void => { - wasConfirmed = true; - - sandbox = sinon.sandbox.create(); - - sandbox.stub(cmdexec, "confirm", (): Promise => Q(wasConfirmed)); - log = sandbox.stub(cmdexec, "log", (message: string): void => { }); - sandbox.stub(cmdexec, "loginWithAccessToken", (): Promise => Q(null)); - - cmdexec.sdk = new SdkStub(); - }); - - afterEach((): void => { - sandbox.restore(); - }); - - it("accessKeyList lists access key names and ID's", (done: MochaDone): void => { - var command: cli.ICommand = { - type: cli.CommandType.accessKeyList, - format: "json" - }; - - cmdexec.execute(command) - .done((): void => { - sinon.assert.calledOnce(log); - assert.equal(log.args[0].length, 1); - - var actual: string = log.args[0][0]; - var expected = "[{\"name\":\"8\",\"id\":\"7\"}]"; - - assert.equal(actual, expected); - done(); - }); - }); - - it("accessKeyRemove removes access key", (done: MochaDone): void => { - var command: cli.IAccessKeyRemoveCommand = { - type: cli.CommandType.accessKeyRemove, - accessKeyName: "8" - }; - - var removeAccessKey: Sinon.SinonSpy = sandbox.spy(cmdexec.sdk, "removeAccessKey"); - - cmdexec.execute(command) - .done((): void => { - sinon.assert.calledOnce(removeAccessKey); - sinon.assert.calledWithExactly(removeAccessKey, "7"); - sinon.assert.calledOnce(log); - sinon.assert.calledWithExactly(log, "Removed access key \"8\"."); - - done(); - }); - }); - - it("accessKeyRemove does not remove access key if cancelled", (done: MochaDone): void => { - var command: cli.IAccessKeyRemoveCommand = { - type: cli.CommandType.accessKeyRemove, - accessKeyName: "8" - }; - - var removeAccessKey: Sinon.SinonSpy = sandbox.spy(cmdexec.sdk, "removeAccessKey"); - - wasConfirmed = false; - - cmdexec.execute(command) - .done((): void => { - sinon.assert.notCalled(removeAccessKey); - sinon.assert.calledOnce(log); - sinon.assert.calledWithExactly(log, "Remove cancelled."); - - done(); - }); - }); - - it("appAdd reports new app name and ID", (done: MochaDone): void => { - var command: cli.IAppAddCommand = { - type: cli.CommandType.appAdd, - appName: "a" - }; - - var deploymentListCommand: cli.IDeploymentListCommand = { - type: cli.CommandType.deploymentList, - appName: "a", - format: "table" - }; - - var addApp: Sinon.SinonSpy = sandbox.spy(cmdexec.sdk, "addApp"); - var deploymentList: Sinon.SinonSpy = sandbox.spy(cmdexec, "deploymentList"); - - cmdexec.execute(command) - .done((): void => { - sinon.assert.calledOnce(addApp); - sinon.assert.calledTwice(log); - sinon.assert.calledWithExactly(log, "Successfully added app \"a\".\nCreated two default deployments:"); - sinon.assert.calledOnce(deploymentList); - sinon.assert.calledWithExactly(deploymentList, deploymentListCommand); - done(); - }); - }); - - it("appList lists app names and ID's", (done: MochaDone): void => { - var command: cli.ICommand = { - type: cli.CommandType.appList, - format: "json" - }; - - cmdexec.execute(command) - .done((): void => { - sinon.assert.calledOnce(log); - assert.equal(log.args[0].length, 1); - - var actual: string = log.args[0][0]; - var expected = "[{\"name\":\"a\",\"id\":\"1\"},{\"name\":\"b\",\"id\":\"2\"}]"; - - assert.equal(actual, expected); - done(); - }); - }); - - it("appRemove removes app", (done: MochaDone): void => { - var command: cli.IAppRemoveCommand = { - type: cli.CommandType.appRemove, - appName: "a" - }; - - var removeApp: Sinon.SinonSpy = sandbox.spy(cmdexec.sdk, "removeApp"); - - cmdexec.execute(command) - .done((): void => { - sinon.assert.calledOnce(removeApp); - sinon.assert.calledWithExactly(removeApp, "1"); - sinon.assert.calledOnce(log); - sinon.assert.calledWithExactly(log, "Removed app \"a\"."); - - done(); - }); - }); - - it("appRemove does not remove app if cancelled", (done: MochaDone): void => { - var command: cli.IAppRemoveCommand = { - type: cli.CommandType.appRemove, - appName: "a" - }; - - var removeApp: Sinon.SinonSpy = sandbox.spy(cmdexec.sdk, "removeApp"); - - wasConfirmed = false; - - cmdexec.execute(command) - .done((): void => { - sinon.assert.notCalled(removeApp); - sinon.assert.calledOnce(log); - sinon.assert.calledWithExactly(log, "Remove cancelled."); - - done(); - }); - }); - - it("appRename renames app", (done: MochaDone): void => { - var command: cli.IAppRenameCommand = { - type: cli.CommandType.appRename, - currentAppName: "a", - newAppName: "c" - }; - - var updateApp: Sinon.SinonSpy = sandbox.spy(cmdexec.sdk, "updateApp"); - - cmdexec.execute(command) - .done((): void => { - sinon.assert.calledOnce(updateApp); - sinon.assert.calledOnce(log); - sinon.assert.calledWithExactly(log, "Renamed app \"a\" to \"c\"."); - - done(); - }); - }); - - it("deploymentAdd reports new app name and ID", (done: MochaDone): void => { - var command: cli.IDeploymentAddCommand = { - type: cli.CommandType.deploymentAdd, - appName: "a", - deploymentName: "b" - }; - - var addDeployment: Sinon.SinonSpy = sandbox.spy(cmdexec.sdk, "addDeployment"); - var getDeploymentKeys: Sinon.SinonSpy = sandbox.spy(cmdexec.sdk, "getDeploymentKeys"); - - cmdexec.execute(command) - .done((): void => { - sinon.assert.calledOnce(addDeployment); - sinon.assert.calledOnce(getDeploymentKeys); - sinon.assert.calledOnce(log); - sinon.assert.calledWithExactly(log, "Added deployment \"b\" with key \"6\" to app \"a\"."); - done(); - }); - }); - - it("deploymentList lists deployment names and deployment keys", (done: MochaDone): void => { - var command: cli.IDeploymentListCommand = { - type: cli.CommandType.deploymentList, - appName: "a", - format: "json" - }; - - cmdexec.execute(command) - .done((): void => { - sinon.assert.calledOnce(log); - assert.equal(log.args[0].length, 1); - - var actual: string = log.args[0][0]; - var expected = "[{\"name\":\"Production\",\"deploymentKey\":\"6\"},{\"name\":\"Staging\",\"deploymentKey\":\"6\"}]"; - - assert.equal(actual, expected); - done(); - }); - }); - - it("deploymentList -v lists deployment names, deployment keys, and package information", (done: MochaDone): void => { - var command: cli.IDeploymentListCommand = { - type: cli.CommandType.deploymentList, - appName: "a", - format: "json", - verbose: true - }; - - cmdexec.execute(command) - .done((): void => { - sinon.assert.calledOnce(log); - assert.equal(log.args[0].length, 1); - - var actual: string = log.args[0][0]; - var expected = "[{\"name\":\"Production\",\"deploymentKey\":\"6\"},{\"name\":\"Staging\",\"deploymentKey\":\"6\",\"package\":{\"appVersion\":\"1.0.0\",\"isMandatory\":true,\"packageHash\":\"jkl\",\"uploadTime\":" + JSON.stringify(new Date(1000)) + ",\"description\":\"fgh\"}}]"; - - assert.equal(actual, expected); - done(); - }); - }); - - it("deploymentRemove removes deployment", (done: MochaDone): void => { - var command: cli.IDeploymentRemoveCommand = { - type: cli.CommandType.deploymentRemove, - appName: "a", - deploymentName: "Staging" - }; - - var removeDeployment: Sinon.SinonSpy = sandbox.spy(cmdexec.sdk, "removeDeployment"); - - cmdexec.execute(command) - .done((): void => { - sinon.assert.calledOnce(removeDeployment); - sinon.assert.calledWithExactly(removeDeployment, "1", "4"); - sinon.assert.calledOnce(log); - sinon.assert.calledWithExactly(log, "Removed deployment \"Staging\" from app \"a\"."); - - done(); - }); - }); - - it("deploymentRemove does not remove deployment if cancelled", (done: MochaDone): void => { - var command: cli.IDeploymentRemoveCommand = { - type: cli.CommandType.deploymentRemove, - appName: "a", - deploymentName: "Staging" - }; - - var removeDeployment: Sinon.SinonSpy = sandbox.spy(cmdexec.sdk, "removeDeployment"); - - wasConfirmed = false; - - cmdexec.execute(command) - .done((): void => { - sinon.assert.notCalled(removeDeployment); - sinon.assert.calledOnce(log); - sinon.assert.calledWithExactly(log, "Remove cancelled."); - - done(); - }); - }); - - it("deploymentRename renames deployment", (done: MochaDone): void => { - var command: cli.IDeploymentRenameCommand = { - type: cli.CommandType.deploymentRename, - appName: "a", - currentDeploymentName: "Staging", - newDeploymentName: "c" - }; - - var updateDeployment: Sinon.SinonSpy = sandbox.spy(cmdexec.sdk, "updateDeployment"); - - cmdexec.execute(command) - .done((): void => { - sinon.assert.calledOnce(updateDeployment); - sinon.assert.calledOnce(log); - sinon.assert.calledWithExactly(log, "Renamed deployment \"Staging\" to \"c\" for app \"a\"."); - - done(); - }); - }); -}); \ No newline at end of file diff --git a/definitions/base-64.d.ts b/definitions/base-64.d.ts deleted file mode 100644 index 3360bee2..00000000 --- a/definitions/base-64.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -declare module "base-64" { - function decode(input: string): string; - function encode(input: string): string; -} \ No newline at end of file diff --git a/definitions/oauth2-server.d.ts b/definitions/oauth2-server.d.ts deleted file mode 100644 index f64a7049..00000000 --- a/definitions/oauth2-server.d.ts +++ /dev/null @@ -1,57 +0,0 @@ -declare module Express { - export interface Request { - user?: any; - } -} - -declare module "oauth2-server" { - import express = require('express'); - - module o { - interface Server { - authorise(): express.RequestHandler; - errorHandler(): express.ErrorRequestHandler; - grant(): express.RequestHandler; - } - - interface Model { - getAccessToken(bearerToken: string, callback: (error: any, accessToken?: AccessToken) => void): void; - getClient(clientId: string, clientSecret: string, callback: (error: any, client?: Client) => void): void; - grantTypeAllowed(clientId: string, grantType: string, callback: (error: any, allowed?: boolean) => void): void; - saveAccessToken(accessToken: string, clientId: string, expires: Date, user: any, callback: (error: any) => void): void; - getUserFromClient(clientId: string, clientSecret: string, callback: (error: any, user?: User) => void): void; - generateToken(type: string, req: Express.Request, callback: (error: any, token?: string) => void): void; - } - - interface AccessToken { - expires: Date; - user?: Object; - userId?: any; - } - - interface Client { - clientId: string; - redirectUri?: string; - } - - interface User { - id: any; - } - - interface ServerOptions { - model: Model; - grants?: string[]; - debug?: boolean; - accessTokenLifetime?: number; - refreshTokenLifetime?: number; - authCodeLifetime?: number; - clientIdRegex?: RegExp; - passthroughErrors?: boolean; - continueAfterResponse?: boolean; - } - } - - function o(options: o.ServerOptions): o.Server; - - export = o; -} diff --git a/definitions/rest-definitions.d.ts b/definitions/rest-definitions.d.ts deleted file mode 100644 index c72a5360..00000000 --- a/definitions/rest-definitions.d.ts +++ /dev/null @@ -1,66 +0,0 @@ -declare module "rest-definitions" { - export interface AccessKey { - id: string; - name: string; - description: string; - } - - export interface PackageInfo { - appVersion: string; - description: string; - label: string; - packageHash: string; - isMandatory: boolean; - } - - export interface UpdateCheckResponse extends PackageInfo { - downloadURL: string; - isAvailable: boolean; - packageSize: number; - updateAppVersion?: boolean; - } - - export interface UpdateCheckRequest { - deploymentKey: string; - appVersion: string; - packageHash: string; - isCompanion: boolean; - } - - export interface Account { - id: string; - username: string; - name: string; - description: string; - email: string; - } - - export interface App { - id: string; - name: string; - description: string; - } - - export interface Deployment { - id: string; - name: string; - description: string; - package?: Package - } - - export interface DeploymentKey { - id: string; - key: string; - name: string; - description: string; - isPrimary: boolean; - } - - export interface Package extends PackageInfo { - size: number; - blobUrl: string; - diffBlobUrl?: string; - diffAgainstPackageHash?: string; - uploadTime: number; - } -} diff --git a/definitions/superagent.d.ts b/definitions/superagent.d.ts deleted file mode 100644 index 7e3a27a6..00000000 --- a/definitions/superagent.d.ts +++ /dev/null @@ -1,110 +0,0 @@ -// Type definitions for SuperAgent 0.15.4 -// Project: https://github.com/visionmedia/superagent -// Definitions by: Alex Varju -// Definitions: https://github.com/borisyankov/DefinitelyTyped - -/// - -declare module "superagent" { - import stream = require('stream'); - - type CallbackHandler = { (err: any, res: request.Response): void; }|{ (res: request.Response): void; }; - - var request: request.SuperAgentStatic; - - module request { - interface SuperAgentStatic extends SuperAgent { - (url: string): SuperAgentRequest; - (method: string, url: string): SuperAgentRequest; - - agent(): SuperAgent; - } - - interface SuperAgent> extends stream.Stream { - get(url: string, callback?: CallbackHandler): Req; - post(url: string, callback?: CallbackHandler): Req; - put(url: string, callback?: CallbackHandler): Req; - head(url: string, callback?: CallbackHandler): Req; - del(url: string, callback?: CallbackHandler): Req; - delete(url: string, callback?: CallbackHandler): Req; - options(url: string, callback?: CallbackHandler): Req; - trace(url: string, callback?: CallbackHandler): Req; - copy(url: string, callback?: CallbackHandler): Req; - lock(url: string, callback?: CallbackHandler): Req; - mkcol(url: string, callback?: CallbackHandler): Req; - move(url: string, callback?: CallbackHandler): Req; - purge(url: string, callback?: CallbackHandler): Req; - propfind(url: string, callback?: CallbackHandler): Req; - proppatch(url: string, callback?: CallbackHandler): Req; - unlock(url: string, callback?: CallbackHandler): Req; - report(url: string, callback?: CallbackHandler): Req; - mkactivity(url: string, callback?: CallbackHandler): Req; - checkout(url: string, callback?: CallbackHandler): Req; - merge(url: string, callback?: CallbackHandler): Req; - // m-search(url: string, callback?: CallbackHandler): Req; - notify(url: string, callback?: CallbackHandler): Req; - subscribe(url: string, callback?: CallbackHandler): Req; - unsubscribe(url: string, callback?: CallbackHandler): Req; - patch(url: string, callback?: CallbackHandler): Req; - search(url: string, callback?: CallbackHandler): Req; - connect(url: string, callback?: CallbackHandler): Req; - - parse(fn: Function): Req; - saveCookies(res: Response): void; - attachCookies(req: Req): void; - } - - interface Response extends NodeJS.ReadableStream { - text: string; - body: any; - files: any; - header: any; - type: string; - charset: string; - status: number; - statusType: number; - info: boolean; - ok: boolean; - redirect: boolean; - clientError: boolean; - serverError: boolean; - error: Error; - accepted: boolean; - noContent: boolean; - badRequest: boolean; - unauthorized: boolean; - notAcceptable: boolean; - notFound: boolean; - forbidden: boolean; - get(header: string): string; - toError(): Error; - } - - interface Request> /* extends NodeJS.WritableStream */ { - abort(): Req; - accept(type: string): Req; - attach(field: string, file: Blob|File, filename?: string): Req; - auth(user: string, pass: string): Req; - buffer(val: boolean): Req; - clearTimeout(): Req; - end(callback?: CallbackHandler): Req; - field(name: string, val: string|Blob|File): Req; - get(field: string): string; - on(name: string, handler: Function): Req; - pipe(stream: NodeJS.WritableStream, options?: Object): stream.Writable; - query(val: Object|string): Req; - send(data: Object|string): Req; - set(field: string, val: string): Req; - set(field: Object): Req; - timeout(ms: number): Req; - type(val: string): Req; - unset(field: string): Req; - withCredentials(): Req; - } - interface SuperAgentRequest extends Request>>> {} - - } - - export = request; -} - diff --git a/definitions/try-json.d.ts b/definitions/try-json.d.ts deleted file mode 100644 index a2fc9e53..00000000 --- a/definitions/try-json.d.ts +++ /dev/null @@ -1,5 +0,0 @@ -declare module "try-json" { - function tryJSON(value: any): any; - - export = tryJSON; -} diff --git a/definitions/yargs.d.ts b/definitions/yargs.d.ts deleted file mode 100644 index 3251e21b..00000000 --- a/definitions/yargs.d.ts +++ /dev/null @@ -1,133 +0,0 @@ -/* -This project is licensed under the MIT license. -Copyrights are respective of each contributor listed at the beginning of each definition file. - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE -*/ - -// Type definitions for yargs -// Project: https://github.com/chevex/yargs -// Definitions by: Martin Poelstra -// Definitions: https://github.com/borisyankov/DefinitelyTyped - -// This file has been changed from the original. - -declare module "yargs" { - - module yargs { - interface Argv { - argv: any; - (...args: any[]): any; - parse(...args: any[]): any; - - alias(shortName: string, longName: string): Argv; - alias(aliases: { [shortName: string]: string }): Argv; - alias(aliases: { [shortName: string]: string[] }): Argv; - - default(key: string, value: any): Argv; - default(defaults: { [key: string]: any }): Argv; - - demand(key: string, msg: string): Argv; - demand(key: string, required?: boolean): Argv; - demand(keys: string[], msg: string): Argv; - demand(keys: string[], required?: boolean): Argv; - demand(positionals: number, required?: boolean): Argv; - demand(positionals: number, msg: string): Argv; - demand(count: number, max?: number, msg?: string): Argv; // new from original - - require(key: string, msg: string): Argv; - require(key: string, required: boolean): Argv; - require(keys: number[], msg: string): Argv; - require(keys: number[], required: boolean): Argv; - require(positionals: number, required: boolean): Argv; - require(positionals: number, msg: string): Argv; - - required(key: string, msg: string): Argv; - required(key: string, required: boolean): Argv; - required(keys: number[], msg: string): Argv; - required(keys: number[], required: boolean): Argv; - required(positionals: number, required: boolean): Argv; - required(positionals: number, msg: string): Argv; - - requiresArg(key: string): Argv; - requiresArg(keys: string[]): Argv; - - describe(key: string, description: string): Argv; - describe(descriptions: { [key: string]: string }): Argv; - - option(key: string, options: Options): Argv; - option(options: { [key: string]: Options }): Argv; - options(key: string, options: Options): Argv; - options(options: { [key: string]: Options }): Argv; - - usage(message: string, options?: { [key: string]: Options }): Argv; - usage(options?: { [key: string]: Options }): Argv; - - command(command: string, description: string, func?: (yargs: Argv) => any): Argv; // changed from original - - example(command: string, description: string): Argv; - - check(func: (argv: any, aliases: { [alias: string]: string }) => any): Argv; - - boolean(key: string): Argv; - boolean(keys: string[]): Argv; - - string(key: string): Argv; - string(keys: string[]): Argv; - - config(key: string): Argv; - config(keys: string[]): Argv; - - wrap(columns: number): Argv; - - strict(): Argv; - - help(): string; - help(option: string, description?: string): Argv; - - version(version: string, option?: string, description?: string): Argv; - - showHelpOnFail(enable: boolean, message?: string): Argv; - - showHelp(func?: (message: string) => any): Argv; - - terminalWidth(): number; // new from original - - /* Undocumented */ - - normalize(key: string): Argv; - normalize(keys: string[]): Argv; - - implies(key: string, value: string): Argv; - implies(implies: { [key: string]: string }): Argv; - - count(key: string): Argv; - count(keys: string[]): Argv; - - fail(func: (msg: string) => any): Argv; // changed from original - } - - interface Options { - type?: string; - alias?: any; - demand?: any; - required?: any; - require?: any; - default?: any; - boolean?: any; - string?: any; - count?: any; - describe?: any; - description?: any; - desc?: any; - requiresArg?: any; - } - } - - var yargs: yargs.Argv; - export = yargs; -} diff --git a/definitions/yazl.d.ts b/definitions/yazl.d.ts deleted file mode 100644 index d3ac8f1b..00000000 --- a/definitions/yazl.d.ts +++ /dev/null @@ -1,32 +0,0 @@ -declare module "yazl" { - import * as events from "events"; - import * as stream from "stream"; - - export interface IDosDateTime { - date: number; - time: number; - } - - export interface IFinalSizeCallback { - (finalSize: number): void; - } - - export interface IOptions { - compress?: boolean; - mode?: number; - mtime?: Date; - size?: number; - } - - export function dateToDosDateTime(date: Date): IDosDateTime; - - export class ZipFile extends events.EventEmitter { - outputStream: stream.Readable; - - public addBuffer(buffer: Buffer, metadataPath: string, options?: IOptions): void; - public addEmptyDirectory(metadataPath: string, options?: IOptions): void; - public addFile(realPath: string, metadataPath: string, options?: IOptions): void; - public addReadStream(readStream: stream.Readable, metadataPath: string, options?: IOptions): void; - public end(finalSizeCallback?: IFinalSizeCallback): void; - } -} \ No newline at end of file diff --git a/gulp/build.js b/gulp/build.js deleted file mode 100644 index 5e3599cd..00000000 --- a/gulp/build.js +++ /dev/null @@ -1,9 +0,0 @@ -"use strict"; - -var gulp = require("gulp"); - -gulp.task("build-sdk", ["content-sdk", "scripts-sdk"]); - -gulp.task("build-cli", ["content-cli", "scripts-cli"]); - -gulp.task("build", ["build-sdk", "build-cli"]); diff --git a/gulp/content.js b/gulp/content.js deleted file mode 100644 index 12975909..00000000 --- a/gulp/content.js +++ /dev/null @@ -1,28 +0,0 @@ -"use strict"; - -var gulp = require("gulp"); -var merge = require("merge2"); -var plugins = require("gulp-load-plugins")(); - -function contentTask(cwd) { - var options = { - cwd: cwd, - base: "./" + cwd - }; - - return gulp.src([ - "{script,test}/**/*.{css,ejs,html,js,json,png,xml}", - "test/resources/**/*", - "*.{public,private}", - "package.json", - "plugin.xml", - "server.js", - "web.config", - ".npmignore", - "README.md" - ], options) - .pipe(gulp.dest("bin", options)); -} - -gulp.task("content-sdk", function() { return contentTask("sdk"); }); -gulp.task("content-cli", function() { return contentTask("cli"); }); diff --git a/gulp/default.js b/gulp/default.js deleted file mode 100644 index 77f87f0e..00000000 --- a/gulp/default.js +++ /dev/null @@ -1,5 +0,0 @@ -"use strict"; - -var gulp = require("gulp"); - -gulp.task("default", ["build"]); \ No newline at end of file diff --git a/gulp/install.js b/gulp/install.js deleted file mode 100644 index 6cfdb295..00000000 --- a/gulp/install.js +++ /dev/null @@ -1,13 +0,0 @@ -var gulp = require("gulp"); -var install = require("gulp-install"); -var path = require("path"); -var runSequence = require("run-sequence"); - -gulp.task("install", function(done) { - var packages = [ - path.join(__dirname, "..", "sdk", "package.json"), - path.join(__dirname, "..", "cli", "package.json") - ]; - return gulp.src(packages) - .pipe(install()); -}); diff --git a/gulp/link.js b/gulp/link.js deleted file mode 100644 index 0feec611..00000000 --- a/gulp/link.js +++ /dev/null @@ -1,61 +0,0 @@ -var gulp = require("gulp"); -var which = require("which"); -var path = require("path"); -var spawn = require("child_process").spawn; -var runSequence = require("run-sequence"); - -function linkPackage(folder, createBinLinks, callback) { - if (typeof createBinLinks === "function") { - callback = createBinLinks; - createBinLinks = false; - } - - which("npm", function(err, resolvedPath) { - if (err) return callback(err); - - var args = ["link"]; - if (!createBinLinks) { - args.push("--no-bin-links"); - } - - var link = spawn(resolvedPath, args, {cwd: folder}); - link.stdout.pipe(process.stdout); - link.stderr.pipe(process.stderr); - link.on("close", callback); - }); -} - -function linkDependency(folder, sourcePackage, callback) { - which("npm", function(err, resolvedPath) { - if (err) return callback(err); - - var link = spawn(resolvedPath, ["link", sourcePackage], {cwd: folder}); - link.stdout.pipe(process.stdout); - link.stderr.pipe(process.stderr); - link.on("close", callback); - }); -} - -gulp.task("link-sdk", ["content-sdk"], function(done) { - linkPackage(path.join(__dirname, "..", "sdk", "bin"), done); -}); - -gulp.task("link-cli", function(done) { - linkDependency(path.join(__dirname, "..", "cli"), "code-push", done); -}); - -gulp.task("link-cli-bin", function(done) { - linkDependency(path.join(__dirname, "..", "cli"), "code-push", function() { - runSequence("build-cli", function() { - linkPackage(path.join(__dirname, "..", "cli", "bin"), /*createBinLinks=*/ true, done); - }); - }); -}); - -gulp.task("link", function(done) { - runSequence("link-sdk", "link-cli", done); -}); - -gulp.task("link-bin", function(done) { - runSequence("link-sdk", "link-cli-bin", done); -}); diff --git a/gulp/scripts.js b/gulp/scripts.js deleted file mode 100644 index 2904eb60..00000000 --- a/gulp/scripts.js +++ /dev/null @@ -1,91 +0,0 @@ -"use strict"; - -var chmod = require("chmod"); -var fs = require("fs"); -var gulp = require("gulp"); -var plugins = require("gulp-load-plugins")(); -var through = require("through2"); -var tsc = require("typescript"); -var tsJsxLoader = require("ts-jsx-loader"); -var merge = require("merge2"); -var dtsGenerator = require("dts-generator"); - -var generatedDefinitionDependencies = { - sdk: [], - cli: ["code-push"] -}; - -function tsJsxPipe(file, enc, cb) { - var fileContent = file.contents.toString(); - file.contents = new Buffer(tsJsxLoader.call({cacheable: function() {} }, fileContent), enc); - cb(null, file); -} - -function scriptTask(cwd, jsx) { - var options = { - cwd: __dirname + "/../" + cwd, - base: __dirname + "/../" + cwd - }; - - var generatedDefinitions = [ - "definitions/external/**/*.d.ts", - "definitions/*.d.ts" - ].concat(generatedDefinitionDependencies[cwd].map(function(dep) { - return "definitions/generated/" + dep + ".d.ts"; - })); - - var tsProj = plugins.typescript.createProject("tsconfig.json", { typescript: tsc, declarationFiles: true }); - - var fullReporter = plugins.typescript.reporter.fullReporter(/*fullFileName=*/ true); - var errorCatch = through.obj(); - - var tsResult = merge([ - gulp.src("{script,test,definitions}/**/*.ts", options), - gulp.src(generatedDefinitions)]) - .pipe(plugins.if(jsx, through.obj(tsJsxPipe))) - .pipe(plugins.typescript(tsProj, /* filterSettings=*/ undefined, { - error: fullReporter.error, - finish: function(results) { - fullReporter.finish(results); - if (results.syntaxErrors || results.globalErrors || results.semanticErrors || results.emitErrors) { - if (!process.env.WATCHING) { - errorCatch.emit("error", new plugins.util.PluginError("gulp-typescript", "TypeScript compilation failed")); - } - } - } - })); - - return merge([ - tsResult.js.pipe(gulp.dest("bin", options)), - tsResult.dts.pipe(gulp.dest("bin/definitions", options)) - ]) - .pipe(errorCatch); -} - -function makeExecutable(path) { - var contents = fs.readFileSync(path); - fs.writeFileSync(path, "#!/usr/bin/env node\n" + contents); - chmod(path, {execute: true}); -} - -gulp.task("scripts-external", ["tsd"]); - -gulp.task("scripts-compile-sdk", ["scripts-external"], function() { return scriptTask("sdk"); }); -gulp.task("scripts-compile-cli", ["scripts-sdk", "scripts-external"], function() { return scriptTask("cli"); }); - -gulp.task("scripts-chmod-cli", ["scripts-compile-cli"], function() { - makeExecutable(__dirname + "/../cli/bin/script/cli.js"); -}); - -gulp.task("scripts-dtsbundle-sdk", ["scripts-compile-sdk"], function () { - dtsGenerator.generate({ - name: "code-push", - main: "code-push/script/index", - baseDir: "sdk/bin/definitions", - files: ["script/acquisition-sdk.d.ts", "script/index.d.ts"], - out: "definitions/generated/code-push.d.ts" - }); -}); - -gulp.task("scripts-sdk", ["scripts-dtsbundle-sdk"]); -gulp.task("scripts-cli", ["scripts-chmod-cli"]); diff --git a/gulp/test.js b/gulp/test.js deleted file mode 100644 index 79f05562..00000000 --- a/gulp/test.js +++ /dev/null @@ -1,55 +0,0 @@ -"use strict"; - -var gulp = require("gulp"); -var plugins = require("gulp-load-plugins")(); - -var mochaConfig = { - reporter: process.env.REPORTER || "spec", - timeout: parseInt(process.env.TIMEOUT) || 5000 -}; - -var projects = { - "sdk": ["build-sdk"], - "cli": ["build-cli"] -}; - -var projectNames = Object.keys(projects); - -var testPaths = projectNames.map(testPathFromName); -var sourcesPaths = projectNames.map(sourcePathFromName); - -function testPathFromName(name) { - return name + "/bin/test/**/*.js" -} - -function sourcePathFromName(name) { - return name + "/bin/script/**/*.js" -} - -function runTests(sources, tests, done) { - require("dotenv").config({ path: ".test.env", silent: true }); - gulp.src(sources) - .pipe(plugins.istanbul({includeUntested: true})) - .pipe(plugins.istanbul.hookRequire()) - .on("finish", function() { - gulp.src(tests, { read: false }) - .pipe(plugins.if(process.env.WATCHING, plugins.plumber())) - .pipe(plugins.mocha(mochaConfig)) - .pipe(plugins.istanbul.writeReports()) - .on("end", done); - }); -} - -function testTask(name, done) { - runTests([sourcePathFromName(name)], [testPathFromName(name)], done); -} - -projectNames.forEach(function(projectName) { - var projectDeps = projects[projectName]; - - gulp.task("test-" + projectName, projectDeps, function (done) { testTask(projectName, done); }); -}); - -gulp.task("test", ["build"], function(done) { - runTests(sourcesPaths, testPaths, done); -}); diff --git a/gulp/tsd.js b/gulp/tsd.js deleted file mode 100644 index 2911cf44..00000000 --- a/gulp/tsd.js +++ /dev/null @@ -1,11 +0,0 @@ -"use strict"; - -var gulp = require("gulp"); -var plugins = require("gulp-load-plugins")(); - -gulp.task("tsd", function(done) { - plugins.tsd({ - command: "reinstall", - config: "tsd.json" - }, done); -}); diff --git a/migration-notice.md b/migration-notice.md new file mode 100644 index 00000000..e5d399d4 --- /dev/null +++ b/migration-notice.md @@ -0,0 +1,74 @@ +# Migration notice + +## CodePush SDK + +CodePush SDK migrated to a new service. We recommend updating CodePush SDK to the latest version. Versions lower than **[4.0.0](https://github.com/microsoft/code-push/releases/tag/v4.0.0)** will not work in the near future. + +### Deprecated methods + +* getSessions +* patchAccessKey +* removeSession +* getAccessKey + +These methods are not supported in versions **[4.0.0](https://github.com/microsoft/code-push/releases/tag/v4.0.0)** and above. + +### Comparison + +* Server URL + + * versions **[4.0.0](https://github.com/microsoft/code-push/releases/tag/v4.0.0)** and above: + + `https://api.appcenter.ms/v0.1` + + * versions lower than **[4.0.0](https://github.com/microsoft/code-push/releases/tag/v4.0.0)**: + + `https://codepush-management.azurewebsites.net` + + `https://codepush.appcenter.ms/v0.1/legacy` + +* Request's path + + For example the path of `getDeployments` method: + + * versions **[4.0.0](https://github.com/microsoft/code-push/releases/tag/v4.0.0)** and above: + + `https://api.appcenter.ms/v0.1/apps/{owner_name}/{app_name}/deployments/` + + * versions lower than **[4.0.0](https://github.com/microsoft/code-push/releases/tag/v4.0.0)**: + + `https://codepush-management.azurewebsites.net/apps/{app_name}/deployments/` + +* Error handling + + Error messages differ. Status codes only differ when the application is not found for the owner: + + * versions **[4.0.0](https://github.com/microsoft/code-push/releases/tag/v4.0.0)** and above: + + ```javascript + { + message: 'Not found. Correlation ID: *****', + statusCode: 404 + } + ``` + + * versions lower than **[4.0.0](https://github.com/microsoft/code-push/releases/tag/v4.0.0)**: + + ```javascript + { + message: 'Internal Server Error', + statusCode: 500 + } + ``` + +* Method signature + + Methods signature was not changed. All methods work the same as in previous versions. + +## CodePush CLI + +The CodePush CLI **[3.0.0](https://www.npmjs.com/package/code-push-cli/v/3.0.0)** is the latest and last version for this CLI. We no longer update the CodePush CLI and recommend migrating to the App Center CLI (). + +## React-native-code-push + +React-native-code-push versions lower than **[5.7.0](https://github.com/microsoft/react-native-code-push/releases/tag/v5.7.0)** will stop working in the near future. diff --git a/mocha.opts b/mocha.opts new file mode 100644 index 00000000..9fbbfec4 --- /dev/null +++ b/mocha.opts @@ -0,0 +1,3 @@ +--exit +--reporter spec +--timeout 5000 diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..6f6bd5c2 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,3519 @@ +{ + "name": "code-push", + "version": "4.2.3", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "code-push", + "version": "4.2.3", + "license": "MIT", + "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" + }, + "devDependencies": { + "@types/mocha": "^9.0.0", + "@types/node": "^14.0.27", + "@types/slash": "^3.0.0", + "@types/superagent": "^4.1.13", + "@types/yazl": "^2.4.2", + "mocha": "^9.2.0", + "shx": "^0.3.4", + "superagent-mock": "^4.0.0", + "typescript": "^5.1.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/cookiejar": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.1.tgz", + "integrity": "sha512-aRnpPa7ysx3aNW60hTiCtLHlQaIFsXFCgQlpakNgDNVFzbtusSY8PwjAQgRWfSk0ekNoBjO51eQRB6upA9uuyw==", + "dev": true + }, + "node_modules/@types/mocha": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-9.0.0.tgz", + "integrity": "sha512-scN0hAWyLVAvLR9AyW7HoFF5sJZglyBsbPuHO4fv7JRvfmPBMfp1ozWqOf/e4wwPNxezBZXRfWzMb6iFLgEVRA==", + "dev": true + }, + "node_modules/@types/node": { + "version": "14.0.27", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.27.tgz", + "integrity": "sha512-kVrqXhbclHNHGu9ztnAwSncIgJv/FaxmzXJvGXNdcCpV1b8u1/Mi6z6m0vwy0LzKeXFTPLH0NzwmoJ3fNCIq0g==", + "dev": true + }, + "node_modules/@types/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-bmUaw0IUPUVldtj4YwU7tbzxllQQgsWdnB45bwTI0f1Lq2Yg8lT2yxV4OGZrMTrP/G9v8eVIhX130xPe/RfPfw==", + "deprecated": "This is a stub types definition. slash provides its own type definitions, so you do not need this installed.", + "dev": true, + "dependencies": { + "slash": "*" + } + }, + "node_modules/@types/superagent": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-4.1.13.tgz", + "integrity": "sha512-YIGelp3ZyMiH0/A09PMAORO0EBGlF5xIKfDpK74wdYvWUs2o96b5CItJcWPdH409b7SAXIIG6p8NdU/4U2Maww==", + "dev": true, + "dependencies": { + "@types/cookiejar": "*", + "@types/node": "*" + } + }, + "node_modules/@types/yazl": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@types/yazl/-/yazl-2.4.2.tgz", + "integrity": "sha512-T+9JH8O2guEjXNxqmybzQ92mJUh2oCwDDMSSimZSe1P+pceZiFROZLYmcbqkzV5EUwz6VwcKXCO2S2yUpra6XQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@ungap/promise-all-settled": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", + "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", + "dev": true + }, + "node_modules/agent-base": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", + "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/agent-base/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "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/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/superagent": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-5.1.0.tgz", + "integrity": "sha512-7V6JVx5N+eTL1MMqRBX0v0bG04UjrjAvvZJTF/VDH/SH2GjSLqlrcYepFlpTrXpm37aSY6h3GGVWGxXl/98TKA==", + "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/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==" + }, + "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==", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "node_modules/balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "node_modules/basic-ftp": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.3.tgz", + "integrity": "sha512-QHX8HLlncOLpy54mh+k/sWIFd0ThmRqwe9ZjELybGZK+tZ8rUb9VO0saKJUROTbE+KhzDUT7xziGpGrW8Kmd+g==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", + "engines": { + "node": "*" + } + }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/camelcase": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", + "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "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==" + }, + "node_modules/data-uri-to-buffer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-5.0.1.tgz", + "integrity": "sha512-a9l6T1qqDogvvnw0nKlfZzqsyikEBZBClF39V3TFoKhDtGBqHu2HkuomJc02j5zft8zrUaXEuoicLeW54RkzPg==", + "engines": { + "node": ">= 14" + } + }, + "node_modules/debug": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/detect-node": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.0.4.tgz", + "integrity": "sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw==" + }, + "node_modules/dezalgo": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", + "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", + "dependencies": { + "asap": "^2.0.0", + "wrappy": "1" + } + }, + "node_modules/diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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==", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "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==", + "engines": { + "node": ">=0.10.0" + } + }, + "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==" + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "bin": { + "flat": "cli.js" + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/formidable": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.2.tgz", + "integrity": "sha512-V8gLm+41I/8kguQ4/o1D3RIHRmhYFG4pnNyonvua+40rqcEmT4+V71yaZ3B457xbbgCsCfjSPi65u/W6vK1U5Q==", + "funding": { + "url": "https://ko-fi.com/tunnckoCore/commissions" + } + }, + "node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz", + "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==", + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-uri": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.1.tgz", + "integrity": "sha512-7ZqONUVqaabogsYNWlYj0t3YZaL6dhuEueZXGF+/YVmf6dHmaFg8/6psJKqhx9QykIDKzpGcy2cn4oV4YC7V/Q==", + "dependencies": { + "basic-ftp": "^5.0.2", + "data-uri-to-buffer": "^5.0.1", + "debug": "^4.3.4", + "fs-extra": "^8.1.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/get-uri/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "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==" + }, + "node_modules/growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true, + "engines": { + "node": ">=4.x" + } + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "bin": { + "he": "bin/he" + } + }, + "node_modules/hexoid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz", + "integrity": "sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==", + "engines": { + "node": ">=8" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.0.tgz", + "integrity": "sha512-+ZT+iBxVUQ1asugqnD6oWoRiS25AkjNfG085dKJGtGxkdwLQrMKU5wJr2bOOFAXzKcTuqq+7fZlTMgG3SRfIYQ==", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/http-proxy-agent/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.1.tgz", + "integrity": "sha512-Eun8zV0kcYS1g19r78osiQLEFIRspRUDd9tIfBCTBPBeMieF/EsJNL8VI3xOIdYRDEkjQnqOYPsZ2DsWsVsFwQ==", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/interpret": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/ip-address": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", + "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", + "dependencies": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.1.tgz", + "integrity": "sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "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==" + }, + "node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mime-db": { + "version": "1.43.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.43.0.tgz", + "integrity": "sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.26", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.26.tgz", + "integrity": "sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==", + "dependencies": { + "mime-db": "1.43.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", + "dev": true + }, + "node_modules/mocha": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.2.2.tgz", + "integrity": "sha512-L6XC3EdwT6YrIk0yXpavvLkn8h+EU+Y5UcCHKECyMbdUIxyMuZj4bX4U9e1nvnvUUvQVsV2VHQr5zLdcUkhW/g==", + "dev": true, + "dependencies": { + "@ungap/promise-all-settled": "1.1.2", + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.5.3", + "debug": "4.3.3", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.2.0", + "growl": "1.10.5", + "he": "1.2.0", + "js-yaml": "4.1.0", + "log-symbols": "4.1.0", + "minimatch": "4.2.1", + "ms": "2.1.3", + "nanoid": "3.3.1", + "serialize-javascript": "6.0.0", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "which": "2.0.2", + "workerpool": "6.2.0", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mochajs" + } + }, + "node_modules/mocha/node_modules/minimatch": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-4.2.1.tgz", + "integrity": "sha512-9Uq1ChtSZO+Mxa/CL1eGizn2vRn3MlLgzhT0Iz8zaY8NdvxvB0d5QdPFmCKf7JKA9Lerx5vRrnwO03jsSfGG9g==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mocha/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==", + "dev": true + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/nanoid": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.1.tgz", + "integrity": "sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==", + "dev": true, + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/netmask": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", + "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", + "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pac-proxy-agent": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.0.0.tgz", + "integrity": "sha512-t4tRAMx0uphnZrio0S0Jw9zg3oDbz1zVhQ/Vy18FjLfP1XOLNUEjaVxYCYRI6NS+BsMBXKIzV6cTLOkO9AtywA==", + "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.0", + "pac-resolver": "^7.0.0", + "socks-proxy-agent": "^8.0.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-proxy-agent/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "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/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/proxy-agent": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.3.0.tgz", + "integrity": "sha512-0LdR757eTj/JfuU7TL2YCuAZnxWXu3tkJbg4Oq3geW/qFNT/32T0sp2HnZ9O0lMR4q3vwAt0+xCA8SR0WAD0og==", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.0", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.0.0", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-agent/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "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==" + }, + "node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/querystringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.1.1.tgz", + "integrity": "sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA==" + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/readable-stream": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz", + "integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", + "dev": true, + "dependencies": { + "resolve": "^1.1.6" + }, + "engines": { + "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/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=" + }, + "node_modules/resolve": { + "version": "1.22.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", + "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.8.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "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/serialize-javascript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/shelljs": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz", + "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==", + "dev": true, + "dependencies": { + "glob": "^7.0.0", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" + }, + "bin": { + "shjs": "bin/shjs" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/shx": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/shx/-/shx-0.3.4.tgz", + "integrity": "sha512-N6A9MLVqjxZYcVn8hLmtneQWIJtp8IKzMP4eMnx+nqkvXoqinUPCbUFLp2UcWTEIUONhlk0ewxr/jaVGlc+J+g==", + "dev": true, + "dependencies": { + "minimist": "^1.2.3", + "shelljs": "^0.8.5" + }, + "bin": { + "shx": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "engines": { + "node": ">=8" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz", + "integrity": "sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==", + "dependencies": { + "ip-address": "^9.0.5", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.1.tgz", + "integrity": "sha512-59EjPbbgg8U3x62hhKOFVAmySQUcfRQ4C7Q/D5sEHnZTQRrQlNKINks44DMR1gwXp0p4LaVIeccX2KHTTcHVqQ==", + "dependencies": { + "agent-base": "^7.0.1", + "debug": "^4.3.4", + "socks": "^2.7.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/socks-proxy-agent/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "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==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "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==" + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/superagent": { + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-8.0.6.tgz", + "integrity": "sha512-HqSe6DSIh3hEn6cJvCkaM1BLi466f1LHi4yubR0tpewlMpk4RUFFy35bKz8SsPBwYfIIJy5eclp+3tCYAuX0bw==", + "dependencies": { + "component-emitter": "^1.3.0", + "cookiejar": "^2.1.3", + "debug": "^4.3.4", + "fast-safe-stringify": "^2.1.1", + "form-data": "^4.0.0", + "formidable": "^2.1.1", + "methods": "^1.1.2", + "mime": "2.6.0", + "qs": "^6.11.0", + "semver": "^7.3.8" + }, + "engines": { + "node": ">=6.4.0 <13 || >=14" + } + }, + "node_modules/superagent-mock": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/superagent-mock/-/superagent-mock-4.0.0.tgz", + "integrity": "sha512-+xj+q+sL5sJIcFxwmj5Wuq59Kns0ocOd8OrMkbEXEwseWImAVvM7cP8G7raQRZ+vloUN32t/yKosD+YAXa9rcg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "superagent": ">=3.6.0" + } + }, + "node_modules/superagent/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/superagent/node_modules/formidable": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-2.1.1.tgz", + "integrity": "sha512-0EcS9wCFEzLvfiks7omJ+SiYJAiD+TzK4Pcw1UlUoGnhUxDcMKjt0P7x8wEb0u6OHu8Nb98WG3nxtlF5C7bvUQ==", + "dependencies": { + "dezalgo": "^1.0.4", + "hexoid": "^1.0.0", + "once": "^1.4.0", + "qs": "^6.11.0" + }, + "funding": { + "url": "https://ko-fi.com/tunnckoCore/commissions" + } + }, + "node_modules/superagent/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/superagent/node_modules/semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/superagent/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tslib": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.1.tgz", + "integrity": "sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig==" + }, + "node_modules/typescript": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz", + "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "engines": { + "node": ">= 4.0.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", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/workerpool": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.0.tgz", + "integrity": "sha512-Rsk5qQHJ9eowMH28Jwhe8HEbmdYDX4lwoMWshiCXugjtHqMD9ZbiqSDLxcsfdqsETPzVUtX5s1Z5kStiIM6l4A==", + "dev": true + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "dependencies": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + }, + "dependencies": { + "@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==" + }, + "@types/cookiejar": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.1.tgz", + "integrity": "sha512-aRnpPa7ysx3aNW60hTiCtLHlQaIFsXFCgQlpakNgDNVFzbtusSY8PwjAQgRWfSk0ekNoBjO51eQRB6upA9uuyw==", + "dev": true + }, + "@types/mocha": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-9.0.0.tgz", + "integrity": "sha512-scN0hAWyLVAvLR9AyW7HoFF5sJZglyBsbPuHO4fv7JRvfmPBMfp1ozWqOf/e4wwPNxezBZXRfWzMb6iFLgEVRA==", + "dev": true + }, + "@types/node": { + "version": "14.0.27", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.27.tgz", + "integrity": "sha512-kVrqXhbclHNHGu9ztnAwSncIgJv/FaxmzXJvGXNdcCpV1b8u1/Mi6z6m0vwy0LzKeXFTPLH0NzwmoJ3fNCIq0g==", + "dev": true + }, + "@types/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-bmUaw0IUPUVldtj4YwU7tbzxllQQgsWdnB45bwTI0f1Lq2Yg8lT2yxV4OGZrMTrP/G9v8eVIhX130xPe/RfPfw==", + "dev": true, + "requires": { + "slash": "*" + } + }, + "@types/superagent": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-4.1.13.tgz", + "integrity": "sha512-YIGelp3ZyMiH0/A09PMAORO0EBGlF5xIKfDpK74wdYvWUs2o96b5CItJcWPdH409b7SAXIIG6p8NdU/4U2Maww==", + "dev": true, + "requires": { + "@types/cookiejar": "*", + "@types/node": "*" + } + }, + "@types/yazl": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@types/yazl/-/yazl-2.4.2.tgz", + "integrity": "sha512-T+9JH8O2guEjXNxqmybzQ92mJUh2oCwDDMSSimZSe1P+pceZiFROZLYmcbqkzV5EUwz6VwcKXCO2S2yUpra6XQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@ungap/promise-all-settled": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", + "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", + "dev": true + }, + "agent-base": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", + "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", + "requires": { + "debug": "^4.3.4" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "requires": { + "ms": "2.1.2" + } + } + } + }, + "ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "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==", + "requires": { + "detect-node": "^2.0.4", + "superagent": "5.1.0", + "url-parse": "^1.4.7" + }, + "dependencies": { + "form-data": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", + "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "superagent": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-5.1.0.tgz", + "integrity": "sha512-7V6JVx5N+eTL1MMqRBX0v0bG04UjrjAvvZJTF/VDH/SH2GjSLqlrcYepFlpTrXpm37aSY6h3GGVWGxXl/98TKA==", + "requires": { + "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" + } + } + } + }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==" + }, + "ast-types": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", + "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", + "requires": { + "tslib": "^2.0.1" + } + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "basic-ftp": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.3.tgz", + "integrity": "sha512-QHX8HLlncOLpy54mh+k/sWIFd0ThmRqwe9ZjELybGZK+tZ8rUb9VO0saKJUROTbE+KhzDUT7xziGpGrW8Kmd+g==" + }, + "binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "requires": { + "fill-range": "^7.1.1" + } + }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=" + }, + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, + "camelcase": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", + "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", + "dev": true + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "dependencies": { + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "requires": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "fsevents": "~2.3.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + } + }, + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + } + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "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==" + }, + "data-uri-to-buffer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-5.0.1.tgz", + "integrity": "sha512-a9l6T1qqDogvvnw0nKlfZzqsyikEBZBClF39V3TFoKhDtGBqHu2HkuomJc02j5zft8zrUaXEuoicLeW54RkzPg==" + }, + "debug": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "requires": { + "ms": "2.1.2" + } + }, + "decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true + }, + "degenerator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", + "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", + "requires": { + "ast-types": "^0.13.4", + "escodegen": "^2.1.0", + "esprima": "^4.0.1" + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, + "detect-node": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.0.4.tgz", + "integrity": "sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw==" + }, + "dezalgo": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", + "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", + "requires": { + "asap": "^2.0.0", + "wrappy": "1" + } + }, + "diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "requires": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2", + "source-map": "~0.6.1" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" + }, + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==" + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==" + }, + "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==" + }, + "fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true + }, + "form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + }, + "formidable": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.2.tgz", + "integrity": "sha512-V8gLm+41I/8kguQ4/o1D3RIHRmhYFG4pnNyonvua+40rqcEmT4+V71yaZ3B457xbbgCsCfjSPi65u/W6vK1U5Q==" + }, + "fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "get-intrinsic": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz", + "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==", + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.3" + } + }, + "get-uri": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.1.tgz", + "integrity": "sha512-7ZqONUVqaabogsYNWlYj0t3YZaL6dhuEueZXGF+/YVmf6dHmaFg8/6psJKqhx9QykIDKzpGcy2cn4oV4YC7V/Q==", + "requires": { + "basic-ftp": "^5.0.2", + "data-uri-to-buffer": "^5.0.1", + "debug": "^4.3.4", + "fs-extra": "^8.1.0" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "requires": { + "ms": "2.1.2" + } + } + } + }, + "glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "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==" + }, + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" + }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true + }, + "hexoid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz", + "integrity": "sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==" + }, + "http-proxy-agent": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.0.tgz", + "integrity": "sha512-+ZT+iBxVUQ1asugqnD6oWoRiS25AkjNfG085dKJGtGxkdwLQrMKU5wJr2bOOFAXzKcTuqq+7fZlTMgG3SRfIYQ==", + "requires": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "requires": { + "ms": "2.1.2" + } + } + } + }, + "https-proxy-agent": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.1.tgz", + "integrity": "sha512-Eun8zV0kcYS1g19r78osiQLEFIRspRUDd9tIfBCTBPBeMieF/EsJNL8VI3xOIdYRDEkjQnqOYPsZ2DsWsVsFwQ==", + "requires": { + "agent-base": "^7.0.2", + "debug": "4" + } + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "interpret": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", + "dev": true + }, + "ip-address": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", + "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", + "requires": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + } + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-core-module": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.1.tgz", + "integrity": "sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true + }, + "is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "jsbn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==" + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, + "log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "requires": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + } + }, + "lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==" + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" + }, + "mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==" + }, + "mime-db": { + "version": "1.43.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.43.0.tgz", + "integrity": "sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==" + }, + "mime-types": { + "version": "2.1.26", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.26.tgz", + "integrity": "sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==", + "requires": { + "mime-db": "1.43.0" + } + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", + "dev": true + }, + "mocha": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.2.2.tgz", + "integrity": "sha512-L6XC3EdwT6YrIk0yXpavvLkn8h+EU+Y5UcCHKECyMbdUIxyMuZj4bX4U9e1nvnvUUvQVsV2VHQr5zLdcUkhW/g==", + "dev": true, + "requires": { + "@ungap/promise-all-settled": "1.1.2", + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.5.3", + "debug": "4.3.3", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.2.0", + "growl": "1.10.5", + "he": "1.2.0", + "js-yaml": "4.1.0", + "log-symbols": "4.1.0", + "minimatch": "4.2.1", + "ms": "2.1.3", + "nanoid": "3.3.1", + "serialize-javascript": "6.0.0", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "which": "2.0.2", + "workerpool": "6.2.0", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" + }, + "dependencies": { + "minimatch": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-4.2.1.tgz", + "integrity": "sha512-9Uq1ChtSZO+Mxa/CL1eGizn2vRn3MlLgzhT0Iz8zaY8NdvxvB0d5QdPFmCKf7JKA9Lerx5vRrnwO03jsSfGG9g==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + } + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "nanoid": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.1.tgz", + "integrity": "sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==", + "dev": true + }, + "netmask": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", + "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==" + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "object-inspect": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", + "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==" + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + }, + "pac-proxy-agent": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.0.0.tgz", + "integrity": "sha512-t4tRAMx0uphnZrio0S0Jw9zg3oDbz1zVhQ/Vy18FjLfP1XOLNUEjaVxYCYRI6NS+BsMBXKIzV6cTLOkO9AtywA==", + "requires": { + "@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.0", + "pac-resolver": "^7.0.0", + "socks-proxy-agent": "^8.0.1" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "requires": { + "ms": "2.1.2" + } + } + } + }, + "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==", + "requires": { + "degenerator": "^5.0.0", + "netmask": "^2.0.2" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true + }, + "proxy-agent": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.3.0.tgz", + "integrity": "sha512-0LdR757eTj/JfuU7TL2YCuAZnxWXu3tkJbg4Oq3geW/qFNT/32T0sp2HnZ9O0lMR4q3vwAt0+xCA8SR0WAD0og==", + "requires": { + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.0", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.0.0", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.1" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "requires": { + "ms": "2.1.2" + } + } + } + }, + "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==" + }, + "qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "requires": { + "side-channel": "^1.0.4" + } + }, + "querystringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.1.1.tgz", + "integrity": "sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA==" + }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "readable-stream": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz", + "integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", + "dev": true, + "requires": { + "resolve": "^1.1.6" + } + }, + "recursive-fs": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/recursive-fs/-/recursive-fs-2.1.0.tgz", + "integrity": "sha512-oed3YruYsD52Mi16s/07eYblQOLi5dTtxpIJNdfCEJ7S5v8dDgVcycar0pRWf4IBuPMIkoctC8RTqGJzIKMNAQ==" + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=" + }, + "resolve": { + "version": "1.22.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", + "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==", + "dev": true, + "requires": { + "is-core-module": "^2.8.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==" + }, + "serialize-javascript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } + }, + "shelljs": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz", + "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==", + "dev": true, + "requires": { + "glob": "^7.0.0", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" + } + }, + "shx": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/shx/-/shx-0.3.4.tgz", + "integrity": "sha512-N6A9MLVqjxZYcVn8hLmtneQWIJtp8IKzMP4eMnx+nqkvXoqinUPCbUFLp2UcWTEIUONhlk0ewxr/jaVGlc+J+g==", + "dev": true, + "requires": { + "minimist": "^1.2.3", + "shelljs": "^0.8.5" + } + }, + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "requires": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + } + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==" + }, + "smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==" + }, + "socks": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz", + "integrity": "sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==", + "requires": { + "ip-address": "^9.0.5", + "smart-buffer": "^4.2.0" + } + }, + "socks-proxy-agent": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.1.tgz", + "integrity": "sha512-59EjPbbgg8U3x62hhKOFVAmySQUcfRQ4C7Q/D5sEHnZTQRrQlNKINks44DMR1gwXp0p4LaVIeccX2KHTTcHVqQ==", + "requires": { + "agent-base": "^7.0.1", + "debug": "^4.3.4", + "socks": "^2.7.1" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "requires": { + "ms": "2.1.2" + } + } + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "optional": true + }, + "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==" + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "superagent": { + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-8.0.6.tgz", + "integrity": "sha512-HqSe6DSIh3hEn6cJvCkaM1BLi466f1LHi4yubR0tpewlMpk4RUFFy35bKz8SsPBwYfIIJy5eclp+3tCYAuX0bw==", + "requires": { + "component-emitter": "^1.3.0", + "cookiejar": "^2.1.3", + "debug": "^4.3.4", + "fast-safe-stringify": "^2.1.1", + "form-data": "^4.0.0", + "formidable": "^2.1.1", + "methods": "^1.1.2", + "mime": "2.6.0", + "qs": "^6.11.0", + "semver": "^7.3.8" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "requires": { + "ms": "2.1.2" + } + }, + "formidable": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-2.1.1.tgz", + "integrity": "sha512-0EcS9wCFEzLvfiks7omJ+SiYJAiD+TzK4Pcw1UlUoGnhUxDcMKjt0P7x8wEb0u6OHu8Nb98WG3nxtlF5C7bvUQ==", + "requires": { + "dezalgo": "^1.0.4", + "hexoid": "^1.0.0", + "once": "^1.4.0", + "qs": "^6.11.0" + } + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "requires": { + "yallist": "^4.0.0" + } + }, + "semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "requires": { + "lru-cache": "^6.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + } + } + }, + "superagent-mock": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/superagent-mock/-/superagent-mock-4.0.0.tgz", + "integrity": "sha512-+xj+q+sL5sJIcFxwmj5Wuq59Kns0ocOd8OrMkbEXEwseWImAVvM7cP8G7raQRZ+vloUN32t/yKosD+YAXa9rcg==", + "dev": true, + "requires": {} + }, + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "tslib": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.1.tgz", + "integrity": "sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig==" + }, + "typescript": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz", + "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==", + "dev": true + }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" + }, + "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==", + "requires": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "workerpool": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.0.tgz", + "integrity": "sha512-Rsk5qQHJ9eowMH28Jwhe8HEbmdYDX4lwoMWshiCXugjtHqMD9ZbiqSDLxcsfdqsETPzVUtX5s1Z5kStiIM6l4A==", + "dev": true + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true + }, + "yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + } + } + }, + "yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true + }, + "yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "requires": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + } + }, + "yazl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/yazl/-/yazl-2.5.1.tgz", + "integrity": "sha512-phENi2PLiHnHb6QBVot+dJnaAZ0xosj7p3fWl+znIjBDlnMI2PsZCJZ306BPTFOaHf5qdDEI8x5qFrSOBN5vrw==", + "requires": { + "buffer-crc32": "~0.2.3" + } + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true + } + } +} diff --git a/package.json b/package.json index 4f800937..755780f7 100644 --- a/package.json +++ b/package.json @@ -1,33 +1,45 @@ { + "name": "code-push", + "version": "4.2.3", + "description": "Management SDK for the CodePush service", + "main": "script/index.js", + "types": "script/index.d.ts", + "scripts": { + "clean": "shx rm -rf bin", + "setup": "npm install --quiet --no-progress", + "prebuild": "npm run clean", + "build": "tsc && npm run content", + "prebuild:release": "npm run clean", + "build:release": "tsc -p ./tsconfig-release.json && npm run check:release && npm run content", + "check:release": "npx ts-node .github/scripts/check-for-declaration.ts", + "test": "npm run build && mocha --recursive bin/test", + "test:debugger": "mocha --recursive --inspect-brk=0.0.0.0 bin/test", + "content": "shx cp {README.md,package.json,.npmignore} bin" + }, + "repository": { + "type": "git", + "url": "https://github.com/Microsoft/code-push.git" + }, + "author": "Microsoft Corporation", + "license": "MIT", + "homepage": "https://microsoft.github.io/code-push", + "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" + }, "devDependencies": { - "chmod": "^0.2.1", - "del": "^1.2.0", - "dotenv": "^1.2.0", - "dts-generator": "^1.5.0", - "express": "^4.13.1", - "gulp": "^3.9.0", - "gulp-debug": "^2.0.1", - "gulp-if": "^1.2.5", - "gulp-install": "^0.4.0", - "gulp-istanbul": "^0.10.0", - "gulp-load-plugins": "^1.0.0-rc.1", - "gulp-mocha": "^2.1.2", - "gulp-plumber": "^1.0.1", - "gulp-tsd": "0.0.4", - "gulp-typescript": "^2.7.8", - "gulp-util": "^3.0.6", - "merge2": "^0.3.6", - "node-libs-browser": "^0.5.2", - "node-rsa": "^0.2.24", - "require-all": "^1.1.0", - "run-sequence": "^1.1.2", - "sinon": "1.16.0", - "superagent-mock": "^1.3.0", - "supertest": "^1.0.1", - "through2": "^2.0.0", - "ts-jsx-loader": "^0.2.1", - "typescript": "1.5.0-alpha", - "webpack": "^1.10.1", - "which": "^1.1.1" + "@types/mocha": "^9.0.0", + "@types/node": "^14.0.27", + "@types/slash": "^3.0.0", + "@types/superagent": "^4.1.13", + "@types/yazl": "^2.4.2", + "mocha": "^9.2.0", + "shx": "^0.3.4", + "superagent-mock": "^4.0.0", + "typescript": "^5.1.6" } } diff --git a/sdk/definitions/harness.d.ts b/sdk/definitions/harness.d.ts deleted file mode 100644 index 044e410d..00000000 --- a/sdk/definitions/harness.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -/// -/// -/// -/// -/// -/// - diff --git a/sdk/package.json b/sdk/package.json deleted file mode 100644 index 9b4fd11a..00000000 --- a/sdk/package.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "code-push", - "version": "1.0.0-beta", - "description": "Source code for the CodePush service SDK", - "main": "script/index.js", - "scripts": { - "test": "gulp" - }, - "repository": { - "type": "git", - "url": "https://github.com/Microsoft/code-push.git" - }, - "author": "Microsoft", - "dependencies": { - "base-64": "^0.1.0", - "fs": "0.0.2", - "node-uuid": "^1.4.3", - "q": "^1.4.1", - "superagent": "~1.2.0", - "try-json": "^1.0.0" - } -} diff --git a/sdk/plugin.xml b/sdk/plugin.xml deleted file mode 100644 index 33158f87..00000000 --- a/sdk/plugin.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - CodePushAcquisition - CodePush Acquisition Plugin for Apache Cordova - MIT - cordova,code,push,acquisition - https://github.com/Microsoft/code-push.git - - - - diff --git a/sdk/script/acquisition-sdk.ts b/sdk/script/acquisition-sdk.ts deleted file mode 100644 index 1ed740c8..00000000 --- a/sdk/script/acquisition-sdk.ts +++ /dev/null @@ -1,193 +0,0 @@ -/// - -import { UpdateCheckResponse, UpdateCheckRequest } from "rest-definitions"; - -export module 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 { - serverUrl: string; - deploymentKey: string; - ignoreAppVersion?: boolean -} - -export class AcquisitionStatus { - public static DeploymentSucceeded = "DeploymentSucceeded"; - public static DeploymentFailed = "DeploymentFailed"; -} - -export class AcquisitionManager { - private _deploymentKey: string; - private _httpRequester: Http.Requester; - private _ignoreAppVersion: boolean; - private _serverUrl: string; - - constructor(httpRequester: Http.Requester, configuration: Configuration) { - this._httpRequester = httpRequester; - - this._serverUrl = configuration.serverUrl; - if (this._serverUrl.slice(-1) !== "/") { - this._serverUrl += "/"; - } - - this._deploymentKey = configuration.deploymentKey; - this._ignoreAppVersion = configuration.ignoreAppVersion; - } - - public queryUpdateWithCurrentPackage(currentPackage: Package, callback?: Callback): void { - if (!currentPackage || !currentPackage.appVersion) { - throw new Error("Calling common acquisition SDK with incorrect package"); // Unexpected; indicates error in our implementation - } - - var updateRequest: UpdateCheckRequest = { - deploymentKey: this._deploymentKey, - appVersion: currentPackage.appVersion, - packageHash: currentPackage.packageHash, - isCompanion: this._ignoreAppVersion - }; - - var requestUrl: string = this._serverUrl + "updateCheck?" + 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) { - callback(new Error(response.statusCode + ": " + response.body), /*remotePackage=*/ null); - return; - } - - try { - var responseObject = JSON.parse(response.body); - var updateInfo: UpdateCheckResponse = responseObject.updateInfo; - } catch (error) { - callback(error, /*remotePackage=*/ null); - return; - } - - if (!updateInfo) { - callback(error, /*remotePackage=*/ null); - return; - } else if (updateInfo.updateAppVersion) { - callback(/*error=*/ null, { updateAppVersion: true, appVersion: updateInfo.appVersion }); - return; - } else if (!updateInfo.isAvailable) { - callback(/*error=*/ null, /*remotePackage=*/ null); - return; - } - - var remotePackage: RemotePackage = { - deploymentKey: this._deploymentKey, - description: updateInfo.description, - label: updateInfo.label, - appVersion: updateInfo.appVersion, - isMandatory: updateInfo.isMandatory, - packageHash: updateInfo.packageHash, - packageSize: updateInfo.packageSize, - downloadUrl: updateInfo.downloadURL - }; - - callback(/*error=*/ null, remotePackage); - }); - } - - public reportStatus(status: string, message?: string, callback?: Callback): void { - var url: string = this._serverUrl + "reportStatus"; - var body: string; - - switch (status) { - case AcquisitionStatus.DeploymentSucceeded: - case AcquisitionStatus.DeploymentFailed: - url += "/deploy"; - body = JSON.stringify({ deploymentKey: this._deploymentKey, status: status, message: message }); - break; - - default: - if (callback) { - callback(new Error("Unrecognized status."), /*not used*/ null); - } - return; - } - - this._httpRequester.request(Http.Verb.POST, url, body, (error: Error, response: Http.Response): void => { - if (callback) { - if (error) { - callback(error, /*not used*/ null); - return; - } - - if (response.statusCode !== 200) { - callback(new Error(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 (!isFirst) { - queryString += "&"; - } - - queryString += encodeURIComponent(property) + "="; - if (value !== null && typeof value !== "undefined") { - queryString += encodeURIComponent(value); - } - - isFirst = false; - } - } - - return queryString; -} diff --git a/sdk/script/index.ts b/sdk/script/index.ts deleted file mode 100644 index 02dd8dec..00000000 --- a/sdk/script/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./management/account-manager"; \ No newline at end of file diff --git a/sdk/script/management/account-manager.ts b/sdk/script/management/account-manager.ts deleted file mode 100644 index 1f807a86..00000000 --- a/sdk/script/management/account-manager.ts +++ /dev/null @@ -1,897 +0,0 @@ -import * as base64 from "base-64"; -import Q = require("q"); -import tryJSON = require("try-json"); -import Promise = Q.Promise; -import request = require("superagent"); -import * as uuid from "node-uuid"; - -declare var fs: any; - -if (typeof window === "undefined") { - fs = require("fs"); -} else { - fs = { - createReadStream: (fileOrPath: string): void => { - throw new Error("Tried to call a node fs function from the browser."); - } - } -} - -import { AccessKey, Account, App, Deployment, DeploymentKey, Package } from "rest-definitions"; -export { AccessKey, Account, App, Deployment, DeploymentKey, Package }; - -export interface CodePushError { - message?: string; - statusCode?: number; -} - -interface PackageToUpload { - label: string; - description: string; - appVersion: string; - isMandatory: boolean; -} - -interface ILoginInfo { - accessKeyName: string; - providerName: string; - providerUniqueId: string; -} - -export class AccountManager { - private _authedAgent: request.SuperAgent; - private _saveAuthedAgent: boolean = false; - - public account: Account; - public serverUrl: string = "http://localhost:3000"; - - public get accountId(): string { - return this.account.id; - } - - constructor(serverUrl?: string) { - // If window is not defined, it means we are in the node environment and not a browser. - this._saveAuthedAgent = (typeof window === "undefined"); - - this.serverUrl = serverUrl; - } - - public loginWithAccessToken(accessToken: string): Promise { - return Promise((resolve, reject, notify) => { - var loginInfo: ILoginInfo = AccountManager.getLoginInfo(accessToken); - - if (!loginInfo || !loginInfo.providerName || !loginInfo.providerUniqueId) { - reject({ message: "Invalid access key." }); - return; - } - - var req = request.post(this.serverUrl + "/auth/login/accessToken"); - - this.attachCredentials(req, request); - - req.type("form") - .send({ identity: JSON.stringify({ providerName: loginInfo.providerName, providerUniqueId: loginInfo.providerUniqueId }) }) - .send({ token: loginInfo.accessKeyName }) - .end((err: any, res: request.Response) => { - if (err) { - reject({ message: this.getErrorMessage(err, res) }); - return; - } - - if (this._saveAuthedAgent) { - this._authedAgent = request.agent(); - this._authedAgent.saveCookies(res); - } - - if (res.ok) { - resolve(null); - } else { - var body = tryJSON(res.text); - if (body) { - reject(body); - } else { - reject({ message: res.text, statusCode: res.status }); - } - } - }); - }); - } - - public logout(): Promise { - return Promise((resolve, reject, notify) => { - var req = request.post(this.serverUrl + "/auth/logout"); - this.attachCredentials(req, request); - - req.end((err: any, res: request.Response) => { - if (err && err.status !== 401) { - reject({ message: this.getErrorMessage(err, res) }); - return; - } - - this._authedAgent = null; - - if (res.ok) { - resolve(null); - } else { - var body = tryJSON(res.text); - if (body) { - reject(body); - } else { - reject({ message: res.text, statusCode: res.status }); - } - } - }); - }); - } - - public isAuthenticated(): Promise { - return Promise((resolve, reject, notify) => { - var requester: request.SuperAgent = this._authedAgent ? this._authedAgent : request; - var req = requester.get(this.serverUrl + "/authenticated"); - this.attachCredentials(req, requester); - - req.end((err: any, res: request.Response) => { - if (err && err.status !== 401) { - reject({ message: this.getErrorMessage(err, res) }); - return; - } - - var status: number = res ? res.status : err.status; - - var authenticated: boolean = status === 200; - - if (authenticated && this._saveAuthedAgent) { - this._authedAgent = request.agent(); - this._authedAgent.saveCookies(res); - } - - resolve(authenticated); - }); - }); - } - - public addAccessKey(description?: string): Promise { - return Promise((resolve, reject, notify) => { - var accessKey: AccessKey = { id: null, name: uuid.v4(), description: description }; - var requester: request.SuperAgent = this._authedAgent ? this._authedAgent : request; - var req = requester.post(this.serverUrl + "/accessKeys/"); - - this.attachCredentials(req, requester); - - req.set("Content-Type", "application/json;charset=UTF-8") - .send(JSON.stringify(accessKey)) - .end((err: any, res: request.Response) => { - if (err) { - reject({ message: this.getErrorMessage(err, res) }); - return; - } - - if (res.ok) { - var location = res.header["location"]; - if (location && location.lastIndexOf("/") !== -1) { - accessKey.id = location.substr(location.lastIndexOf("/") + 1); - resolve(accessKey); - } else { - resolve(null); - } - } else { - var body = tryJSON(res.text); - if (body) { - reject(body); - } else { - reject({ message: res.text, statusCode: res.status }); - } - } - }); - }); - } - - public getAccessKey(accessKeyId: string): Promise { - return Promise((resolve, reject, notify) => { - var requester: request.SuperAgent = this._authedAgent ? this._authedAgent : request; - var req = requester.get(this.serverUrl + "/accessKeys/" + accessKeyId); - - this.attachCredentials(req, requester); - - req.end((err: any, res: request.Response) => { - if (err) { - reject({ message: this.getErrorMessage(err, res) }); - return; - } - - var body = tryJSON(res.text); - if (res.ok) { - if (body) { - resolve(body.accessKey); - } else { - reject({ message: "Could not parse response: " + res.text, statusCode: res.status }); - } - } else { - if (body) { - reject(body); - } else { - reject({ message: res.text, statusCode: res.status }); - } - } - }); - }); - } - - public getAccessKeys(): Promise { - return Promise((resolve, reject, notify) => { - var requester: request.SuperAgent = this._authedAgent ? this._authedAgent : request; - var req = requester.get(this.serverUrl + "/accessKeys"); - - this.attachCredentials(req, requester); - - req.end((err: any, res: request.Response) => { - if (err) { - reject({ message: this.getErrorMessage(err, res) }); - return; - } - - var body = tryJSON(res.text); - if (res.ok) { - if (body) { - resolve(body.accessKeys); - } else { - reject({ message: "Could not parse response: " + res.text, statusCode: res.status }); - } - } else { - if (body) { - reject(body); - } else { - reject({ message: res.text, statusCode: res.status }); - } - } - }); - }); - } - - public removeAccessKey(accessKeyId: string): Promise { - return Promise((resolve, reject, notify) => { - var requester: request.SuperAgent = this._authedAgent ? this._authedAgent : request; - var req = requester.del(this.serverUrl + "/accessKeys/" + accessKeyId); - - this.attachCredentials(req, requester); - - req.end((err: any, res: request.Response) => { - if (err) { - reject({ message: this.getErrorMessage(err, res) }); - return; - } - - if (res.ok) { - resolve(null); - } else { - var body = tryJSON(res.text); - if (body) { - reject(body); - } else { - reject({ message: res.text, statusCode: res.status }); - } - } - }); - }); - } - - // Account - public getAccountInfo(): Promise { - return Promise((resolve, reject, notify) => { - var requester = (this._authedAgent ? this._authedAgent : request); - - var req = requester.get(this.serverUrl + "/account"); - this.attachCredentials(req, requester); - - req.end((err: any, res: request.Response) => { - if (err) { - reject({ message: this.getErrorMessage(err, res) }); - return; - } - - var body = tryJSON(res.text); - - if (res.ok) { - if (body) { - this.account = body.account; - resolve(this.account); - } else { - reject({ message: "Could not parse response: " + res.text, statusCode: res.status }); - } - } else { - if (body) { - reject(body); - } else { - reject({ message: res.text, statusCode: res.status }); - } - } - }); - }); - } - - public updateAccountInfo(accountInfoToChange: Account): Promise { - return Promise((resolve, reject, notify) => { - var requester = (this._authedAgent ? this._authedAgent : request); - - var req = requester.put(this.serverUrl + "/account"); - this.attachCredentials(req, requester); - - req.set("Content-Type", "application/json;charset=UTF-8") - .send(JSON.stringify(accountInfoToChange)) - .end((err: any, res: request.Response) => { - if (err) { - reject({ message: this.getErrorMessage(err, res) }); - return; - } - - if (res.ok) { - resolve(null); - } else { - var body = tryJSON(res.text); - if (body) { - reject(body); - } else { - reject({ message: res.text, statusCode: res.status }); - } - } - }); - }); - } - - // Apps - public getApps(): Promise { - return Promise((resolve, reject, notify) => { - var requester = (this._authedAgent ? this._authedAgent : request); - - var req = requester.get(this.serverUrl + "/apps"); - this.attachCredentials(req, requester); - - req.end((err: any, res: request.Response) => { - if (err) { - reject({ message: this.getErrorMessage(err, res) }); - return; - } - - var body = tryJSON(res.text); - if (res.ok) { - if (body) { - resolve(body.apps); - } else { - reject({ message: "Could not parse response: " + res.text, statusCode: res.status }); - } - } else { - if (body) { - reject(body); - } else { - reject({ message: res.text, statusCode: res.status }); - } - } - }); - }); - } - - public getApp(appId: string): Promise { - return Promise((resolve, reject, notify) => { - var requester = (this._authedAgent ? this._authedAgent : request); - - var req = requester.get(this.serverUrl + "/apps/" + appId); - this.attachCredentials(req, requester); - - req.end((err: any, res: request.Response) => { - if (err) { - reject({ message: this.getErrorMessage(err, res) }); - return; - } - - var body = tryJSON(res.text); - if (res.ok) { - if (body) { - resolve(body.app); - } else { - reject({ message: "Could not parse response: " + res.text, statusCode: res.status }); - } - } else { - if (body) { - reject(body); - } else { - reject({ message: res.text, statusCode: res.status }); - } - } - }); - }); - } - - public addApp(appName: string, description?: string): Promise { - return Promise((resolve, reject, notify) => { - var app = { name: appName, description: description }; - var requester = (this._authedAgent ? this._authedAgent : request); - - var req = requester.post(this.serverUrl + "/apps/"); - this.attachCredentials(req, requester); - - req.set("Content-Type", "application/json;charset=UTF-8") - .send(JSON.stringify(app)) - .end((err: any, res: request.Response) => { - if (err) { - reject({ message: this.getErrorMessage(err, res) }); - return; - } - - if (res.ok) { - var location = res.header["location"]; - if (location && location.lastIndexOf("/") !== -1) { - app.id = location.substr(location.lastIndexOf("/") + 1); - resolve(app); - } else { - resolve(null); - } - } else { - var body = tryJSON(res.text); - if (body) { - reject(body); - } else { - reject({ message: res.text, statusCode: res.status }); - } - } - }); - }); - } - - public removeApp(app: App | string): Promise { - return Promise((resolve, reject, notify) => { - var id: string = (typeof app === "string") ? app : app.id; - var requester = (this._authedAgent ? this._authedAgent : request); - - var req = requester.del(this.serverUrl + "/apps/" + id); - this.attachCredentials(req, requester); - - req.end((err: any, res: request.Response) => { - if (err) { - reject({ message: this.getErrorMessage(err, res) }); - return; - } - - if (res.ok) { - resolve(null); - } else { - var body = tryJSON(res.text); - if (body) { - reject(body); - } else { - reject({ message: res.text, statusCode: res.status }); - } - } - }); - }); - } - - public updateApp(infoToChange: App): Promise { - return Promise((resolve, reject, notify) => { - var requester = (this._authedAgent ? this._authedAgent : request); - var req = requester.put(this.serverUrl + "/apps/" + infoToChange.id); - this.attachCredentials(req, requester); - - req.set("Content-Type", "application/json;charset=UTF-8") - .send(JSON.stringify(infoToChange)) - .end((err: any, res: request.Response) => { - if (err) { - reject({ message: this.getErrorMessage(err, res) }); - return; - } - - if (res.ok) { - resolve(null); - } else { - var body = tryJSON(res.text); - if (body) { - reject(body); - } else { - reject({ message: res.text, statusCode: res.status }); - } - } - }); - }); - } - - // Deployments - public addDeployment(appId: string, name: string, description?: string): Promise { - return Promise((resolve, reject, notify) => { - var deployment = { name: name, description: description }; - - var requester = (this._authedAgent ? this._authedAgent : request); - var req = requester.post(this.serverUrl + "/apps/" + appId + "/deployments/");; - this.attachCredentials(req, requester); - - req.set("Content-Type", "application/json;charset=UTF-8") - .send(JSON.stringify(deployment)) - .end((err: any, res: request.Response) => { - if (err) { - reject({ message: this.getErrorMessage(err, res) }); - return; - } - - if (res.ok) { - var location = res.header["location"]; - if (location && location.lastIndexOf("/") !== -1) { - deployment.id = location.substr(location.lastIndexOf("/") + 1); - resolve(deployment); - } else { - resolve(null); - } - } else { - var body = tryJSON(res.text); - if (body) { - reject(body); - } else { - reject({ message: res.text, statusCode: res.status }); - } - } - }); - }); - } - - public getDeployments(appId: string): Promise { - return Promise((resolve, reject, notify) => { - var requester = (this._authedAgent ? this._authedAgent : request); - var req = requester.get(this.serverUrl + "/apps/" + appId + "/deployments"); - this.attachCredentials(req, requester); - - req.end((err: any, res: request.Response) => { - if (err) { - reject({ message: this.getErrorMessage(err, res) }); - return; - } - - var body = tryJSON(res.text); - if (res.ok) { - if (body) { - resolve(body.deployments); - } else { - reject({ message: "Could not parse response: " + res.text, statusCode: res.status }); - } - } else { - if (body) { - reject(body); - } else { - reject({ message: res.text, statusCode: res.status }); - } - } - }); - }); - } - - public getDeployment(appId: string, deploymentId: string) { - return Promise((resolve, reject, notify) => { - var requester = (this._authedAgent ? this._authedAgent : request); - var req = requester.get(this.serverUrl + "/apps/" + appId + "/deployments/" + deploymentId); - this.attachCredentials(req, requester); - - req.end((err: any, res: request.Response) => { - if (err) { - reject({ message: this.getErrorMessage(err, res) }); - return; - } - - var body = tryJSON(res.text); - if (res.ok) { - if (body) { - resolve(body.deployment); - } else { - reject({ message: "Could not parse response: " + res.text, statusCode: res.status }); - } - } else { - if (body) { - reject(body); - } else { - reject({ message: res.text, statusCode: res.status }); - } - } - }); - }); - } - - public updateDeployment(appId: string, infoToChange: Deployment): Promise { - return Promise((resolve, reject, notify) => { - var requester = (this._authedAgent ? this._authedAgent : request); - var req = requester.put(this.serverUrl + "/apps/" + appId + "/deployments/" + infoToChange.id); - this.attachCredentials(req, requester); - - req.set("Content-Type", "application/json;charset=UTF-8") - .send(JSON.stringify(infoToChange)) - .end((err: any, res: request.Response) => { - if (err) { - reject({ message: this.getErrorMessage(err, res) }); - return; - } - - if (res.ok) { - resolve(null); - } else { - var body = tryJSON(res.text); - if (body) { - reject(body); - } else { - reject({ message: res.text, statusCode: res.status }); - } - } - }); - }); - } - - public removeDeployment(appId: string, deployment: Deployment | string): Promise { - return Promise((resolve, reject, notify) => { - var id: string = (typeof deployment === "string") ? deployment : deployment.id; - var requester = (this._authedAgent ? this._authedAgent : request); - var req = requester.del(this.serverUrl + "/apps/" + appId + "/deployments/" + id); - this.attachCredentials(req, requester); - - req.end((err: any, res: request.Response) => { - if (err) { - reject({ message: this.getErrorMessage(err, res) }); - return; - } - - if (res.ok) { - resolve(null); - } else { - var body = tryJSON(res.text); - if (body) { - reject(body); - } else { - reject({ message: res.text, statusCode: res.status }); - } - } - }); - }); - } - - // Deployment key - public addDeploymentKey(appId: string, deploymentId: string, name: string, description?: string): Promise { - return Promise((resolve, reject, notify) => { - var deploymentKey: DeploymentKey = this.generateDeploymentKey(name, description, /*isPrimary*/ false); - var requester = (this._authedAgent ? this._authedAgent : request); - var req = requester.post(this.serverUrl + "/apps/" + appId + "/deployments/" + deploymentId + "/deploymentKeys") - this.attachCredentials(req, requester); - - req.set("Content-Type", "application/json;charset=UTF-8") - .send(JSON.stringify(deploymentKey)) - .end((err: any, res: request.Response) => { - if (err) { - reject({ message: this.getErrorMessage(err, res) }); - return; - } - - if (res.ok) { - var body = tryJSON(res.text); - if (res.ok) { - if (body) { - resolve(body.deploymentKey); - } else { - reject({ message: "Could not parse response: " + res.text, statusCode: res.status }); - } - } else { - if (body) { - reject(body); - } else { - reject({ message: res.text, statusCode: res.status }); - } - } - } - }); - }); - } - - public getDeploymentKeys(appId: string, deploymentId: string): Promise { - return Promise((resolve, reject, notify) => { - var requester = (this._authedAgent ? this._authedAgent : request); - var req = requester.get(this.serverUrl + "/apps/" + appId + "/deployments/" + deploymentId + "/deploymentKeys") - this.attachCredentials(req, requester); - - req.end((err: any, res: request.Response) => { - if (err) { - reject({ message: this.getErrorMessage(err, res) }); - return; - } - - var body = tryJSON(res.text); - if (res.ok) { - if (body) { - resolve(body.deploymentKeys); - } else { - reject({ message: "Could not parse response: " + res.text, statusCode: res.status }); - } - } else { - if (body) { - reject(body); - } else { - reject({ message: res.text, statusCode: res.status }); - } - } - }); - }); - } - - public getDeploymentKey(appId: string, deploymentId: string, deploymentKeyId: string): Promise { - return Promise((resolve, reject, notify) => { - var requester = (this._authedAgent ? this._authedAgent : request); - var req = requester.get(this.serverUrl + "/apps/" + appId + "/deployments/" + deploymentId + "/deploymentKeys/" + deploymentKeyId) - this.attachCredentials(req, requester); - - req.end((err: any, res: request.Response) => { - if (err) { - reject({ message: this.getErrorMessage(err, res) }); - return; - } - - var body = tryJSON(res.text); - if (res.ok) { - if (body) { - resolve(body.deploymentKey); - } else { - reject({ message: "Could not parse response: " + res.text, statusCode: res.status }); - } - } else { - if (body) { - reject(body); - } else { - reject({ message: res.text, statusCode: res.status }); - } - } - }); - }); - } - - public updateDeploymentKey(appId: string, deploymentId: string, deploymentKeyId: string, infoToChange: any): Promise { - return Promise((resolve, reject, notify) => { - var requester = (this._authedAgent ? this._authedAgent : request); - var req = requester.put(this.serverUrl + "/apps/" + appId + "/deployments/" + deploymentId + "/deploymentKeys/" + deploymentKeyId) - this.attachCredentials(req, requester); - - req.set("Content-Type", "application/json;charset=UTF-8") - .send(JSON.stringify(infoToChange)) - .end((err: any, res: request.Response) => { - if (err) { - reject({ message: this.getErrorMessage(err, res) }); - return; - } - - if (res.ok) { - resolve(null); - } else { - var body = tryJSON(res.text); - if (body) { - reject(body); - } else { - reject({ message: res.text, statusCode: res.status }); - } - } - }); - }); - } - - public deleteDeploymentKey(appId: string, deploymentId: string, deploymentKey: DeploymentKey | string): Promise { - var id: string = (typeof deploymentKey === "string") ? deploymentKey : deploymentKey.id; - return Promise((resolve, reject, notify) => { - var requester = (this._authedAgent ? this._authedAgent : request); - var req = requester.del(this.serverUrl + "/apps/" + appId + "/deployments/" + deploymentId + "/deploymentKeys/" + id) - this.attachCredentials(req, requester); - - req.end((err: any, res: request.Response) => { - if (err) { - reject({ message: this.getErrorMessage(err, res) }); - return; - } - - if (res.ok) { - resolve(null); - } else { - var body = tryJSON(res.text); - if (body) { - reject(body); - } else { - reject({ message: res.text, statusCode: res.status }); - } - } - }); - }); - } - - public addPackage(appId: string, deploymentId: string, fileOrPath: File | string, description: string, label: string, appVersion: string, isMandatory: boolean = false): Promise { - return Promise((resolve, reject, notify) => { - var packageInfo: PackageToUpload = this.generatePackageInfo(description, label, appVersion, isMandatory); - var requester = (this._authedAgent ? this._authedAgent : request); - var req = requester.put(this.serverUrl + "/apps/" + appId + "/deployments/" + deploymentId + "/package"); - this.attachCredentials(req, requester); - - var file: any; - if (typeof fileOrPath === "string") { - file = fs.createReadStream(fileOrPath); - } else { - file = fileOrPath; - } - - req.field("package", file) - .field("packageInfo", JSON.stringify(packageInfo)) - .end((err: any, res: request.Response) => { - if (err) { - reject({ message: this.getErrorMessage(err, res) }); - return; - } - - if (res.ok) { - resolve(null); - } else { - var body = tryJSON(res.text); - if (body) { - reject(body); - } else { - reject({ message: res.text, statusCode: res.status }); - } - } - }); - }); - } - - public getPackage(appId: string, deploymentId: string): Promise { - return Promise((resolve, reject, notify) => { - var requester = (this._authedAgent ? this._authedAgent : request); - var req = requester.get(this.serverUrl + "/apps/" + appId + "/deployments/" + deploymentId + "/package"); - this.attachCredentials(req, requester); - - req.end((err: any, res: request.Response) => { - if (err) { - reject({ message: this.getErrorMessage(err, res) }); - return; - } - - var body = tryJSON(res.text); - if (res.ok) { - if (body) { - resolve(body.package); - } else { - reject({ message: "Could not parse response: " + res.text, statusCode: res.status }); - } - } else { - if (body) { - reject(body); - } else { - reject({ message: res.text, statusCode: res.status }); - } - } - }); - }); - } - - private static getLoginInfo(accessKey: string): ILoginInfo { - var decoded: string = base64.decode(accessKey); - - return tryJSON(decoded); - } - - private getErrorMessage(error: Error, response: request.Response): string { - return response && response.text ? response.text : error.message; - } - - private generatePackageInfo(description: string, label: string, appVersion: string, isMandatory: boolean): PackageToUpload { - return { - description: description, - label: label, - appVersion: appVersion, - isMandatory: isMandatory - }; - } - - private generateDeploymentKey(name: string, description?: string, isPrimary?: boolean, id?: string): DeploymentKey { - return { id: id, name: name, description: description, isPrimary: !!isPrimary }; - } - - private attachCredentials(request: request.Request, requester: request.SuperAgent): void { - if (this._saveAuthedAgent) { - if (requester && requester.attachCookies) { - requester.attachCookies(request); - } - } else { - request.withCredentials(); - } - } -} diff --git a/sdk/script/samples/acquisition-native-stub.ts b/sdk/script/samples/acquisition-native-stub.ts deleted file mode 100644 index 202d3a85..00000000 --- a/sdk/script/samples/acquisition-native-stub.ts +++ /dev/null @@ -1,27 +0,0 @@ -/** - ******************************************************* - * * - * Copyright (C) Microsoft. All rights reserved. * - * * - ******************************************************* - */ - -import * as Acquisition from "../acquisition-sdk"; -export * from "../acquisition-sdk"; - -export interface NativeSample { - // constructor(configuration: Configuration): void; - - beforeApply(callback: Acquisition.Callback): void; - afterApply(callback: Acquisition.Callback): void; - - queryUpdate(callback: Acquisition.Callback): void; - download(session: Acquisition.RemotePackage, callback?: Acquisition.Callback): void; - abort(session: Acquisition.RemotePackage, callback?: Acquisition.Callback): void; - apply(newPackage: Acquisition.LocalPackage, callback?: Acquisition.Callback): void; - - getCurrentPackage(callback?: Acquisition.Callback): void; -} - -export var NativeImplementation: { new (configuration: Acquisition.Configuration): NativeSample }; - diff --git a/sdk/script/samples/typescript-acquisition.ts b/sdk/script/samples/typescript-acquisition.ts deleted file mode 100644 index fdf950af..00000000 --- a/sdk/script/samples/typescript-acquisition.ts +++ /dev/null @@ -1,52 +0,0 @@ -import Acquisition = require("./acquisition-native-stub"); - -class MyApp { - private static AppStoreScriptVersion = "1.5"; - private static AppUpdateTimeoutMs = 30 * 60 * 1000; - private static ServerUrl = "http://localhost:7127/"; - - private _acquisition: Acquisition.NativeSample; - - constructor() { - this._acquisition = new Acquisition.NativeImplementation({ serverUrl: MyApp.ServerUrl, deploymentKey: "fa3s34a5s6d7f8we9a9r"}); - } - - public onAppStartup(): void { - this.registerLifecycleEvents(); - this.getLatestApp(); - window.setInterval(() => this.getLatestApp(), MyApp.AppUpdateTimeoutMs); - } - - private registerLifecycleEvents(): void { - this._acquisition.beforeApply((error: Error, newPackageInfo: Acquisition.LocalPackage) => { - if (newPackageInfo.label.charAt(0) > "1") { - // Migrate user data - } - }); - - this._acquisition.afterApply((error: Error, oldPackageInfo: Acquisition.LocalPackage) => { - if (oldPackageInfo.label.charAt(0) < "1") { - // Display dialog to user about changes - return Q.Promise((resolve: () => void) => { - resolve(); - }); - } - }); - } - - private getLatestApp(): void { - this._acquisition.queryUpdate((error: Error, remotePackage: Acquisition.RemotePackage) => this.downloadAndApplyPackage(remotePackage)); - } - - private downloadAndApplyPackage(remotePackage: Acquisition.RemotePackage): void { - if (remotePackage) { - this._acquisition.download(remotePackage, (error: Error, localPackage: Acquisition.LocalPackage) => this.applyPackage(localPackage)); - } - } - - private applyPackage(localPackage: Acquisition.LocalPackage): void { - if (localPackage) { - this._acquisition.apply(localPackage); - } - } -} diff --git a/sdk/test/acquisition-rest-mock.ts b/sdk/test/acquisition-rest-mock.ts deleted file mode 100644 index a8681d23..00000000 --- a/sdk/test/acquisition-rest-mock.ts +++ /dev/null @@ -1,107 +0,0 @@ -/// - -import * as express from "express"; -import * as querystring from "querystring"; - -import * as acquisitionSdk from "../script/acquisition-sdk"; -import * as rest from "rest-definitions"; - -export var validDeploymentKey = "asdfasdfawerqw"; -export var latestPackage = { - downloadURL: "http://www.windowsazure.com/blobs/awperoiuqpweru", - description: "Angry flappy birds", - appVersion: "1.5.0", - label: "2.4.0", - isMandatory: false, - isAvailable: true, - updateAppVersion: false, - packageHash: "hash240", - packageSize: 1024 -}; - -export var serverUrl = "http://myurl.com"; -var reportStatusDeployUrl = serverUrl + "/reportStatus/deploy"; -var updateCheckUrl = serverUrl + "/updateCheck?"; - -export class HttpRequester implements acquisitionSdk.Http.Requester { - 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); - } else if (verb === acquisitionSdk.Http.Verb.POST && url === reportStatusDeployUrl) { - Server.onReportStatus(callback); - } 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({ updateInfo: { isAvailable: false } }) - }); - } else { - callback(/*error=*/ null, { - statusCode: 200, - body: JSON.stringify({ updateInfo: latestPackage }) - }); - } - } - - public static onUpdateCheck(params: any, callback: acquisitionSdk.Callback): void { - var updateRequest: rest.UpdateCheckRequest = { - deploymentKey: params.deploymentKey, - appVersion: params.appVersion, - packageHash: params.packageHash, - isCompanion: !!(params.isCompanion) - }; - - if (!updateRequest.deploymentKey || !updateRequest.appVersion) { - callback(/*error=*/ null, { statusCode: 400 }); - } else { - var updateInfo = { isAvailable: false }; - if (updateRequest.deploymentKey === validDeploymentKey) { - if (updateRequest.isCompanion || updateRequest.appVersion === latestPackage.appVersion) { - if (updateRequest.packageHash !== latestPackage.packageHash) { - updateInfo = latestPackage; - } - } else if (updateRequest.appVersion < latestPackage.appVersion) { - updateInfo = { updateAppVersion: true, appVersion: latestPackage.appVersion }; - } - } - - callback(/*error=*/ null, { - statusCode: 200, - body: JSON.stringify({ updateInfo: updateInfo }) - }); - } - } - - public static onReportStatus(callback: acquisitionSdk.Callback): void { - callback(/*error*/ null, /*response*/ { statusCode: 200 }); - } -} diff --git a/sdk/test/management-sdk.ts b/sdk/test/management-sdk.ts deleted file mode 100644 index 39681d7f..00000000 --- a/sdk/test/management-sdk.ts +++ /dev/null @@ -1,260 +0,0 @@ -/// - -import * as assert from "assert"; -import * as Q from "q"; - -import {AccountManager} from "../script/management/account-manager"; - -var request = require("superagent"); - -var manager: AccountManager; -describe("Management SDK", () => { - - beforeEach(() => { - manager = new AccountManager("http://localhost"); - }); - - after(() => { - // Prevent an exception that occurs due to how superagent-mock overwrites methods - request.Request.prototype._callback = function () { }; - }); - - it("methods reject the promise with status code info when an error occurs", (done: MochaDone) => { - mockReturn("Text", 404); - - var methodsWithErrorHandling: any[] = [ - manager.addApp.bind(manager, "appName"), - manager.getApp.bind(manager, "appId"), - manager.updateApp.bind(manager, {}), - manager.removeApp.bind(manager, "appId"), - - manager.addDeployment.bind(manager, "appId", "name"), - manager.getDeployment.bind(manager, "appId", "deploymentId"), - manager.getDeployments.bind(manager, "appId"), - manager.updateDeployment.bind(manager, "appId", { id: "deploymentToChange" }), - manager.removeDeployment.bind(manager, "appId", { id: "deploymentToChange" }), - - manager.getDeploymentKey.bind(manager, "appId", "deploymentId", "deploymentKeyId"), - manager.getDeploymentKeys.bind(manager, "appId", "deploymentId"), - manager.updateDeploymentKey.bind(manager, "appId", "deploymentId", "deploymentKeyId", {}), - - manager.getPackage.bind(manager, ""), - manager.logout.bind(manager), - ]; - - var result = Q(null); - methodsWithErrorHandling.forEach(function (f) { - result = result.then(() => { - return testErrors(f); - }); - }); - - result.done(() => { - done(); - }); - - // Test that the proper error code and text is passed through on a server error - function testErrors(method: any): Q.Promise { - return Q.Promise((resolve: any, reject: any, notify: any) => { - method().done(() => { - assert.fail("Should have thrown an error"); - reject(); - }, (error: any) => { - assert.equal(error.message, "Text"); - resolve(); - }); - }); - } - }); - - it("isAuthenticated handles successful auth", (done: MochaDone) => { - mockReturn(JSON.stringify({ authenticated: true }), 200, {}); - manager.isAuthenticated().done((authenticated: boolean) => { - assert(authenticated, "Should be authenticated"); - done(); - }); - }); - - it("isAuthenticated handles unsuccessful auth", (done: MochaDone) => { - mockReturn("Unauthorized", 401, {}); - manager.isAuthenticated().done((authenticated: boolean) => { - assert(!authenticated, "Should not be authenticated"); - done(); - }); - }); - - it("isAuthenticated handles unexpected status codes", (done: MochaDone) => { - mockReturn("Not Found", 404, {}); - manager.isAuthenticated().done((authenticated: boolean) => { - assert.fail("isAuthenticated should have rejected the promise"); - done(); - }, (err) => { - assert.equal(err.message, "Not Found", "Error message should be 'Not Found'"); - done(); - }); - }); - - it("addApp handles location header", (done: MochaDone) => { - mockReturn(JSON.stringify({ success: true }), 200, { location: "/appId" }); - manager.addApp("appName").done((obj) => { - assert.ok(obj); - done(); - }, rejectHandler); - }); - - it("addApp handles missing location header", (done: MochaDone) => { - mockReturn(JSON.stringify({ success: true }), 200, {}); - manager.addApp("appName").done((obj) => { - assert.ok(!obj); - done(); - }, rejectHandler); - }); - - it("getApp handles JSON response", (done: MochaDone) => { - mockReturn(JSON.stringify({ app: {} }), 200, {}); - - manager.getApp("appId").done((obj: any) => { - assert.ok(obj); - done(); - }, rejectHandler); - }); - - it("updateApp handles success response", (done: MochaDone) => { - mockReturn(JSON.stringify({ apps: [] }), 200, {}); - - manager.updateApp({}).done((obj: any) => { - assert.ok(!obj); - done(); - }, rejectHandler); - }); - - it("removeApp handles success response", (done: MochaDone) => { - mockReturn("", 200, {}); - - manager.removeApp("appId").done((obj: any) => { - assert.ok(!obj); - done(); - }, rejectHandler); - }); - - it("addDeployment handles success response", (done: MochaDone) => { - mockReturn("", 200, { location: "/deploymentId" }); - - manager.addDeployment("appId", "name").done((obj: any) => { - assert.ok(obj); - done(); - }, rejectHandler); - }); - - it("addDeployment handles missing location header", (done: MochaDone) => { - mockReturn("", 200, {}); - - manager.addDeployment("appId", "name").done((obj: any) => { - assert.ok(!obj); - done(); - }, rejectHandler); - }); - - it("getDeployment handles JSON response", (done: MochaDone) => { - mockReturn(JSON.stringify({ deployment: {} }), 200, {}); - - manager.getDeployment("appId", "deploymentId").done((obj: any) => { - assert.ok(obj); - done(); - }, rejectHandler); - }); - - it("getDeployments handles JSON response", (done: MochaDone) => { - mockReturn(JSON.stringify({ deployments: [] }), 200, {}); - - manager.getDeployments("appId").done((obj: any) => { - assert.ok(obj); - done(); - }, rejectHandler); - }); - - it("updateDeployment handles success response", (done: MochaDone) => { - mockReturn(JSON.stringify({ apps: [] }), 200, {}); - - manager.updateDeployment("appId", {id: "deploymentId"}).done((obj: any) => { - assert.ok(!obj); - done(); - }, rejectHandler); - }); - - it("removeDeployment handles success response", (done: MochaDone) => { - mockReturn("", 200, {}); - - manager.removeDeployment("appId", "deploymentId").done((obj: any) => { - assert.ok(!obj); - done(); - }, rejectHandler); - }); - - it("getDeploymentKey handles JSON response", (done: MochaDone) => { - mockReturn(JSON.stringify({ deploymentKey: {} }), 200, {}); - - manager.getDeploymentKey("appId", "deploymentId", "deploymentKeyId").done((obj: any) => { - assert.ok(obj); - done(); - }, rejectHandler); - }); - - it("getDeploymentKeys handles JSON response", (done: MochaDone) => { - mockReturn(JSON.stringify({ deploymentKeys: [] }), 200, {}); - - manager.getDeploymentKeys("appId", "deploymentId").done((obj: any) => { - assert.ok(obj); - done(); - }, rejectHandler); - }); - - it("removeDeploymentKey handles success response", (done: MochaDone) => { - mockReturn("", 200, {}); - - manager.updateDeploymentKey("appId", "deploymentId", "deploymentKeyId", {}).done((obj: any) => { - assert.ok(!obj); - done(); - }, rejectHandler); - }); - - it("removeDeploymentKey handles success response", (done: MochaDone) => { - mockReturn("", 200, {}); - - manager.deleteDeploymentKey("appId", "deploymentId", "deploymentKeyId").done((obj: any) => { - assert.ok(!obj); - done(); - }, rejectHandler); - }); - - it("getPackage handles success response", (done: MochaDone) => { - mockReturn(JSON.stringify({ package: {} }), 200); - - manager.getPackage("appId", "deploymentId").done((obj: any) => { - assert.ok(obj); - done(); - }, rejectHandler); - }); -}); - -// Helper method that is used everywhere that an assert.fail() is needed in a promise handler -function rejectHandler(val: any): void { - assert.fail(); -} - -// Wrapper for superagent-mock that abstracts away information not needed for SDK tests -function mockReturn(bodyText: string, statusCode: number, header = {}) { - require("superagent-mock")(request, [{ - pattern: "http://localhost/(\\w+)/?", - fixtures: function (match: any, params: any) { - var isOk = statusCode >= 200 && statusCode < 300; - if (!isOk) { - var err: any = new Error(bodyText); - err.status = statusCode; - throw err; - } - return { text: bodyText, status: statusCode, ok: isOk, header: header, headers: {} }; - }, - callback: function (match: any, data: any) { return data; } - }]); -} diff --git a/sdk/test/superagent-mock-config.js b/sdk/test/superagent-mock-config.js deleted file mode 100644 index a68c18e9..00000000 --- a/sdk/test/superagent-mock-config.js +++ /dev/null @@ -1,55 +0,0 @@ -// ./superagent-mock-config.js file -module.exports = [ - { - pattern: 'http://localhost/(\\w+)/', - - /** - * returns the data - * - * @param match array Result of the resolution of the regular expression - * @param params object sent by 'send' function - */ - fixtures: function (match, params) { - return {text: "Error", status: 403, ok: false}; - - /** - * example: - * request.get('https://error.example/404').end(function(err, res){ - * console.log(err); // 404 - * }) - */ - if (match[1] === '404') { - throw new Error(404); - } - - /** - * example: - * request.get('https://error.example/200').end(function(err, res){ - * console.log(res.body); // "Data fixtures" - * }) - */ - - /** - * example: - * request.get('https://domain.send.example/').send({superhero: "me"}).end(function(err, res){ - * console.log(res.body); // "Data fixtures - superhero:me" - * }) - */ - if(params["superhero"]) { - return 'Data fixtures - superhero:' + params["superhero"]; - } else { - return 'Data fixtures'; - } - }, - - /** - * returns the result of the request - * - * @param match array Result of the resolution of the regular expression - * @param data mixed Data returns by `fixtures` attribute - */ - callback: function (match, data) { - return data; - } - }, -]; \ No newline at end of file diff --git a/cli/definitions/recursive-fs.d.ts b/src/@types/recursive-fs.d.ts similarity index 100% rename from cli/definitions/recursive-fs.d.ts rename to src/@types/recursive-fs.d.ts diff --git a/src/script/acquisition-sdk.ts b/src/script/acquisition-sdk.ts new file mode 100644 index 00000000..b916f004 --- /dev/null +++ b/src/script/acquisition-sdk.ts @@ -0,0 +1,290 @@ +import { UpdateCheckResponse, UpdateCheckRequest, DeploymentStatusReport, DownloadReport } from "./types"; +import { CodePushHttpError, CodePushDeployStatusError, CodePushPackageError } from "./code-push-error" + +export module 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/script/code-push-error.ts b/src/script/code-push-error.ts new file mode 100644 index 00000000..21ca7fd9 --- /dev/null +++ b/src/script/code-push-error.ts @@ -0,0 +1,34 @@ +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/script/index.ts b/src/script/index.ts new file mode 100644 index 00000000..d8806c3d --- /dev/null +++ b/src/script/index.ts @@ -0,0 +1,2 @@ +import AccountManager = require("./management-sdk"); +export = AccountManager; diff --git a/src/script/management-sdk.ts b/src/script/management-sdk.ts new file mode 100644 index 00000000..d361b2e7 --- /dev/null +++ b/src/script/management-sdk.ts @@ -0,0 +1,396 @@ +import * as fs from "fs"; +import * as path from "path"; +import slash = require("slash"); +import * as recursiveFs from "recursive-fs"; +import * as yazl from "yazl"; +import Adapter from "../utils/adapter/adapter" +import RequestManager from "../utils/request-manager" +import { CodePushUnauthorizedError } from "./code-push-error" +import FileUploadClient, { IProgress } from "appcenter-file-upload-client"; + +import { AccessKey, AccessKeyRequest, Account, App, AppCreationRequest, CollaboratorMap, Deployment, DeploymentMetrics, Headers, Package, PackageInfo, ReleaseUploadAssets, UploadReleaseProperties, CodePushError } from "./types"; + +interface JsonResponse { + headers: Headers; + body?: any; +} + +interface PackageFile { + isTemporary: boolean; + path: string; +} + +// A template string tag function that URL encodes the substituted values +function urlEncode(strings: TemplateStringsArray, ...values: string[]): string { + var result = ""; + for (var i = 0; i < strings.length; i++) { + result += strings[i]; + if (i < values.length) { + result += encodeURIComponent(values[i]); + } + } + + return result; +} + +class AccountManager { + public static AppPermission = { + OWNER: "Owner", + COLLABORATOR: "Collaborator" + }; + + private _accessKey: string; + private _requestManager: RequestManager; + private _adapter: Adapter; + private _fileUploadClient: FileUploadClient; + + constructor(accessKey: string, customHeaders?: Headers, serverUrl?: string, proxy?: string) { + if (!accessKey) throw new CodePushUnauthorizedError("A token must be specified."); + + this._accessKey = accessKey; + this._requestManager = new RequestManager(accessKey, customHeaders, serverUrl, proxy); + this._adapter = new Adapter(this._requestManager); + this._fileUploadClient = new FileUploadClient(); + } + + public get accessKey(): string { + return this._accessKey; + } + + public async isAuthenticated(throwIfUnauthorized?: boolean): Promise { + let res: JsonResponse; + let codePushError: CodePushError; + + try { + res = await this._requestManager.get(urlEncode`/user`, false); + } catch (error) { + codePushError = error as CodePushError; + if (codePushError && (codePushError.statusCode !== RequestManager.ERROR_UNAUTHORIZED || throwIfUnauthorized)) { + throw codePushError; + } + } + + const authenticated: boolean = !!res && !!res.body; + + return authenticated; + } + + // Access keys + public async addAccessKey(friendlyName: string, ttl?: number): Promise { + if (!friendlyName) { + throw new CodePushUnauthorizedError("A name must be specified when adding an access key."); + } + + const accessKeyRequest: AccessKeyRequest = { + description: friendlyName + }; + + const res: JsonResponse = await this._requestManager.post(urlEncode`/api_tokens`, JSON.stringify(accessKeyRequest), /*expectResponseBody=*/ true); + const accessKey = this._adapter.toLegacyAccessKey(res.body); + return accessKey; + } + + public async getAccessKeys(): Promise { + const res: JsonResponse = await this._requestManager.get(urlEncode`/api_tokens`); + const accessKeys = this._adapter.toLegacyAccessKeyList(res.body); + return accessKeys; + } + + public async removeAccessKey(name: string): Promise { + const accessKey = await this._adapter.resolveAccessKey(name); + + await this._requestManager.del(urlEncode`/api_tokens/${accessKey.id}`); + return null; + } + + // Account + public async getAccountInfo(): Promise { + const res: JsonResponse = await this._requestManager.get(urlEncode`/user`); + const accountInfo = this._adapter.toLegacyAccount(res.body); + return accountInfo; + } + + // Apps + public async getApps(): Promise { + const res: JsonResponse = await this._requestManager.get(urlEncode`/apps`); + const apps = await this._adapter.toLegacyApps(res.body); + return apps; + } + + public async getApp(appName: string): Promise { + const appParams = await this._adapter.parseApiAppName(appName); + const res: JsonResponse = await this._requestManager.get(urlEncode`/apps/${appParams.appOwner}/${appParams.appName}`); + const app = await this._adapter.toLegacyApp(res.body); + return app; + } + + public async addApp(appName: string, appOs: string, appPlatform: string, manuallyProvisionDeployments: boolean = false): Promise { + var app: AppCreationRequest = { + name: appName, + os: appOs, + platform: appPlatform, + manuallyProvisionDeployments: manuallyProvisionDeployments + }; + + const apigatewayAppCreationRequest = this._adapter.toApigatewayAppCreationRequest(app); + + const path = apigatewayAppCreationRequest.org ? `/orgs/${apigatewayAppCreationRequest.org}/apps` : `/apps`; + await this._requestManager.post(path, JSON.stringify(apigatewayAppCreationRequest.appcenterClientApp), /*expectResponseBody=*/ false); + + if (!manuallyProvisionDeployments) { + await this._adapter.addStandardDeployments(appName); + } + return app; + } + + public async removeApp(appName: string): Promise { + const appParams = await this._adapter.parseApiAppName(appName); + await this._requestManager.del(urlEncode`/apps/${appParams.appOwner}/${appParams.appName}`); + return null; + } + + public async renameApp(oldAppName: string, newAppName: string): Promise { + const { appOwner, appName } = await this._adapter.parseApiAppName(oldAppName); + const updatedApp = await this._adapter.getRenamedApp(newAppName, appOwner, appName); + + await this._requestManager.patch(urlEncode`/apps/${appOwner}/${appName}`, JSON.stringify(updatedApp)); + return null; + } + + public async transferApp(appName: string, orgName: string): Promise { + const appParams = await this._adapter.parseApiAppName(appName); + + await this._requestManager.post(urlEncode`/apps/${appParams.appOwner}/${appParams.appName}/transfer/${orgName}`, /*requestBody=*/ null, /*expectResponseBody=*/ false); + return null; + } + + // Collaborators + public async getCollaborators(appName: string): Promise { + const appParams = await this._adapter.parseApiAppName(appName); + + const res: JsonResponse = await this._requestManager.get(urlEncode`/apps/${appParams.appOwner}/${appParams.appName}/users`); + const collaborators = await this._adapter.toLegacyCollaborators(res.body, appParams.appOwner); + return collaborators; + } + + public async addCollaborator(appName: string, email: string): Promise { + const appParams = await this._adapter.parseApiAppName(appName); + const userEmailRequest = { + user_email: email + }; + await this._requestManager.post(urlEncode`/apps/${appParams.appOwner}/${appParams.appName}/invitations`, JSON.stringify(userEmailRequest), /*expectResponseBody=*/ false); + return null; + } + + public async removeCollaborator(appName: string, email: string): Promise { + const appParams = await this._adapter.parseApiAppName(appName); + + await this._requestManager.del(urlEncode`/apps/${appParams.appOwner}/${appParams.appName}/invitations/${email}`); + return null; + } + + // Deployments + public async addDeployment(appName: string, deploymentName: string): Promise { + const deployment = { name: deploymentName }; + const appParams = await this._adapter.parseApiAppName(appName); + const res = await this._requestManager.post(urlEncode`/apps/${appParams.appOwner}/${appParams.appName}/deployments/`, JSON.stringify(deployment), /*expectResponseBody=*/ true); + + return this._adapter.toLegacyDeployment(res.body); + } + + public async clearDeploymentHistory(appName: string, deploymentName: string): Promise { + const appParams = await this._adapter.parseApiAppName(appName); + + await this._requestManager.del(urlEncode`/apps/${appParams.appOwner}/${appParams.appName}/deployments/${deploymentName}/releases`); + return null; + } + + public async getDeployments(appName: string): Promise { + const appParams = await this._adapter.parseApiAppName(appName); + const res: JsonResponse = await this._requestManager.get(urlEncode`/apps/${appParams.appOwner}/${appParams.appName}/deployments/`); + + return this._adapter.toLegacyDeployments(res.body); + } + + public async getDeployment(appName: string, deploymentName: string): Promise { + const appParams = await this._adapter.parseApiAppName(appName); + const res: JsonResponse = await this._requestManager.get(urlEncode`/apps/${appParams.appOwner}/${appParams.appName}/deployments/${deploymentName}`); + + return this._adapter.toLegacyDeployment(res.body); + } + + public async renameDeployment(appName: string, oldDeploymentName: string, newDeploymentName: string): Promise { + const appParams = await this._adapter.parseApiAppName(appName); + await this._requestManager.patch(urlEncode`/apps/${appParams.appOwner}/${appParams.appName}/deployments/${oldDeploymentName}`, JSON.stringify({ name: newDeploymentName })); + + return null; + } + + public async removeDeployment(appName: string, deploymentName: string): Promise { + const appParams = await this._adapter.parseApiAppName(appName); + await this._requestManager.del(urlEncode`/apps/${appParams.appOwner}/${appParams.appName}/deployments/${deploymentName}`); + + return null; + } + + public async getDeploymentMetrics(appName: string, deploymentName: string): Promise { + const appParams = await this._adapter.parseApiAppName(appName); + + const res = await this._requestManager.get(urlEncode`/apps/${appParams.appOwner}/${appParams.appName}/deployments/${deploymentName}/metrics`); + const deploymentMetrics = this._adapter.toLegacyDeploymentMetrics(res.body); + return deploymentMetrics; + } + + public async getDeploymentHistory(appName: string, deploymentName: string): Promise { + const appParams = await this._adapter.parseApiAppName(appName); + const res = await this._requestManager.get(urlEncode`/apps/${appParams.appOwner}/${appParams.appName}/deployments/${deploymentName}/releases`); + + return this._adapter.toLegacyDeploymentHistory(res.body); + } + + // Releases + public async release(appName: string, deploymentName: string, filePath: string, targetBinaryVersion: string, updateMetadata: PackageInfo, uploadProgressCallback?: (progress: number) => void): Promise { + updateMetadata.appVersion = targetBinaryVersion; + const packageFile: PackageFile = await this.packageFileFromPath(filePath); + const appParams = await this._adapter.parseApiAppName(appName); + + const assetJsonResponse: JsonResponse = await this._requestManager.post(urlEncode`/apps/${appParams.appOwner}/${appParams.appName}/deployments/${deploymentName}/uploads`, null, true) + const assets = assetJsonResponse.body as ReleaseUploadAssets; + + await this._fileUploadClient.upload({ + assetId: assets.id, + assetDomain: assets.upload_domain, + assetToken: assets.token, + file: packageFile.path, + onProgressChanged: (progressData: IProgress) => { + if (uploadProgressCallback) { + uploadProgressCallback(progressData.percentCompleted); + } + }, + }); + + const releaseUploadProperties: UploadReleaseProperties = this._adapter.toReleaseUploadProperties(updateMetadata, assets, deploymentName); + const releaseJsonResponse: JsonResponse = await this._requestManager.post(urlEncode`/apps/${appParams.appOwner}/${appParams.appName}/deployments/${deploymentName}/releases`, JSON.stringify(releaseUploadProperties), true); + const releasePackage: Package = this._adapter.releaseToPackage(releaseJsonResponse.body); + + return releasePackage; + } + + public async patchRelease(appName: string, deploymentName: string, label: string, updateMetadata: PackageInfo): Promise { + const appParams = await this._adapter.parseApiAppName(appName); + const requestBody = this._adapter.toRestReleaseModification(updateMetadata); + + await this._requestManager.patch(urlEncode`/apps/${appParams.appOwner}/${appParams.appName}/deployments/${deploymentName}/releases/${label}`, JSON.stringify(requestBody), /*expectResponseBody=*/ false) + return null; + } + + public async promote(appName: string, sourceDeploymentName: string, destinationDeploymentName: string, updateMetadata: PackageInfo): Promise { + const appParams = await this._adapter.parseApiAppName(appName); + const requestBody = this._adapter.toRestReleaseModification(updateMetadata); + const res = await this._requestManager.post(urlEncode`/apps/${appParams.appOwner}/${appParams.appName}/deployments/${sourceDeploymentName}/promote_release/${destinationDeploymentName}`, JSON.stringify(requestBody), /*expectResponseBody=*/ true); + const releasePackage: Package = this._adapter.releaseToPackage(res.body); + + return releasePackage; + } + + public async rollback(appName: string, deploymentName: string, targetRelease?: string): Promise { + const appParams = await this._adapter.parseApiAppName(appName); + const requestBody = targetRelease ? { + label: targetRelease + } : {}; + + await this._requestManager.post(urlEncode`/apps/${appParams.appOwner}/${appParams.appName}/deployments/${deploymentName}/rollback_release`, JSON.stringify(requestBody), /*expectResponseBody=*/ false); + return null; + } + + // Deprecated + public getAccessKey(accessKeyName: string): CodePushError { + throw { + message: 'Method is deprecated', + statusCode: 404 + } + } + + // Deprecated + public getSessions(): CodePushError { + throw this.getDeprecatedMethodError(); + } + + // Deprecated + public patchAccessKey(oldName: string, newName?: string, ttl?: number): CodePushError { + throw this.getDeprecatedMethodError(); + } + + // Deprecated + public removeSession(machineName: string): CodePushError { + throw this.getDeprecatedMethodError(); + } + + private packageFileFromPath(filePath: string): Promise { + var getPackageFilePromise: Promise; + if (fs.lstatSync(filePath).isDirectory()) { + getPackageFilePromise = new Promise((resolve: (file: PackageFile) => void, reject: (reason: Error) => void): void => { + var directoryPath: string = filePath; + + recursiveFs.readdirr(directoryPath, (error?: any, directories?: string[], files?: string[]): void => { + if (error) { + reject(error); + return; + } + + var baseDirectoryPath = path.dirname(directoryPath); + var fileName: string = this.generateRandomFilename(15) + ".zip"; + var zipFile = new yazl.ZipFile(); + var writeStream: fs.WriteStream = fs.createWriteStream(fileName); + + zipFile.outputStream.pipe(writeStream) + .on("error", (error: Error): void => { + reject(error); + }) + .on("close", (): void => { + filePath = path.join(process.cwd(), fileName); + + resolve({ isTemporary: true, path: filePath }); + }); + + for (var i = 0; i < files.length; ++i) { + var file: string = files[i]; + var relativePath: string = path.relative(baseDirectoryPath, file); + + // yazl does not like backslash (\) in the metadata path. + relativePath = slash(relativePath); + + zipFile.addFile(file, relativePath); + } + + zipFile.end(); + }); + }); + } else { + getPackageFilePromise = new Promise((resolve: (file: PackageFile) => void, reject: (reason: Error) => void): void => { + resolve({ isTemporary: false, path: filePath }); + }); + } + return getPackageFilePromise; + } + + private generateRandomFilename(length: number): string { + var filename: string = ""; + var validChar: string = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + + for (var i = 0; i < length; i++) { + filename += validChar.charAt(Math.floor(Math.random() * validChar.length)); + } + + return filename; + } + + private getDeprecatedMethodError() { + return { + message: 'Method is deprecated', + statusCode: 404 + }; + } +} + +export = AccountManager; diff --git a/src/script/types.ts b/src/script/types.ts new file mode 100644 index 00000000..3145d2ae --- /dev/null +++ b/src/script/types.ts @@ -0,0 +1,197 @@ +/** + * Annotations for properties on 'inout' interfaces: + * - generated: This property cannot be specified on any input requests (PUT/PATCH/POST). + * As a result, generated properties are always marked as optional. + * - key: This property is the identifier for an object, with certain uniqueness constraints. + */ + +interface AccessKeyBase { + createdBy?: string; + /*legacy*/ description?: string; + /*key*/ friendlyName?: string; + /*generated key*/ name?: string; +} + +/*out*/ +export interface ServerAccessKey extends AccessKeyBase { + /*generated*/ createdTime?: number; + expires: number; + /*generated*/ isSession?: boolean; +} + +/*in*/ +export interface AccessKeyRequest extends AccessKeyBase { + ttl?: number; +} + +/*out*/ +export interface DeploymentMetrics { + [packageLabelOrAppVersion: string]: UpdateMetrics; +} + +/*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; +} + +/*inout*/ +export interface PackageInfo { + appVersion?: string; + description?: string; + isDisabled?: boolean; + isMandatory?: boolean; + /*generated*/ label?: string; + /*generated*/ packageHash?: string; + rollout?: number; +} + +/*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; +} + +/*out*/ +export interface UpdateMetrics { + active: number; + downloaded?: number; + failed?: number; + installed?: number; +} + +/*out*/ +export interface Account { + /*key*/ email: string; + name: string; + linkedProviders: string[]; +} + +/*out*/ +export interface CollaboratorProperties { + isCurrentAccount?: boolean; + permission: string; +} + +/*out*/ +export interface CollaboratorMap { + [email: string]: CollaboratorProperties; +} + +/*inout*/ +export interface App { + /*generated*/ collaborators?: CollaboratorMap; + /*key*/ name: string; + /* generated */ deployments?: string[]; + os?: string; + platform?: string; +} + +/*in*/ +export interface AppCreationRequest extends App { + manuallyProvisionDeployments?: boolean; +} + +/*inout*/ +export interface Deployment { + /*generated key*/ key?: string; + /*key*/ name: string; + /*generated*/ package?: Package; +} + +/*out*/ +export interface BlobInfo { + size: number; + url: string; +} + +/*out*/ +export interface PackageHashToBlobInfoMap { + [packageHash: string]: BlobInfo; +} + +/*inout*/ +export interface Package extends PackageInfo { + /*generated*/ blobUrl: string; + /*generated*/ diffPackageMap?: PackageHashToBlobInfoMap; + /*generated*/ originalLabel?: string; // Set on "Promote" and "Rollback" + /*generated*/ originalDeployment?: string; // Set on "Promote" + /*generated*/ releasedBy?: string; // Set by commitPackage + /*generated*/ releaseMethod?: string; // "Upload", "Promote" or "Rollback". Unknown if unspecified + /*generated*/ size: number; + /*generated*/ uploadTime: number; + /*legacy*/ releasedByUserId?: string; + /*legacy*/ manifestBlobUrl?: string; +} + +/*out*/ +export interface CodePushError { + message: string; + statusCode: number; +} + +/*in*/ +export interface AccessKey { + createdTime: number; + expires: number; + name: string; + key?: string; +} + +/*inout*/ +export interface Session { + loggedInTime: number; + machineName: string; +} + +/*in*/ +export interface ReleaseUploadAssets { + id: string; + upload_domain: string; + token: string; +} + +/*out*/ +export interface UploadReleaseProperties { + release_upload: ReleaseUploadAssets, + target_binary_version: string, + deployment_name?: string, + description?: string, + disabled?: boolean, + mandatory?: boolean, + no_duplicate_release_error?: boolean, + rollout?: number +} + +export type Headers = { [headerName: string]: string }; diff --git a/src/test/acquisition-rest-mock.ts b/src/test/acquisition-rest-mock.ts new file mode 100644 index 00000000..b3620d83 --- /dev/null +++ b/src/test/acquisition-rest-mock.ts @@ -0,0 +1,121 @@ +import * as querystring from "querystring"; + +import * as acquisitionSdk from "../script/acquisition-sdk"; +import * as types from "../script/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/sdk/test/acquisition-sdk.ts b/src/test/acquisition-sdk.ts similarity index 54% rename from sdk/test/acquisition-sdk.ts rename to src/test/acquisition-sdk.ts index a0531f26..18aa86f8 100644 --- a/sdk/test/acquisition-sdk.ts +++ b/src/test/acquisition-sdk.ts @@ -1,25 +1,26 @@ -/// - import * as assert from "assert"; -import * as express from "express"; -import * as http from "http"; import * as acquisitionSdk from "../script/acquisition-sdk"; -import * as mockApi from "./acquisition-rest-mock"; -import * as rest from "rest-definitions"; +import * as acquisitionRestMock from "./acquisition-rest-mock"; +import * as types from "../script/types"; +import { CodePushPackageError } from "../script/code-push-error" +import { updateMockUrl } from "./acquisition-rest-mock"; -var latestPackage: rest.UpdateCheckResponse = clone(mockApi.latestPackage); +const mockApi = acquisitionRestMock; +var latestPackage: types.UpdateCheckResponse = clone(mockApi.latestPackage); var configuration: acquisitionSdk.Configuration = { - serverUrl: mockApi.serverUrl, + appVersion: "1.5.0", + clientUniqueId: "My iPhone", deploymentKey: mockApi.validDeploymentKey, + serverUrl: mockApi.serverUrl, } var templateCurrentPackage: acquisitionSdk.Package = { deploymentKey: mockApi.validDeploymentKey, - description: "sdfsdf", - label: "0.0.1", - appVersion: latestPackage.appVersion, + description: "Standard description", + label: "v1", + appVersion: latestPackage.target_binary_range, packageHash: "hash001", isMandatory: false, packageSize: 100 @@ -28,25 +29,27 @@ var templateCurrentPackage: acquisitionSdk.Package = { var scriptUpdateResult: acquisitionSdk.RemotePackage = { deploymentKey: mockApi.validDeploymentKey, description: latestPackage.description, - downloadUrl: latestPackage.downloadURL, + downloadUrl: latestPackage.download_url, label: latestPackage.label, - appVersion: latestPackage.appVersion, - isMandatory: latestPackage.isMandatory, - packageHash: latestPackage.packageHash, - packageSize: latestPackage.packageSize + 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.appVersion + 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: MochaDone) => { + 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); @@ -55,9 +58,9 @@ describe("Acquisition SDK", () => { }); }); - it("Package with equal package hash gives no update", (done: MochaDone) => { + it("Package with equal package hash gives no update", (done: Mocha.Done) => { var equalVersionPackage: acquisitionSdk.Package = clone(templateCurrentPackage); - equalVersionPackage.packageHash = latestPackage.packageHash; + equalVersionPackage.packageHash = latestPackage.package_hash; var acquisition = new acquisitionSdk.AcquisitionManager(new mockApi.HttpRequester(), configuration); acquisition.queryUpdateWithCurrentPackage(equalVersionPackage, (error: Error, returnPackage: acquisitionSdk.RemotePackage | acquisitionSdk.NativeUpdateNotification) => { @@ -67,7 +70,7 @@ describe("Acquisition SDK", () => { }); }); - it("Package with higher different hash and higher label version gives update", (done: MochaDone) => { + it("Package with higher different hash and higher label version gives update", (done: Mocha.Done) => { var higherVersionPackage: acquisitionSdk.Package = clone(templateCurrentPackage); higherVersionPackage.packageHash = "hash990"; @@ -79,7 +82,7 @@ describe("Acquisition SDK", () => { }); }); - it("Package with lower native version gives update notification", (done: MochaDone) => { + it("Package with lower native version gives update notification", (done: Mocha.Done) => { var lowerAppVersionPackage: acquisitionSdk.Package = clone(templateCurrentPackage); lowerAppVersionPackage.appVersion = "0.0.1"; @@ -91,7 +94,7 @@ describe("Acquisition SDK", () => { }); }); - it("Package with higher native version gives no update", (done: MochaDone) => { + it("Package with higher native version gives no update", (done: Mocha.Done) => { var higherAppVersionPackage: acquisitionSdk.Package = clone(templateCurrentPackage); higherAppVersionPackage.appVersion = "9.9.0"; @@ -103,23 +106,23 @@ describe("Acquisition SDK", () => { }); }); - it("An empty response gives no update", (done: MochaDone) => { + it("An empty response gives no update", (done: Mocha.Done) => { var lowerAppVersionPackage: acquisitionSdk.Package = clone(templateCurrentPackage); lowerAppVersionPackage.appVersion = "0.0.1"; - var emptyReponse: acquisitionSdk.Http.Response = { + var emptyResponse: acquisitionSdk.Http.Response = { statusCode: 200, body: JSON.stringify({}) }; - var acquisition = new acquisitionSdk.AcquisitionManager(new mockApi.CustomResponseHttpRequester(emptyReponse), configuration); - acquisition.queryUpdateWithCurrentPackage(lowerAppVersionPackage, (error: Error, returnPackage: acquisitionSdk.RemotePackage|acquisitionSdk.NativeUpdateNotification) => { + 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: MochaDone) => { + it("An unexpected (but valid) JSON response gives no update", (done: Mocha.Done) => { var lowerAppVersionPackage: acquisitionSdk.Package = clone(templateCurrentPackage); lowerAppVersionPackage.appVersion = "0.0.1"; @@ -129,13 +132,13 @@ describe("Acquisition SDK", () => { }; var acquisition = new acquisitionSdk.AcquisitionManager(new mockApi.CustomResponseHttpRequester(unexpectedResponse), configuration); - acquisition.queryUpdateWithCurrentPackage(lowerAppVersionPackage, (error: Error, returnPackage: acquisitionSdk.RemotePackage|acquisitionSdk.NativeUpdateNotification) => { + 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: MochaDone) => { + 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"; @@ -150,9 +153,8 @@ describe("Acquisition SDK", () => { }); }); - it("If latest package is mandatory, returned package is mandatory", (done: MochaDone) => { - mockApi.latestPackage.isMandatory = true; - + 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); @@ -161,9 +163,10 @@ describe("Acquisition SDK", () => { }); }); - it("If invalid arguments are provided, an error is raised", (done: MochaDone) => { + 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 { @@ -172,35 +175,37 @@ describe("Acquisition SDK", () => { 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: MochaDone) => { + 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 invalidJsonReponse: acquisitionSdk.Http.Response = { + var invalidJsonResponse: acquisitionSdk.Http.Response = { statusCode: 200, body: "invalid {{ json" }; - var acquisition = new acquisitionSdk.AcquisitionManager(new mockApi.CustomResponseHttpRequester(invalidJsonReponse), configuration); - acquisition.queryUpdateWithCurrentPackage(lowerAppVersionPackage, (error: Error, returnPackage: acquisitionSdk.RemotePackage|acquisitionSdk.NativeUpdateNotification) => { + 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: MochaDone) => { - // TODO: behaviour is not defined + it("If deploymentKey is not valid...", (done: Mocha.Done) => { + // TODO: behavior is not defined done(); }); - it("reportStatus(...) signals completion", (done: MochaDone): void => { + it("reportStatusDeploy(...) signals completion", (done: Mocha.Done): void => { var acquisition = new acquisitionSdk.AcquisitionManager(new mockApi.HttpRequester(), configuration); - acquisition.reportStatus(acquisitionSdk.AcquisitionStatus.DeploymentFailed, "message", ((error: Error, parameter: void): void => { + acquisition.reportStatusDeploy(templateCurrentPackage, acquisitionSdk.AcquisitionStatus.DeploymentFailed, "1.5.0", mockApi.validDeploymentKey, ((error: Error, parameter: void): void => { if (error) { throw error; } @@ -210,6 +215,82 @@ describe("Acquisition SDK", () => { 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 { diff --git a/src/test/management-sdk.ts b/src/test/management-sdk.ts new file mode 100644 index 00000000..5f13d7c6 --- /dev/null +++ b/src/test/management-sdk.ts @@ -0,0 +1,508 @@ +import assert from "assert"; + +import AccountManager = require("../script/management-sdk"); +import adapterTypes = require("../utils/adapter/adapter-types"); + +var request = require("superagent"); + +var manager: AccountManager; + +const testUser: adapterTypes.UserProfile = { + id: "testId", + avatar_url: "testAvatarUrl", + can_change_password: false, + display_name: "testDisplayName", + email: "testEmail", + name: "testUserName", + permissions: ["manager"], +} + +const testDeployment: adapterTypes.Deployment = { + createdTime: 123, + name: "testDeployment1", + key: "testKey1" +}; + +const testDeployment2: adapterTypes.Deployment = { + createdTime: 123, + name: "testDeployment2", + key: "testKey2" +}; +const testApp: adapterTypes.App = { + name: "testAppName", + owner: { ...testUser, type: "user" } +} + +const codePushRelease: adapterTypes.CodePushRelease = { + description: "testDescription", + target_binary_range: "testTargetBinaryRange", + upload_time: 123456789, + blob_url: "testBlobUrl", + size: 123456789 +} + +describe("Management SDK", () => { + + beforeEach(() => { + manager = new AccountManager(/*accessKey=*/ "dummyAccessKey", /*customHeaders=*/ null, /*serverUrl=*/ "http://localhost"); + }); + + after(() => { + // Prevent an exception that occurs due to how superagent-mock overwrites methods + request.Request.prototype._callback = function () { }; + }); + + it("methods reject the promise with status code info when an error occurs", (done: Mocha.Done) => { + mockReturn("Text", 404); + + var methodsWithErrorHandling: any[] = [ + manager.addApp.bind(manager, "appName", "iOS", "React-Native"), + manager.getApp.bind(manager, "appName"), + manager.renameApp.bind(manager, "appName", {}), + manager.removeApp.bind(manager, "appName"), + manager.transferApp.bind(manager, "appName", "email1"), + + manager.addDeployment.bind(manager, "appName", "deploymentName"), + manager.getDeployment.bind(manager, "appName", "deploymentName"), + manager.getDeployments.bind(manager, "appName"), + manager.renameDeployment.bind(manager, "appName", "deploymentName", { name: "newDeploymentName" }), + manager.removeDeployment.bind(manager, "appName", "deploymentName"), + + manager.addCollaborator.bind(manager, "appName", "email1"), + manager.getCollaborators.bind(manager, "appName"), + manager.removeCollaborator.bind(manager, "appName", "email1"), + + manager.patchRelease.bind(manager, "appName", "deploymentName", "label", { description: "newDescription" }), + manager.promote.bind(manager, "appName", "deploymentName", "newDeploymentName", { description: "newDescription" }), + manager.rollback.bind(manager, "appName", "deploymentName", "targetReleaseLabel") + ]; + + var result = Promise.resolve(null); + methodsWithErrorHandling.forEach(function (f) { + result = result.then(() => { + return testErrors(f); + }); + }); + + result.then(() => { + done(); + }); + + // Test that the proper error code and text is passed through on a server error + function testErrors(method: any): Promise { + return new Promise((resolve: any, reject: any) => { + method().then(() => { + assert.fail("Should have thrown an error"); + reject(); + }, (error: any) => { + assert.equal(error.message, "Text"); + assert(error.statusCode); + resolve(); + }); + }); + } + }); + + describe("isAuthenticated", () => { + it("isAuthenticated handles successful auth", (done: Mocha.Done) => { + mockReturn(JSON.stringify(testUser), 200); + manager.isAuthenticated() + .then((authenticated: boolean) => { + assert(authenticated, "Should be authenticated"); + done(); + }); + }); + + it("isAuthenticated handles unsuccessful auth", (done: Mocha.Done) => { + mockReturn("Unauthorized", 401); + manager.isAuthenticated() + .then((authenticated: boolean) => { + assert(!authenticated, "Should not be authenticated"); + done(); + }); + }); + + it("isAuthenticated handles unsuccessful auth with promise rejection", (done: Mocha.Done) => { + mockReturn("Unauthorized", 401); + + // use optional parameter to ask for rejection of the promise if not authenticated + manager.isAuthenticated(true) + .then((authenticated: boolean) => { + assert.fail("isAuthenticated should have rejected the promise"); + done(); + }, (err) => { + assert.equal(err.message, "Unauthorized", "Error message should be 'Unauthorized'"); + done(); + }); + }); + + it("isAuthenticated handles unexpected status codes", (done: Mocha.Done) => { + mockReturn("Not Found", 404); + manager.isAuthenticated() + .then((authenticated: boolean) => { + assert.fail("isAuthenticated should have rejected the promise"); + done(); + }, (err) => { + assert.equal(err.message, "Not Found", "Error message should be 'Not Found'"); + done(); + }); + }); + }); + + describe("addApp", () => { + it("addApp handles successful response", (done: Mocha.Done) => { + mockReturn(JSON.stringify(testApp), 201, null, { location: "/appName" }); + manager.addApp("appName", "iOS", "React-Native") + .then((obj) => { + assert.ok(obj); + done(); + }, rejectHandler); + }); + + it("addApp handles error response", (done: Mocha.Done) => { + mockReturn(JSON.stringify({ success: false }), 404); + manager.addApp("appName", "iOS", "React-Native") + .then((obj) => { + throw new Error("Call should not complete successfully"); + }, (error: Error) => done()); + }); + }); + + describe("getApp", () => { + it("getApp handles JSON response", (done: Mocha.Done) => { + mockReturn(JSON.stringify(testApp), 200); + mockUser(); + mockReturn(JSON.stringify([testDeployment, testDeployment2]), 200, `/apps/${testUser.name}/${testApp.name}/deployments/`) + + manager.getApp("appName") + .then((obj: any) => { + assert.ok(obj); + done(); + }, rejectHandler); + }); + }); + + describe("updateApp", () => { + it("updateApp handles success response", (done: Mocha.Done) => { + mockReturn(JSON.stringify(testApp), 200); + + manager.renameApp("appName", "newAppName") + .then((obj: any) => { + assert.ok(!obj); + done(); + }, rejectHandler); + }); + }); + + describe("removeApp", () => { + it("removeApp handles success response", (done: Mocha.Done) => { + mockReturn("", 200); + mockUser(); + + manager.removeApp("appName") + .then((obj: any) => { + assert.ok(!obj); + done(); + }, rejectHandler); + }); + }); + + describe("transferApp", () => { + it("transferApp handles successful response", (done: Mocha.Done) => { + mockReturn("", 201); + mockUser(); + manager.transferApp("appName", "email1") + .then( + () => done(), + rejectHandler + ); + }); + }); + + describe("addDeployment", () => { + it("addDeployment handles success response", (done: Mocha.Done) => { + const testDeploymentWithoutPackage: adapterTypes.Deployment = { ...testDeployment, latest_release: null } + + mockReturn(JSON.stringify(testDeploymentWithoutPackage), 201, null, { location: "/deploymentName" }); + + manager.addDeployment("appName", "deploymentName") + .then((obj: any) => { + assert.ok(obj); + done(); + }, rejectHandler); + }); + }); + + describe("getDeployment", () => { + it("getDeployment handles JSON response", (done: Mocha.Done) => { + mockReturn(JSON.stringify(testDeployment), 200); + mockUser(); + + manager.getDeployment("appName", "deploymentName") + .then((obj: any) => { + assert.ok(obj); + done(); + }, rejectHandler); + }); + }); + + describe("getDeployments", () => { + it("getDeployments handles JSON response", (done: Mocha.Done) => { + mockReturn(JSON.stringify([testDeployment, testDeployment2]), 200); + mockUser(); + + manager.getDeployments("appName") + .then((obj: any) => { + assert.ok(obj); + done(); + }, rejectHandler); + }); + }); + + describe("renameDeployment", () => { + it("renameDeployment handles success response", (done: Mocha.Done) => { + mockReturn(JSON.stringify(testApp), 200); + + manager.renameDeployment("appName", "deploymentName", "newDeploymentName") + .then((obj: any) => { + assert.ok(!obj); + done(); + }, rejectHandler); + }); + }); + + describe("removeDeployment", () => { + it("removeDeployment handles success response", (done: Mocha.Done) => { + mockReturn("", 200); + mockUser(); + + manager.removeDeployment("appName", "deploymentName") + .then((obj: any) => { + assert.ok(!obj); + done(); + }, rejectHandler); + }); + }); + + describe("getDeploymentHistory", () => { + it("getDeploymentHistory handles success response with no packages", (done: Mocha.Done) => { + mockReturn(JSON.stringify([]), 200); + mockUser(); + + manager.getDeploymentHistory("appName", "deploymentName") + .then((obj: any) => { + assert.ok(obj); + assert.equal(obj.length, 0); + done(); + }, rejectHandler); + }); + + it("getDeploymentHistory handles success response with two packages", (done: Mocha.Done) => { + const release: adapterTypes.CodePushRelease = { ...codePushRelease, label: "v1" }; + const release2: adapterTypes.CodePushRelease = { ...codePushRelease, label: "v2" }; + + mockReturn(JSON.stringify([release, release2]), 200); + mockUser(); + + manager.getDeploymentHistory("appName", "deploymentName") + .then((obj: any) => { + assert.ok(obj); + assert.equal(obj.length, 2); + assert.equal(obj[0].label, "v1"); + assert.equal(obj[1].label, "v2"); + done(); + }, rejectHandler); + }); + + it("getDeploymentHistory handles error response", (done: Mocha.Done) => { + mockReturn("", 404); + + manager.getDeploymentHistory("appName", "deploymentName") + .then((obj: any) => { + throw new Error("Call should not complete successfully"); + }, (error: Error) => done()); + }); + }); + + describe("clearDeploymentHistory", () => { + it("clearDeploymentHistory handles success response", (done: Mocha.Done) => { + mockReturn("", 204); + mockUser(); + + manager.clearDeploymentHistory("appName", "deploymentName") + .then((obj: any) => { + assert.ok(!obj); + done(); + }, rejectHandler); + }); + + it("clearDeploymentHistory handles error response", (done: Mocha.Done) => { + mockReturn("", 404); + + manager.clearDeploymentHistory("appName", "deploymentName") + .then((obj: any) => { + throw new Error("Call should not complete successfully"); + }, (error: Error) => done()); + }); + }); + + describe("addCollaborator", () => { + it("addCollaborator handles successful response", (done: Mocha.Done) => { + mockReturn("", 201, null, { location: "/collaborators" }); + mockUser(); + manager.addCollaborator("appName", "email1") + .then( + () => done(), + rejectHandler + ); + }); + + it("addCollaborator handles error response", (done: Mocha.Done) => { + mockReturn("", 404); + manager.addCollaborator("appName", "email1") + .then( + () => { throw new Error("Call should not complete successfully") }, + () => done() + ); + }); + }); + + describe("getCollaborators", () => { + it("getCollaborators handles success response with no collaborators", (done: Mocha.Done) => { + mockReturn(JSON.stringify([]), 200); + mockUser(); + + manager.getCollaborators("appName") + .then((obj: any) => { + assert.ok(obj); + assert.equal(Object.keys(obj).length, 0); + done(); + }, rejectHandler); + }); + + it("getCollaborators handles success response with multiple collaborators", (done: Mocha.Done) => { + const testUser2: adapterTypes.UserProfile = { + ...testUser, + email: "testEmail2", + permissions: ["developer"] + } + + mockReturn(JSON.stringify([testUser, testUser2]), 200); + mockUser(); + + manager.getCollaborators("appName") + .then((obj: any) => { + assert.ok(obj); + assert.equal(obj[testUser.email].permission, "Owner"); + assert.equal(obj[testUser2.email].permission, "Collaborator"); + done(); + }, rejectHandler); + }); + }); + + describe("removeCollaborator", () => { + it("removeCollaborator handles success response", (done: Mocha.Done) => { + mockReturn("", 200); + mockUser(); + + manager.removeCollaborator("appName", "email1") + .then((obj: any) => { + assert.ok(!obj); + done(); + }, rejectHandler); + }); + }); + + describe("patchRelease", () => { + it("patchRelease handles success response", (done: Mocha.Done) => { + const newReleasePackage: adapterTypes.CodePushRelease = { ...codePushRelease, description: "newDescription" }; + mockReturn(JSON.stringify(newReleasePackage), 200); + + manager.patchRelease("appName", "deploymentName", "label", { description: "newDescription" }) + .then((obj: any) => { + assert.ok(!obj); + done(); + }, rejectHandler); + }); + + it("patchRelease handles error response", (done: Mocha.Done) => { + mockReturn("", 400); + + manager.patchRelease("appName", "deploymentName", "label", {}) + .then((obj: any) => { + throw new Error("Call should not complete successfully"); + }, (error: Error) => done()); + }); + }); + + describe("promote", () => { + it("promote handles success response", (done: Mocha.Done) => { + const newReleasePackage: adapterTypes.CodePushRelease = { ...codePushRelease, description: "newDescription" }; + + mockReturn(JSON.stringify(newReleasePackage), 200); + + manager.promote("appName", "deploymentName", "newDeploymentName", { description: "newDescription" }) + .then((obj: any) => { + assert.ok(obj); + assert.equal(obj.description, "newDescription") + done(); + }, rejectHandler); + }); + + it("promote handles error response", (done: Mocha.Done) => { + mockReturn("", 400); + + manager.promote("appName", "deploymentName", "newDeploymentName", { rollout: 123 }) + .then((obj: any) => { + throw new Error("Call should not complete successfully"); + }, (error: Error) => done()); + }); + }); + + describe("rollback", () => { + it("rollback handles success response", (done: Mocha.Done) => { + mockReturn(JSON.stringify(codePushRelease), 200); + + manager.rollback("appName", "deploymentName", "v1") + .then((obj: any) => { + assert.ok(!obj); + done(); + }, rejectHandler); + }); + + it("rollback handles error response", (done: Mocha.Done) => { + mockReturn("", 400); + + manager.rollback("appName", "deploymentName", "v1") + .then((obj: any) => { + throw new Error("Call should not complete successfully"); + }, (error: Error) => done()); + }); + }); +}); + +// Helper method that is used everywhere that an assert.fail() is needed in a promise handler +function rejectHandler(val: any): void { + assert.fail(); +} + +function mockUser(): void { + mockReturn(JSON.stringify(testUser), 200, "/user"); +} + +// Wrapper for superagent-mock that abstracts away information not needed for SDK tests +function mockReturn(bodyText: string, statusCode: number, pattern?: string, header = {}): void { + require("superagent-mock")(request, [{ + pattern: "http://localhost" + (pattern ? pattern : "/(\\w+)/?"), + fixtures: function (match: any, params: any): any { + var isOk = statusCode >= 200 && statusCode < 300; + if (!isOk) { + var err: any = new Error(bodyText); + err.status = statusCode; + throw err; + } + return { text: bodyText, status: statusCode, ok: isOk, header: header, headers: {} }; + }, + callback: function (match: any, data: any): any { return data; } + }]); +} diff --git a/src/test/superagent-mock-config.js b/src/test/superagent-mock-config.js new file mode 100644 index 00000000..9a387ea2 --- /dev/null +++ b/src/test/superagent-mock-config.js @@ -0,0 +1,55 @@ +// ./superagent-mock-config.js file +module.exports = [ + { + pattern: 'http://localhost/(\\w+)/', + + /** + * returns the data + * + * @param match array Result of the resolution of the regular expression + * @param params object sent by 'send' function + */ + fixtures: function (match, params) { + return { text: "Error", status: 403, ok: false }; + + /** + * example: + * request.get('https://error.example/404').end(function(err, res){ + * console.log(err); // 404 + * }) + */ + if (match[1] === '404') { + throw new Error(404); + } + + /** + * example: + * request.get('https://error.example/200').end(function(err, res){ + * console.log(res.body); // "Data fixtures" + * }) + */ + + /** + * example: + * request.get('https://domain.send.example/').send({superhero: "me"}).end(function(err, res){ + * console.log(res.body); // "Data fixtures - superhero:me" + * }) + */ + if (params["superhero"]) { + return 'Data fixtures - superhero:' + params["superhero"]; + } else { + return 'Data fixtures'; + } + }, + + /** + * returns the result of the request + * + * @param match array Result of the resolution of the regular expression + * @param data mixed Data returns by `fixtures` attribute + */ + callback: function (match, data) { + return data; + } + }, +]; diff --git a/src/utils/adapter/adapter-types.ts b/src/utils/adapter/adapter-types.ts new file mode 100644 index 00000000..fc8aa37f --- /dev/null +++ b/src/utils/adapter/adapter-types.ts @@ -0,0 +1,121 @@ +import { PackageHashToBlobInfoMap } from "../../script/types" + +export type AppOs = 'iOS' | 'Android' | 'Tizen' | 'Windows' | 'Linux' | 'Custom'; // "Custom" is used for apps migrated from CodePush (where OS is unknown) +export type AppPlatform = + | 'Cordova' + | 'Java' + | 'Objective-C-Swift' + | 'React-Native' + | 'Unity' + | 'UWP' + | 'Xamarin' + | 'Electron' + | 'Unknown'; // "Unknown" is used for apps migrated from CodePush (where platform is unknown) +export type AppMemberPermissions = 'manager' | 'developer' | 'viewer' | 'tester'; +export type AppOrigin = 'app-center' | 'codepush'; + +export interface UserProfile { + id: string; + avatar_url: string; + can_change_password: boolean; + display_name: string; + email: string; + name: string; + permissions?: AppMemberPermissions[]; +} + +export interface ApiToken { + id: string; + api_token: string; + description: string; + created_at: string; +} + +export interface ApiTokensGetResponse { + id: string; + description: string; + created_at: string; +} + +export interface App { + id?: string; + app_secret?: string; + azure_subscription_id?: string; + description?: string; + display_name?: string; + icon_url?: string; + name?: string; + os?: AppOs; + owner?: Owner; + platform?: AppPlatform; + origin?: AppOrigin; +} + +interface Owner { + id: string; + avatar_url: string; + display_name: string; + email: string; + name: string; + type: OwnerType; +} + +type OwnerType = 'org' | 'user'; + +export interface Deployment { + createdTime: number; + id?: string; + name: string; + key: string; + latest_release?: any; + removedEmail?: string; +} + +export interface CodePushRelease { + target_binary_range?: string; + is_disabled?: boolean; + package_hash?: string; + released_by?: string; + description?: string; + release_method?: string; + upload_time?: number; + is_mandatory?: boolean; + blob_url?: string; + label?: string + rollout?: number; + size?: number; + original_label?: string; + original_deployment?: string; + diff_package_map?: PackageHashToBlobInfoMap; +} + +export interface ReleaseModification { + target_binary_range: string; + description: string; + is_disabled: boolean; + is_mandatory: boolean; + rollout: number; + label?: string; +} + +export interface DeploymentMetrics { + label: string, + active: number, + downloaded: number, + failed: number, + installed: number +} + +export interface UpdatedApp { + name: string; + display_name?: string; +} + +export interface ApigatewayAppCreationRequest { + org: string, + appcenterClientApp: App +} +export interface appParams { + appOwner: string; + appName: string; +} diff --git a/src/utils/adapter/adapter.ts b/src/utils/adapter/adapter.ts new file mode 100644 index 00000000..5bfd35da --- /dev/null +++ b/src/utils/adapter/adapter.ts @@ -0,0 +1,434 @@ +import * as adapterTypes from "./adapter-types"; +import * as sdkTypes from "../../script/types"; +import RequestManager from "../request-manager"; + +class Adapter { + constructor(private readonly _requestManager: RequestManager) { } + + public toLegacyAccount(profile: adapterTypes.UserProfile): sdkTypes.Account { + return { + name: profile.name, + email: profile.email, + linkedProviders: [] + }; + } + + public toLegacyAccessKey(apiToken: adapterTypes.ApiToken): sdkTypes.AccessKey { + const accessKey: sdkTypes.AccessKey = { + createdTime: Date.parse(apiToken.created_at), + expires: Date.parse('9999-12-31T23:59:59'), // never, + key: apiToken.api_token, + name: apiToken.description + }; + + return accessKey; + } + + public toLegacyAccessKeyList(apiTokens: adapterTypes.ApiTokensGetResponse[]): sdkTypes.AccessKey[] { + console.log(apiTokens); + const accessKeyList: sdkTypes.AccessKey[] = apiTokens.map((apiToken) => { + const accessKey: sdkTypes.AccessKey = { + createdTime: Date.parse(apiToken.created_at), + expires: Date.parse('9999-12-31T23:59:59'), // never, + name: apiToken.description, + }; + + return accessKey; + }); + + accessKeyList.sort( + (first: sdkTypes.AccessKey, second: sdkTypes.AccessKey) => { + const firstTime = first.createdTime || 0; + const secondTime = second.createdTime || 0; + return firstTime - secondTime; + } + ); + + return accessKeyList; + } + + public async toLegacyApp(app: adapterTypes.App): Promise { + const [user, deployments] = await Promise.all([this.getUser(), this.getDeployments(app.owner.name, app.name)]); + const deploymentsNames = deployments.map((deployment: adapterTypes.Deployment) => deployment.name); + return this.toLegacyRestApp(app, user, deploymentsNames); + }; + + public async toLegacyApps(apps: adapterTypes.App[]): Promise { + const user = await this.getUser(); + const sortedApps = await Promise.all( + apps.sort((first: adapterTypes.App, second: adapterTypes.App) => { + const firstOwner = first.owner.name || ''; + const secondOwner = second.owner.name || ''; + + // First sort by owner, then by app name + if (firstOwner !== secondOwner) { + return firstOwner.localeCompare(secondOwner); + } else { + return first.name.localeCompare(second.name); + } + }) + ); + + const legacyApps = await Promise.all( + sortedApps.map(async (app) => { + const deployments: adapterTypes.Deployment[] = await this.getDeployments(app.owner.name, app.name); + const deploymentsNames = deployments.map((deployment: adapterTypes.Deployment) => deployment.name); + + return this.toLegacyRestApp(app, user, deploymentsNames); + }) + ); + + return legacyApps; + }; + + public toApigatewayAppCreationRequest(appToCreate: sdkTypes.AppCreationRequest): adapterTypes.ApigatewayAppCreationRequest { + if ( + appToCreate.os !== 'iOS' && + appToCreate.os !== 'Android' && + appToCreate.os !== 'Windows' && + appToCreate.os !== 'Linux' + ) { + throw this.getCodePushError(`The app OS "${appToCreate.os}" isn't valid. It should be "iOS", "Android", "Windows" or "Linux".`, RequestManager.ERROR_CONFLICT); + } + + if ( + appToCreate.platform !== 'React-Native' && + appToCreate.platform !== 'Cordova' && + appToCreate.platform !== 'Electron' + ) { + throw this.getCodePushError(`The app platform "${appToCreate.platform}" isn't valid. It should be "React-Native", "Cordova" or "Electron".`, RequestManager.ERROR_CONFLICT); + } + + const org: string = this.getOrgFromLegacyAppRequest(appToCreate); + const appcenterClientApp: adapterTypes.App = this.toAppcenterClientApp(appToCreate); + + if (!this.isValidAppCenterAppName(appcenterClientApp.display_name)) { + throw this.getCodePushError(`The app name "${appcenterClientApp.display_name}" isn't valid. It can only contain alphanumeric characters, dashes, periods, or underscores.`, RequestManager.ERROR_CONFLICT); + } + + return { org, appcenterClientApp }; + } + + public async addStandardDeployments(apiAppName: string): Promise { + const { appOwner, appName } = await this.parseApiAppName(apiAppName); + const deploymentsToCreate = ['Staging', 'Production']; + await Promise.all( + deploymentsToCreate.map(async (deploymentName) => { + const deployment = { name: deploymentName }; + return await this._requestManager.post(`/apps/${appOwner}/${appName}/deployments/`, JSON.stringify(deployment), /*expectResponseBody=*/ true); + }) + ); + + return; + }; + + public async getRenamedApp(newName: string, appOwner: string, oldName: string): Promise { + const app = await this.getApp(appOwner, oldName); + + if (newName.indexOf('/') !== -1) { + throw this.getCodePushError(`The new app name "${newName}" must be unqualified, not having a '/' character.`, RequestManager.ERROR_CONFLICT); + } + + if (!this.isValidAppCenterAppName(newName)) { + throw this.getCodePushError(`The app name "${newName}" isn't valid. It can only contain alphanumeric characters, dashes, periods, or underscores.`, RequestManager.ERROR_CONFLICT); + } + + // If the display name was set on the existing app, then it was different than the app name. In that case, leave the display name unchanged; + // the user can change the display name through the Mobile Center web portal if they want to rename it. + // But if the display name and app name were the same, then rename them both. + const updatedApp = + app.name === app.display_name + ? { + name: newName, + display_name: newName + } + : { name: newName }; + + return updatedApp; + } + + public async resolveAccessKey(accessKeyName: string): Promise { + const accessKeys = await this.getApiTokens(); + const foundAccessKey = accessKeys.find((key) => { + return key.description === accessKeyName; + }); + + if (!foundAccessKey) { + throw this.getCodePushError(`Access key "${accessKeyName}" does not exist.`, RequestManager.ERROR_NOT_FOUND); + } + + return foundAccessKey; + } + + public toLegacyDeployments(deployments: adapterTypes.Deployment[]): sdkTypes.Deployment[] { + deployments.sort((first: adapterTypes.Deployment, second: adapterTypes.Deployment) => { + return first.name.localeCompare(second.name); + }); + + return this.toLegacyRestDeployments(deployments); + }; + + public toLegacyDeployment(deployment: adapterTypes.Deployment): sdkTypes.Deployment { + return this.toLegacyRestDeployment(deployment); + }; + + public async toLegacyCollaborators( + userList: adapterTypes.UserProfile[], + appOwner: string, + ): Promise { + const callingUser = await this.getUser(); + const legacyCollaborators: sdkTypes.CollaboratorMap = {}; + userList.forEach((user) => { + legacyCollaborators[user.email] = { + isCurrentAccount: callingUser.email === user.email, + permission: this.toLegacyUserPermission(user.permissions[0], user.name && user.name === appOwner) + }; + }); + return legacyCollaborators; + } + + public async toLegacyDeploymentMetrics( + deploymentMetrics: adapterTypes.DeploymentMetrics[], + ): Promise { + const legacyDeploymentMetrics: sdkTypes.DeploymentMetrics = {}; + deploymentMetrics.forEach((deployment) => { + legacyDeploymentMetrics[deployment.label] = { + active: deployment.active, + downloaded: deployment.downloaded, + failed: deployment.failed, + installed: deployment.installed + }; + }); + return legacyDeploymentMetrics; + } + + public async parseApiAppName(apiAppName: string): Promise { + const callingUser = await this.getUser(); + // If the separating / is not included, assume the owner is the calling user and only the app name is provided + if (!apiAppName.includes("/")) { + return { + appOwner: callingUser.name, + appName: apiAppName, + }; + } + const [appOwner, appName] = apiAppName.split("/"); + return { + appOwner: appOwner, + appName: appName, + }; + } + + public toLegacyDeploymentHistory(releases: adapterTypes.CodePushRelease[]): sdkTypes.Package[] { + return releases.map((release) => this.releaseToPackage(release)); + } + + private toLegacyRestApp(app: adapterTypes.App, user: adapterTypes.UserProfile, deployments: string[]): sdkTypes.App { + const isCurrentAccount: boolean = user.id === app.owner.id; + const isNameAndDisplayNameSame: boolean = app.name === app.display_name; + + let appName: string = app.name; + if (!isCurrentAccount) { + appName = app.owner.name + '/' + app.name; + } + + if (!isNameAndDisplayNameSame) { + appName += ` (${app.display_name})`; + } + + return { + name: appName, + collaborators: { + [app.owner.name]: { + isCurrentAccount: user.id === app.owner.id, + permission: 'Owner' + } + }, + deployments, + os: app.os, + platform: app.platform + }; + } + + public toReleaseUploadProperties(updateMetadata: sdkTypes.PackageInfo, releaseUploadAssets: sdkTypes.ReleaseUploadAssets, deploymentName: string): sdkTypes.UploadReleaseProperties { + const releaseUpload: sdkTypes.UploadReleaseProperties = { + release_upload: releaseUploadAssets, + target_binary_version: updateMetadata.appVersion, + deployment_name: deploymentName, + no_duplicate_release_error: false, // This property is not implemented in CodePush SDK Management + } + + if (updateMetadata.description) releaseUpload.description = updateMetadata.description; + + if (updateMetadata.isDisabled) releaseUpload.disabled = updateMetadata.isDisabled; + + if (updateMetadata.isMandatory) releaseUpload.mandatory = updateMetadata.isMandatory; + + if (updateMetadata.rollout) releaseUpload.rollout = updateMetadata.rollout; + + return releaseUpload; + } + + public toRestReleaseModification( + legacyCodePushReleaseInfo: sdkTypes.PackageInfo + ): adapterTypes.ReleaseModification { + let releaseModification: adapterTypes.ReleaseModification = {} as adapterTypes.ReleaseModification ; + + if (legacyCodePushReleaseInfo.appVersion) releaseModification.target_binary_range = legacyCodePushReleaseInfo.appVersion; + + if (legacyCodePushReleaseInfo.isDisabled) releaseModification.is_disabled = legacyCodePushReleaseInfo.isDisabled; + + if (legacyCodePushReleaseInfo.isMandatory !== undefined) releaseModification.is_mandatory = legacyCodePushReleaseInfo.isMandatory === true; + + if (legacyCodePushReleaseInfo.description) releaseModification.description = legacyCodePushReleaseInfo.description; + + if (legacyCodePushReleaseInfo.rollout) releaseModification.rollout = legacyCodePushReleaseInfo.rollout; + + if (legacyCodePushReleaseInfo.label) releaseModification.label = legacyCodePushReleaseInfo.label; + + return releaseModification; + } + + public releaseToPackage(releasePackage: adapterTypes.CodePushRelease): sdkTypes.Package { + const sdkPackage: sdkTypes.Package = { + blobUrl: releasePackage.blob_url, + size: releasePackage.size, + uploadTime: releasePackage.upload_time, + isDisabled: !!releasePackage.is_disabled, + isMandatory: !!releasePackage.is_mandatory, + } + + if (releasePackage.target_binary_range) sdkPackage.appVersion = releasePackage.target_binary_range; + + if (releasePackage.description) sdkPackage.description = releasePackage.description; + + if (releasePackage.label) sdkPackage.label = releasePackage.label; + + if (releasePackage.package_hash) sdkPackage.packageHash = releasePackage.package_hash; + + if (releasePackage.rollout) sdkPackage.rollout = releasePackage.rollout; + + if (releasePackage.diff_package_map) sdkPackage.diffPackageMap = releasePackage.diff_package_map; + + if (releasePackage.original_label) sdkPackage.originalLabel = releasePackage.original_label; + + if (releasePackage.original_deployment) sdkPackage.originalDeployment = releasePackage.original_deployment; + + if (releasePackage.released_by) sdkPackage.releasedBy = releasePackage.released_by; + + if (releasePackage.release_method) sdkPackage.releaseMethod = releasePackage.release_method; + + return sdkPackage; + } + + private toLegacyRestDeployments(apiGatewayDeployments: adapterTypes.Deployment[]): sdkTypes.Deployment[] { + const deployments: sdkTypes.Deployment[] = apiGatewayDeployments.map((deployment) => { + return this.toLegacyRestDeployment(deployment); + }); + + return deployments; + } + + private toLegacyRestDeployment(deployment: adapterTypes.Deployment): sdkTypes.Deployment { + const apiGatewayPackage = deployment.latest_release ? this.releaseToPackage(deployment.latest_release) : null; + + const restDeployment: sdkTypes.Deployment = { + name: deployment.name, + key: deployment.key, + package: apiGatewayPackage + }; + + return restDeployment; + } + + private async getUser(): Promise { + try { + const res = await this._requestManager.get(`/user`); + return res.body; + } catch (error) { + throw error; + } + } + + private async getApiTokens(): Promise { + try { + const res = await this._requestManager.get(`/api_tokens`); + return res.body; + } catch (error) { + throw error; + } + } + + private async getApp(appOwner: string, appName: string): Promise { + try { + const res = await this._requestManager.get(`/apps/${appOwner}/${appName}`); + return res.body; + } catch (error) { + throw error; + } + } + + private async getDeployments(appOwner: string, appName: string): Promise { + try { + const res = await this._requestManager.get(`/apps/${appOwner}/${appName}/deployments/`); + return res.body; + } catch (error) { + throw error; + } + } + + private toLegacyUserPermission(expectedPermission: adapterTypes.AppMemberPermissions, isOwner: boolean): string { + if (expectedPermission === 'manager') { + return isOwner ? 'Owner' : 'Manager'; + } else if (expectedPermission === 'developer') { + return 'Collaborator'; + } + return 'Reader'; + } + + private getOrgFromLegacyAppRequest(legacyCreateAppRequest: sdkTypes.AppCreationRequest) { + const slashIndex = legacyCreateAppRequest.name.indexOf('/'); + const org = slashIndex !== -1 ? legacyCreateAppRequest.name.substring(0, slashIndex) : null; + + return org; + } + + private toAppcenterClientApp(legacyCreateAppRequest: sdkTypes.AppCreationRequest): adapterTypes.App { + // If the app name contains a slash, then assume that the app is intended to be owned by an org, with the org name + // before the slash. Update the app info accordingly. + const slashIndex = legacyCreateAppRequest.name.indexOf('/'); + + return { + os: legacyCreateAppRequest.os as adapterTypes.AppOs, + platform: legacyCreateAppRequest.platform as adapterTypes.AppPlatform, + display_name: + slashIndex !== -1 ? legacyCreateAppRequest.name.substring(slashIndex + 1) : legacyCreateAppRequest.name + }; + } + + private isValidAppCenterAppName(name: any): boolean { + return this.getStringValidator(/*maxLength=*/ 1000, /*minLength=*/ 1)(name) && /^[a-zA-Z0-9-._]+$/.test(name); // Only allow alphanumeric characters, dashes, periods, or underscores + } + + private getStringValidator(maxLength: number = 1000, minLength: number = 0): (value: any) => boolean { + return function isValidString(value: string): boolean { + if (typeof value !== 'string') { + return false; + } + + if (maxLength > 0 && value.length > maxLength) { + return false; + } + + return value.length >= minLength; + }; + } + + private getCodePushError(message: string, errorCode: number): sdkTypes.CodePushError { + return { + message: message, + statusCode: errorCode + }; + } +} + +export = Adapter; diff --git a/src/utils/request-manager.ts b/src/utils/request-manager.ts new file mode 100644 index 00000000..e9b18ca5 --- /dev/null +++ b/src/utils/request-manager.ts @@ -0,0 +1,129 @@ +import superagent = require("superagent"); +import { ProxyAgent } from "proxy-agent"; +import { CodePushUnauthorizedError } from "../script/code-push-error" +import { CodePushError, Headers } from "../script/types"; + +interface JsonResponse { + headers: Headers; + body?: any; +} + +class RequestManager { + public static SERVER_URL = "https://api.appcenter.ms/v0.1"; + + public static ERROR_GATEWAY_TIMEOUT = 504; // Used if there is a network error + public static ERROR_INTERNAL_SERVER = 500; + public static ERROR_NOT_FOUND = 404; + public static ERROR_CONFLICT = 409; // Used if the resource already exists + public static ERROR_UNAUTHORIZED = 401; + + private _accessKey: string; + private _serverUrl: string; + private _customHeaders: Headers; + private _proxy: string; + + constructor(accessKey: string, customHeaders?: Headers, serverUrl?: string, proxy?: string) { + if (!accessKey) throw new CodePushUnauthorizedError("A token must be specified."); + + this._accessKey = accessKey; + this._customHeaders = customHeaders; + this._serverUrl = serverUrl || RequestManager.SERVER_URL; + this._proxy = proxy; + } + + public get(endpoint: string, expectResponseBody: boolean = true): Promise { + return this.makeApiRequest("get", endpoint, /*requestBody=*/ null, expectResponseBody, /*contentType=*/ null); + } + + public post(endpoint: string, requestBody: string, expectResponseBody: boolean, contentType: string = "application/json;charset=UTF-8"): Promise { + return this.makeApiRequest("post", endpoint, requestBody, expectResponseBody, contentType); + } + + public patch(endpoint: string, requestBody: string, expectResponseBody: boolean = false, contentType: string = "application/json;charset=UTF-8"): Promise { + return this.makeApiRequest("patch", endpoint, requestBody, expectResponseBody, contentType); + } + + public del(endpoint: string, expectResponseBody: boolean = false): Promise { + return this.makeApiRequest("del", endpoint, /*requestBody=*/ null, expectResponseBody, /*contentType=*/ null) + } + + private makeApiRequest(method: string, endpoint: string, requestBody: string, expectResponseBody: boolean, contentType: string): Promise { + return new Promise((resolve, reject) => { + var request: superagent.Request = (superagent)[method](this._serverUrl + endpoint); + + if (this._proxy) { + (request).agent(new ProxyAgent({ getProxyForUrl: () => this._proxy })) + } + + this.attachCredentials(request); + + if (requestBody) { + if (contentType) { + request = request.set("Content-Type", contentType); + } + + request = request.send(requestBody); + } + + request.end((err: any, res: superagent.Response) => { + if (err) { + reject(this.getCodePushError(err, res)); + return; + } + + try { + var body = JSON.parse(res.text); + } catch (err) { + } + + if (res.ok) { + if (expectResponseBody && !body) { + reject({ message: `Could not parse response: ${res.text}`, statusCode: RequestManager.ERROR_INTERNAL_SERVER }); + } else { + resolve({ + headers: res.header, + body: body + }); + } + } else { + if (body) { + reject({ message: body.message, statusCode: this.getErrorStatus(err, res) }); + } else { + reject({ message: res.text, statusCode: this.getErrorStatus(err, res) }); + } + } + }); + }) + } + + private getCodePushError(error: any, response?: superagent.Response): CodePushError { + if (error.syscall === "getaddrinfo") { + error.message = `Unable to connect to the CodePush server. Are you offline, or behind a firewall or proxy?\n(${error.message})`; + } + + return { + message: this.getErrorMessage(error, response), + statusCode: this.getErrorStatus(error, response) + }; + } + + private getErrorStatus(error: any, response?: superagent.Response): number { + return (error && error.status) || (response && response.status) || RequestManager.ERROR_GATEWAY_TIMEOUT; + } + + private getErrorMessage(error: Error, response?: superagent.Response): string { + return response && response.body.message ? response.body.message : error.message; + } + + private attachCredentials(request: superagent.Request): void { + if (this._customHeaders) { + for (var headerName in this._customHeaders) { + request.set(headerName, this._customHeaders[headerName]); + } + } + + request.set("x-api-token", `${this._accessKey}`); + } +} + +export = RequestManager; diff --git a/tsconfig-release.json b/tsconfig-release.json new file mode 100644 index 00000000..ee51e4be --- /dev/null +++ b/tsconfig-release.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "sourceMap": false, + "declaration": true + }, + "exclude": [ + "src/test" + ] +} diff --git a/tsconfig.json b/tsconfig.json index 7e254f65..ad75c04c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,8 +1,15 @@ { "compilerOptions": { + "target": "ES5", "module": "commonjs", "noImplicitAny": true, - "noResolve": true, - "target": "ES5" + "noEmitOnError": true, + "moduleResolution": "node", + "sourceMap": true, + "rootDir": "src", + "outDir": "bin", + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "skipLibCheck": true } } diff --git a/tsd.json b/tsd.json deleted file mode 100644 index 04bcb506..00000000 --- a/tsd.json +++ /dev/null @@ -1,77 +0,0 @@ -{ - "version": "v4", - "repo": "borisyankov/DefinitelyTyped", - "ref": "master", - "path": "definitions/external", - "installed": { - "body-parser/body-parser.d.ts": { - "commit": "f5c3f32801622770aa14328269a624f53aec538a" - }, - "express/express.d.ts": { - "commit": "f5c3f32801622770aa14328269a624f53aec538a" - }, - "jsonwebtoken/jsonwebtoken.d.ts": { - "commit": "f5c3f32801622770aa14328269a624f53aec538a" - }, - "mocha/mocha.d.ts": { - "commit": "563b64359d06907fb83c1b2367f9fd5b970d5ad7" - }, - "node/node.d.ts": { - "commit": "563b64359d06907fb83c1b2367f9fd5b970d5ad7" - }, - "q/Q.d.ts": { - "commit": "f5c3f32801622770aa14328269a624f53aec538a" - }, - "supertest/supertest.d.ts": { - "commit": "f5c3f32801622770aa14328269a624f53aec538a" - }, - "multer/multer.d.ts": { - "commit": "43b6bf88758852b9ab713a9b011487f047f94f4e" - }, - "node-uuid/node-uuid.d.ts": { - "commit": "84dfeeac378552c22297a5d555b1999a396d7e7c" - }, - "passport/passport.d.ts": { - "commit": "3e117d8c9ddf6443d7ed71a74f6bc8b6bcd36077" - }, - "passport-local/passport-local.d.ts": { - "commit": "3e117d8c9ddf6443d7ed71a74f6bc8b6bcd36077" - }, - "express-session/express-session.d.ts": { - "commit": "3e117d8c9ddf6443d7ed71a74f6bc8b6bcd36077" - }, - "cookie-parser/cookie-parser.d.ts": { - "commit": "3e117d8c9ddf6443d7ed71a74f6bc8b6bcd36077" - }, - "react/react.d.ts": { - "commit": "3e117d8c9ddf6443d7ed71a74f6bc8b6bcd36077" - }, - "request/request.d.ts": { - "commit": "977da240f263e88215a27cb44f2e31524ca07135" - }, - "form-data/form-data.d.ts": { - "commit": "977da240f263e88215a27cb44f2e31524ca07135" - }, - "shortid/shortid.d.ts": { - "commit": "8f51d25ad3523cd01f2d92e343247852f6b70dff" - }, - "sinon/sinon.d.ts": { - "commit": "639337b32bed76f70e1e7918c4ad076c4492e1ed" - }, - "chalk/chalk.d.ts": { - "commit": "4df20c9706ce6ca27137617770b57f3a0d3f9689" - }, - "open/open.d.ts": { - "commit": "e95958ac847d9a343bdc8d7cbc796a5f6da29f71" - }, - "redis/redis.d.ts": { - "commit": "fa8d9683f37d07ed79617a6e037916c6718aeb11" - }, - "semver/semver.d.ts": { - "commit": "71a7d5306ae4f9893aafd2d85d38aac8789ebf33" - }, - "applicationinsights/applicationinsights.d.ts": { - "commit": "832c51db815c766fa42a2e995a7b3136ec6d15c2" - } - } -}