├── .asf.yaml ├── .eslintignore ├── .eslintrc.yml ├── .github ├── ISSUE_TEMPLATE.md ├── ISSUE_TEMPLATE │ ├── BUG_REPORT.md │ ├── FEATURE_REQUEST.md │ └── SUPPORT_QUESTION.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── ci.yml │ └── release-audit.yml ├── .gitignore ├── .npmignore ├── .npmrc ├── .ratignore ├── CONTRIBUTING.md ├── LICENSE ├── NOTICE ├── README.md ├── RELEASENOTES.md ├── cordova-common.js ├── licence_checker.yml ├── package-lock.json ├── package.json ├── spec ├── ActionStack.spec.js ├── ConfigChanges │ ├── ConfigChanges.spec.js │ └── ConfigFile.spec.js ├── ConfigParser │ └── ConfigParser.spec.js ├── CordovaCheck.spec.js ├── CordovaError │ └── CordovaError.spec.js ├── CordovaLogger.spec.js ├── FileUpdater.spec.js ├── PlatformJson.spec.js ├── PluginInfo │ ├── PluginInfo.spec.js │ └── PluginInfoProvider.spec.js ├── PluginManager.spec.js ├── events.spec.js ├── fixtures │ ├── echo-args.cmd │ ├── plugins │ │ ├── @scope │ │ │ └── test │ │ │ │ └── plugin.xml │ │ ├── ChildBrowser │ │ │ ├── plugin.xml │ │ │ ├── src │ │ │ │ └── android │ │ │ │ │ └── ChildBrowser.java │ │ │ └── www │ │ │ │ ├── childbrowser.js │ │ │ │ ├── childbrowser │ │ │ │ └── image.jpg │ │ │ │ └── childbrowser_file.html │ │ ├── com.adobe.vars │ │ │ └── plugin.xml │ │ ├── org.apache.bplist │ │ │ └── plugin.xml │ │ ├── org.apache.plist │ │ │ └── plugin.xml │ │ ├── org.test.configtest │ │ │ └── plugin.xml │ │ ├── org.test.editconfigtest │ │ │ └── plugin.xml │ │ ├── org.test.editconfigtest_two │ │ │ └── plugin.xml │ │ ├── org.test.multiple-children │ │ │ └── plugin.xml │ │ ├── org.test.plugins.childbrowser │ │ │ ├── plugin.xml │ │ │ ├── src │ │ │ │ ├── android │ │ │ │ │ └── ChildBrowser.java │ │ │ │ └── ios │ │ │ │ │ ├── ChildBrowser.bundle │ │ │ │ │ ├── arrow_left.png │ │ │ │ │ ├── arrow_left@2x.png │ │ │ │ │ ├── arrow_right.png │ │ │ │ │ ├── arrow_right@2x.png │ │ │ │ │ ├── but_refresh.png │ │ │ │ │ ├── but_refresh@2x.png │ │ │ │ │ ├── compass.png │ │ │ │ │ └── compass@2x.png │ │ │ │ │ ├── ChildBrowserCommand.h │ │ │ │ │ ├── ChildBrowserCommand.m │ │ │ │ │ ├── ChildBrowserViewController.h │ │ │ │ │ ├── ChildBrowserViewController.m │ │ │ │ │ ├── ChildBrowserViewController.xib │ │ │ │ │ ├── TargetDirTest.h │ │ │ │ │ ├── TargetDirTest.m │ │ │ │ │ └── preserveDirs │ │ │ │ │ ├── PreserveDirsTest.h │ │ │ │ │ └── PreserveDirsTest.m │ │ │ └── www │ │ │ │ ├── childbrowser.js │ │ │ │ ├── childbrowser │ │ │ │ └── image.jpg │ │ │ │ └── childbrowser_file.html │ │ ├── org.test.plugins.dummyplugin │ │ │ ├── android-resource.xml │ │ │ ├── extra.gradle │ │ │ ├── plugin-lib │ │ │ │ ├── AndroidManifest.xml │ │ │ │ ├── libFile │ │ │ │ └── project.properties │ │ │ ├── plugin.xml │ │ │ ├── src │ │ │ │ ├── android │ │ │ │ │ └── DummyPlugin.java │ │ │ │ ├── blackberry10 │ │ │ │ │ └── index.js │ │ │ │ ├── ios │ │ │ │ │ ├── Custom.framework │ │ │ │ │ │ ├── someFheader.h │ │ │ │ │ │ └── somebinlib │ │ │ │ │ ├── DummyPlugin.bundle │ │ │ │ │ ├── DummyPluginCommand.h │ │ │ │ │ ├── DummyPluginCommand.m │ │ │ │ │ ├── SourceWithFramework.m │ │ │ │ │ ├── TargetDirTest.h │ │ │ │ │ ├── TargetDirTest.m │ │ │ │ │ └── libsqlite3.dylib │ │ │ │ ├── tizen │ │ │ │ │ └── dummer.js │ │ │ │ ├── windows │ │ │ │ │ ├── dummer.js │ │ │ │ │ ├── dummy1.dll │ │ │ │ │ ├── dummy1.vcxproj │ │ │ │ │ ├── dummy2.dll │ │ │ │ │ ├── dummy2.vcxproj │ │ │ │ │ ├── dummy3.dll │ │ │ │ │ ├── dummy3.vcxproj │ │ │ │ │ ├── dummy4.dll │ │ │ │ │ └── dummy4.vcxproj │ │ │ │ ├── wp7 │ │ │ │ │ └── DummyPlugin.cs │ │ │ │ └── wp8 │ │ │ │ │ └── DummyPlugin.cs │ │ │ └── www │ │ │ │ ├── dummyplugin.js │ │ │ │ └── dummyplugin │ │ │ │ └── image.jpg │ │ ├── org.test.plugins.withcocoapods │ │ │ └── plugin.xml │ │ ├── org.test.shareddeps │ │ │ └── plugin.xml │ │ ├── org.test.src │ │ │ └── plugin.xml │ │ └── org.test.xmlpassthrough │ │ │ └── plugin.xml │ ├── projects │ │ ├── android │ │ │ ├── AndroidManifest.xml │ │ │ ├── assets │ │ │ │ └── www │ │ │ │ │ └── .gitkeep │ │ │ ├── res │ │ │ │ └── xml │ │ │ │ │ └── config.xml │ │ │ └── src │ │ │ │ └── .gitkeep │ │ ├── android_two │ │ │ ├── AndroidManifest.xml │ │ │ ├── assets │ │ │ │ └── www │ │ │ │ │ └── .gitkeep │ │ │ ├── res │ │ │ │ └── xml │ │ │ │ │ └── config.xml │ │ │ └── src │ │ │ │ └── .gitkeep │ │ ├── android_two_no_perms │ │ │ ├── AndroidManifest.xml │ │ │ ├── assets │ │ │ │ └── www │ │ │ │ │ └── .gitkeep │ │ │ ├── res │ │ │ │ └── xml │ │ │ │ │ └── config.xml │ │ │ └── src │ │ │ │ └── .gitkeep │ │ ├── ios-config-xml │ │ │ ├── CordovaLib │ │ │ │ ├── CordovaLib.xcodeproj │ │ │ │ │ └── project.pbxproj │ │ │ │ └── VERSION │ │ │ ├── SampleApp.xcodeproj │ │ │ │ ├── project.orig.pbxproj │ │ │ │ └── project.pbxproj │ │ │ ├── SampleApp │ │ │ │ ├── SampleApp-Info.plist │ │ │ │ ├── SampleApp-binary.plist │ │ │ │ └── config.xml │ │ │ └── www │ │ │ │ └── .gitkeep │ │ └── windows-bom-test.xml │ ├── test-config.xml │ ├── test-config0.xml │ ├── test-configfile.xml │ └── test-editconfig.xml ├── helper.js ├── plist-helpers.spec.js ├── superspawn.spec.js ├── support │ └── jasmine.json └── util │ └── xml-helpers.spec.js └── src ├── ActionStack.js ├── ConfigChanges ├── ConfigChanges.js ├── ConfigFile.js ├── ConfigKeeper.js └── munge-util.js ├── ConfigParser └── ConfigParser.js ├── CordovaCheck.js ├── CordovaError.js ├── CordovaLogger.js ├── FileUpdater.js ├── PlatformJson.js ├── PluginInfo ├── PluginInfo.js └── PluginInfoProvider.js ├── PluginManager.js ├── events.js ├── superspawn.js └── util ├── formatError.js ├── plist-helpers.js └── xml-helpers.js /.asf.yaml: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | 18 | github: 19 | description: Apache Cordova Common Tooling Library 20 | homepage: https://cordova.apache.org/ 21 | 22 | labels: 23 | - cordova 24 | - mobile 25 | - javascript 26 | - nodejs 27 | - hacktoberfest 28 | 29 | features: 30 | wiki: false 31 | issues: true 32 | projects: true 33 | 34 | enabled_merge_buttons: 35 | squash: true 36 | merge: false 37 | rebase: false 38 | 39 | notifications: 40 | commits: commits@cordova.apache.org 41 | issues: issues@cordova.apache.org 42 | pullrequests_status: issues@cordova.apache.org 43 | pullrequests_comment: issues@cordova.apache.org 44 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | coverage/ 2 | spec/fixtures/ 3 | -------------------------------------------------------------------------------- /.eslintrc.yml: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | 18 | root: true 19 | extends: '@cordova/eslint-config/node' 20 | 21 | overrides: 22 | - files: [spec/**/*.js] 23 | extends: '@cordova/eslint-config/node-tests' 24 | 25 | rules: 26 | no-unused-vars: 27 | - error 28 | - args: after-used 29 | vars: all 30 | ignoreRestSiblings: true 31 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 5 | 6 | ### Issue Type 7 | 8 | 9 | - [ ] Bug Report 10 | - [ ] Feature Request 11 | - [ ] Support Question 12 | 13 | ## Description 14 | 15 | ## Information 16 | 17 | 18 | ### Command or Code 19 | 20 | 21 | ### Environment, Platform, Device 22 | 23 | 24 | 25 | 26 | ### Version information 27 | 34 | 35 | 36 | 37 | ## Checklist 38 | 39 | 40 | - [ ] I searched for already existing GitHub issues about this 41 | - [ ] I updated all Cordova tooling to their most recent version 42 | - [ ] I included all the necessary information above 43 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/BUG_REPORT.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🐛 Bug Report 3 | about: If something isn't working as expected. 4 | 5 | --- 6 | 7 | # Bug Report 8 | 9 | ## Problem 10 | 11 | ### What is expected to happen? 12 | 13 | 14 | 15 | ### What does actually happen? 16 | 17 | 18 | 19 | ## Information 20 | 21 | 22 | 23 | 24 | ### Command or Code 25 | 26 | 27 | 28 | 29 | ### Environment, Platform, Device 30 | 31 | 32 | 33 | 34 | ### Version information 35 | 42 | 43 | 44 | 45 | ## Checklist 46 | 47 | 48 | - [ ] I searched for existing GitHub issues 49 | - [ ] I updated all Cordova tooling to most recent version 50 | - [ ] I included all the necessary information above 51 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/FEATURE_REQUEST.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🚀 Feature Request 3 | about: A suggestion for a new functionality 4 | 5 | --- 6 | 7 | # Feature Request 8 | 9 | ## Motivation Behind Feature 10 | 11 | 12 | 13 | 14 | ## Feature Description 15 | 20 | 21 | 22 | 23 | ## Alternatives or Workarounds 24 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/SUPPORT_QUESTION.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 💬 Support Question 3 | about: If you have a question, please check out our Slack or StackOverflow! 4 | 5 | --- 6 | 7 | 8 | 9 | Apache Cordova uses GitHub Issues as a feature request and bug tracker _only_. 10 | For usage and support questions, please check out the resources below. Thanks! 11 | 12 | --- 13 | 14 | You can get answers to your usage and support questions about **Apache Cordova** on: 15 | 16 | * Slack Community Chat: https://cordova.slack.com (you can sign-up at http://slack.cordova.io/) 17 | * StackOverflow: https://stackoverflow.com/questions/tagged/cordova using the tag `cordova` 18 | 19 | --- 20 | 21 | If you are using a tool that uses Cordova internally, like e.g. Ionic, check their support channels: 22 | 23 | * **Ionic Framework** 24 | * [Ionic Community Forum](https://forum.ionicframework.com/) 25 | * [Ionic Worldwide Slack](https://ionicworldwide.herokuapp.com/) 26 | * **PhoneGap** 27 | * [PhoneGap Developer Community](https://forums.adobe.com/community/phonegap) 28 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 8 | 9 | ### Platforms affected 10 | 11 | 12 | 13 | ### Motivation and Context 14 | 15 | 16 | 17 | 18 | 19 | ### Description 20 | 21 | 22 | 23 | 24 | ### Testing 25 | 26 | 27 | 28 | 29 | ### Checklist 30 | 31 | - [ ] I've run the tests to see all new and existing tests pass 32 | - [ ] I added automated test coverage as appropriate for this change 33 | - [ ] Commit is prefixed with `(platform)` if this change only applies to one platform (e.g. `(android)`) 34 | - [ ] If this Pull Request resolves an issue, I linked to the issue in the text above (and used the correct [keyword to close issues using keywords](https://help.github.com/articles/closing-issues-using-keywords/)) 35 | - [ ] I've updated the documentation if necessary 36 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | 18 | name: Node CI 19 | 20 | on: 21 | push: 22 | branches-ignore: 23 | - 'dependabot/**' 24 | pull_request: 25 | branches: 26 | - '*' 27 | 28 | jobs: 29 | test: 30 | name: NodeJS ${{ matrix.node-version }} on ${{ matrix.os }} 31 | runs-on: ${{ matrix.os }} 32 | strategy: 33 | matrix: 34 | node-version: [16.x, 18.x, 20.x, 22.x] 35 | os: [ubuntu-latest, windows-latest, macos-latest] 36 | 37 | steps: 38 | - uses: actions/checkout@v4 39 | 40 | - name: Use Node.js ${{ matrix.node-version }} 41 | uses: actions/setup-node@v4 42 | with: 43 | node-version: ${{ matrix.node-version }} 44 | 45 | - name: Environment Information 46 | run: | 47 | node --version 48 | npm --version 49 | 50 | - uses: github/codeql-action/init@v3 51 | with: 52 | languages: javascript 53 | queries: security-and-quality 54 | config: | 55 | paths-ignore: 56 | - coverage 57 | - node_modules 58 | 59 | - name: npm install and test 60 | run: npm cit 61 | env: 62 | CI: true 63 | 64 | - uses: github/codeql-action/analyze@v3 65 | 66 | - uses: codecov/codecov-action@v4 67 | if: success() 68 | with: 69 | name: ${{ runner.os }} node.js ${{ matrix.node-version }} 70 | token: ${{ secrets.CORDOVA_CODECOV_TOKEN }} 71 | fail_ci_if_error: false 72 | -------------------------------------------------------------------------------- /.github/workflows/release-audit.yml: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | 18 | name: Release Auditing 19 | 20 | on: 21 | push: 22 | branches-ignore: 23 | - 'dependabot/**' 24 | pull_request: 25 | branches: 26 | - '*' 27 | 28 | jobs: 29 | test: 30 | name: Audit Licenses 31 | runs-on: ubuntu-latest 32 | steps: 33 | # Checkout project 34 | - uses: actions/checkout@v4 35 | 36 | # Check license headers 37 | - uses: erisu/apache-rat-action@v1 38 | 39 | # Setup environment with node 40 | - uses: actions/setup-node@v4 41 | with: 42 | node-version: 20 43 | 44 | # Install node packages 45 | - name: npm install packages 46 | run: npm i 47 | 48 | # Check node package licenses 49 | - uses: erisu/license-checker-action@e929758f9416f30234ac454fc9054ca4b803871d 50 | with: 51 | license-config: 'licence_checker.yml' 52 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | .nyc_output 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .* 2 | spec 3 | coverage 4 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmjs.org 2 | 3 | -------------------------------------------------------------------------------- /.ratignore: -------------------------------------------------------------------------------- 1 | \.(.*) 2 | coverage 3 | fixtures 4 | node_modules 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | 21 | 22 | # Contributing to Apache Cordova 23 | 24 | Anyone can contribute to Cordova. And we need your contributions. 25 | 26 | There are multiple ways to contribute: report bugs, improve the docs, and 27 | contribute code. 28 | 29 | For instructions on this, start with the 30 | [contribution overview](http://cordova.apache.org/contribute/). 31 | 32 | The details are explained there, but the important items are: 33 | - Check for Github issues that corresponds to your contribution and link or create them if necessary. 34 | - Run the tests so your patch doesn't break existing functionality. 35 | 36 | We look forward to your contributions! 37 | 38 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Apache Cordova 2 | Copyright 2015 The Apache Software Foundation 3 | 4 | This product includes software developed at 5 | The Apache Software Foundation (http://www.apache.org/). 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 21 | 22 | # cordova-common 23 | 24 | [![NPM](https://nodei.co/npm/cordova-common.png)](https://nodei.co/npm/cordova-common/) 25 | 26 | [![Node CI](https://github.com/apache/cordova-common/workflows/Node%20CI/badge.svg?branch=master)](https://github.com/apache/cordova-common/actions?query=branch%3Amaster) 27 | 28 | Exposes shared functionality used by [cordova-lib](https://github.com/apache/cordova-lib/) and Cordova platforms. 29 | 30 | ## Exposed APIs 31 | 32 | ### `events` 33 | 34 | Represents special instance of NodeJS `EventEmitter` which is intended to be used to post events to `cordova-lib` and `cordova-cli` 35 | 36 | Usage: 37 | 38 | ```js 39 | const { events } = require('cordova-common'); 40 | events.emit('warn', 'Some warning message') 41 | ``` 42 | 43 | Here are the following supported events by `cordova-cli`: 44 | 45 | * `verbose` 46 | * `log` 47 | * `info` 48 | * `warn` 49 | * `error` 50 | 51 | ### `CordovaError` 52 | 53 | An error class used by Cordova to throw cordova-specific errors. The `CordovaError` class is inherited from `Error`, so it is a valid instance of `Error`. (`instanceof` check succeeds). 54 | 55 | Usage: 56 | 57 | ```js 58 | const { CordovaError } = require('cordova-common'); 59 | throw new CordovaError('Some error message', SOME_ERR_CODE); 60 | ``` 61 | 62 | See [CordovaError](src/CordovaError/CordovaError.js) for supported error codes. 63 | 64 | ### `ConfigParser` 65 | 66 | Exposes functionality to deal with cordova project `config.xml` files. For `ConfigParser` API reference check [ConfigParser Readme](src/ConfigParser/README.md). 67 | 68 | Usage: 69 | 70 | ```js 71 | const { ConfigParser } = require('cordova-common'); 72 | const appConfig = new ConfigParser('path/to/cordova-app/config.xml'); 73 | console.log(`${appconfig.name()}:${appConfig.version()}`); 74 | ``` 75 | 76 | ### `PluginInfoProvider` and `PluginInfo` 77 | 78 | `PluginInfo` is a wrapper for cordova plugins' `plugin.xml` files. This class may be instantiated directly or via `PluginInfoProvider`. The difference is that `PluginInfoProvider` caches `PluginInfo` instances based on plugin source directory. 79 | 80 | Usage: 81 | 82 | ```js 83 | const { PluginInfo, PluginInfoProvider } = require('cordova-common'); 84 | 85 | // The following instances are equal 86 | const plugin1 = new PluginInfo('path/to/plugin_directory'); 87 | const plugin2 = new PluginInfoProvider().get('path/to/plugin_directory'); 88 | 89 | console.log(`The plugin ${plugin1.id} has version ${plugin1.version}`) 90 | ``` 91 | 92 | ### `ActionStack` 93 | 94 | Utility module for dealing with sequential tasks. Provides a set of tasks that are needed to be done and reverts all tasks that are already completed if one of those tasks fail to complete. Used internally by `cordova-lib` and platform's plugin installation routines. 95 | 96 | Usage: 97 | 98 | ```js 99 | const { ActionStack } = require('cordova-common'); 100 | 101 | const stack = new ActionStack(); 102 | const action1 = stack.createAction(task1, [], task1_reverter, []); 103 | const action2 = stack.createAction(task2, [], task2_reverter, []); 104 | 105 | stack.push(action1); 106 | stack.push(action2); 107 | 108 | stack.process() 109 | .then(() => { 110 | // all actions succeded 111 | }) 112 | .catch(error => { 113 | // One of actions failed with error 114 | }); 115 | ``` 116 | 117 | ### `superspawn` 118 | 119 | Module for spawning child processes with some advanced logic. 120 | 121 | Usage: 122 | 123 | ```js 124 | const { superspawn } = require('cordova-common'); 125 | 126 | superspawn.spawn('adb', ['devices']) 127 | .progress(data => { 128 | if (data.stderr) console.error(`"adb devices" raised an error: ${data.stderr}`); 129 | }) 130 | .then(devices => { 131 | // Do something... 132 | }); 133 | ``` 134 | 135 | ### `xmlHelpers` 136 | 137 | A set of utility methods for dealing with XML files. 138 | 139 | Usage: 140 | 141 | ```js 142 | const { xmlHelpers } = require('cordova-common'); 143 | 144 | const doc1 = xmlHelpers.parseElementtreeSync('some/xml/file'); 145 | const doc2 = xmlHelpers.parseElementtreeSync('another/xml/file'); 146 | 147 | xmlHelpers.mergeXml(doc1, doc2); // doc2 now contains all the nodes from doc1 148 | ``` 149 | 150 | ### Other APIs 151 | 152 | The APIs listed below are also exposed but are intended to be only used internally by cordova plugin installation routines. 153 | 154 | * `PlatformJson` 155 | * `ConfigChanges` 156 | * `ConfigKeeper` 157 | * `ConfigFile` 158 | 159 | ## Setup 160 | 161 | * Clone this repository onto your local machine 162 | 163 | ```bash 164 | git clone https://github.com/apache/cordova-common.git 165 | ``` 166 | 167 | * Navigate to cordova-common directory, install dependencies and npm-link 168 | 169 | ```bash 170 | cd cordova-common && npm install && npm link 171 | ``` 172 | 173 | * Navigate to cordova-lib directory and link cordova-common 174 | 175 | ```bash 176 | cd && npm link cordova-common && npm install 177 | ``` 178 | -------------------------------------------------------------------------------- /cordova-common.js: -------------------------------------------------------------------------------- 1 | /** 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | module.exports = { 21 | get events () { return require('./src/events'); }, 22 | get superspawn () { return require('./src/superspawn'); }, 23 | 24 | get ActionStack () { return require('./src/ActionStack'); }, 25 | get CordovaError () { return require('./src/CordovaError'); }, 26 | get CordovaLogger () { return require('./src/CordovaLogger'); }, 27 | get CordovaCheck () { return require('./src/CordovaCheck'); }, 28 | get PlatformJson () { return require('./src/PlatformJson'); }, 29 | get ConfigParser () { return require('./src/ConfigParser/ConfigParser'); }, 30 | get FileUpdater () { return require('./src/FileUpdater'); }, 31 | 32 | get PluginInfo () { return require('./src/PluginInfo/PluginInfo'); }, 33 | get PluginInfoProvider () { return require('./src/PluginInfo/PluginInfoProvider'); }, 34 | 35 | get PluginManager () { return require('./src/PluginManager'); }, 36 | 37 | get ConfigChanges () { return require('./src/ConfigChanges/ConfigChanges'); }, 38 | get ConfigKeeper () { return require('./src/ConfigChanges/ConfigKeeper'); }, 39 | get ConfigFile () { return require('./src/ConfigChanges/ConfigFile'); }, 40 | 41 | get xmlHelpers () { return require('./src/util/xml-helpers'); } 42 | }; 43 | -------------------------------------------------------------------------------- /licence_checker.yml: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | 18 | # Compiled list of allowed 3RD PARTY LICENSES from: 19 | # 20 | # ASF CATEGORY A: WHAT CAN WE INCLUDE IN AN ASF PROJECT 21 | # https://www.apache.org/legal/resolved.html#category-a 22 | # 23 | # Licenses converted into the SPDX standardized short identifier format. 24 | # https://spdx.org/licenses/ 25 | allowed-licenses: 26 | - 0BSD 27 | - AFL-3.0 28 | - Apache-1.1 29 | - Apache-2.0 30 | - APAFML 31 | - BlueOak-1.0.0 32 | - BSD-2-Clause 33 | - BSD-3-Clause 34 | - BSD-3-Clause-LBNL 35 | - BSL-1.0 36 | - CC-PDDC 37 | - CC0-1.0 38 | - EPICS 39 | - HPND 40 | - ICU 41 | - ISC 42 | - MIT 43 | - MIT-0 44 | - MS-PL 45 | - MulanPSL-2.0 46 | - NCSA 47 | - OGL-UK-3.0 48 | - PHP-3.01 49 | - PostgreSQL 50 | - PSF-2.0 51 | - Python-2.0 52 | - SMLNJ 53 | - Unicode-DFS-2016 54 | - Unlicense 55 | - UPL-1.0 56 | - W3C 57 | - WTFPL 58 | - X11 59 | - Xnet 60 | - Zlib 61 | - ZPL-2.0 62 | 63 | ignored-packages: 64 | - caniuse-lite@1.0.30001446 65 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Apache Software Foundation", 3 | "name": "cordova-common", 4 | "description": "Apache Cordova tools and platforms shared routines", 5 | "license": "Apache-2.0", 6 | "version": "5.0.2-dev", 7 | "repository": "github:apache/cordova-common", 8 | "bugs": "https://github.com/apache/cordova-common/issues", 9 | "main": "cordova-common.js", 10 | "engines": { 11 | "node": ">=16.0.0" 12 | }, 13 | "scripts": { 14 | "test": "npm run lint && npm run cover", 15 | "test:unit": "jasmine \"spec/**/*.spec.js\"", 16 | "lint": "eslint .", 17 | "cover": "nyc npm run test:unit" 18 | }, 19 | "dependencies": { 20 | "@netflix/nerror": "^1.1.3", 21 | "ansi": "^0.3.1", 22 | "bplist-parser": "^0.3.2", 23 | "cross-spawn": "^7.0.6", 24 | "elementtree": "^0.1.7", 25 | "endent": "^2.1.0", 26 | "fast-glob": "^3.3.3", 27 | "lodash.zip": "^4.2.0", 28 | "plist": "^3.1.0", 29 | "q": "^1.5.1", 30 | "read-chunk": "^3.2.0", 31 | "strip-bom": "^4.0.0" 32 | }, 33 | "devDependencies": { 34 | "@cordova/eslint-config": "^5.1.0", 35 | "jasmine": "^4.6.0", 36 | "jasmine-spec-reporter": "^7.0.0", 37 | "nyc": "^15.1.0", 38 | "rewire": "^6.0.0", 39 | "tmp": "^0.2.3" 40 | }, 41 | "nyc": { 42 | "all": true, 43 | "exclude": [ 44 | "coverage/", 45 | "spec/" 46 | ], 47 | "reporter": [ 48 | "lcov", 49 | "text" 50 | ] 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /spec/ActionStack.spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | const path = require('path'); 20 | const ActionStack = require('../src/ActionStack'); 21 | const android_one_project = path.join(__dirname, '..', 'projects', 'android_one'); 22 | 23 | describe('action-stack', function () { 24 | let stack; 25 | beforeEach(function () { 26 | stack = new ActionStack(); 27 | }); 28 | describe('processing of actions', function () { 29 | it('Test 001 : should process actions one at a time until all are done', function () { 30 | const first_spy = jasmine.createSpy(); 31 | const first_args = [1]; 32 | const second_spy = jasmine.createSpy(); 33 | const second_args = [2]; 34 | const third_spy = jasmine.createSpy(); 35 | const third_args = [3]; 36 | stack.push(stack.createAction(first_spy, first_args, function () {}, [])); 37 | stack.push(stack.createAction(second_spy, second_args, function () {}, [])); 38 | stack.push(stack.createAction(third_spy, third_args, function () {}, [])); 39 | stack.process('android', android_one_project); 40 | expect(first_spy).toHaveBeenCalledWith(first_args[0]); 41 | expect(second_spy).toHaveBeenCalledWith(second_args[0]); 42 | expect(third_spy).toHaveBeenCalledWith(third_args[0]); 43 | }); 44 | it('Test 002 : should revert processed actions if an exception occurs', function () { 45 | spyOn(console, 'log'); 46 | const first_spy = jasmine.createSpy(); 47 | const first_args = [1]; 48 | const first_reverter = jasmine.createSpy(); 49 | const first_reverter_args = [true]; 50 | const process_err = new Error('process_err'); 51 | const second_spy = jasmine.createSpy().and.callFake(function () { 52 | throw process_err; 53 | }); 54 | const second_args = [2]; 55 | const third_spy = jasmine.createSpy(); 56 | const third_args = [3]; 57 | stack.push(stack.createAction(first_spy, first_args, first_reverter, first_reverter_args)); 58 | stack.push(stack.createAction(second_spy, second_args, function () {}, [])); 59 | stack.push(stack.createAction(third_spy, third_args, function () {}, [])); 60 | 61 | // process should throw 62 | return stack.process('android', android_one_project) 63 | .then(() => { 64 | fail('Expected promise to be rejected'); 65 | }, error => { 66 | expect(error).toEqual(process_err); 67 | // first two actions should have been called, but not the third 68 | expect(first_spy).toHaveBeenCalledWith(first_args[0]); 69 | expect(second_spy).toHaveBeenCalledWith(second_args[0]); 70 | expect(third_spy).not.toHaveBeenCalledWith(third_args[0]); 71 | // first reverter should have been called after second action exploded 72 | expect(first_reverter).toHaveBeenCalledWith(first_reverter_args[0]); 73 | }); 74 | }); 75 | }); 76 | }); 77 | -------------------------------------------------------------------------------- /spec/ConfigChanges/ConfigFile.spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | Unless required by applicable law or agreed to in writing, 11 | software distributed under the License is distributed on an 12 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 13 | KIND, either express or implied. See the License for the 14 | specific language governing permissions and limitations 15 | under the License. 16 | */ 17 | 18 | const rewire = require('rewire'); 19 | const fs = require('node:fs'); 20 | const path = require('node:path'); 21 | const readChunk = require('read-chunk'); 22 | 23 | describe('ConfigFile tests', function () { 24 | let ConfigFile; 25 | beforeEach(() => { 26 | ConfigFile = rewire('../../src/ConfigChanges/ConfigFile'); 27 | }); 28 | 29 | describe('instance methods', () => { 30 | describe('save', () => { 31 | it('calls fs.writeFileSync', function () { 32 | spyOn(fs, 'writeFileSync'); 33 | ConfigFile.prototype.save(); 34 | expect(fs.writeFileSync).toHaveBeenCalled(); 35 | }); 36 | }); 37 | }); 38 | 39 | describe('static methods', () => { 40 | describe('isBinaryPlist', () => { 41 | it('should return false if not binary', function () { 42 | spyOn(readChunk, 'sync').and.returnValue('not bplist'); 43 | expect(ConfigFile.isBinaryPlist('someFile')).toBe(false); 44 | }); 45 | 46 | it('should return true if binary', function () { 47 | spyOn(readChunk, 'sync').and.returnValue('bplist'); 48 | expect(ConfigFile.isBinaryPlist('someFile')).toBe(true); 49 | }); 50 | }); 51 | 52 | describe('getIOSProjectname', () => { 53 | it('should throw error', function () { 54 | expect(function () { ConfigFile.getIOSProjectname('some/project/name'); }).toThrow(); 55 | }); 56 | }); 57 | 58 | describe('resolveConfigFilePath', () => { 59 | const projectDir = path.join('project_dir', 'app', 'src', 'main'); 60 | 61 | it('should return file path', function () { 62 | const filePath = path.join('project_dir', 'file'); 63 | expect(ConfigFile.resolveConfigFilePath('project_dir', 'platform', 'file')).toBe(filePath); 64 | }); 65 | 66 | it('should return AndroidManifest.xml file path', function () { 67 | const androidManifestPath = path.join(projectDir, 'AndroidManifest.xml'); 68 | expect(ConfigFile.resolveConfigFilePath('project_dir', 'android', 'AndroidManifest.xml')).toBe(androidManifestPath); 69 | }); 70 | 71 | it('should return android config.xml file path', function () { 72 | const configPath = path.join(projectDir, 'res', 'xml', 'config.xml'); 73 | expect(ConfigFile.resolveConfigFilePath('project_dir', 'android', 'config.xml')).toBe(configPath); 74 | }); 75 | 76 | it('should return android strings.xml file path', function () { 77 | const stringsPath = path.join(projectDir, 'res', 'values', 'strings.xml'); 78 | expect(ConfigFile.resolveConfigFilePath('project_dir', 'android', 'strings.xml')).toBe(stringsPath); 79 | }); 80 | 81 | it('should return ios config.xml file path', function () { 82 | spyOn(ConfigFile, 'getIOSProjectname').and.returnValue('iospath'); 83 | const configPath = path.join('project_dir', 'iospath', 'config.xml'); 84 | expect(ConfigFile.resolveConfigFilePath('project_dir', 'ios', 'config.xml')).toBe(configPath); 85 | }); 86 | 87 | it('should return osx config.xml file path', function () { 88 | spyOn(ConfigFile, 'getIOSProjectname').and.returnValue('osxpath'); 89 | const configPath = path.join('project_dir', 'osxpath', 'config.xml'); 90 | expect(ConfigFile.resolveConfigFilePath('project_dir', 'osx', 'config.xml')).toBe(configPath); 91 | }); 92 | 93 | it('should return android resource file path when path is normalized', function () { 94 | const file = path.join('res', 'xml'); 95 | const configPath = path.join('project_dir', 'app', 'src', 'main', file, 'xml'); 96 | expect(ConfigFile.resolveConfigFilePath('project_dir', 'android', file)).toBe(configPath); 97 | }); 98 | 99 | it('should return android resource file path when path is not normalized', function () { 100 | const file = 'res/xml'; 101 | const configPath = path.join('project_dir', 'app', 'src', 'main', file, 'xml'); 102 | expect(ConfigFile.resolveConfigFilePath('project_dir', 'android', file)).toBe(configPath); 103 | }); 104 | 105 | it('should return *-Info.plist file', function () { 106 | const projName = 'XXX'; 107 | const expectedPlistPath = `${projName}${path.sep}${projName}-Info.plist`; 108 | 109 | ConfigFile.__set__('getIOSProjectname', () => projName); 110 | spyOn(require('fast-glob'), 'sync').and.returnValue([ 111 | `AAA/${projName}-Info.plist`, 112 | `Pods/Target Support Files/Pods-${projName}/Info.plist`, 113 | `Pods/Target Support Files/Pods-${projName}/Pods-${projName}-Info.plist`, 114 | expectedPlistPath 115 | ]); 116 | 117 | expect(ConfigFile.resolveConfigFilePath('', 'ios', '*-Info.plist')).toBe(expectedPlistPath); 118 | }); 119 | }); 120 | }); 121 | }); 122 | -------------------------------------------------------------------------------- /spec/CordovaCheck.spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | const fs = require('node:fs'); 21 | const path = require('node:path'); 22 | const CordovaCheck = require('../src/CordovaCheck'); 23 | 24 | const cwd = process.cwd(); 25 | const home = process.env[(process.platform === 'win32') ? 'USERPROFILE' : 'HOME']; 26 | const origPWD = process.env.PWD; 27 | const testDirName = 'cordova-common-test-somedir'; 28 | 29 | function touchFile (filepath) { 30 | fs.mkdirSync(path.dirname(filepath), { recursive: true }); 31 | fs.writeFileSync(filepath, ''); 32 | } 33 | 34 | describe('findProjectRoot method', function () { 35 | afterEach(function () { 36 | process.env.PWD = origPWD; 37 | process.chdir(cwd); 38 | 39 | const somedir = path.join(home, testDirName); 40 | fs.rmSync(somedir, { recursive: true, force: true }); 41 | }); 42 | 43 | it('Test 001 : should return false if it hits the home directory', function () { 44 | const somedir = path.join(home, testDirName); 45 | fs.mkdirSync(somedir, { recursive: true }); 46 | expect(CordovaCheck.findProjectRoot(somedir)).toEqual(false); 47 | }); 48 | it('Test 002 : should return false if it cannot find a .cordova directory up the directory tree', function () { 49 | const somedir = path.join(home, '..'); 50 | expect(CordovaCheck.findProjectRoot(somedir)).toEqual(false); 51 | }); 52 | it('Test 003 : should return the first directory it finds with a .cordova folder in it', function () { 53 | const somedir = path.join(home, testDirName); 54 | const anotherdir = path.join(somedir, 'anotherdir'); 55 | fs.mkdirSync(anotherdir, { recursive: true }); 56 | touchFile(path.join(somedir, 'www', 'config.xml')); 57 | expect(CordovaCheck.findProjectRoot(somedir)).toEqual(somedir); 58 | }); 59 | it('Test 004 : should ignore PWD when its undefined', function () { 60 | delete process.env.PWD; 61 | const somedir = path.join(home, testDirName); 62 | const anotherdir = path.join(somedir, 'anotherdir'); 63 | fs.mkdirSync(anotherdir, { recursive: true }); 64 | fs.mkdirSync(path.join(somedir, 'www'), { recursive: true }); 65 | touchFile(path.join(somedir, 'config.xml')); 66 | process.chdir(anotherdir); 67 | expect(CordovaCheck.findProjectRoot()).toEqual(somedir); 68 | }); 69 | it('Test 005 : should use PWD when available', function () { 70 | const somedir = path.join(home, testDirName); 71 | const anotherdir = path.join(somedir, 'anotherdir'); 72 | fs.mkdirSync(anotherdir, { recursive: true }); 73 | touchFile(path.join(somedir, 'www', 'config.xml')); 74 | process.env.PWD = anotherdir; 75 | process.chdir(path.sep); 76 | expect(CordovaCheck.findProjectRoot()).toEqual(somedir); 77 | }); 78 | it('Test 006 : should use cwd as a fallback when PWD is not a cordova dir', function () { 79 | const somedir = path.join(home, testDirName); 80 | const anotherdir = path.join(somedir, 'anotherdir'); 81 | fs.mkdirSync(anotherdir, { recursive: true }); 82 | touchFile(path.join(somedir, 'www', 'config.xml')); 83 | process.env.PWD = path.sep; 84 | process.chdir(anotherdir); 85 | expect(CordovaCheck.findProjectRoot()).toEqual(somedir); 86 | }); 87 | it('Test 007 : should ignore platform www/config.xml', function () { 88 | const somedir = path.join(home, testDirName); 89 | const anotherdir = path.join(somedir, 'anotherdir'); 90 | touchFile(path.join(anotherdir, 'www', 'config.xml')); 91 | fs.mkdirSync(path.join(somedir, 'www'), { recursive: true }); 92 | touchFile(path.join(somedir, 'config.xml')); 93 | expect(CordovaCheck.findProjectRoot(anotherdir)).toEqual(somedir); 94 | }); 95 | }); 96 | -------------------------------------------------------------------------------- /spec/CordovaError/CordovaError.spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | const endent = require('endent').default; 21 | const CordovaError = require('../../src/CordovaError'); 22 | 23 | describe('CordovaError class', () => { 24 | let error; 25 | 26 | beforeEach(() => { 27 | error = new CordovaError('error'); 28 | }); 29 | 30 | it('should be an error', () => { 31 | expect(error).toEqual(jasmine.any(Error)); 32 | }); 33 | 34 | it('should have a name property', () => { 35 | expect(error.name).toEqual('CordovaError'); 36 | }); 37 | 38 | it('should have a working toString method', () => { 39 | expect(error.toString()).toEqual('CordovaError: error'); 40 | }); 41 | 42 | describe('given a cause', () => { 43 | let cause; 44 | 45 | beforeEach(() => { 46 | cause = new Error('cause'); 47 | error = new CordovaError('error', cause); 48 | }); 49 | 50 | it('should save it', () => { 51 | expect(error.cause()).toBe(cause); 52 | expect(CordovaError.cause(error)).toBe(cause); 53 | }); 54 | 55 | it('should include the cause in toString result', () => { 56 | const stringifiedError = 'CordovaError: error: cause'; 57 | expect(String(error)).toEqual(stringifiedError); 58 | expect(error.toString()).toEqual(stringifiedError); 59 | }); 60 | 61 | it('should include the cause stack in CordovaError.fullStack', () => { 62 | cause.stack = 'CAUSE_STACK'; 63 | error.stack = 'ERROR_STACK'; 64 | 65 | expect(CordovaError.fullStack(error)).toEqual(endent` 66 | ERROR_STACK 67 | caused by: CAUSE_STACK 68 | `); 69 | }); 70 | }); 71 | 72 | describe('given options', () => { 73 | it('should apply name option', () => { 74 | const name = 'FooError'; 75 | error = new CordovaError('error', { name }); 76 | 77 | expect(error.name).toEqual(name); 78 | }); 79 | 80 | it('should apply cause option', () => { 81 | const cause = new Error('cause'); 82 | error = new CordovaError('error', { cause }); 83 | 84 | expect(CordovaError.cause(error)).toBe(cause); 85 | }); 86 | 87 | it('should apply info option', () => { 88 | const info = { foo: 'bar' }; 89 | error = new CordovaError('error', { info }); 90 | 91 | expect(CordovaError.info(error)).toEqual(info); 92 | }); 93 | }); 94 | }); 95 | -------------------------------------------------------------------------------- /spec/CordovaLogger.spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | const CordovaError = require('../src/CordovaError'); 21 | const CordovaLogger = require('../src/CordovaLogger'); 22 | const EventEmitter = require('events').EventEmitter; 23 | 24 | const DEFAULT_LEVELS = ['verbose', 'normal', 'warn', 'info', 'error', 'results']; 25 | 26 | describe('CordovaLogger class', function () { 27 | it('Test 001 : should be constructable', function () { 28 | expect(new CordovaLogger()).toEqual(jasmine.any(CordovaLogger)); 29 | }); 30 | 31 | it('Test 002 : should expose default levels as constants', function () { 32 | DEFAULT_LEVELS.forEach(function (level) { 33 | const constant = level.toUpperCase(); 34 | expect(CordovaLogger[constant]).toBeDefined(); 35 | expect(CordovaLogger[constant]).toBe(level); 36 | }); 37 | }); 38 | 39 | it('Test 003 : should return the same instance via "get" method', function () { 40 | expect(CordovaLogger.get()).toBeDefined(); 41 | expect(CordovaLogger.get()).toBe(CordovaLogger.get()); 42 | expect(CordovaLogger.get()).toEqual(jasmine.any(CordovaLogger)); 43 | }); 44 | 45 | describe('instance', function () { 46 | let logger; 47 | 48 | beforeEach(function () { 49 | logger = new CordovaLogger(); 50 | }); 51 | 52 | it('Test 004 : should have defaults levels', function () { 53 | DEFAULT_LEVELS.forEach(function (level) { 54 | expect(logger.levels[level]).toBeDefined(); 55 | expect(logger.levels[level]).toEqual(jasmine.any(Number)); 56 | expect(logger[level]).toBeDefined(); 57 | expect(logger[level]).toEqual(jasmine.any(Function)); 58 | expect(logger[level].length).toBe(1); 59 | }); 60 | }); 61 | 62 | describe('addLevel method', function () { 63 | it('Test 005 : should add a new level and a corresponding shortcut method', function () { 64 | spyOn(logger, 'log'); 65 | logger.addLevel('debug', 100000, 'grey'); 66 | expect(logger.levels.debug).toBe(100000); 67 | expect(logger.debug).toEqual(jasmine.any(Function)); 68 | 69 | logger.debug('debug message'); 70 | expect(logger.log).toHaveBeenCalledWith('debug', 'debug message'); 71 | }); 72 | 73 | it('Test 006 : should not add a shortcut method fi the property with the same name already exists', function () { 74 | const logMethod = logger.log; 75 | logger.addLevel('log', 500); 76 | expect(logger.log).toBe(logMethod); // "log" method remains unchanged 77 | }); 78 | }); 79 | 80 | describe('setLevel method', function () { 81 | it('Test 007 : should set logger\'s level to \'NORMAL\' if provided level does not exist', function () { 82 | logger.setLevel('debug'); 83 | expect(logger.logLevel).toBe(CordovaLogger.NORMAL); // default value 84 | }); 85 | }); 86 | 87 | describe('subscribe method', function () { 88 | it('Test 008 : should throw if called without EventEmitter instance', function () { 89 | expect(function () { logger.subscribe(); }).toThrow(); 90 | expect(function () { logger.subscribe(123); }).toThrow(); 91 | }); 92 | 93 | it('Test 009 : should attach corresponding listeners to supplied emitter', function () { 94 | const eventNamesExclusions = { 95 | log: 'normal', 96 | warning: 'warn' 97 | }; 98 | 99 | const listenerSpy = jasmine.createSpy('listenerSpy') 100 | .and.callFake(function (eventName) { 101 | eventName = eventNamesExclusions[eventName] || eventName; 102 | expect(logger.levels[eventName]).toBeDefined(); 103 | }); 104 | 105 | const emitter = new EventEmitter().on('newListener', listenerSpy); 106 | logger.subscribe(emitter); 107 | }); 108 | }); 109 | 110 | describe('log method', function () { 111 | function CursorSpy (name) { 112 | const cursorMethods = ['reset', 'write']; 113 | const spy = jasmine.createSpyObj(name, cursorMethods); 114 | 115 | // Make spy methods chainable, as original Cursor acts 116 | cursorMethods.forEach(function (method) { spy[method].and.returnValue(spy); }); 117 | 118 | return spy; 119 | } 120 | 121 | beforeEach(function () { 122 | // Empty colors table to make it easier to mock 123 | logger.colors = {}; 124 | logger.stdoutCursor = new CursorSpy('stdoutCursor'); 125 | logger.stderrCursor = new CursorSpy('stderrCursor'); 126 | }); 127 | 128 | it('Test 010 : should ignore message if severity is less than logger\'s level', function () { 129 | logger.setLevel('error').log('verbose', 'some_messgge'); 130 | expect(logger.stdoutCursor.write).not.toHaveBeenCalled(); 131 | expect(logger.stderrCursor.write).not.toHaveBeenCalled(); 132 | }); 133 | 134 | it('Test 011 : should log everything except error messages to stdout', function () { 135 | logger.setLevel('verbose'); 136 | DEFAULT_LEVELS.forEach(function (level) { 137 | logger.log(level, 'message'); 138 | }); 139 | 140 | // Multiply calls number to 2 because 'write' method is get called twice (with message and EOL) 141 | expect(logger.stdoutCursor.write.calls.count()).toBe((DEFAULT_LEVELS.length - 1) * 2); 142 | expect(logger.stderrCursor.write.calls.count()).toBe(1 * 2); 143 | }); 144 | 145 | it('Test 012 : should log Error objects to stderr despite of loglevel', function () { 146 | logger.setLevel('verbose').log('verbose', new Error()); 147 | expect(logger.stdoutCursor.write).not.toHaveBeenCalled(); 148 | expect(logger.stderrCursor.write).toHaveBeenCalled(); 149 | }); 150 | 151 | it('Test 013 : should handle CordovaError instances separately from Error ones', function () { 152 | const errorMock = new CordovaError(); 153 | spyOn(errorMock, 'toString').and.returnValue('error_message'); 154 | 155 | logger.setLevel('verbose').log('verbose', errorMock); 156 | expect(errorMock.toString).toHaveBeenCalled(); 157 | expect(logger.stderrCursor.write.calls.argsFor(0)).toMatch('Error: error_message'); 158 | }); 159 | }); 160 | 161 | describe('adjustLevel method', function () { 162 | it('Test 014 : should properly adjust log level', function () { 163 | const resetLogLevel = function () { 164 | logger.setLevel('normal'); 165 | }; 166 | 167 | resetLogLevel(); 168 | expect(logger.adjustLevel({ verbose: true }).logLevel).toEqual('verbose'); 169 | 170 | resetLogLevel(); 171 | expect(logger.adjustLevel(['--verbose']).logLevel).toEqual('verbose'); 172 | 173 | resetLogLevel(); 174 | expect(logger.adjustLevel({ silent: true }).logLevel).toEqual('error'); 175 | 176 | resetLogLevel(); 177 | expect(logger.adjustLevel(['--silent']).logLevel).toEqual('error'); 178 | 179 | resetLogLevel(); 180 | expect(logger.adjustLevel({ verbose: true, silent: true }).logLevel).toEqual('verbose'); 181 | 182 | resetLogLevel(); 183 | expect(logger.adjustLevel(['--verbose', '--silent']).logLevel).toEqual('verbose'); 184 | 185 | resetLogLevel(); 186 | }); 187 | }); 188 | }); 189 | }); 190 | -------------------------------------------------------------------------------- /spec/PluginInfo/PluginInfo.spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | const PluginInfo = require('../../src/PluginInfo/PluginInfo'); 21 | const path = require('path'); 22 | const pluginsDir = path.join(__dirname, '../fixtures/plugins'); 23 | const pluginPassthrough = new PluginInfo(path.join(pluginsDir, 'org.test.xmlpassthrough')); 24 | 25 | describe('PluginInfo', function () { 26 | it('Test 001 : should read a plugin.xml file', function () { 27 | let p; 28 | expect(function () { 29 | p = new PluginInfo(path.join(pluginsDir, 'ChildBrowser')); 30 | }).not.toThrow(); 31 | 32 | expect(p).toBeDefined(); 33 | expect(p.name).toEqual('Child Browser'); 34 | 35 | expect(p.getInfo('android').length).toBe(2); 36 | expect(p.getAssets('android').length).toBe(2); 37 | expect(p.getConfigFiles('android').length).toBe(4); 38 | expect(p.getSourceFiles('android').length).toBe(1); 39 | expect(p.getPreferences('android')).toEqual({}); 40 | expect(p.getDependencies('android')).toEqual([]); 41 | expect(p.getHeaderFiles('android')).toEqual([]); 42 | expect(p.getLibFiles('android')).toEqual([]); 43 | expect(p.getFrameworks('android')).toEqual([]); 44 | expect(p.getResourceFiles('android')).toEqual([]); 45 | }); 46 | 47 | it('Test 002 : should throw when there is no plugin.xml file', function () { 48 | expect(() => new PluginInfo('/non/existent/dir')).toThrow(); 49 | }); 50 | 51 | describe('Framework', () => { 52 | it('Test 003: replace framework src', function () { 53 | const p = new PluginInfo(path.join(pluginsDir, 'org.test.src')); 54 | const result = p.getFrameworks('android', { cli_variables: { FCM_VERSION: '9.0.0' } }); 55 | expect(result[2].src).toBe('com.google.firebase:firebase-messaging:9.0.0'); 56 | }); 57 | 58 | it('Test 004: framework src uses default variable', function () { 59 | const p = new PluginInfo(path.join(pluginsDir, 'org.test.src')); 60 | const result = p.getFrameworks('android', {}); 61 | expect(result[2].src).toBe('com.google.firebase:firebase-messaging:11.0.1'); 62 | }); 63 | 64 | it('Test 006: framework supports xml passthrough', function () { 65 | const frameworks = pluginPassthrough.getFrameworks('android', {}); 66 | expect(frameworks.length).toBe(1); 67 | expect(frameworks[0].anattrib).toBe('value'); 68 | }); 69 | }); 70 | 71 | describe('Podspec', () => { 72 | it('Test 005: read podspec', function () { 73 | const p = new PluginInfo(path.join(pluginsDir, 'org.test.plugins.withcocoapods')); 74 | const result = p.getPodSpecs('ios'); 75 | expect(result.length).toBe(1); 76 | const podSpec = result[0]; 77 | expect(Object.keys(podSpec.declarations).length).toBe(2); 78 | expect(Object.keys(podSpec.sources).length).toBe(1); 79 | expect(Object.keys(podSpec.libraries).length).toBe(4); 80 | expect(podSpec.declarations['use-frameworks']).toBe('true'); 81 | expect(podSpec.sources['https://github.com/CocoaPods/Specs.git'].source).toBe('https://github.com/CocoaPods/Specs.git'); 82 | expect(podSpec.libraries.AFNetworking.spec).toBe('~> 3.2'); 83 | expect(podSpec.libraries.Eureka['swift-version']).toBe('4.1'); 84 | }); 85 | }); 86 | 87 | describe('Asset', () => { 88 | it('Test 008: Asset supports xml passthrough', function () { 89 | const assets = pluginPassthrough.getAssets('android'); 90 | expect(assets.length).toBe(1); 91 | expect(assets[0].anattrib).toBe('value'); 92 | }); 93 | }); 94 | 95 | describe('Dependency', () => { 96 | it('Test 009: Dependency supports xml passthrough', function () { 97 | const dependencies = pluginPassthrough.getDependencies('android'); 98 | expect(dependencies.length).toBe(1); 99 | expect(dependencies[0].anattrib).toBe('value'); 100 | }); 101 | }); 102 | 103 | describe('Config File', () => { 104 | it('Test 010: config-file supports xml passthrough', function () { 105 | const configFiles = pluginPassthrough.getConfigFiles('android'); 106 | expect(configFiles.length).toBe(1); 107 | expect(configFiles[0].anattrib).toBe('value'); 108 | }); 109 | }); 110 | 111 | describe('Edit Config', () => { 112 | it('Test 011: edit-config supports xml passthrough', function () { 113 | const editConfigs = pluginPassthrough.getEditConfigs('android'); 114 | expect(editConfigs.length).toBe(1); 115 | expect(editConfigs[0].anattrib).toBe('value'); 116 | }); 117 | }); 118 | 119 | describe('Source File', () => { 120 | it('Test 012: source-file supports xml passthrough', function () { 121 | const sourceFiles = pluginPassthrough.getSourceFiles('android'); 122 | expect(sourceFiles.length).toBe(1); 123 | expect(sourceFiles[0].anattrib).toBe('value'); 124 | }); 125 | }); 126 | 127 | describe('Header File', () => { 128 | it('Test 013: header-file supports xml passthrough', function () { 129 | const headerFiles = pluginPassthrough.getHeaderFiles('android'); 130 | expect(headerFiles.length).toBe(1); 131 | expect(headerFiles[0].anattrib).toBe('value'); 132 | }); 133 | }); 134 | 135 | describe('Resource File', () => { 136 | it('Test 014: resource-file supports xml passthrough', function () { 137 | const resourceFiles = pluginPassthrough.getResourceFiles('android'); 138 | expect(resourceFiles.length).toBe(1); 139 | expect(resourceFiles[0].anattrib).toBe('value'); 140 | }); 141 | }); 142 | 143 | describe('Lib File', () => { 144 | it('Test 015: lib-file supports xml passthrough', function () { 145 | const libFiles = pluginPassthrough.getLibFiles('android'); 146 | expect(libFiles.length).toBe(1); 147 | expect(libFiles[0].anattrib).toBe('value'); 148 | }); 149 | }); 150 | 151 | describe('Hook', () => { 152 | it('Test 016: hook supports xml passthrough', function () { 153 | const hooks = pluginPassthrough.getHookScripts('hi', 'android'); 154 | expect(hooks.length).toBe(1); 155 | expect(hooks[0].attrib.anattrib).toBe('value'); 156 | }); 157 | }); 158 | 159 | describe('JS Module', () => { 160 | it('Test 017: js-modules supports xml passthrough', function () { 161 | const modules = pluginPassthrough.getJsModules('android'); 162 | expect(modules.length).toBe(1); 163 | expect(modules[0].anattrib).toBe('value'); 164 | }); 165 | }); 166 | 167 | describe('Engine', () => { 168 | it('Test 018: engine supports xml passthrough', function () { 169 | const engines = pluginPassthrough.getEngines('android'); 170 | expect(engines.length).toBe(1); 171 | expect(engines[0].anattrib).toBe('value'); 172 | }); 173 | }); 174 | 175 | describe('Platform', () => { 176 | it('Test 019: platform supports xml passthrough', function () { 177 | const platforms = pluginPassthrough.getPlatforms(); 178 | expect(platforms.length).toBe(1); 179 | expect(platforms[0].anattrib).toBe('value'); 180 | }); 181 | }); 182 | }); 183 | -------------------------------------------------------------------------------- /spec/PluginInfo/PluginInfoProvider.spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | const PluginInfoProvider = require('../../src/PluginInfo/PluginInfoProvider'); 21 | const path = require('path'); 22 | 23 | const pluginsDir = path.join(__dirname, '../fixtures/plugins'); 24 | 25 | describe('PluginInfoProvider', function () { 26 | describe('getAllWithinSearchPath', function () { 27 | it('Test 001 : should load all plugins in a dir', function () { 28 | const pluginInfoProvider = new PluginInfoProvider(); 29 | const plugins = pluginInfoProvider.getAllWithinSearchPath(pluginsDir); 30 | expect(plugins.length).not.toBe(0); 31 | expect(plugins).toContain(jasmine.objectContaining({ 32 | id: 'org.test.scoped', 33 | dir: path.join(pluginsDir, '@scope/test') 34 | })); 35 | }); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /spec/PluginManager.spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | const fs = require('node:fs'); 21 | const path = require('node:path'); 22 | const rewire = require('rewire'); 23 | const PluginInfo = require('../src/PluginInfo/PluginInfo'); 24 | const ConfigChanges = require('../src/ConfigChanges/ConfigChanges'); 25 | 26 | const PluginManager = rewire('../src/PluginManager'); 27 | 28 | const DUMMY_PLUGIN = path.join(__dirname, 'fixtures/plugins/org.test.plugins.dummyplugin'); 29 | const FAKE_PLATFORM = 'cordova-atari'; 30 | const FAKE_LOCATIONS = { 31 | root: '/some/fake/path', 32 | platformWww: '/some/fake/path/platform_www', 33 | www: '/some/www/dir' 34 | }; 35 | 36 | describe('PluginManager class', function () { 37 | beforeEach(function () { 38 | spyOn(ConfigChanges, 'PlatformMunger'); 39 | spyOn(fs, 'writeFileSync'); 40 | spyOn(fs, 'mkdirSync'); 41 | }); 42 | 43 | it('Test 001 : should be constructable', function () { 44 | expect(new PluginManager(FAKE_PLATFORM, FAKE_LOCATIONS)).toEqual(jasmine.any(PluginManager)); 45 | }); 46 | 47 | it('Test 002 : should return new instance for every PluginManager.get call', function () { 48 | expect(PluginManager.get(FAKE_PLATFORM, FAKE_LOCATIONS)).toEqual(jasmine.any(PluginManager)); 49 | expect(PluginManager.get(FAKE_PLATFORM, FAKE_LOCATIONS)) 50 | .not.toBe(PluginManager.get(FAKE_PLATFORM, FAKE_LOCATIONS)); 51 | }); 52 | 53 | describe('instance', function () { 54 | let actions, manager; 55 | let FAKE_PROJECT; 56 | const ActionStackOrig = PluginManager.__get__('ActionStack'); 57 | 58 | beforeEach(function () { 59 | FAKE_PROJECT = jasmine.createSpyObj('project', ['getInstaller', 'getUninstaller', 'write']); 60 | manager = new PluginManager('windows', FAKE_LOCATIONS, FAKE_PROJECT); 61 | actions = jasmine.createSpyObj('actions', ['createAction', 'push', 'process']); 62 | actions.process.and.returnValue(Promise.resolve()); 63 | PluginManager.__set__('ActionStack', function () { return actions; }); 64 | }); 65 | 66 | afterEach(function () { 67 | PluginManager.__set__('ActionStack', ActionStackOrig); 68 | }); 69 | 70 | describe('addPlugin method', function () { 71 | it('should return a promise', function () { 72 | return expectAsync(manager.addPlugin(new PluginInfo(DUMMY_PLUGIN), {})).toBeResolved(); 73 | }); 74 | 75 | it('Test 003 : should reject if "plugin" parameter is not specified or not a PluginInfo instance', () => { 76 | return Promise.resolve() 77 | .then(() => expectAsync(manager.addPlugin(null, {})).toBeRejected()) 78 | .then(() => expectAsync(manager.addPlugin({}, {})).toBeRejected()); 79 | }); 80 | 81 | it('Test 004 : should iterate through all plugin\'s files and frameworks', () => { 82 | return manager.addPlugin(new PluginInfo(DUMMY_PLUGIN), {}) 83 | .then(function () { 84 | expect(FAKE_PROJECT.getInstaller.calls.count()).toBe(16); 85 | expect(FAKE_PROJECT.getUninstaller.calls.count()).toBe(16); 86 | 87 | expect(actions.push.calls.count()).toBe(16); 88 | expect(actions.process).toHaveBeenCalled(); 89 | expect(FAKE_PROJECT.write).toHaveBeenCalled(); 90 | }); 91 | }); 92 | 93 | it('Test 005 : should save plugin metadata to www directory', () => { 94 | const metadataPath = path.join(manager.locations.www, 'cordova_plugins.js'); 95 | const platformWwwMetadataPath = path.join(manager.locations.platformWww, 'cordova_plugins.js'); 96 | 97 | return manager.addPlugin(new PluginInfo(DUMMY_PLUGIN), {}) 98 | .then(function () { 99 | expect(fs.writeFileSync).toHaveBeenCalledWith(metadataPath, jasmine.any(String), 'utf8'); 100 | expect(fs.writeFileSync).not.toHaveBeenCalledWith(platformWwwMetadataPath, jasmine.any(String), 'utf8'); 101 | }); 102 | }); 103 | 104 | it('Test 006 : should save plugin metadata to both www ans platform_www directories when options.usePlatformWww is specified', () => { 105 | const metadataPath = path.join(manager.locations.www, 'cordova_plugins.js'); 106 | const platformWwwMetadataPath = path.join(manager.locations.platformWww, 'cordova_plugins.js'); 107 | 108 | return manager.addPlugin(new PluginInfo(DUMMY_PLUGIN), { usePlatformWww: true }) 109 | .then(function () { 110 | expect(fs.writeFileSync).toHaveBeenCalledWith(metadataPath, jasmine.any(String), 'utf8'); 111 | expect(fs.writeFileSync).toHaveBeenCalledWith(platformWwwMetadataPath, jasmine.any(String), 'utf8'); 112 | }); 113 | }); 114 | }); 115 | }); 116 | }); 117 | -------------------------------------------------------------------------------- /spec/events.spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | const events = require('../src/events'); 21 | 22 | describe('Cordova events', function () { 23 | describe('emit method', function () { 24 | it('Test 001 : should emit events to a listener', function () { 25 | const logSpy = jasmine.createSpy('logSpy'); 26 | events.on('log', logSpy); 27 | 28 | events.emit('log', 'a test message'); 29 | expect(logSpy).toHaveBeenCalledWith('a test message'); 30 | }); 31 | 32 | it('Test 002 : should report if there were any listeners or not', function () { 33 | const r1 = events.emit('myname', 'first'); 34 | expect(r1).toBe(false); 35 | 36 | const listenerSpy = jasmine.createSpy('listenerSpy'); 37 | events.on('myname', listenerSpy); 38 | const r2 = events.emit('myname', 'second'); 39 | expect(r2).toBe(true); 40 | expect(listenerSpy).toHaveBeenCalled(); 41 | }); 42 | }); 43 | 44 | describe('forwardEventsTo method', function () { 45 | afterEach(function () { 46 | events.forwardEventsTo(null); 47 | }); 48 | 49 | it('Test 003 : should forward events to another event emitter', function () { 50 | const EventEmitter = require('events').EventEmitter; 51 | const anotherEventEmitter = new EventEmitter(); 52 | const logSpy = jasmine.createSpy('logSpy'); 53 | anotherEventEmitter.on('log', logSpy); 54 | 55 | events.forwardEventsTo(anotherEventEmitter); 56 | events.emit('log', 'forwarding test message'); 57 | expect(logSpy).toHaveBeenCalledWith('forwarding test message'); 58 | }); 59 | 60 | it('Test 004 : should not go to infinite loop when trying to forward to self', function () { 61 | expect(function () { 62 | events.forwardEventsTo(events); 63 | events.emit('log', 'test message'); 64 | }).not.toThrow(); 65 | }); 66 | 67 | it('Test 005 : should reset forwarding after trying to forward to self', function () { 68 | const EventEmitter = require('events').EventEmitter; 69 | const anotherEventEmitter = new EventEmitter(); 70 | const logSpy = jasmine.createSpy('logSpy'); 71 | anotherEventEmitter.on('log', logSpy); 72 | 73 | // should forward events to another event emitter at this point 74 | events.forwardEventsTo(anotherEventEmitter); 75 | events.emit('log', 'test message #1'); 76 | expect(logSpy).toHaveBeenCalledWith('test message #1'); 77 | 78 | logSpy.calls.reset(); 79 | 80 | // should *not* forward events to another event emitter at this point 81 | events.forwardEventsTo(events); 82 | events.emit('log', 'test message #2'); 83 | expect(logSpy).not.toHaveBeenCalled(); 84 | }); 85 | }); 86 | }); 87 | -------------------------------------------------------------------------------- /spec/fixtures/echo-args.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | for %%x in (%*) do ( 4 | echo %%~x 5 | ) 6 | -------------------------------------------------------------------------------- /spec/fixtures/plugins/@scope/test/plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /spec/fixtures/plugins/ChildBrowser/plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 20 | 21 | 25 | 26 | Child Browser 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | No matter what platform you are installing to, this notice is very important. 41 | 42 | 43 | 44 | 45 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 56 | 57 | 58 | 59 | 60 | 62 | 63 | 64 | 66 | Please make sure you read this because it is very important to complete the installation of your plugin. 67 | 68 | 69 | 70 | 71 | 73 | 74 | 75 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | $APP_ID 84 | 85 | 86 | 87 | 88 | 89 | PackageName 90 | $PACKAGE_NAME 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 116 | 117 | 118 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | -------------------------------------------------------------------------------- /spec/fixtures/plugins/ChildBrowser/src/android/ChildBrowser.java: -------------------------------------------------------------------------------- 1 | /* Licensed to the Apache Software Foundation (ASF) under one 2 | or more contributor license agreements. See the NOTICE file 3 | distributed with this work for additional information 4 | regarding copyright ownership. The ASF licenses this file 5 | to you under the Apache License, Version 2.0 (the 6 | "License"); you may not use this file except in compliance 7 | with the License. You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, 12 | software distributed under the License is distributed on an 13 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | KIND, either express or implied. See the License for the 15 | specific language governing permissions and limitations 16 | under the License. 17 | */ 18 | -------------------------------------------------------------------------------- /spec/fixtures/plugins/ChildBrowser/www/childbrowser.js: -------------------------------------------------------------------------------- 1 | /** 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | -------------------------------------------------------------------------------- /spec/fixtures/plugins/ChildBrowser/www/childbrowser/image.jpg: -------------------------------------------------------------------------------- 1 | foo 2 | -------------------------------------------------------------------------------- /spec/fixtures/plugins/ChildBrowser/www/childbrowser_file.html: -------------------------------------------------------------------------------- 1 | This is a test file. 2 | -------------------------------------------------------------------------------- /spec/fixtures/plugins/com.adobe.vars/plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 20 | 21 | 25 | 26 | Use Variables 27 | 28 | 29 | 30 | Remember that your api key is $API_KEY! 31 | 32 | 33 | 34 | 35 | $PACKAGE_NAME 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | $PACKAGE_NAME 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | $PACKAGE_NAME 54 | 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /spec/fixtures/plugins/org.apache.bplist/plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 20 | 21 | 25 | 26 | Binary PList updates 27 | 28 | 29 | 30 | 31 | 32 | UINewsstandIcon 33 | 34 | CFBundleIconFiles 35 | 36 | Newsstand-Cover-Icon.png 37 | Newsstand-Cover-Icon@2x.png 38 | 39 | UINewsstandBindingType 40 | UINewsstandBindingTypeMagazine 41 | UINewsstandBindingEdge 42 | UINewsstandBindingEdgeLeft 43 | 44 | 45 | 46 | 47 | 48 | 49 | schema-a 50 | schema-b 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /spec/fixtures/plugins/org.apache.plist/plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 20 | 21 | 25 | 26 | PList updates 27 | 28 | 29 | 30 | 31 | 32 | UINewsstandIcon 33 | 34 | CFBundleIconFiles 35 | 36 | Newsstand-Cover-Icon.png 37 | Newsstand-Cover-Icon@2x.png 38 | 39 | UINewsstandBindingType 40 | UINewsstandBindingTypeMagazine 41 | UINewsstandBindingEdge 42 | UINewsstandBindingEdgeLeft 43 | 44 | 45 | 46 | 47 | 48 | 49 | schema-a 50 | schema-b 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /spec/fixtures/plugins/org.test.configtest/plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 20 | 21 | 25 | 26 | Does Code Fil Write Even Work? Hopefully the Tests Will Tell Us 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /spec/fixtures/plugins/org.test.editconfigtest/plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 20 | 21 | 25 | 26 | Test edit-config 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /spec/fixtures/plugins/org.test.editconfigtest_two/plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 20 | 21 | 25 | 26 | Test edit-config with conflicts 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /spec/fixtures/plugins/org.test.multiple-children/plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 20 | 21 | 25 | 26 | Pushwoosh 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 51 | 54 | 56 | 57 | 58 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 83 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 99 | 100 | 101 | 102 | 103 | 104 | 106 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /spec/fixtures/plugins/org.test.plugins.childbrowser/plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 20 | 21 | 25 | 26 | Child Browser 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | No matter what platform you are installing to, this notice is very important. 41 | 42 | 43 | 44 | 45 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 56 | 57 | 58 | 59 | 60 | 62 | 63 | 64 | 66 | Please make sure you read this because it is very important to complete the installation of your plugin. 67 | 68 | 69 | 70 | 71 | 72 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | $APP_ID 81 | 82 | 83 | 84 | 85 | 86 | PackageName 87 | $PACKAGE_NAME 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 114 | 115 | 116 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | -------------------------------------------------------------------------------- /spec/fixtures/plugins/org.test.plugins.childbrowser/src/android/ChildBrowser.java: -------------------------------------------------------------------------------- 1 | ./org.test.plugins.childbrowser/src/android/org.test.plugins.childbrowser.java 2 | -------------------------------------------------------------------------------- /spec/fixtures/plugins/org.test.plugins.childbrowser/src/ios/ChildBrowser.bundle/arrow_left.png: -------------------------------------------------------------------------------- 1 | ./org.test.plugins.childbrowser/src/ios/org.test.plugins.childbrowser.bundle/arrow_left.png 2 | -------------------------------------------------------------------------------- /spec/fixtures/plugins/org.test.plugins.childbrowser/src/ios/ChildBrowser.bundle/arrow_left@2x.png: -------------------------------------------------------------------------------- 1 | ./org.test.plugins.childbrowser/src/ios/org.test.plugins.childbrowser.bundle/arrow_left@2x.png 2 | -------------------------------------------------------------------------------- /spec/fixtures/plugins/org.test.plugins.childbrowser/src/ios/ChildBrowser.bundle/arrow_right.png: -------------------------------------------------------------------------------- 1 | ./org.test.plugins.childbrowser/src/ios/org.test.plugins.childbrowser.bundle/arrow_right.png 2 | -------------------------------------------------------------------------------- /spec/fixtures/plugins/org.test.plugins.childbrowser/src/ios/ChildBrowser.bundle/arrow_right@2x.png: -------------------------------------------------------------------------------- 1 | ./org.test.plugins.childbrowser/src/ios/org.test.plugins.childbrowser.bundle/arrow_right@2x.png 2 | -------------------------------------------------------------------------------- /spec/fixtures/plugins/org.test.plugins.childbrowser/src/ios/ChildBrowser.bundle/but_refresh.png: -------------------------------------------------------------------------------- 1 | ./org.test.plugins.childbrowser/src/ios/org.test.plugins.childbrowser.bundle/but_refresh.png 2 | -------------------------------------------------------------------------------- /spec/fixtures/plugins/org.test.plugins.childbrowser/src/ios/ChildBrowser.bundle/but_refresh@2x.png: -------------------------------------------------------------------------------- 1 | ./org.test.plugins.childbrowser/src/ios/org.test.plugins.childbrowser.bundle/but_refresh@2x.png 2 | -------------------------------------------------------------------------------- /spec/fixtures/plugins/org.test.plugins.childbrowser/src/ios/ChildBrowser.bundle/compass.png: -------------------------------------------------------------------------------- 1 | ./org.test.plugins.childbrowser/src/ios/org.test.plugins.childbrowser.bundle/compass.png 2 | -------------------------------------------------------------------------------- /spec/fixtures/plugins/org.test.plugins.childbrowser/src/ios/ChildBrowser.bundle/compass@2x.png: -------------------------------------------------------------------------------- 1 | ./org.test.plugins.childbrowser/src/ios/org.test.plugins.childbrowser.bundle/compass@2x.png 2 | -------------------------------------------------------------------------------- /spec/fixtures/plugins/org.test.plugins.childbrowser/src/ios/ChildBrowserCommand.h: -------------------------------------------------------------------------------- 1 | ./org.test.plugins.childbrowser/src/ios/ChildBrowserCommand.h 2 | -------------------------------------------------------------------------------- /spec/fixtures/plugins/org.test.plugins.childbrowser/src/ios/ChildBrowserCommand.m: -------------------------------------------------------------------------------- 1 | ./org.test.plugins.childbrowser/src/ios/ChildBrowserCommand.m 2 | -------------------------------------------------------------------------------- /spec/fixtures/plugins/org.test.plugins.childbrowser/src/ios/ChildBrowserViewController.h: -------------------------------------------------------------------------------- 1 | ./org.test.plugins.childbrowser/src/ios/ChildBrowserViewController.h 2 | -------------------------------------------------------------------------------- /spec/fixtures/plugins/org.test.plugins.childbrowser/src/ios/ChildBrowserViewController.m: -------------------------------------------------------------------------------- 1 | ./org.test.plugins.childbrowser/src/ios/ChildBrowserViewController.m 2 | -------------------------------------------------------------------------------- /spec/fixtures/plugins/org.test.plugins.childbrowser/src/ios/ChildBrowserViewController.xib: -------------------------------------------------------------------------------- 1 | ./org.test.plugins.childbrowser/src/ios/ChildBrowserViewController.xib 2 | -------------------------------------------------------------------------------- /spec/fixtures/plugins/org.test.plugins.childbrowser/src/ios/TargetDirTest.h: -------------------------------------------------------------------------------- 1 | ./org.test.plugins.childbrowser/src/ios/TargetDirTest.h 2 | -------------------------------------------------------------------------------- /spec/fixtures/plugins/org.test.plugins.childbrowser/src/ios/TargetDirTest.m: -------------------------------------------------------------------------------- 1 | ./org.test.plugins.childbrowser/src/ios/TargetDirTest.m 2 | -------------------------------------------------------------------------------- /spec/fixtures/plugins/org.test.plugins.childbrowser/src/ios/preserveDirs/PreserveDirsTest.h: -------------------------------------------------------------------------------- 1 | ./org.test.plugins.childbrowser/src/ios/preserveDirs/PreserveDirsTest.h 2 | -------------------------------------------------------------------------------- /spec/fixtures/plugins/org.test.plugins.childbrowser/src/ios/preserveDirs/PreserveDirsTest.m: -------------------------------------------------------------------------------- 1 | ./org.test.plugins.childbrowser/src/ios/preserveDirs/PreserveDirsTest.m 2 | -------------------------------------------------------------------------------- /spec/fixtures/plugins/org.test.plugins.childbrowser/www/childbrowser.js: -------------------------------------------------------------------------------- 1 | //org.test.plugins.childbrowser/www/childbrowser.js 2 | -------------------------------------------------------------------------------- /spec/fixtures/plugins/org.test.plugins.childbrowser/www/childbrowser/image.jpg: -------------------------------------------------------------------------------- 1 | ./org.test.plugins.childbrowser/www/childbrowser/image.jpg 2 | -------------------------------------------------------------------------------- /spec/fixtures/plugins/org.test.plugins.childbrowser/www/childbrowser_file.html: -------------------------------------------------------------------------------- 1 | ./org.test.plugins.childbrowser/www/childbrowser_file.html 2 | -------------------------------------------------------------------------------- /spec/fixtures/plugins/org.test.plugins.dummyplugin/android-resource.xml: -------------------------------------------------------------------------------- 1 | ./org.test.plugins.dummyplugin/android-resource.xml 2 | -------------------------------------------------------------------------------- /spec/fixtures/plugins/org.test.plugins.dummyplugin/extra.gradle: -------------------------------------------------------------------------------- 1 | extra.gradle 2 | -------------------------------------------------------------------------------- /spec/fixtures/plugins/org.test.plugins.dummyplugin/plugin-lib/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /spec/fixtures/plugins/org.test.plugins.dummyplugin/plugin-lib/libFile: -------------------------------------------------------------------------------- 1 | libFile contents 2 | -------------------------------------------------------------------------------- /spec/fixtures/plugins/org.test.plugins.dummyplugin/plugin-lib/project.properties: -------------------------------------------------------------------------------- 1 | target=android-11 2 | -------------------------------------------------------------------------------- /spec/fixtures/plugins/org.test.plugins.dummyplugin/src/android/DummyPlugin.java: -------------------------------------------------------------------------------- 1 | ./org.test.plugins.dummyplugin/src/android/DummyPlugin.java 2 | -------------------------------------------------------------------------------- /spec/fixtures/plugins/org.test.plugins.dummyplugin/src/blackberry10/index.js: -------------------------------------------------------------------------------- 1 | //org.test.plugins.dummyplugin/src/blackberry10/index.js 2 | -------------------------------------------------------------------------------- /spec/fixtures/plugins/org.test.plugins.dummyplugin/src/ios/Custom.framework/someFheader.h: -------------------------------------------------------------------------------- 1 | ./org.test.plugins.dummyplugin/src/ios/Custom.framework/someFheader.h 2 | -------------------------------------------------------------------------------- /spec/fixtures/plugins/org.test.plugins.dummyplugin/src/ios/Custom.framework/somebinlib: -------------------------------------------------------------------------------- 1 | ./org.test.plugins.dummyplugin/src/ios/Custom.framework/somebinlib 2 | -------------------------------------------------------------------------------- /spec/fixtures/plugins/org.test.plugins.dummyplugin/src/ios/DummyPlugin.bundle: -------------------------------------------------------------------------------- 1 | ./org.test.plugins.dummyplugin/src/ios/DummyPlugin.bundle 2 | -------------------------------------------------------------------------------- /spec/fixtures/plugins/org.test.plugins.dummyplugin/src/ios/DummyPluginCommand.h: -------------------------------------------------------------------------------- 1 | ./org.test.plugins.dummyplugin/src/ios/DummyPluginCommand.h 2 | -------------------------------------------------------------------------------- /spec/fixtures/plugins/org.test.plugins.dummyplugin/src/ios/DummyPluginCommand.m: -------------------------------------------------------------------------------- 1 | ./org.test.plugins.dummyplugin/src/ios/DummyPluginCommand.m 2 | -------------------------------------------------------------------------------- /spec/fixtures/plugins/org.test.plugins.dummyplugin/src/ios/SourceWithFramework.m: -------------------------------------------------------------------------------- 1 | ./org.test.plugins.dummyplugin/src/ios/SourceWithFramework.m 2 | -------------------------------------------------------------------------------- /spec/fixtures/plugins/org.test.plugins.dummyplugin/src/ios/TargetDirTest.h: -------------------------------------------------------------------------------- 1 | ./org.test.plugins.dummyplugin/src/ios/TargetDirTest.h 2 | -------------------------------------------------------------------------------- /spec/fixtures/plugins/org.test.plugins.dummyplugin/src/ios/TargetDirTest.m: -------------------------------------------------------------------------------- 1 | ./org.test.plugins.dummyplugin/src/ios/TargetDirTest.m 2 | -------------------------------------------------------------------------------- /spec/fixtures/plugins/org.test.plugins.dummyplugin/src/ios/libsqlite3.dylib: -------------------------------------------------------------------------------- 1 | ./org.test.plugins.dummyplugin/src/ios/libsqlite3.dylib 2 | -------------------------------------------------------------------------------- /spec/fixtures/plugins/org.test.plugins.dummyplugin/src/tizen/dummer.js: -------------------------------------------------------------------------------- 1 | //org.test.plugins.dummyplugin/src/tizen/dummer.js 2 | -------------------------------------------------------------------------------- /spec/fixtures/plugins/org.test.plugins.dummyplugin/src/windows/dummer.js: -------------------------------------------------------------------------------- 1 | //org.test.plugins.dummyplugin/src/windows/dummer.js 2 | -------------------------------------------------------------------------------- /spec/fixtures/plugins/org.test.plugins.dummyplugin/src/windows/dummy1.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apache/cordova-common/584670d4bb1a687380a0bd360ab398f490836b29/spec/fixtures/plugins/org.test.plugins.dummyplugin/src/windows/dummy1.dll -------------------------------------------------------------------------------- /spec/fixtures/plugins/org.test.plugins.dummyplugin/src/windows/dummy1.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {ef7dd979-6f12-4bdf-8754-9468ce799c4d} 5 | dummy1 6 | 7 | 8 | -------------------------------------------------------------------------------- /spec/fixtures/plugins/org.test.plugins.dummyplugin/src/windows/dummy2.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apache/cordova-common/584670d4bb1a687380a0bd360ab398f490836b29/spec/fixtures/plugins/org.test.plugins.dummyplugin/src/windows/dummy2.dll -------------------------------------------------------------------------------- /spec/fixtures/plugins/org.test.plugins.dummyplugin/src/windows/dummy2.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {6a760eea-8c27-442e-b98b-a487964ded42} 5 | dummy2 6 | 7 | 8 | -------------------------------------------------------------------------------- /spec/fixtures/plugins/org.test.plugins.dummyplugin/src/windows/dummy3.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apache/cordova-common/584670d4bb1a687380a0bd360ab398f490836b29/spec/fixtures/plugins/org.test.plugins.dummyplugin/src/windows/dummy3.dll -------------------------------------------------------------------------------- /spec/fixtures/plugins/org.test.plugins.dummyplugin/src/windows/dummy3.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {5318c3c8-0921-4870-b4ad-8cce06c88238} 5 | dummy3 6 | 7 | 8 | -------------------------------------------------------------------------------- /spec/fixtures/plugins/org.test.plugins.dummyplugin/src/windows/dummy4.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apache/cordova-common/584670d4bb1a687380a0bd360ab398f490836b29/spec/fixtures/plugins/org.test.plugins.dummyplugin/src/windows/dummy4.dll -------------------------------------------------------------------------------- /spec/fixtures/plugins/org.test.plugins.dummyplugin/src/windows/dummy4.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {8e430a1b-094b-4c27-8b76-fdd7021dbfe9} 5 | dummy4 6 | 7 | 8 | -------------------------------------------------------------------------------- /spec/fixtures/plugins/org.test.plugins.dummyplugin/src/wp7/DummyPlugin.cs: -------------------------------------------------------------------------------- 1 | ./org.test.plugins.dummyplugin/src/wp7/DummyPlugin.cs 2 | -------------------------------------------------------------------------------- /spec/fixtures/plugins/org.test.plugins.dummyplugin/src/wp8/DummyPlugin.cs: -------------------------------------------------------------------------------- 1 | ./org.test.plugins.dummyplugin/src/wp8/DummyPlugin.cs 2 | -------------------------------------------------------------------------------- /spec/fixtures/plugins/org.test.plugins.dummyplugin/www/dummyplugin.js: -------------------------------------------------------------------------------- 1 | //org.test.plugins.dummyplugin/www/dummyplugin.js 2 | -------------------------------------------------------------------------------- /spec/fixtures/plugins/org.test.plugins.dummyplugin/www/dummyplugin/image.jpg: -------------------------------------------------------------------------------- 1 | ./org.test.plugins.dummyplugin/www/dummyplugin/image.jpg 2 | -------------------------------------------------------------------------------- /spec/fixtures/plugins/org.test.plugins.withcocoapods/plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 20 | 21 | 25 | 26 | 27 | withcocoapods 28 | 29 | my description 30 | Ken Naito 31 | dummy,plugin,cocoapods 32 | BSD 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /spec/fixtures/plugins/org.test.shareddeps/plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 20 | 21 | 25 | 26 | Sharing Dependencies with the Multi-Child Plugin, woo 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /spec/fixtures/plugins/org.test.src/plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | PushPlugin 4 | 5 | This plugin allows your application to receive push notifications on Android, iOS and Windows devices. 6 | Android uses Firebase Cloud Messaging. 7 | iOS uses Apple APNS Notifications. 8 | Windows uses Microsoft WNS Notifications. 9 | 10 | MIT 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | $FCM_VERSION 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | remote-notification 79 | 80 | 81 | 82 | development 83 | 84 | 85 | production 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /spec/fixtures/plugins/org.test.xmlpassthrough/plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 20 | 21 | 25 | 26 | For testing xml passthrough 27 | 28 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /spec/fixtures/projects/android/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 20 | 21 | 23 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 54 | 56 | 57 | 58 | 59 | 60 | 61 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /spec/fixtures/projects/android/assets/www/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apache/cordova-common/584670d4bb1a687380a0bd360ab398f490836b29/spec/fixtures/projects/android/assets/www/.gitkeep -------------------------------------------------------------------------------- /spec/fixtures/projects/android/res/xml/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 20 | 21 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /spec/fixtures/projects/android/src/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apache/cordova-common/584670d4bb1a687380a0bd360ab398f490836b29/spec/fixtures/projects/android/src/.gitkeep -------------------------------------------------------------------------------- /spec/fixtures/projects/android_two/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 20 | 21 | 23 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 54 | 56 | 57 | 58 | 59 | 60 | 61 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /spec/fixtures/projects/android_two/assets/www/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apache/cordova-common/584670d4bb1a687380a0bd360ab398f490836b29/spec/fixtures/projects/android_two/assets/www/.gitkeep -------------------------------------------------------------------------------- /spec/fixtures/projects/android_two/res/xml/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 20 | 21 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /spec/fixtures/projects/android_two/src/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apache/cordova-common/584670d4bb1a687380a0bd360ab398f490836b29/spec/fixtures/projects/android_two/src/.gitkeep -------------------------------------------------------------------------------- /spec/fixtures/projects/android_two_no_perms/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 20 | 21 | 23 | 31 | 32 | 34 | 36 | 37 | 38 | 39 | 40 | 41 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /spec/fixtures/projects/android_two_no_perms/assets/www/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apache/cordova-common/584670d4bb1a687380a0bd360ab398f490836b29/spec/fixtures/projects/android_two_no_perms/assets/www/.gitkeep -------------------------------------------------------------------------------- /spec/fixtures/projects/android_two_no_perms/res/xml/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 20 | 21 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /spec/fixtures/projects/android_two_no_perms/src/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apache/cordova-common/584670d4bb1a687380a0bd360ab398f490836b29/spec/fixtures/projects/android_two_no_perms/src/.gitkeep -------------------------------------------------------------------------------- /spec/fixtures/projects/ios-config-xml/CordovaLib/VERSION: -------------------------------------------------------------------------------- 1 | 3.5.0 2 | -------------------------------------------------------------------------------- /spec/fixtures/projects/ios-config-xml/SampleApp/SampleApp-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 23 | 24 | 25 | CFBundleIcons 26 | 27 | CFBundlePrimaryIcon 28 | 29 | CFBundleIconFiles 30 | 31 | icon.png 32 | icon@2x.png 33 | icon-72.png 34 | icon-72@2x.png 35 | 36 | UIPrerenderedIcon 37 | 38 | 39 | 40 | UISupportedInterfaceOrientations~ipad 41 | 42 | UIInterfaceOrientationPortrait 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationPortraitUpsideDown 45 | UIInterfaceOrientationLandscapeRight 46 | 47 | UISupportedInterfaceOrientations 48 | 49 | UIInterfaceOrientationPortrait 50 | 51 | CFValidSchemas 52 | 53 | schema-a 54 | 55 | CFBundleDevelopmentRegion 56 | English 57 | CFBundleDisplayName 58 | ${PRODUCT_NAME} 59 | CFBundleExecutable 60 | ${EXECUTABLE_NAME} 61 | CFBundleIconFile 62 | icon.png 63 | CFBundleIdentifier 64 | com.example.friendstring 65 | CFBundleInfoDictionaryVersion 66 | 6.0 67 | CFBundleName 68 | ${PRODUCT_NAME} 69 | CFBundlePackageType 70 | APPL 71 | CFBundleSignature 72 | ???? 73 | CFBundleVersion 74 | 1.0 75 | LSRequiresIPhoneOS 76 | 77 | NSMainNibFile 78 | 79 | NSMainNibFile~ipad 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /spec/fixtures/projects/ios-config-xml/SampleApp/SampleApp-binary.plist: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apache/cordova-common/584670d4bb1a687380a0bd360ab398f490836b29/spec/fixtures/projects/ios-config-xml/SampleApp/SampleApp-binary.plist -------------------------------------------------------------------------------- /spec/fixtures/projects/ios-config-xml/SampleApp/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /spec/fixtures/projects/ios-config-xml/www/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apache/cordova-common/584670d4bb1a687380a0bd360ab398f490836b29/spec/fixtures/projects/ios-config-xml/www/.gitkeep -------------------------------------------------------------------------------- /spec/fixtures/projects/windows-bom-test.xml: -------------------------------------------------------------------------------- 1 |  2 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /spec/fixtures/test-config0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Hello Cordova 4 | 5 | A sample Apache Cordova application that responds to the deviceready event. 6 | 7 | 8 | Apache Cordova Team 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /spec/fixtures/test-configfile.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Hello Cordova 4 | 5 | A sample Apache Cordova application that responds to the deviceready event. 6 | 7 | 8 | Apache Cordova Team 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /spec/fixtures/test-editconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Hello Cordova 4 | 5 | A sample Apache Cordova application that responds to the deviceready event. 6 | 7 | 8 | Apache Cordova Team 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /spec/helper.js: -------------------------------------------------------------------------------- 1 | /** 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | const SpecReporter = require('jasmine-spec-reporter').SpecReporter; 20 | 21 | jasmine.getEnv().clearReporters(); 22 | jasmine.getEnv().addReporter(new SpecReporter({ 23 | spec: { 24 | displayPending: true, 25 | displayDuration: true 26 | }, 27 | summary: { 28 | displayDuration: true, 29 | displayStacktrace: 'raw' 30 | } 31 | })); 32 | -------------------------------------------------------------------------------- /spec/plist-helpers.spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | const plistHelpers = require('../src/util/plist-helpers'); 21 | 22 | describe('prunePLIST', function () { 23 | const doc = { 24 | FirstConfigKey: { 25 | FirstPreferenceName: '*', 26 | SecondPreferenceName: 'a + b', 27 | ThirdPreferenceName: 'x-msauth-$(CFBundleIdentifier:rfc1034identifier)' 28 | }, 29 | 30 | SecondConfigKey: { 31 | FirstPreferenceName: 'abc' 32 | } 33 | }; 34 | 35 | const xml = '' + 36 | 'FirstPreferenceName' + 37 | '*' + 38 | 'SecondPreferenceName' + 39 | 'a + b' + 40 | 'ThirdPreferenceName' + 41 | 'x-msauth-$(CFBundleIdentifier:rfc1034identifier)' + 42 | ''; 43 | 44 | const selector = 'FirstConfigKey'; 45 | 46 | it('Test 01: should remove property from plist file using provided selector', () => { 47 | const pruneStatus = plistHelpers.prunePLIST(doc, xml, selector); 48 | 49 | expect(pruneStatus).toBeTruthy(); 50 | expect(doc).toEqual( 51 | { 52 | SecondConfigKey: { 53 | FirstPreferenceName: 'abc' 54 | } 55 | } 56 | ); 57 | }); 58 | }); 59 | 60 | describe('plistGraft', function () { 61 | const doc = { 62 | 'keychain-access-groups': [ 63 | '$(AppIdentifierPrefix)io.cordova.hellocordova', 64 | '$(AppIdentifierPrefix)com.example.mylib' 65 | ] 66 | }; 67 | 68 | const xml = '' + 69 | '$(AppIdentifierPrefix)io.cordova.hellocordova' + 70 | '$(AppIdentifierPrefix)com.example.mylib' + 71 | ''; 72 | 73 | const selector = 'keychain-access-groups'; 74 | 75 | it('Test 01: should not mangle existing plist entries', () => { 76 | const graftStatus = plistHelpers.graftPLIST(doc, xml, selector); 77 | 78 | expect(graftStatus).toBeTruthy(); 79 | expect(doc).toEqual( 80 | { 81 | 'keychain-access-groups': [ 82 | '$(AppIdentifierPrefix)io.cordova.hellocordova', 83 | '$(AppIdentifierPrefix)com.example.mylib' 84 | ] 85 | } 86 | ); 87 | }); 88 | }); 89 | -------------------------------------------------------------------------------- /spec/superspawn.spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | const path = require('path'); 21 | const superspawn = require('../src/superspawn'); 22 | 23 | const LS = process.platform === 'win32' ? 'dir' : 'ls'; 24 | 25 | describe('spawn method', function () { 26 | let progressSpy; 27 | 28 | beforeEach(function () { 29 | progressSpy = jasmine.createSpy('progress'); 30 | }); 31 | 32 | it('should resolve on success', () => { 33 | return expectAsync(superspawn.spawn(LS)).toBeResolved(); 34 | }); 35 | 36 | it('should reject on failure', () => { 37 | return expectAsync(superspawn.spawn('invalid_command')).toBeRejected(); 38 | }); 39 | 40 | it('Test 002 : should notify about stdout "data" events', () => { 41 | return superspawn.spawn(LS, [], { stdio: 'pipe' }) 42 | .progress(progressSpy) 43 | .then(function () { 44 | expect(progressSpy).toHaveBeenCalledWith({ stdout: jasmine.any(String) }); 45 | }); 46 | }); 47 | 48 | it('Test 003 : should notify about stderr "data" events', () => { 49 | return superspawn.spawn(LS, ['doesnt-exist'], { stdio: 'pipe' }) 50 | .progress(progressSpy) 51 | .then(() => { 52 | fail('Expected promise to be rejected'); 53 | }, () => { 54 | expect(progressSpy).toHaveBeenCalledWith({ stderr: jasmine.any(String) }); 55 | }); 56 | }); 57 | 58 | it('Test 004 : reject handler should pass in Error object with stdout and stderr properties', () => { 59 | const cp = require('child_process'); 60 | spyOn(cp, 'spawn').and.callFake(() => { 61 | return { 62 | stdout: { 63 | setEncoding: function () {}, 64 | on: function (evt, handler) { 65 | // some sample stdout output 66 | handler('business as usual'); 67 | } 68 | }, 69 | stderr: { 70 | setEncoding: function () {}, 71 | on: function (evt, handler) { 72 | // some sample stderr output 73 | handler('mayday mayday'); 74 | } 75 | }, 76 | on: function (evt, handler) { 77 | // What's passed to handler here is the exit code, so we can control 78 | // resolve/reject flow via this argument. 79 | handler(1); // this will trigger error flow 80 | }, 81 | removeListener: function () {} 82 | }; 83 | }); 84 | return superspawn.spawn('this aggression', ['will', 'not', 'stand', 'man'], {}) 85 | .then(() => { 86 | fail('Expected promise to be rejected'); 87 | }, err => { 88 | expect(err).toBeDefined(); 89 | expect(err.stdout).toContain('usual'); 90 | expect(err.stderr).toContain('mayday'); 91 | }); 92 | }); 93 | 94 | it('Test 005 : should not throw but reject', () => { 95 | if (process.platform === 'win32') { 96 | pending('Test should not run on Windows'); 97 | } 98 | 99 | // Our non-executable (as in no execute permission) script 100 | const TEST_SCRIPT = path.join(__dirname, 'fixtures/echo-args.cmd'); 101 | 102 | let promise; 103 | expect(() => { promise = superspawn.spawn(TEST_SCRIPT, []); }).not.toThrow(); 104 | 105 | return promise.then(() => { 106 | fail('Expected promise to be rejected'); 107 | }, err => { 108 | expect(err).toEqual(jasmine.any(Error)); 109 | expect(err.code).toBe('EACCES'); 110 | }); 111 | }); 112 | 113 | describe('operation on windows', () => { 114 | const TEST_SCRIPT = path.join(__dirname, 'fixtures/echo-args.cmd'); 115 | const TEST_ARGS = ['install', 'foo@^1.2.3', 'c o r d o v a']; 116 | 117 | it('should escape arguments if `cmd` is not an *.exe', () => { 118 | if (process.platform !== 'win32') { 119 | pending('test should only run on windows'); 120 | } 121 | 122 | return superspawn.spawn(TEST_SCRIPT, TEST_ARGS).then(output => { 123 | expect(output.split(/\r?\n/)).toEqual(TEST_ARGS); 124 | }); 125 | }); 126 | }); 127 | }); 128 | -------------------------------------------------------------------------------- /spec/support/jasmine.json: -------------------------------------------------------------------------------- 1 | { 2 | "spec_dir": "spec", 3 | "helpers": [ 4 | "helper.js" 5 | ], 6 | "random": false 7 | } 8 | -------------------------------------------------------------------------------- /src/ActionStack.js: -------------------------------------------------------------------------------- 1 | /** 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | const events = require('./events'); 21 | 22 | class ActionStack { 23 | constructor () { 24 | this.stack = []; 25 | this.completed = []; 26 | } 27 | 28 | createAction (handler, action_params, reverter, revert_params) { 29 | return { 30 | handler: { 31 | run: handler, 32 | params: action_params 33 | }, 34 | reverter: { 35 | run: reverter, 36 | params: revert_params 37 | } 38 | }; 39 | } 40 | 41 | push (tx) { 42 | this.stack.push(tx); 43 | } 44 | 45 | // Returns a promise. 46 | process (platform) { 47 | events.emit('verbose', `Beginning processing of action stack for ${platform} project...`); 48 | 49 | while (this.stack.length) { 50 | const action = this.stack.shift(); 51 | const handler = action.handler.run; 52 | const action_params = action.handler.params; 53 | 54 | try { 55 | handler.apply(null, action_params); 56 | } catch (e) { 57 | events.emit('warn', 'Error during processing of action! Attempting to revert...'); 58 | this.stack.unshift(action); 59 | let issue = 'Uh oh!\n'; 60 | // revert completed tasks 61 | while (this.completed.length) { 62 | const undo = this.completed.shift(); 63 | const revert = undo.reverter.run; 64 | const revert_params = undo.reverter.params; 65 | 66 | try { 67 | revert.apply(null, revert_params); 68 | } catch (err) { 69 | events.emit('warn', 'Error during reversion of action! We probably really messed up your project now, sorry! D:'); 70 | issue += `A reversion action failed: ${err.message}\n`; 71 | } 72 | } 73 | e.message = issue + e.message; 74 | return Promise.reject(e); 75 | } 76 | this.completed.push(action); 77 | } 78 | events.emit('verbose', 'Action stack processing complete.'); 79 | 80 | return Promise.resolve(); 81 | } 82 | } 83 | 84 | module.exports = ActionStack; 85 | -------------------------------------------------------------------------------- /src/ConfigChanges/ConfigKeeper.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, 9 | * software distributed under the License is distributed on an 10 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 11 | * KIND, either express or implied. See the License for the 12 | * specific language governing permissions and limitations 13 | * under the License. 14 | * 15 | */ 16 | 17 | const path = require('path'); 18 | const ConfigFile = require('./ConfigFile'); 19 | 20 | /****************************************************************************** 21 | * ConfigKeeper class 22 | * 23 | * Used to load and store config files to avoid re-parsing and writing them out 24 | * multiple times. 25 | * 26 | * The config files are referred to by a fake path constructed as 27 | * project_dir/platform/file 28 | * where file is the name used for the file in config munges. 29 | ******************************************************************************/ 30 | class ConfigKeeper { 31 | constructor (project_dir, plugins_dir) { 32 | this.project_dir = project_dir; 33 | this.plugins_dir = plugins_dir; 34 | this._cache = new Map(); 35 | } 36 | 37 | get (project_dir, platform, file) { 38 | // This fixes a bug with older plugins - when specifying config xml instead of res/xml/config.xml 39 | // https://issues.apache.org/jira/browse/CB-6414 40 | if (file === 'config.xml' && platform === 'android') { 41 | file = 'res/xml/config.xml'; 42 | } 43 | 44 | const fake_path = path.join(project_dir, platform, file); 45 | 46 | if (!this._cache.has(fake_path)) { 47 | // File was not cached, need to load. 48 | const config_file = new ConfigFile(project_dir, platform, file); 49 | this._cache.set(fake_path, config_file); 50 | } 51 | 52 | return this._cache.get(fake_path); 53 | } 54 | 55 | save_all () { 56 | this._cache.forEach(config_file => { 57 | if (config_file.is_changed) config_file.save(); 58 | }); 59 | } 60 | } 61 | 62 | module.exports = ConfigKeeper; 63 | -------------------------------------------------------------------------------- /src/ConfigChanges/munge-util.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, 9 | * software distributed under the License is distributed on an 10 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 11 | * KIND, either express or implied. See the License for the 12 | * specific language governing permissions and limitations 13 | * under the License. 14 | * 15 | */ 16 | 17 | // @ts-check 18 | 19 | /** 20 | * @typedef {Object} MungeElement 21 | * @property {String} xml 22 | * @property {Number} count 23 | * @property {import('elementtree').Attributes} [oldAttrib] 24 | * 25 | * @property {'merge' | 'overwrite' | 'remove'} [mode] edit-config only 26 | * 27 | * @property {String} [id] 'config.xml' or the id of the plugin from whose 28 | * plugin.xml this was taken; edit-config only 29 | * @property {String} [after] a ;-separated priority list of tags after which 30 | * the insertion should be made. E.g. if we need to insert an element C, and the 31 | * order of children has to be As, Bs, Cs then `after` will be equal to "C;B;A". 32 | * config-file only 33 | */ 34 | 35 | /** 36 | * @typedef {Object} FileMunge 37 | * @property {Object.} parents 38 | */ 39 | 40 | /** 41 | * @typedef {Object} Munge 42 | * @property {Object.} files 43 | */ 44 | 45 | /** 46 | * Adds element.count to obj[file][selector][element] 47 | * 48 | * @return {Boolean} true iff it didn't exist before 49 | */ 50 | exports.deep_add = (...args) => { 51 | const { element, siblings } = processArgs(...args, { create: true }); 52 | const matchingSibling = siblings.find(sibling => sibling.xml === element.xml); 53 | 54 | if (matchingSibling) { 55 | matchingSibling.after = matchingSibling.after || element.after; 56 | matchingSibling.count += element.count; 57 | } else { 58 | siblings.push(element); 59 | } 60 | 61 | return !matchingSibling; 62 | }; 63 | 64 | /** 65 | * Subtracts element.count from obj[file][selector][element] 66 | * 67 | * @return {Boolean} true iff element was removed or not found 68 | */ 69 | exports.deep_remove = (...args) => { 70 | const { element, siblings } = processArgs(...args); 71 | const index = siblings.findIndex(sibling => sibling.xml === element.xml); 72 | 73 | if (index < 0) return true; 74 | 75 | const matchingSibling = siblings[index]; 76 | 77 | if (matchingSibling.oldAttrib) { 78 | element.oldAttrib = Object.assign({}, matchingSibling.oldAttrib); 79 | } 80 | matchingSibling.count -= element.count; 81 | 82 | if (matchingSibling.count > 0) return false; 83 | 84 | siblings.splice(index, 1); 85 | return true; 86 | }; 87 | 88 | /** 89 | * Find element with given key in obj 90 | * 91 | * @return {MungeElement} the sought-after object or undefined if not found 92 | */ 93 | exports.deep_find = (...args) => { 94 | const { element, siblings } = processArgs(...args); 95 | 96 | const elementXml = (element.xml || element); 97 | return siblings.find(sibling => sibling.xml === elementXml); 98 | }; 99 | 100 | function processArgs (obj, fileName, selector, element, opts) { 101 | if (Array.isArray(fileName)) { 102 | opts = selector; 103 | [fileName, selector, element] = fileName; 104 | } 105 | 106 | const siblings = getElements(obj, [fileName, selector], opts); 107 | return { element, siblings }; 108 | } 109 | 110 | /** 111 | * Get the element array for given keys 112 | * 113 | * If a key entry is missing, create it if opts.create is true else return [] 114 | * 115 | * @param {Munge} obj 116 | * @param {String[]} keys [fileName, selector] 117 | * @param {{create: Boolean}} [opts] 118 | * @return {MungeElement[]} 119 | */ 120 | function getElements ({ files }, [fileName, selector], opts = { create: false }) { 121 | if (!files[fileName] && !opts.create) return []; 122 | 123 | const { parents: fileChanges } = (files[fileName] = files[fileName] || { parents: {} }); 124 | if (!fileChanges[selector] && !opts.create) return []; 125 | 126 | return (fileChanges[selector] = fileChanges[selector] || []); 127 | } 128 | 129 | /** 130 | * All values from munge are added to base as 131 | * base[file][selector][child] += munge[file][selector][child] 132 | * 133 | * @param {Munge} base 134 | * @param {Munge} munge 135 | * @return {Munge} A munge object containing values that exist in munge but not 136 | * in base. 137 | */ 138 | exports.increment_munge = (base, munge) => { 139 | return mungeItems(base, munge, exports.deep_add); 140 | }; 141 | 142 | /** 143 | * Update the base munge object as 144 | * base[file][selector][child] -= munge[file][selector][child] 145 | * 146 | * @param {Munge} base 147 | * @param {Munge} munge 148 | * @return {Munge} nodes that reached zero value are removed from base and added 149 | * to the returned munge object 150 | */ 151 | exports.decrement_munge = (base, munge) => { 152 | return mungeItems(base, munge, exports.deep_remove); 153 | }; 154 | 155 | /** 156 | * For every key [file, selector, element] in munge run mungeOperation on base. 157 | * 158 | * @param {Munge} base 159 | * @param {Munge} munge 160 | * @param {typeof exports.deep_add} mungeOperation - TODO how can I constrain 161 | * that to an enum of functions 162 | * @return {Munge} - the union of all changes for which mungeOperation returned 163 | * true 164 | */ 165 | function mungeItems (base, { files }, mungeOperation) { 166 | const diff = { files: {} }; 167 | 168 | for (const file in files) { 169 | for (const selector in files[file].parents) { 170 | for (const element of files[file].parents[selector]) { 171 | // if node not in base, add it to diff and base 172 | // else increment it's value in base without adding to diff 173 | 174 | const hasChanges = mungeOperation(base, [file, selector, element]); 175 | if (hasChanges) exports.deep_add(diff, [file, selector, element]); 176 | } 177 | } 178 | } 179 | 180 | return diff; 181 | } 182 | 183 | /** 184 | * Clones given munge 185 | * 186 | * @param {Munge} munge 187 | * @return {Munge} clone of munge 188 | */ 189 | exports.clone_munge = munge => exports.increment_munge({ files: {} }, munge); 190 | -------------------------------------------------------------------------------- /src/CordovaCheck.js: -------------------------------------------------------------------------------- 1 | /** 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | const fs = require('node:fs'); 21 | const path = require('node:path'); 22 | 23 | function isRootDir (dir) { 24 | if (fs.existsSync(path.join(dir, 'www'))) { 25 | if (fs.existsSync(path.join(dir, 'config.xml'))) { 26 | // For sure is. 27 | return fs.existsSync(path.join(dir, 'platforms')) ? 2 : 1; 28 | } 29 | // Might be (or may be under platforms/). 30 | if (fs.existsSync(path.join(dir, 'www', 'config.xml'))) { 31 | return 1; 32 | } 33 | } 34 | 35 | return 0; 36 | } 37 | 38 | // Runs up the directory chain looking for a .cordova directory. 39 | // IF it is found we are in a Cordova project. 40 | // Omit argument to use CWD. 41 | function isCordova (dir) { 42 | if (!dir) { 43 | // Prefer PWD over cwd so that symlinked dirs within your PWD work correctly (CB-5687). 44 | const pwd = process.env.PWD; 45 | const cwd = process.cwd(); 46 | const hasPwd = pwd && pwd !== cwd && pwd !== 'undefined'; 47 | 48 | return (hasPwd && isCordova(pwd)) || isCordova(cwd); 49 | } 50 | 51 | let bestReturnValueSoFar = false; 52 | 53 | for (let i = 0; i < 1000; ++i) { 54 | const result = isRootDir(dir); 55 | 56 | if (result === 2) { 57 | return dir; 58 | } 59 | 60 | if (result === 1) { 61 | bestReturnValueSoFar = dir; 62 | } 63 | 64 | const parentDir = path.normalize(path.join(dir, '..')); 65 | // Detect fs root. 66 | if (parentDir === dir) { 67 | return bestReturnValueSoFar; 68 | } 69 | 70 | dir = parentDir; 71 | } 72 | 73 | console.error('Hit an unhandled case in CordovaCheck.isCordova'); 74 | return false; 75 | } 76 | 77 | module.exports = { 78 | findProjectRoot: isCordova 79 | }; 80 | -------------------------------------------------------------------------------- /src/CordovaError.js: -------------------------------------------------------------------------------- 1 | /** 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | // @ts-check 21 | 22 | const { VError } = require('@netflix/nerror'); 23 | 24 | /** 25 | * @public 26 | * @typedef {Object} CordovaErrorOptions 27 | * @param {String} [name] - Name of the error. 28 | * @param {Error} [cause] - Indicates that the new error was caused by `cause`. 29 | * @param {Object} [info] - Specifies arbitrary informational properties. 30 | */ 31 | 32 | /** 33 | * A custom exception class derived from VError 34 | */ 35 | class CordovaError extends VError { 36 | /** 37 | * @param {String} message - Error message 38 | * @param {Error|CordovaErrorOptions} [causeOrOpts] - The Error that caused 39 | * this to be thrown or a CordovaErrorOptions object. 40 | */ 41 | constructor (message, causeOrOpts = {}) { 42 | const defaults = { name: 'CordovaError' }; 43 | const overrides = { strict: false, skipPrintf: true }; 44 | const userOpts = causeOrOpts instanceof Error 45 | ? { cause: causeOrOpts } 46 | : causeOrOpts; 47 | const opts = Object.assign(defaults, userOpts, overrides); 48 | 49 | super(opts, message); 50 | } 51 | } 52 | 53 | module.exports = CordovaError; 54 | -------------------------------------------------------------------------------- /src/CordovaLogger.js: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | const ansi = require('ansi'); 21 | const EventEmitter = require('events').EventEmitter; 22 | const EOL = require('os').EOL; 23 | const formatError = require('./util/formatError'); 24 | 25 | const INSTANCE_KEY = Symbol.for('org.apache.cordova.common.CordovaLogger'); 26 | 27 | /** 28 | * @typedef {'verbose'|'normal'|'warn'|'info'|'error'|'results'} CordovaLoggerLevel 29 | */ 30 | 31 | /** 32 | * Implements logging facility that anybody could use. 33 | * 34 | * Should not be instantiated directly! `CordovaLogger.get()` method should be 35 | * used instead to acquire the logger instance. 36 | */ 37 | class CordovaLogger { 38 | // Encapsulate the default logging level values with constants: 39 | static get VERBOSE () { return 'verbose'; } 40 | 41 | static get NORMAL () { return 'normal'; } 42 | 43 | static get WARN () { return 'warn'; } 44 | 45 | static get INFO () { return 'info'; } 46 | 47 | static get ERROR () { return 'error'; } 48 | 49 | static get RESULTS () { return 'results'; } 50 | 51 | /** 52 | * Static method to create new or acquire existing instance. 53 | * 54 | * @returns {CordovaLogger} Logger instance 55 | */ 56 | static get () { 57 | // This singleton instance pattern is based on the ideas from 58 | // https://derickbailey.com/2016/03/09/creating-a-true-singleton-in-node-js-with-es6-symbols/ 59 | if (Object.getOwnPropertySymbols(global).indexOf(INSTANCE_KEY) === -1) { 60 | global[INSTANCE_KEY] = new CordovaLogger(); 61 | } 62 | return global[INSTANCE_KEY]; 63 | } 64 | 65 | constructor () { 66 | /** @private */ 67 | this.levels = {}; 68 | /** @private */ 69 | this.colors = {}; 70 | /** @private */ 71 | this.stdout = process.stdout; 72 | /** @private */ 73 | this.stderr = process.stderr; 74 | 75 | /** @private */ 76 | this.stdoutCursor = ansi(this.stdout); 77 | /** @private */ 78 | this.stderrCursor = ansi(this.stderr); 79 | 80 | this.addLevel(CordovaLogger.VERBOSE, 1000, 'grey'); 81 | this.addLevel(CordovaLogger.NORMAL, 2000); 82 | this.addLevel(CordovaLogger.WARN, 2000, 'yellow'); 83 | this.addLevel(CordovaLogger.INFO, 3000, 'blue'); 84 | this.addLevel(CordovaLogger.ERROR, 5000, 'red'); 85 | this.addLevel(CordovaLogger.RESULTS, 10000); 86 | 87 | this.setLevel(CordovaLogger.NORMAL); 88 | } 89 | 90 | /** 91 | * Emits log message to process' stdout/stderr depending on message's 92 | * severity and current log level. If severity is less than current 93 | * logger's level, then the message is ignored. 94 | * 95 | * @param {CordovaLoggerLevel} logLevel - The message's log level. The 96 | * logger should have corresponding level added (via logger.addLevel), 97 | * otherwise `CordovaLogger.NORMAL` level will be used. 98 | * 99 | * @param {string} message - The message, that should be logged to 100 | * process's stdio. 101 | * 102 | * @returns {CordovaLogger} Return the current instance, to allow chaining. 103 | */ 104 | log (logLevel, message) { 105 | // if there is no such logLevel defined, or provided level has 106 | // less severity than active level, then just ignore this call and return 107 | if (!this.levels[logLevel] || this.levels[logLevel] < this.levels[this.logLevel]) { 108 | // return instance to allow to chain calls 109 | return this; 110 | } 111 | 112 | const isVerbose = this.logLevel === CordovaLogger.VERBOSE; 113 | let cursor = this.stdoutCursor; 114 | 115 | if (message instanceof Error || logLevel === CordovaLogger.ERROR) { 116 | message = formatError(message, isVerbose); 117 | cursor = this.stderrCursor; 118 | } 119 | 120 | const color = this.colors[logLevel]; 121 | if (color) { 122 | cursor.bold().fg[color](); 123 | } 124 | 125 | cursor.write(message).reset().write(EOL); 126 | 127 | return this; 128 | } 129 | 130 | /** 131 | * Adds a new level to logger instance. 132 | * 133 | * This method also creates a shortcut method to log events with the level 134 | * provided. 135 | * (i.e. after adding new level 'debug', the method `logger.debug(message)` 136 | * will exist, equal to `logger.log('debug', message)`) 137 | * 138 | * @param {CordovaLoggerLevel} level - A log level name. The levels with 139 | * the following names are added by default to every instance: 'verbose', 140 | * 'normal', 'warn', 'info', 'error', 'results'. 141 | * 142 | * @param {number} severity - A number that represents level's severity. 143 | * 144 | * @param {string} color - A valid color name, that will be used to log 145 | * messages with this level. Any CSS color code or RGB value is allowed 146 | * (according to ansi documentation: 147 | * https://github.com/TooTallNate/ansi.js#features). 148 | * 149 | * @returns {CordovaLogger} Return the current instance, to allow chaining. 150 | */ 151 | addLevel (level, severity, color) { 152 | this.levels[level] = severity; 153 | 154 | if (color) { 155 | this.colors[level] = color; 156 | } 157 | 158 | // Define own method with corresponding name 159 | if (!this[level]) { 160 | Object.defineProperty(this, level, { 161 | get () { return this.log.bind(this, level); } 162 | }); 163 | } 164 | 165 | return this; 166 | } 167 | 168 | /** 169 | * Sets the current logger level to provided value. 170 | * 171 | * If logger doesn't have level with this name, `CordovaLogger.NORMAL` will 172 | * be used. 173 | * 174 | * @param {CordovaLoggerLevel} logLevel - Level name. The level with this 175 | * name should be added to logger before. 176 | * 177 | * @returns {CordovaLogger} Current instance, to allow chaining. 178 | */ 179 | setLevel (logLevel) { 180 | this.logLevel = this.levels[logLevel] ? logLevel : CordovaLogger.NORMAL; 181 | 182 | return this; 183 | } 184 | 185 | /** 186 | * Adjusts the current logger level according to the passed options. 187 | * 188 | * @param {Object|Array} opts - An object or args array with 189 | * options. 190 | * 191 | * @returns {CordovaLogger} Current instance, to allow chaining. 192 | */ 193 | adjustLevel (opts) { 194 | if (opts.verbose || (Array.isArray(opts) && opts.includes('--verbose'))) { 195 | this.setLevel('verbose'); 196 | } else if (opts.silent || (Array.isArray(opts) && opts.includes('--silent'))) { 197 | this.setLevel('error'); 198 | } 199 | 200 | return this; 201 | } 202 | 203 | /** 204 | * Attaches logger to EventEmitter instance provided. 205 | * 206 | * @param {EventEmitter} eventEmitter - An EventEmitter instance to attach 207 | * the logger to. 208 | * 209 | * @returns {CordovaLogger} Current instance, to allow chaining. 210 | */ 211 | subscribe (eventEmitter) { 212 | if (!(eventEmitter instanceof EventEmitter)) { 213 | throw new Error('Subscribe method only accepts an EventEmitter instance as argument'); 214 | } 215 | 216 | eventEmitter.on('verbose', this.verbose) 217 | .on('log', this.normal) 218 | .on('info', this.info) 219 | .on('warn', this.warn) 220 | .on('warning', this.warn) 221 | // Set up event handlers for logging and results emitted as events. 222 | .on('results', this.results); 223 | 224 | return this; 225 | } 226 | } 227 | 228 | module.exports = CordovaLogger; 229 | -------------------------------------------------------------------------------- /src/PluginInfo/PluginInfoProvider.js: -------------------------------------------------------------------------------- 1 | /** 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | const fs = require('node:fs'); 21 | const path = require('node:path'); 22 | const PluginInfo = require('./PluginInfo'); 23 | const events = require('../events'); 24 | const fastGlob = require('fast-glob'); 25 | 26 | class PluginInfoProvider { 27 | constructor () { 28 | this._cache = {}; 29 | this._getAllCache = {}; 30 | } 31 | 32 | get (dirName) { 33 | const absPath = path.resolve(dirName); 34 | if (!this._cache[absPath]) { 35 | this._cache[absPath] = new PluginInfo(dirName); 36 | } 37 | return this._cache[absPath]; 38 | } 39 | 40 | // Normally you don't need to put() entries, but it's used 41 | // when copying plugins, and in unit tests. 42 | put (pluginInfo) { 43 | const absPath = path.resolve(pluginInfo.dir); 44 | this._cache[absPath] = pluginInfo; 45 | } 46 | 47 | // Used for plugin search path processing. 48 | // Given a dir containing multiple plugins, create a PluginInfo object for 49 | // each of them and return as array. 50 | // Should load them all in parallel and return a promise, but not yet. 51 | getAllWithinSearchPath (dirName) { 52 | const absPath = path.resolve(dirName); 53 | if (!this._getAllCache[absPath]) { 54 | this._getAllCache[absPath] = getAllHelper(absPath, this); 55 | } 56 | return this._getAllCache[absPath]; 57 | } 58 | } 59 | 60 | function getAllHelper (absPath, provider) { 61 | if (!fs.existsSync(absPath)) { 62 | return []; 63 | } 64 | // If dir itself is a plugin, return it in an array with one element. 65 | if (fs.existsSync(path.join(absPath, 'plugin.xml'))) { 66 | return [provider.get(absPath)]; 67 | } 68 | 69 | // Match normal and scoped plugins 70 | const pluginXmlPaths = fastGlob.sync('{,@*/}*/plugin.xml', { 71 | fs, // we pass in fs here, to be able to mock it in our tests 72 | cwd: absPath, 73 | onlyFiles: true, 74 | absolute: true 75 | }).map(path.normalize); 76 | 77 | return pluginXmlPaths.map(pluginXmlPath => { 78 | try { 79 | return provider.get(path.dirname(pluginXmlPath)); 80 | } catch (err) { 81 | events.emit('warn', `Error parsing ${pluginXmlPath}:\n${err.stack}`); 82 | return null; 83 | } 84 | }).filter(p => p); 85 | } 86 | 87 | module.exports = PluginInfoProvider; 88 | -------------------------------------------------------------------------------- /src/PluginManager.js: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | const fs = require('node:fs'); 21 | const path = require('node:path'); 22 | 23 | const ActionStack = require('./ActionStack'); 24 | const PlatformJson = require('./PlatformJson'); 25 | const CordovaError = require('./CordovaError'); 26 | const PlatformMunger = require('./ConfigChanges/ConfigChanges').PlatformMunger; 27 | const PluginInfoProvider = require('./PluginInfo/PluginInfoProvider'); 28 | 29 | /** 30 | * Represents an entity for adding/removing plugins for platforms 31 | */ 32 | class PluginManager { 33 | /** 34 | * @param {String} platform Platform name 35 | * @param {Object} locations - Platform files and directories 36 | * @param {IDEProject} ideProject The IDE project to add/remove plugin changes to/from 37 | */ 38 | constructor (platform, locations, ideProject) { 39 | this.platform = platform; 40 | this.locations = locations; 41 | this.project = ideProject; 42 | 43 | const platformJson = PlatformJson.load(locations.root, platform); 44 | this.munger = new PlatformMunger(platform, locations.root, platformJson, new PluginInfoProvider()); 45 | } 46 | 47 | /** 48 | * @constructs PluginManager 49 | * A convenience shortcut to new PluginManager(...) 50 | * 51 | * @param {String} platform Platform name 52 | * @param {Object} locations - Platform files and directories 53 | * @param {IDEProject} ideProject The IDE project to add/remove plugin changes to/from 54 | * @returns new PluginManager instance 55 | */ 56 | static get (platform, locations, ideProject) { 57 | return new PluginManager(platform, locations, ideProject); 58 | } 59 | 60 | static get INSTALL () { return 'install'; } 61 | 62 | static get UNINSTALL () { return 'uninstall'; } 63 | 64 | /** 65 | * Describes and implements common plugin installation/uninstallation routine. The flow is the following: 66 | * * Validate and set defaults for options. Note that options are empty by default. Everything 67 | * needed for platform IDE project must be passed from outside. Plugin variables (which 68 | * are the part of the options) also must be already populated with 'PACKAGE_NAME' variable. 69 | * * Collect all plugin's native and web files, get installers/uninstallers and process 70 | * all these via ActionStack. 71 | * * Save the IDE project, so the changes made by installers are persisted. 72 | * * Generate config changes munge for plugin and apply it to all required files 73 | * * Generate metadata for plugin and plugin modules and save it to 'cordova_plugins.js' 74 | * 75 | * @param {PluginInfo} plugin A PluginInfo structure representing plugin to install 76 | * @param {Object} [options={}] An installation options. It is expected but is not necessary 77 | * that options would contain 'variables' inner object with 'PACKAGE_NAME' field set by caller. 78 | * 79 | * @returns {Promise} 80 | */ 81 | doOperation (operation, plugin, options) { 82 | if (operation !== PluginManager.INSTALL && operation !== PluginManager.UNINSTALL) { return Promise.reject(new CordovaError('The parameter is incorrect. The opeation must be either "add" or "remove"')); } 83 | 84 | if (!plugin || plugin.constructor.name !== 'PluginInfo') { return Promise.reject(new CordovaError('The parameter is incorrect. The first parameter should be a PluginInfo instance')); } 85 | 86 | // Set default to empty object to play safe when accesing properties 87 | options = options || {}; 88 | 89 | const actions = new ActionStack(); 90 | 91 | // gather all files need to be handled during operation ... 92 | plugin.getFilesAndFrameworks(this.platform, options) 93 | .concat(plugin.getAssets(this.platform)) 94 | .concat(plugin.getJsModules(this.platform)) 95 | // ... put them into stack ... 96 | .forEach(item => { 97 | const installer = this.project.getInstaller(item.itemType); 98 | const uninstaller = this.project.getUninstaller(item.itemType); 99 | const actionArgs = [item, plugin, this.project, options]; 100 | 101 | let action; 102 | if (operation === PluginManager.INSTALL) { 103 | action = actions.createAction(installer, actionArgs, uninstaller, actionArgs); 104 | } else /* op === PluginManager.UNINSTALL */{ 105 | action = actions.createAction(uninstaller, actionArgs, installer, actionArgs); 106 | } 107 | actions.push(action); 108 | }); 109 | 110 | // ... and run through the action stack 111 | return actions.process(this.platform) 112 | .then(() => { 113 | if (this.project.write) { 114 | this.project.write(); 115 | } 116 | 117 | if (operation === PluginManager.INSTALL) { 118 | // Ignore passed `is_top_level` option since platform itself doesn't know 119 | // anything about managing dependencies - it's responsibility of caller. 120 | this.munger.add_plugin_changes(plugin, options.variables, /* is_top_level= */true, /* should_increment= */true, options.force); 121 | this.munger.platformJson.addPluginMetadata(plugin); 122 | } else { 123 | this.munger.remove_plugin_changes(plugin, /* is_top_level= */true); 124 | this.munger.platformJson.removePluginMetadata(plugin); 125 | } 126 | 127 | // Save everything (munge and plugin/modules metadata) 128 | this.munger.save_all(); 129 | 130 | const metadata = this.munger.platformJson.generateMetadata(); 131 | fs.writeFileSync(path.join(this.locations.www, 'cordova_plugins.js'), metadata, 'utf8'); 132 | 133 | // CB-11022 save plugin metadata to both www and platform_www if options.usePlatformWww is specified 134 | if (options.usePlatformWww) { 135 | fs.writeFileSync(path.join(this.locations.platformWww, 'cordova_plugins.js'), metadata, 'utf8'); 136 | } 137 | }); 138 | } 139 | 140 | addPlugin (plugin, installOptions) { 141 | return this.doOperation(PluginManager.INSTALL, plugin, installOptions); 142 | } 143 | 144 | removePlugin (plugin, uninstallOptions) { 145 | return this.doOperation(PluginManager.UNINSTALL, plugin, uninstallOptions); 146 | } 147 | } 148 | 149 | module.exports = PluginManager; 150 | -------------------------------------------------------------------------------- /src/events.js: -------------------------------------------------------------------------------- 1 | /** 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | const EventEmitter = require('events').EventEmitter; 21 | 22 | const MAX_LISTENERS = 20; 23 | 24 | const INSTANCE_KEY = Symbol.for('org.apache.cordova.common.CordovaEvents'); 25 | 26 | let EVENTS_RECEIVER = null; 27 | 28 | class CordovaEventEmitter extends EventEmitter { 29 | /** 30 | * Sets up current instance to forward emitted events to another EventEmitter 31 | * instance. 32 | * 33 | * @param {EventEmitter} [eventEmitter] The emitter instance to forward 34 | * events to. Falsy value, when passed, disables forwarding. 35 | */ 36 | forwardEventsTo (eventEmitter) { 37 | // If no argument is specified disable events forwarding 38 | if (!eventEmitter) { 39 | EVENTS_RECEIVER = undefined; 40 | return; 41 | } 42 | 43 | if (!(eventEmitter instanceof EventEmitter)) { 44 | throw new Error('Cordova events can be redirected to another EventEmitter instance only'); 45 | } 46 | 47 | // CB-10940 Skipping forwarding to this to avoid infinite recursion. 48 | // This is the case when the modules are npm-linked. 49 | if (this !== eventEmitter) { 50 | EVENTS_RECEIVER = eventEmitter; 51 | } else { 52 | // Reset forwarding if we are subscribing to this 53 | EVENTS_RECEIVER = undefined; 54 | } 55 | } 56 | 57 | /** 58 | * Sets up current instance to forward emitted events to another EventEmitter 59 | * instance. 60 | * 61 | * @param {EventEmitter} [eventEmitter] The emitter instance to forward 62 | * events to. Falsy value, when passed, disables forwarding. 63 | */ 64 | emit (eventName, ...args) { 65 | if (EVENTS_RECEIVER) { 66 | EVENTS_RECEIVER.emit(eventName, ...args); 67 | } 68 | 69 | return super.emit(eventName, ...args); 70 | } 71 | } 72 | 73 | // This singleton instance pattern is based on the ideas from 74 | // https://derickbailey.com/2016/03/09/creating-a-true-singleton-in-node-js-with-es6-symbols/ 75 | if (Object.getOwnPropertySymbols(global).indexOf(INSTANCE_KEY) === -1) { 76 | const events = new CordovaEventEmitter(); 77 | events.setMaxListeners(MAX_LISTENERS); 78 | global[INSTANCE_KEY] = events; 79 | } 80 | 81 | module.exports = global[INSTANCE_KEY]; 82 | -------------------------------------------------------------------------------- /src/superspawn.js: -------------------------------------------------------------------------------- 1 | /** 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | const crossSpawn = require('cross-spawn'); 21 | const fs = require('node:fs'); 22 | const Q = require('q'); 23 | const events = require('./events'); 24 | const iswin32 = process.platform === 'win32'; 25 | 26 | /** 27 | * A special implementation for child_process.spawn that handles 28 | * Windows-specific issues with batch files and spaces in paths. Returns a 29 | * promise that succeeds only for return code 0. It is also possible to 30 | * subscribe on spawned process' stdout and stderr streams using progress 31 | * handler for resultant promise. 32 | * 33 | * @example spawn('mycommand', [], {stdio: 'pipe'}) .progress(function (stdio){ 34 | * if (stdio.stderr) { console.error(stdio.stderr); } }) 35 | * .then(function(result){ // do other stuff }) 36 | * 37 | * @param {String} cmd A command to spawn 38 | * @param {String[]} [args=[]] An array of arguments, passed to spawned 39 | * process 40 | * @param {Object} [opts={}] A configuration object 41 | * @param {String|String[]|Object} opts.stdio Property that configures how 42 | * spawned process' stdio will behave. Has the same meaning and possible 43 | * values as 'stdio' options for child_process.spawn method 44 | * (https://nodejs.org/api/child_process.html#child_process_options_stdio). 45 | * @param {Object} [env={}] A map of extra environment variables 46 | * @param {String} [cwd=process.cwd()] Working directory for the command 47 | * @param {Boolean} [chmod=false] If truthy, will attempt to set the execute 48 | * bit before executing on non-Windows platforms 49 | * 50 | * @return {Promise} A promise that is either fulfilled if the spawned 51 | * process is exited with zero error code or rejected otherwise. If the 52 | * 'stdio' option set to 'default' or 'pipe', the promise also emits progress 53 | * messages with the following contents: 54 | * { 55 | * 'stdout': ..., 56 | * 'stderr': ... 57 | * } 58 | */ 59 | exports.spawn = (cmd, args, opts) => { 60 | args = args || []; 61 | opts = opts || {}; 62 | const spawnOpts = {}; 63 | const d = Q.defer(); 64 | 65 | if (opts.stdio !== 'default') { 66 | // Ignore 'default' value for stdio because it corresponds to child_process's default 'pipe' option 67 | spawnOpts.stdio = opts.stdio; 68 | } 69 | 70 | if (opts.cwd) { 71 | spawnOpts.cwd = opts.cwd; 72 | } 73 | 74 | if (opts.env) { 75 | spawnOpts.env = Object.assign({}, process.env, opts.env); 76 | } 77 | 78 | if (opts.chmod && !iswin32) { 79 | try { 80 | // This fails when module is installed in a system directory (e.g. via sudo npm install) 81 | fs.chmodSync(cmd, '755'); 82 | } catch (e) { 83 | // If the perms weren't set right, then this will come as an error upon execution. 84 | } 85 | } 86 | 87 | events.emit(opts.printCommand ? 'log' : 'verbose', `Running command: ${cmd} ${args.join(' ')}`); 88 | 89 | // At least until Node.js 8, child_process.spawn will throw exceptions 90 | // instead of emitting error events in certain cases (like EACCES), Thus we 91 | // have to wrap the execution in try/catch to convert them into rejections. 92 | let child; 93 | try { 94 | child = crossSpawn.spawn(cmd, args, spawnOpts); 95 | } catch (e) { 96 | whenDone(e); 97 | return d.promise; 98 | } 99 | let capturedOut = ''; 100 | let capturedErr = ''; 101 | 102 | if (child.stdout) { 103 | child.stdout.setEncoding('utf8'); 104 | child.stdout.on('data', data => { 105 | capturedOut += data; 106 | d.notify({ stdout: data }); 107 | }); 108 | } 109 | 110 | if (child.stderr) { 111 | child.stderr.setEncoding('utf8'); 112 | child.stderr.on('data', data => { 113 | capturedErr += data; 114 | d.notify({ stderr: data }); 115 | }); 116 | } 117 | 118 | child.on('close', whenDone); 119 | child.on('error', whenDone); 120 | function whenDone (arg) { 121 | if (child) { 122 | child.removeListener('close', whenDone); 123 | child.removeListener('error', whenDone); 124 | } 125 | const code = typeof arg === 'number' ? arg : arg && arg.code; 126 | 127 | events.emit('verbose', `Command finished with error code ${code}: ${cmd} ${args}`); 128 | if (code === 0) { 129 | d.resolve(capturedOut.trim()); 130 | } else { 131 | let errMsg = `${cmd}: Command failed with exit code ${code}`; 132 | if (capturedErr) { 133 | errMsg += ` Error output:\n${capturedErr.trim()}`; 134 | } 135 | const err = new Error(errMsg); 136 | if (capturedErr) { 137 | err.stderr = capturedErr; 138 | } 139 | if (capturedOut) { 140 | err.stdout = capturedOut; 141 | } 142 | err.code = code; 143 | d.reject(err); 144 | } 145 | } 146 | 147 | return d.promise; 148 | }; 149 | 150 | exports.maybeSpawn = (cmd, args, opts) => { 151 | if (fs.existsSync(cmd)) { 152 | return exports.spawn(cmd, args, opts); 153 | } 154 | return Q(null); 155 | }; 156 | -------------------------------------------------------------------------------- /src/util/formatError.js: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | const CordovaError = require('../CordovaError'); 21 | 22 | /** 23 | * Formats an error for logging. 24 | * 25 | * @param {Error} error - The error to be formatted. 26 | * @param {boolean} isVerbose - Whether the include additional debugging 27 | * information when formatting the error. 28 | * 29 | * @returns {string} The formatted error message. 30 | */ 31 | module.exports = function formatError (error, isVerbose) { 32 | let message = ''; 33 | 34 | if (error instanceof CordovaError) { 35 | message = error.toString(isVerbose); 36 | } else if (error instanceof Error) { 37 | if (isVerbose) { 38 | message = error.stack; 39 | } else { 40 | message = error.message; 41 | } 42 | } else { 43 | // Plain text error message 44 | message = error; 45 | } 46 | 47 | if (typeof message === 'string' && !message.toUpperCase().startsWith('ERROR:')) { 48 | // Needed for backward compatibility with external tools 49 | message = `Error: ${message}`; 50 | } 51 | 52 | return message; 53 | }; 54 | -------------------------------------------------------------------------------- /src/util/plist-helpers.js: -------------------------------------------------------------------------------- 1 | /** 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | // contains PLIST utility functions 21 | const util = require('node:util'); 22 | const plist = require('plist'); 23 | 24 | // adds node to doc at selector 25 | module.exports.graftPLIST = graftPLIST; 26 | function graftPLIST (doc, xml, selector) { 27 | const obj = plist.parse(`${xml}`); 28 | const node = doc[selector]; 29 | 30 | if (node && Array.isArray(node) && Array.isArray(obj)) { 31 | const isNew = item => !node.some(nodeChild => nodeEqual(item, nodeChild)); 32 | doc[selector] = node.concat(obj.filter(isNew)); 33 | } else { 34 | // plist uses objects for . If we have two dicts we merge them instead of 35 | // overriding the old one. See CB-6472 36 | const isDict = o => typeof o === 'object' && !util.types.isDate(o); // arrays checked above 37 | if (isDict(node) && isDict(obj)) { 38 | Object.assign(obj, node); 39 | } 40 | 41 | doc[selector] = obj; 42 | } 43 | 44 | return true; 45 | } 46 | 47 | // removes node from doc at selector 48 | module.exports.prunePLIST = prunePLIST; 49 | function prunePLIST (doc, xml, selector) { 50 | const obj = plist.parse(`${xml}`); 51 | 52 | pruneObject(doc, selector, obj); 53 | 54 | return true; 55 | } 56 | 57 | function pruneObject (doc, selector, fragment) { 58 | if (Array.isArray(fragment) && Array.isArray(doc[selector])) { 59 | let empty = true; 60 | 61 | for (const i in fragment) { 62 | for (const j in doc[selector]) { 63 | empty = pruneObject(doc[selector], j, fragment[i]) && empty; 64 | } 65 | } 66 | 67 | if (empty) { 68 | delete doc[selector]; 69 | return true; 70 | } 71 | } else if (nodeEqual(doc[selector], fragment)) { 72 | delete doc[selector]; 73 | return true; 74 | } 75 | 76 | return false; 77 | } 78 | 79 | function nodeEqual (node1, node2) { 80 | if (typeof node1 !== typeof node2) { 81 | return false; 82 | } else if (typeof node1 === 'string') { 83 | node2 = escapeRE(node2).replace(/\\\$\(\S+\)/gm, '(.*?)'); 84 | return new RegExp(`^${node2}$`).test(node1); 85 | } else { 86 | for (const key in node2) { 87 | if (!nodeEqual(node1[key], node2[key])) return false; 88 | } 89 | 90 | return true; 91 | } 92 | } 93 | 94 | // escape string for use in regex 95 | function escapeRE (str) { 96 | return str.replace(/[-[\]/{}()*+?.\\^$|]/g, '\\$&'); 97 | } 98 | --------------------------------------------------------------------------------