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 966987f4..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](https://github.com/Microsoft/cordova-plugin-code-push) and [React Native](https://github.com/Microsoft/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 6b5ae354..00000000 --- a/cli/README.md +++ /dev/null @@ -1,212 +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/Microsoft/cordova-plugin-code-push) and [React Native](http://github.com/Microsoft/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/696206/11605036/5a934e56-9aaa-11e5-87ad-01cddeaf07dc.PNG) - -## Installation - -* Install [Node.js](https://nodejs.org/) -* Install the CodePush CLI: `npm install -g code-push-cli` - -## Quick Start - -1. Create a [CodePush account](#account-creation) push using the CodePush CLI -2. Register your [app](#app-management) with the service, and optionally create any additional [deployments](#deployment-management) -3. 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)) -4. [Deploy](#update-deployment) an update for your registered app -5. Live long and prosper! ([details](https://en.wikipedia.org/wiki/Vulcan_salute)) - -## Account creation - -Before you can begin releasing app updates, you need to create a CodePush account. You can do this by simply running the following command once you've installed the CLI: - -``` -code-push register -``` - -This will launch a browser, asking you to authenticate with either your GitHub or Microsoft account. Once authenticated, it will create a CodePush account "linked" to your GitHub/MSA identity, and generate an access token you can copy/paste into the CLI in order to login. - -*Note: After registering, you are automatically logged-in with the CLI, so until you explicitly log out, you don't need to login again from the same machine.* - -## 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 registering. You can do this by running the following command: - -``` -code-push login -``` - -This will launch a browser, asking you to authenticate with either your GitHub or Microsoft account. 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. -The list of access keys will display the name of the machine the token was created on, as well as the time the login occurred. This should make it easy to spot keys you don't want to keep around. - -``` -code-push access-key ls -code-push access-key rm -``` - -If you need additional keys, that can be used to authenticate against the CodePush service without needing to give access to your GitHub and/or Microsoft crendentials, you can run the following command to create one (along with a description of what it is for): - -``` -code-push access-key add "VSO Integration" -``` - -After creating the new key, you can specify its value using the `--accessKey` flag of the `login` command, which allows you to perform the "headless" authentication, as opposed to launching a browser. - -``` -code-push login --accessKey -``` - -If you want to log out of your current session, but still be able to reuse the same key for future logins, run the following command: - -``` -code-push logout --local -``` - -## 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/Microsoft/cordova-plugin-code-push) and [React Native](http://github.com/Microsoft/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 -``` - -## Releasing app updates - -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 - -This specifies the location of the content you want to release. You can provide 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, since the CLI will automatically zip them for you. - -It's important that the path you specify refers to the platform-specific, prepared/bundled version of your app. The following table outlines which command you should run before releasing, as well as the location you can subsequently point at using the `package` parameter: - -| Platform | Prepare command | Package path (relative to project root) | -|------------------------|--------------------------------------------------------------------------------------------------|--------------------------------------------| -| Cordova (Android) | `cordova prepare android` | `./platforms/android/assets/www` directory | -| Cordova (iOS) | `cordova prepare ios` | `./platforms/ios/www ` directory | -| React Native (Android) | `react-native bundle --platform android --entry-file --bundle-output ` | Value of the `--bundle-output` option | -| React Native (iOS) | `react-native bundle --platform ios --entry-file --bundle-output ` | Value of the `--bundle-output` option | - -### App store version parameter - -This specifies the semver compliant store/binary version of the application you are releasing the update for. Only users running this exact version will receive the update. 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 before being able to get it. - -The following table outlines the value that CodePush expects you to provide for each respective app type: - -| Platform | Source of app store version | -|------------------------|------------------------------------------------------------------------------| -| Cordova | The `` attribute in the `config.xml` file | -| React Native (Android) | The `android.defaultConfig.versionName` property in your `build.gradle` file | -| React Native (iOS) | The `CFBundleShortVersionString` key in the `Info.plist` file | - -### Deployment name parameter - -This specifies which deployment you want to release the update to. 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"* - - -## Promoting updates across deployments - -Once you've tested an update against a specific deployment, and you want to promote it "downstream" (e.g. dev->staging, staging->production), you can simply use the following command to copy the code and metadata (e.g. mandatory, description, app store version) from one deployment to another: - -``` -code-push promote -code-push promote MyApp Staging Production -``` - -The release produced by a promotion will be annotated in the output of the `deployment history` command. - -## Rolling back undesired updates - -If you release an update that is broken or contains unintended features, it is easy to roll it back using the `rollback` command: - -``` -code-push rollback -code-push rollback MyApp Production -``` - -This has the effect of issuing another release with the same contents and metadata as the version prior to the latest one. Note that this means that issuing a second consecutive `rollback` command simply cancels out the first one. - -The release produced by a rollback will be annotated in the output of the `deployment history` command. - -## Viewing release history - -You can view a history of the 50 most recent releases for a specific app deployment using the following command: - -``` -code-push deployment history -``` - -![Deployment History](https://cloud.githubusercontent.com/assets/696206/11605068/14e440d0-9aab-11e5-8837-69ab09bfb66c.PNG) - -*NOTE: The history command can also be run using the "h" alias* diff --git a/cli/definitions/cli.ts b/cli/definitions/cli.ts deleted file mode 100644 index 6eaf3a33..00000000 --- a/cli/definitions/cli.ts +++ /dev/null @@ -1,114 +0,0 @@ -export enum CommandType { - accessKeyAdd, - accessKeyList, - accessKeyRemove, - appAdd, - appList, - appRemove, - appRename, - deploymentAdd, - deploymentList, - deploymentRemove, - deploymentRename, - deploymentHistory, - login, - logout, - promote, - register, - release, - rollback -} - -export interface ICommand { - type: CommandType; -} - -export interface IAccessKeyAddCommand extends ICommand { - description: string; -} - -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; -} - -export interface IDeploymentRemoveCommand extends ICommand { - appName: string; - deploymentName: string; -} - -export interface IDeploymentRenameCommand extends ICommand { - appName: string; - currentDeploymentName: string; - newDeploymentName: string; -} - -export interface IDeploymentHistoryCommand extends ICommand { - appName: string; - deploymentName: string; - format: string; -} - -export interface ILoginCommand extends ICommand { - serverUrl: string; - accessKey: string; -} - -export interface ILogoutCommand extends ICommand { - isLocal: boolean; -} - -export interface IPromoteCommand extends ICommand { - appName: string; - sourceDeploymentName: string; - destDeploymentName: 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; -} - -export interface IRollbackCommand extends ICommand { - appName: string; - deploymentName: string; - targetRelease: 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 6cd36e17..00000000 --- a/cli/package.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "name": "code-push-cli", - "version": "1.3.0-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" - ], - "homepage": "https://microsoft.github.io/code-push", - "author": "Microsoft Corporation", - "license": "MIT", - "dependencies": { - "base-64": "^0.1.0", - "chalk": "^1.1.0", - "cli-table": "^0.3.1", - "code-push": "1.3.0-beta", - "fs": "0.0.2", - "moment": "^2.10.6", - "opener": "^1.4.1", - "progress": "^1.1.8", - "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", - "update-notifier": "^0.5.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 9507741a..00000000 --- a/cli/script/cli.ts +++ /dev/null @@ -1,22 +0,0 @@ -/// - -import { Promise } from "q"; -import * as parser from "./command-parser"; -import { execute } from "./command-executor"; -import * as chalk from "chalk"; - -function run(): void { - if (!parser.command) { - parser.showHelp(/*showRootDescription*/false); - return; - } - - execute(parser.command) - .catch((error: any): void => { - console.error(chalk.red("[Error] " + error.message)); - process.exit(1); - }) - .done(); -} - -run(); diff --git a/cli/script/command-executor.ts b/cli/script/command-executor.ts deleted file mode 100644 index c81aa271..00000000 --- a/cli/script/command-executor.ts +++ /dev/null @@ -1,957 +0,0 @@ -/// - -import * as base64 from "base-64"; -import * as chalk from "chalk"; -import * as fs from "fs"; -import * as moment from "moment"; -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 * as semver from "semver"; -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, Package } from "code-push"; -var packageJson = require("../package.json"); -import Promise = Q.Promise; -var progress = require("progress"); - -var configFilePath: string = path.join(process.env.LOCALAPPDATA || process.env.HOME, ".code-push.config"); -var userAgent: string = packageJson.name + "/" + packageJson.version; - -interface IStandardLoginConnectionInfo { - accessKeyName: string; - providerName: string; - providerUniqueId: string; - serverUrl: string; -} - -interface IAccessKeyLoginConnectionInfo { - accessKey: 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 currently logged in. Run the 'code-push login' command to authenticate with the CodePush server."); }); - } - - sdk = new AccountManager(connectionInfo.serverUrl, userAgent); - - var accessToken: string; - - var standardLoginConnectionInfo: IStandardLoginConnectionInfo = connectionInfo; - var accessKeyLoginConnectionInfo: IAccessKeyLoginConnectionInfo = connectionInfo; - - if (standardLoginConnectionInfo.providerName) { - accessToken = base64.encode(JSON.stringify({ - accessKeyName: standardLoginConnectionInfo.accessKeyName, - providerName: standardLoginConnectionInfo.providerName, - providerUniqueId: standardLoginConnectionInfo.providerUniqueId - })); - } else { - accessToken = accessKeyLoginConnectionInfo.accessKey; - } - - 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: IStandardLoginConnectionInfo|IAccessKeyLoginConnectionInfo; - -function accessKeyAdd(command: cli.IAccessKeyAddCommand): Promise { - var hostname: string = os.hostname(); - return sdk.addAccessKey(hostname, command.description) - .then((accessKey: AccessKey) => { - log("Successfully created a new access key" + (command.description ? (" \"" + command.description + "\"") : "") + ": " + accessKey.name); - }); -} - -function accessKeyList(command: cli.IAccessKeyListCommand): Promise { - throwForInvalidOutputFormat(command.format); - - return sdk.getAccessKeys() - .then((accessKeys: AccessKey[]): void => { - printAccessKeys(command.format, accessKeys); - }); -} - -function removeLocalAccessKey(): Promise { - return Q.fcall(() => { throw new 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 (connectionInfo && (command.accessKeyName === (connectionInfo).accessKeyName || command.accessKeyName === (connectionInfo).accessKey)) { - 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("Successfully removed the \"" + command.accessKeyName + "\" access key."); - }); - } - - log("Access key removal cancelled."); - }); - }); - } -} - -function appAdd(command: cli.IAppAddCommand): Promise { - return sdk.addApp(command.appName) - .then((app: App): Promise => { - log("Successfully added the \"" + command.appName + "\" app, along with the following default deployments:"); - var deploymentListCommand: cli.IDeploymentListCommand = { - type: cli.CommandType.deploymentList, - appName: app.name, - format: "table" - }; - return deploymentList(deploymentListCommand, /*showPackage=*/ false); - }); -} - -function appList(command: cli.IAppListCommand): Promise { - throwForInvalidOutputFormat(command.format); - var apps: App[]; - - return sdk.getApps() - .then((retrievedApps: App[]): Promise => { - apps = retrievedApps; - var deploymentListPromises: Promise[] = apps.map((app: App) => { - return sdk.getDeployments(app.id) - .then((deployments: Deployment[]) => { - var deploymentList: string[] = deployments - .map((deployment: Deployment) => deployment.name) - .sort((first: string, second: string) => { - return first.toLowerCase().localeCompare(second.toLowerCase()); - }); - return deploymentList; - }); - }); - return Q.all(deploymentListPromises); - }) - .then((deploymentLists: string[][]): void => { - printAppList(command.format, apps, deploymentLists); - }); -} - -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("Successfully removed the \"" + command.appName + "\" app."); - }); - } - - log("App removal 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("Successfully renamed the \"" + command.currentAppName + "\" app to \"" + command.newAppName + "\"."); - }); -} - -function deleteConnectionInfoCache(): void { - try { - fs.unlinkSync(configFilePath); - - log("Successfully logged-out. The session token file located at " + chalk.cyan(configFilePath) + " has been deleted.\r\n"); - } 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) - .then((deployment: Deployment): Promise => { - return sdk.getDeploymentKeys(appId, deployment.id); - }).then((deploymentKeys: DeploymentKey[]) => { - log("Successfully added the \"" + command.deploymentName + "\" deployment with key \"" + deploymentKeys[0].key + "\" to the \"" + command.appName + "\" app."); - }); - }) -} - -export var deploymentList = (command: cli.IDeploymentListCommand, showPackage: boolean = true): 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 deploymentKeyPromises: Promise[] = deployments.map((deployment: Deployment) => { - return sdk.getDeploymentKeys(theAppId, deployment.id) - .then((deploymentKeys: DeploymentKey[]): string => { - return deploymentKeys[0].key; - }); - }); - return Q.all(deploymentKeyPromises) - .then((deploymentKeyList: string[]) => { - printDeploymentList(command, deployments, deploymentKeyList, showPackage); - }); - }); -} - -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("Successfully removed the \"" + command.deploymentName + "\" deployment from the \"" + command.appName + "\" app."); - }) - } - - log("Deployment removal 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("Successfully renamed the \"" + command.currentDeploymentName + "\" deployment to \"" + command.newDeploymentName + "\" for the \"" + command.appName + "\" app."); - }); - }); -} - -function deploymentHistory(command: cli.IDeploymentHistoryCommand): Promise { - throwForInvalidOutputFormat(command.format); - var storedAppId: string; - - return getAppId(command.appName) - .then((appId: string): Promise => { - throwForInvalidAppId(appId, command.appName); - storedAppId = appId; - - return getDeploymentId(appId, command.deploymentName); - }) - .then((deploymentId: string): Promise => { - throwForInvalidDeploymentId(deploymentId, command.deploymentName, command.appName); - - return sdk.getPackageHistory(storedAppId, deploymentId); - }) - .then((packageHistory: Package[]): void => { - printDeploymentHistory(command, packageHistory); - }); -} - -function deserializeConnectionInfo(): IStandardLoginConnectionInfo|IAccessKeyLoginConnectionInfo { - var savedConnection: string; - - try { - savedConnection = fs.readFileSync(configFilePath, { encoding: "utf8" }); - } catch (ex) { - return; - } - - var credentialsObject: IStandardLoginConnectionInfo|IAccessKeyLoginConnectionInfo = tryJSON(savedConnection); - return credentialsObject; -} - -function notifyAlreadyLoggedIn(): Promise { - return Q.fcall(() => { throw new Error("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(command); - - case cli.CommandType.register: - return register(command); - } - - return loginWithAccessToken() - .then((): Promise => { - switch (command.type) { - case cli.CommandType.accessKeyAdd: - return accessKeyAdd(command); - - 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.deploymentHistory: - return deploymentHistory(command); - - case cli.CommandType.promote: - return promote(command); - - case cli.CommandType.release: - return release(command); - - case cli.CommandType.rollback: - return rollback(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 = `A browser is being launched to authenticate your account. Follow the instructions ` + - `it displays to complete your ${action === "register" ? "registration" : "login"}.\r\n`; - - log(message); - var hostname: string = os.hostname(); - var url: string = serverUrl + "/auth/" + action + "?hostname=" + hostname; - opener(url); -} - -function login(command: cli.ILoginCommand): Promise { - // Check if one of the flags were provided. - if (command.accessKey) { - sdk = new AccountManager(command.serverUrl, userAgent); - return sdk.loginWithAccessToken(command.accessKey) - .then((): void => { - // The access token is valid. - serializeConnectionInfo(command.serverUrl, command.accessKey); - }); - } else { - 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) { - throw new Error("Invalid access token."); - } - - sdk = new AccountManager(serverUrl, userAgent); - - return sdk.loginWithAccessToken(accessToken) - .then((): void => { - // The access token is valid. - serializeConnectionInfo(serverUrl, accessToken); - }); - }); -} - -function logout(command: cli.ILogoutCommand): Promise { - if (connectionInfo) { - var setupPromise: Promise = loginWithAccessToken(); - if (!command.isLocal) { - var accessKeyName: string; - setupPromise = setupPromise - .then((): Promise => { - var standardLoginConnectionInfo: IStandardLoginConnectionInfo = connectionInfo; - var accessKeyLoginConnectionInfo: IAccessKeyLoginConnectionInfo = connectionInfo; - - if (standardLoginConnectionInfo.accessKeyName) { - accessKeyName = standardLoginConnectionInfo.accessKeyName; - return getAccessKeyId(standardLoginConnectionInfo.accessKeyName); - } else { - accessKeyName = accessKeyLoginConnectionInfo.accessKey; - return getAccessKeyId(accessKeyLoginConnectionInfo.accessKey); - } - }) - .then((accessKeyId: string): Promise => { - return sdk.removeAccessKey(accessKeyId); - }) - .then((): void => { - log("Removed access key " + accessKeyName + "."); - }); - } - - return setupPromise - .then((): Promise => sdk.logout(), (): Promise => sdk.logout()) - .then((): void => deleteConnectionInfoCache(), (): void => deleteConnectionInfoCache()); - } - - return Q.fcall(() => { throw new Error("You are not logged in."); }); -} - -function formatDate(unixOffset: number): string { - var date: moment.Moment = moment(unixOffset); - var now: moment.Moment = moment(); - if (now.diff(date, "days") < 30) { - return date.fromNow(); // "2 hours ago" - } else if (now.year() === date.year()) { - return date.format("MMM D"); // "Nov 6" - } else { - return date.format("MMM D, YYYY"); // "Nov 6, 2014" - } -} - -function printAppList(format: string, apps: App[], deploymentLists: string[][]): void { - if (format === "json") { - var dataSource: any[] = apps.map((app: App, index: number) => { - return { "name": app.name, "deployments": deploymentLists[index] }; - }); - printJson(dataSource); - } else if (format === "table") { - var headers = ["Name", "Deployments"]; - printTable(headers, (dataSource: any[]): void => { - apps.forEach((app: App, index: number): void => { - var row = [app.name, wordwrap(50)(deploymentLists[index].join(", "))]; - dataSource.push(row); - }); - }); - } -} - -function printDeploymentList(command: cli.IDeploymentListCommand, deployments: Deployment[], deploymentKeys: Array, showPackage: boolean = true): void { - if (command.format === "json") { - var dataSource: any[] = deployments.map((deployment: Deployment, index: number) => { - return { "name": deployment.name, "deploymentKey": deploymentKeys[index], "package": deployment.package }; - }); - printJson(dataSource); - } else if (command.format === "table") { - var headers = ["Name", "Deployment Key"]; - if (showPackage) { - headers.push("Package Metadata"); - } - printTable(headers, (dataSource: any[]): void => { - deployments.forEach((deployment: Deployment, index: number): void => { - var row = [deployment.name, deploymentKeys[index]]; - if (showPackage) { - row.push(getPackageString(deployment.package)); - } - dataSource.push(row); - }); - }); - } -} - -function printDeploymentHistory(command: cli.IDeploymentHistoryCommand, packageHistory: Package[]): void { - if (command.format === "json") { - printJson(packageHistory); - } else if (command.format === "table") { - printTable(["Label", "Release Time", "App Version", "Mandatory", "Description"], (dataSource: any[]) => { - packageHistory.forEach((packageObject: Package) => { - var releaseTime: string = formatDate(packageObject.uploadTime); - var releaseSource: string; - if (packageObject.releaseMethod === "Promote") { - releaseSource = `Promoted ${ packageObject.originalLabel } from "${ packageObject.originalDeployment }"`; - } else if (packageObject.releaseMethod === "Rollback") { - var labelNumber: number = parseInt(packageObject.label.substring(1)); - var lastLabel: string = "v" + (labelNumber - 1); - releaseSource = `Rolled back ${ lastLabel } to ${ packageObject.originalLabel }`; - } - - if (releaseSource) { - releaseTime += "\n" + chalk.magenta(`(${releaseSource})`).toString(); - } - - dataSource.push([ - packageObject.label, - releaseTime, - packageObject.appVersion, - packageObject.isMandatory ? "Yes" : "No", - packageObject.description ? wordwrap(30)(packageObject.description) : "" - ]); - }); - }); - } -} - -function getPackageString(packageObject: Package): string { - if (!packageObject) { - return ""; - } - - return "Label: " + packageObject.label + "\n" + - (packageObject.description ? wordwrap(70)("Description: " + packageObject.description) + "\n" : "") + - "App Version: " + packageObject.appVersion + "\n" + - "Mandatory: " + (packageObject.isMandatory ? "Yes" : "No") + "\n" + - "Hash: " + packageObject.packageHash + "\n" + - "Release Time: " + formatDate(packageObject.uploadTime); -} - -function printJson(object: any): void { - log(JSON.stringify(object, /*replacer=*/ null, /*spacing=*/ 2)); -} - -function printAccessKeys(format: string, keys: AccessKey[]): void { - if (format === "json") { - printJson(keys); - } else if (format === "table") { - printTable(["Key", "Time Created", "Created From", "Description"], (dataSource: any[]): void => { - keys.forEach((key: AccessKey): void => { - dataSource.push([ - key.name, - key.createdTime ? formatDate(key.createdTime) : "", - key.createdBy ? key.createdBy : "", - key.description ? key.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 promote(command: cli.IPromoteCommand): Promise { - var appId: string; - var sourceDeploymentId: string; - var destDeploymentId: string; - - return getAppId(command.appName) - .then((appIdResult: string): Promise => { - throwForInvalidAppId(appIdResult, command.appName); - appId = appIdResult; - return getDeploymentId(appId, command.sourceDeploymentName); - }) - .then((deploymentId: string): Promise => { - throwForInvalidDeploymentId(deploymentId, command.sourceDeploymentName, command.appName); - sourceDeploymentId = deploymentId; - return getDeploymentId(appId, command.destDeploymentName); - }) - .then((deploymentId: string): Promise => { - throwForInvalidDeploymentId(deploymentId, command.destDeploymentName, command.appName); - destDeploymentId = deploymentId; - return sdk.promotePackage(appId, sourceDeploymentId, destDeploymentId); - }) - .then((): void => { - log("Successfully promoted the \"" + command.sourceDeploymentName + "\" deployment of the \"" + command.appName + "\" app to the \"" + command.destDeploymentName + "\" deployment."); - }); -} - -function release(command: cli.IReleaseCommand): Promise { - if (command.package.search(/\.zip$/i) !== -1) { - throw new Error("It is unnecessary to package releases in a .zip file. Please specify the path of the desired directory or file directly."); - } else if (semver.valid(command.appStoreVersion) === null) { - throw new Error("Please use a semver compliant app store version, for example \"1.0.3\"."); - } - - 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 }); - } - - var lastTotalProgress = 0; - var progressBar = new progress("Upload progress:[:bar] :percent :etas", { - complete: "=", - incomplete: " ", - width: 50, - total: 100 - }); - - var uploadProgress = (currentProgress: number): void => { - progressBar.tick(currentProgress - lastTotalProgress); - lastTotalProgress = currentProgress; - } - - return getPackageFilePromise - .then((file: IPackageFile): Promise => { - return sdk.addPackage(appId, deploymentId, file.path, command.description, /*label*/ null, command.appStoreVersion, command.mandatory, uploadProgress) - .then((): void => { - log("Successfully released an update containing the \"" + command.package + "\" " + (isSingleFilePackage ? "file" : "directory") + " to the \"" + command.deploymentName + "\" deployment of the \"" + command.appName + "\" app."); - - if (file.isTemporary) { - fs.unlinkSync(filePath); - } - }); - }); - }); - }); -} - -function rollback(command: cli.IRollbackCommand): Promise { - var appId: string; - - return confirm() - .then((wasConfirmed: boolean) => { - if (!wasConfirmed) { - log("Rollback cancelled.") - return; - } - - return getAppId(command.appName) - .then((appIdResult: string): Promise => { - throwForInvalidAppId(appIdResult, command.appName); - appId = appIdResult; - return getDeploymentId(appId, command.deploymentName); - }) - .then((deploymentId: string): Promise => { - throwForInvalidDeploymentId(deploymentId, command.deploymentName, command.appName); - return sdk.rollbackPackage(appId, deploymentId, command.targetRelease || undefined); - }) - .then((): void => { - log("Successfully performed a rollback on the \"" + command.deploymentName + "\" deployment of the \"" + command.appName + "\" app."); - }); - }); -} - -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 = tryBase64Decode(accessToken); - var standardLoginConnectionInfo: IStandardLoginConnectionInfo = tryJSON(json); - - if (standardLoginConnectionInfo) { - // This is a normal login. - standardLoginConnectionInfo.serverUrl = serverUrl; - json = JSON.stringify(standardLoginConnectionInfo); - fs.writeFileSync(configFilePath, json, { encoding: "utf8" }); - } else { - // This login uses an access token - var accessKeyLoginConnectionInfo: IAccessKeyLoginConnectionInfo = { serverUrl: serverUrl, accessKey: accessToken }; - json = JSON.stringify(accessKeyLoginConnectionInfo); - fs.writeFileSync(configFilePath, json, { encoding: "utf8" }); - } - - log("\r\nSuccessfully logged-in. Your session token was written to " + chalk.cyan(configFilePath) + ". You can run the " + chalk.cyan("code-push logout") + " command at any time to delete this file and terminate your session.\r\n"); -} - -function tryBase64Decode(encoded: string): string { - try { - return base64.decode(encoded); - } catch (ex) { - return null; - } -} - -function throwForMissingCredentials(accessKeyName: string, providerName: string, providerUniqueId: string): void { - if (!accessKeyName) throw new Error("Access key is missing."); - if (!providerName) throw new Error("Provider name is missing."); - if (!providerUniqueId) throw new Error("Provider unique ID is missing."); - -} - -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 8bdce906..00000000 --- a/cli/script/command-parser.ts +++ /dev/null @@ -1,489 +0,0 @@ -import * as yargs from "yargs"; -import * as cli from "../definitions/cli"; -import * as chalk from "chalk"; -import * as updateNotifier from "update-notifier"; - -var packageJson = require("../package.json"); -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; - -export 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*/ 3, /*max*/ 3) // Require exactly two non-option arguments. - .example("access-key " + commandName + " \"VSO Integration\"", "Generates a new access key with the description \"VSO Integration\""); - - addCommonConfiguration(yargs); -} - -function accessKeyList(commandName: string, yargs: yargs.Argv): void { - isValidCommand = true; - yargs.usage(USAGE_PREFIX + " access-key " + commandName + " [--format ]") - .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 ]") - .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" }); - 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); -} - -function deploymentHistory(commandName: string, yargs: yargs.Argv): void { - isValidCommand = true; - yargs.usage(USAGE_PREFIX + " deployment " + commandName + " [--format ]") - .demand(/*count*/ 4, /*max*/ 4) // Require exactly four non-option arguments. - .example("deployment " + commandName + " MyApp MyDeployment", "Shows the release history for deployment \"MyDeployment\" from app \"MyApp\" in tabular format") - .example("deployment " + commandName + " MyApp MyDeployment --format json", "Shows the release history for deployment \"MyDeployment\" from app \"MyApp\" in JSON format") - .option("format", { default: "table", demand: false, description: "The output format (\"json\" or \"table\")", type: "string" }); - - 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("add", "Create a new access key associated with your account", (yargs: yargs.Argv) => accessKeyAdd("add", 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)) - .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)) - .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("remove", "Remove an app from your account", (yargs: yargs.Argv) => appRemove("remove", yargs)) - .command("rm", "Remove an app from your account", (yargs: yargs.Argv) => appRemove("rm", 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("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)) - .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 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 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" }); - - addCommonConfiguration(yargs); - }) - .command("promote", "Promote the package from one deployment of your app to another", (yargs: yargs.Argv) => { - yargs.usage(USAGE_PREFIX + " promote ") - .demand(/*count*/ 4, /*max*/ 4) // Require exactly four non-option arguments. - .example("promote MyApp Staging Production", "Promote the latest \"Staging\" package of \"MyApp\" to \"Production\""); - - addCommonConfiguration(yargs); - }) - .command("rollback", "Performs a rollback on the latest package of a specific deployment", (yargs: yargs.Argv) => { - yargs.usage(USAGE_PREFIX + " rollback [--targetRelease ]") - .demand(/*count*/ 3, /*max*/ 3) // Require exactly three non-option arguments. - .example("rollback MyApp Production", "Perform a rollback on the \"Production\" deployment of \"MyApp\"") - .example("rollback MyApp Production --targetRelease v4", "Perform a rollback on the \"Production\" deployment of \"MyApp\" to the v4 release.") - .option("targetRelease", { alias: "r", default: null, demand: false, description: "The label of the release to be rolled back to (e.g. v4)", type: "string" }); - - 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("remove", "Remove a deployment from an app", (yargs: yargs.Argv) => deploymentRemove("remove", yargs)) - .command("rm", "Remove a deployment from an app", (yargs: yargs.Argv) => deploymentRemove("rm", 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("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("history", "Show the release history of a specific deployment", (yargs: yargs.Argv) => deploymentHistory("history", yargs)) - .command("h", "Show the release history of a specific deployment", (yargs: yargs.Argv) => deploymentHistory("h", 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 [--accessKey ]") - .demand(/*count*/ 1, /*max*/ 2) // Require one non-optional and one optional argument. - .example("login", "Logs in to the CodePush server") - .example("login --accessKey mykey", "Logs in on behalf of the user who owns and created the access key \"mykey\"") - .option("accessKey", { alias: "key", default: null, demand: false, description: "The access key to be used for this session", type: "string" }) - .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; - yargs.usage(USAGE_PREFIX + " logout [--local]") - .demand(/*count*/ 1, /*max*/ 1) // Require exactly one non-option argument. - .example("logout", "Log out and also remove the access key used for the current session.") - .example("logout --local", "Log out but allow the use of the same access key for future logins.") - .option("local", { demand: false, description: "Whether to delete the current session's access key on the server", type: "boolean" }); - addCommonConfiguration(yargs); - }) - .command("register", "Register a new account with the CodePush server", (yargs: yargs.Argv) => { - isValidCommandCategory = true; - isValidCommand = true; - yargs.usage(USAGE_PREFIX + " register") - .demand(/*count*/ 1, /*max*/ 2) // Require one non-optional and one optional argument. - .example("register", "Creates a new user account on the CodePush server") - .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 "add": - if (arg2) { - cmd = { type: cli.CommandType.accessKeyAdd }; - (cmd).description = arg2; - } - break; - - 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"]; - } - 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; - - case "history": - case "h": - if (arg2 && arg3) { - cmd = { type: cli.CommandType.deploymentHistory }; - - var deploymentHistoryCommand = cmd; - - deploymentHistoryCommand.appName = arg2; - deploymentHistoryCommand.deploymentName = arg3; - deploymentHistoryCommand.format = argv["format"]; - } - break; - } - break; - - case "login": - cmd = { type: cli.CommandType.login }; - - var loginCommand = cmd; - - loginCommand.serverUrl = getServerUrl(arg1); - loginCommand.accessKey = argv["accessKey"]; - break; - - case "logout": - cmd = { type: cli.CommandType.logout }; - - var logoutCommand = cmd; - - logoutCommand.isLocal = argv["local"]; - break; - - case "promote": - if (arg1 && arg2 && arg3) { - cmd = { type: cli.CommandType.promote }; - - var deploymentPromoteCommand = cmd; - - deploymentPromoteCommand.appName = arg1; - deploymentPromoteCommand.sourceDeploymentName = arg2; - deploymentPromoteCommand.destDeploymentName = arg3; - } - 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; - - case "rollback": - if (arg1 && arg2) { - cmd = { type: cli.CommandType.rollback }; - - var rollbackCommand = cmd; - - rollbackCommand.appName = arg1; - rollbackCommand.deploymentName = arg2; - rollbackCommand.targetRelease = argv["targetRelease"]; - } - 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(); diff --git a/cli/test/cli.ts b/cli/test/cli.ts deleted file mode 100644 index 1df4482d..00000000 --- a/cli/test/cli.ts +++ /dev/null @@ -1,500 +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"; -import * as os from "os"; - -function assertJsonDescribesObject(json: string, object: Object): void { - // Make sure JSON is indented correctly - assert.equal(json, JSON.stringify(object, /*replacer=*/ null, /*spacing=*/ 2)); -} - -export class SdkStub { - public addAccessKey(machine: string, description?: string): Promise { - return Q({ - id: "accessKeyId", - name: "key123", - createdTime: new Date().getTime(), - createdBy: os.hostname(), - description: description - }); - } - - 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", - createdTime: 0, - createdBy: os.hostname(), - description: "Test Description" - }]); - } - - 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 getPackageHistory(appId: string, deploymentId: string): Promise { - return Q([ - { - appVersion: "1.0.0", - isMandatory: false, - packageHash: "463acc7d06adc9c46233481d87d9e8264b3e9ffe60fe98d721e6974209dc71a0", - blobUrl: "https://fakeblobstorage.net/storagev2/blobid1", - uploadTime: 1447113596270, - label: "v1" - }, - { - description: "New update - this update does a whole bunch of things, including testing linewrapping", - appVersion: "1.0.1", - isMandatory: false, - packageHash: "463acc7d06adc9c46233481d87d9e8264b3e9ffe60fe98d721e6974209dc71a0", - blobUrl: "https://fakeblobstorage.net/storagev2/blobid2", - uploadTime: 1447118476669, - label: "v2" - } - ]); - } - - 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("accessKeyAdd creates access key with description", (done: MochaDone): void => { - var command: cli.IAccessKeyAddCommand = { - type: cli.CommandType.accessKeyAdd, - description: "Test description" - }; - - 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 = "Successfully created a new access key \"Test description\": key123"; - - assert.equal(actual, expected); - done(); - }); - }); - - it("accessKeyList lists access key names and ID's", (done: MochaDone): void => { - var command: cli.IAccessKeyListCommand = { - 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 = [ - { - id: "7", - name: "8", - createdTime: 0, - createdBy: os.hostname(), - description: "Test Description" - } - ]; - - assertJsonDescribesObject(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, "Successfully removed the \"8\" access key."); - - 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, "Access key removal cancelled."); - - done(); - }); - }); - - it("appAdd reports new app name and ID", (done: MochaDone): void => { - var command: cli.IAppAddCommand = { - type: cli.CommandType.appAdd, - appName: "a" - }; - - 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 the \"a\" app, along with the following default deployments:"); - sinon.assert.calledOnce(deploymentList); - done(); - }); - }); - - it("appList lists app names and ID's", (done: MochaDone): void => { - var command: cli.IAppListCommand = { - 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", deployments: ["Production", "Staging"] }, - { name: "b", deployments: ["Production", "Staging"] } - ]; - - assertJsonDescribesObject(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, "Successfully removed the \"a\" app."); - - 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, "App removal 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, "Successfully renamed the \"a\" app 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, "Successfully added the \"b\" deployment with key \"6\" to the \"a\" app."); - done(); - }); - }); - - it("deploymentList lists deployment names, deployment keys, and package information", (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", - package: { - appVersion: "1.0.0", - description: "fgh", - label: "ghi", - packageHash: "jkl", - isMandatory: true, - size: 10, - blobUrl: "http://mno.pqr", - uploadTime: +1000 - } - } - ]; - - assertJsonDescribesObject(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, "Successfully removed the \"Staging\" deployment from the \"a\" app."); - - 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, "Deployment removal 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, "Successfully renamed the \"Staging\" deployment to \"c\" for the \"a\" app."); - - done(); - }); - }); - - it("deploymentHistory lists package history information", (done: MochaDone): void => { - var command: cli.IDeploymentHistoryCommand = { - type: cli.CommandType.deploymentHistory, - appName: "a", - deploymentName: "Staging", - format: "json" - }; - - var getPackageHistory: Sinon.SinonSpy = sandbox.spy(cmdexec.sdk, "getPackageHistory"); - - cmdexec.execute(command) - .done((): void => { - sinon.assert.calledOnce(getPackageHistory); - sinon.assert.calledOnce(log); - assert.equal(log.args[0].length, 1); - - var actual: string = log.args[0][0]; - var expected: codePush.Package[] = [ - { - appVersion: "1.0.0", - isMandatory: false, - packageHash: "463acc7d06adc9c46233481d87d9e8264b3e9ffe60fe98d721e6974209dc71a0", - blobUrl: "https://fakeblobstorage.net/storagev2/blobid1", - uploadTime: 1447113596270, - label: "v1" - }, - { - description: "New update - this update does a whole bunch of things, including testing linewrapping", - appVersion: "1.0.1", - isMandatory: false, - packageHash: "463acc7d06adc9c46233481d87d9e8264b3e9ffe60fe98d721e6974209dc71a0", - blobUrl: "https://fakeblobstorage.net/storagev2/blobid2", - uploadTime: 1447118476669, - label: "v2" - } - ]; - - assertJsonDescribesObject(actual, expected); - 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 459e5b22..00000000 --- a/definitions/rest-definitions.d.ts +++ /dev/null @@ -1,71 +0,0 @@ -declare module "rest-definitions" { - export interface AccessKey { - id: string; - name: string; - createdTime: number; - createdBy: 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; - /*generated*/ releaseMethod?: string; // "Upload", "Promote" or "Rollback". Unknown if unspecified - /*generated*/ originalLabel?: string; // Set on "Promote" and "Rollback" - /*generated*/ originalDeployment?: string; // Set on "Promote" - } -} 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/clean.js b/gulp/clean.js deleted file mode 100644 index 58d3a393..00000000 --- a/gulp/clean.js +++ /dev/null @@ -1,16 +0,0 @@ -"use strict"; - -var gulp = require("gulp"); -var del = require("del"); - -function deleteTask(glob, next) { - del(glob, null, next); -} - -var cliCleanList = ["cli/bin/**/*", "!cli/bin/.*"]; -var sdkCleanList = ["sdk/bin/**/*", "!sdk/bin/.*"]; - -gulp.task("clean-cli", function(next) { deleteTask(cliCleanList, next); }); -gulp.task("clean-sdk", function(next) { deleteTask(sdkCleanList, next); }); - -gulp.task("clean", ["clean-cli", "clean-sdk"]); 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 24ef460d..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 8ade705e..00000000 --- a/sdk/package.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "code-push", - "version": "1.3.0-beta", - "description": "Management SDK for the CodePush service", - "main": "script/index.js", - "scripts": { - "test": "gulp" - }, - "repository": { - "type": "git", - "url": "https://github.com/Microsoft/code-push.git" - }, - "author": "Microsoft Corporation", - "license": "MIT", - "homepage": "https://microsoft.github.io/code-push", - "dependencies": { - "base-64": "^0.1.0", - "fs": "0.0.2", - "node-uuid": "^1.4.3", - "q": "^1.4.1", - "superagent": "git://github.com/visionmedia/superagent.git#45a1290b2fe5a56a1d77ca74e0b236178d58d848", - "try-json": "^1.0.0" - } -} diff --git a/sdk/plugin.xml b/sdk/plugin.xml deleted file mode 100644 index 561fc8a3..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 1b1c3a49..00000000 --- a/sdk/script/management/account-manager.ts +++ /dev/null @@ -1,889 +0,0 @@ -import * as base64 from "base-64"; -import Q = require("q"); -import crypto = require("crypto"); -import tryJSON = require("try-json"); -import Promise = Q.Promise; -import request = require("superagent"); - -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; - private _userAgent: string; - - public account: Account; - public serverUrl: string = "http://localhost:3000"; - - public get accountId(): string { - return this.account.id; - } - - constructor(serverUrl: string, userAgent: string) { - // If window is not defined, it means we are in the node environment and not a browser. - this._saveAuthedAgent = (typeof window === "undefined"); - - this._userAgent = userAgent; - this.serverUrl = serverUrl; - } - - public loginWithAccessToken(accessToken: string): Promise { - return Promise((resolve, reject, notify) => { - var loginInfo: ILoginInfo = AccountManager.getLoginInfo(accessToken); - - var req = request.post(this.serverUrl + "/auth/login/accessToken"); - this.attachCredentials(req, request); - req = req.type("form"); - - if (loginInfo && loginInfo.providerName && loginInfo.providerUniqueId) { - // Login the old way. - req = req.send({ identity: JSON.stringify({ providerName: loginInfo.providerName, providerUniqueId: loginInfo.providerUniqueId }) }) - .send({ token: loginInfo.accessKeyName }); - } else { - // Note: We can't send an empty identity string, or PassportAuth will short circuit and fail. - req = req.send({ identity: "accessKey" }) - .send({ token: accessToken }); - } - - req.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(machine: string, description?: string): Promise { - return Promise((resolve, reject, notify) => { - return this.generateAccessKey().then((newAccessKey: string) => { - var accessKey: AccessKey = { id: null, name: newAccessKey, createdTime: new Date().getTime(), createdBy: machine, 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 }); - } - } - }); - }); - } - - 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 addPackage(appId: string, deploymentId: string, fileOrPath: File | string, description: string, label: string, appVersion: string, isMandatory: boolean = false, uploadProgressCallback?: (progress: number) => void): 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.attach("package", file) - .field("packageInfo", JSON.stringify(packageInfo)) - .on("progress", (event: any) => { - if (uploadProgressCallback && event && event.total > 0) { - var currentProgress: number = event.loaded / event.total * 100; - uploadProgressCallback(currentProgress); - } - }) - .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 promotePackage(appId: string, sourceDeploymentId: string, destDeploymentId: string): Promise { - return Promise((resolve, reject, notify) => { - var requester = (this._authedAgent ? this._authedAgent : request); - var req = requester.post(this.serverUrl + "/apps/" + appId + "/deployments/" + sourceDeploymentId + "/promote/" + destDeploymentId); - 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 rollbackPackage(appId: string, deploymentId: string, targetRelease?: string): Promise { - return Promise((resolve, reject, notify) => { - var requester = (this._authedAgent ? this._authedAgent : request); - var req = requester.post(this.serverUrl + "/apps/" + appId + "/deployments/" + deploymentId + "/rollback/" + (targetRelease || "")); - 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 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 }); - } - } - }); - }); - } - - public getPackageHistory(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 + "/packageHistory"); - 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.packageHistory); - } 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 { - try { - var decoded: string = base64.decode(accessKey); - return tryJSON(decoded); - } catch (ex) { - return null; - } - } - - 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(); - } - - if (this._userAgent) { - request.set("User-Agent", this._userAgent); - } - } - - private generateAccessKey(): Promise { - return this.getAccountInfo().then(() => { - var accessKey = crypto.randomBytes(21) - .toString("base64") - .replace(/\+/g, "_") // URL-friendly characters - .replace(/\//g, "-") - .concat(this.accountId); - - return accessKey; - }) - } -} \ No newline at end of file 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 3085011f..00000000 --- a/sdk/test/management-sdk.ts +++ /dev/null @@ -1,261 +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(/*serverUrl=*/ "http://localhost", /*userAgent=*/ "unit-test/1.0.0"); - }); - - 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.getDeploymentKeys.bind(manager, "appId", "deploymentId"), - - 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("getDeploymentKeys handles JSON response", (done: MochaDone) => { - mockReturn(JSON.stringify({ deploymentKeys: [] }), 200, {}); - - manager.getDeploymentKeys("appId", "deploymentId").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); - }); - - it("getPackageHistory handles success response with no packages", (done: MochaDone) => { - mockReturn(JSON.stringify({ packageHistory: [] }), 200); - - manager.getPackageHistory("appId", "deploymentId").done((obj: any) => { - assert.ok(obj); - assert.equal(obj.length, 0); - done(); - }, rejectHandler); - }); - - it("getPackageHistory handles success response with two packages", (done: MochaDone) => { - mockReturn(JSON.stringify({ packageHistory: [ { label: "v1" }, { label: "v2" } ] }), 200); - - manager.getPackageHistory("appId", "deploymentId").done((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("getPackageHistory handles error response", (done: MochaDone) => { - mockReturn("", 404); - - manager.getPackageHistory("appId", "deploymentId").done((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(); -} - -// 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 8849aa57..00000000 --- a/tsd.json +++ /dev/null @@ -1,86 +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" - }, - "moment/moment.d.ts": { - "commit": "708609e0764daeb5eb64104af7aca50c520c4e6e" - }, - "moment/moment-node.d.ts": { - "commit": "708609e0764daeb5eb64104af7aca50c520c4e6e" - }, - "update-notifier/update-notifier.d.ts": { - "commit": "715df764419937a75a25dcd76bfc954157bc7b96" - } - } -}