Comparing version 0.6.1 to 0.6.2
23
main.js
@@ -34,7 +34,4 @@ #!/usr/bin/env node | ||
, 'uninstall' : Boolean | ||
, 'fetch' : Boolean | ||
, 'list' : Boolean | ||
, 'v' : Boolean | ||
, 'debug' : Boolean | ||
, 'prepare' : Boolean | ||
, 'plugins': path | ||
@@ -65,15 +62,2 @@ , 'link': Boolean | ||
} | ||
else if (cli_opts.list) { | ||
plugins.listAllPlugins(function(plugins) { | ||
for(var i = 0, j = plugins.length ; i < j ; i++) { | ||
console.log(plugins[i].value.name, '-', plugins[i].value.description); | ||
} | ||
}); | ||
} | ||
else if (cli_opts.prepare && cli_opts.project) { | ||
plugman.prepare(cli_opts.project, cli_opts.platform, plugins_dir); | ||
} | ||
else if (cli_opts.fetch) { | ||
plugman.fetch(cli_opts.plugin, plugins_dir, cli_opts.link); | ||
} | ||
else if (!cli_opts.platform || !cli_opts.project || !cli_opts.plugin) { | ||
@@ -100,10 +84,5 @@ printUsage(); | ||
console.log('Usage\n---------'); | ||
console.log('Fetch a plugin:\n\t' + package.name + ' --fetch --plugin <directory|git-url|name> [--plugins_dir <directory>]\n'); | ||
console.log('Install an already fetched plugin:\n\t' + package.name + ' --platform <'+ platforms +'> --project <directory> --plugin <name> [--plugins_dir <directory>]\n'); | ||
console.log('Install a plugin (will call fetch if cannot be found):\n\t' + package.name + ' --platform <'+ platforms +'> --project <directory> --plugin <name> [--plugins_dir <directory>] [--variable <name>=<value>]\n'); | ||
console.log('Uninstall a plugin:\n\t' + package.name + ' --uninstall --platform <'+ platforms +'> --project <directory> --plugin <name> [--plugins_dir <directory>]\n'); | ||
console.log('List plugins:\n\t' + package.name + ' --list [--plugins_dir <directory>]\n'); | ||
console.log('Prepare project:\n\t' + package.name + ' --prepare --platform <ios|android|bb10> --project <directory> [--plugins_dir <directory>]'); | ||
console.log('\n\t--plugins_dir defaults to <project>/cordova/plugins, but can be any directory containing a subdirectory for each plugin'); | ||
} | ||
@@ -5,3 +5,3 @@ { | ||
"description": "install/uninstall Cordova plugins", | ||
"version": "0.6.1", | ||
"version": "0.6.2", | ||
"repository": { | ||
@@ -8,0 +8,0 @@ "type": "git", |
109
README.md
@@ -7,3 +7,6 @@ # plugman | ||
## Quickstart | ||
npm install -g plugman | ||
## Design Goals | ||
@@ -19,5 +22,4 @@ | ||
plugman --fetch --plugin <directory|git-url|name> [--plugins_dir <directory>] | ||
plugman --install --platform <ios|android|bb10> --project <directory> --plugin <name|url> [--plugins_dir <directory>] | ||
plugman --install --platform <ios|android|bb10> --project <directory> --plugin <name|url> [--plugins_dir <directory>] [--variable <name>=<value> [--variable <name>=<value> ...]] | ||
plugman --uninstall --platform <ios|android|bb10> --project <directory> --plugin <name> [--plugins_dir <directory>] | ||
plugman --remove --plugin <name> [--plugins_dir <directory>] | ||
plugman --list [--plugins_dir <directory>] | ||
@@ -29,11 +31,13 @@ plugman --prepare --platform <ios|android|bb10> --project <directory> [--plugins_dir <directory>] | ||
* `--uninstall`: Uninstalls an already-`--install`'ed plugin from a cordova project | ||
* `--remove`: Removes a `--fetch`'ed plugin | ||
* `--list`: Lists all `--fetch`'ed plugins | ||
* `--prepare`: Based on all installed plugins, will set up properly injecting plugin JavaScript code and setting up permissions properly. Implicitly called after `--install` and `--uninstall` commands. See below for more details. | ||
`--plugins_dir` defaults to `<project>/cordova/plugins`, but can be any directory containing a subdirectory for each fetched plugin | ||
Other parameters: | ||
Note that `--fetch` and `--remove` deal with the local cache of the plugin's files and don't care about platforms, while `--install` and `--uninstall` require specifying the target platform and the location of the project, and actually do installation of plugin code and assets. | ||
* `--plugins_dir` defaults to `<project>/cordova/plugins`, but can be any directory containing a subdirectory for each fetched plugin. | ||
* `--variable` allows to specify certain variables at install time, necessary for certain plugins requiring API keys or other custom, user-defined parameters. | ||
Note that `--fetch` deals with the local cache of the plugin's files (in the `--plugins_dir` location) and doesn't care about platforms, while `--install` and `--uninstall` require specifying the target platform and the location of the project, and actually do installation of plugin code and assets. | ||
### Supported Platforms | ||
@@ -47,3 +51,4 @@ | ||
The plugins found [https://github.com/MobileChromeApps/chrome-cordova/plugins](here) are maintained actively by a contributor to plugman, and should serve as good examples. | ||
- Google has a [https://github.com/MobileChromeApps/chrome-cordova/plugins](bunch of plugins) which are maintained actively by a contributor to plugman | ||
- Adobe maintains plugins for its Build cloud service, which are open sourced and [available on GitHub](https://github.com/phonegap-build) | ||
@@ -92,3 +97,3 @@ ## Development | ||
Last edited April 17 2013. | ||
Last edited May 2 2013. | ||
@@ -181,6 +186,4 @@ The `plugin.xml` file is an XML document in the plugins namespace - | ||
All assets tags require both a `src` attribute and a `target` attribute. | ||
All assets tags require both a `src` attribute and a `target` attribute. Web-only plugins would contains mainly <asset> elements. <asset> elements can also be nested under <platform> elements, to specify platform-specific web assets (see below). | ||
Web-only plugins would contains mainly <asset> elements. | ||
#### src (required) | ||
@@ -191,3 +194,3 @@ | ||
If a file does not exist at the source location, plugman will stop/reverse the installation process and notify the user, and exit with a non-zero code. | ||
If a file does not exist at the specified `src` location, plugman will stop/reverse the installation process and notify the user, and exit with a non-zero code. | ||
@@ -236,3 +239,5 @@ #### target (required) | ||
<js-module> elements can also be nested under <platform>, to declare platform-specific JavaScript module bindings. | ||
### <platform> | ||
@@ -306,2 +311,4 @@ | ||
Two file types that have been tested for modification with this element are `xml` and `plist` files. | ||
The `config-file` element only allows for appending | ||
@@ -311,12 +318,23 @@ new children into an XML document. The children are XML literals that are the | ||
Example: | ||
Example for XML: | ||
<config-file target="AndroidManifest.xml" parent="/manifest/application"> | ||
<activity android:name="com.foo.Foo" | ||
android:label="@string/app_name"> | ||
<intent-filter> | ||
</intent-filter> | ||
<activity android:name="com.foo.Foo" android:label="@string/app_name"> | ||
<intent-filter> | ||
</intent-filter> | ||
</activity> | ||
</config-file> | ||
Example for plist: | ||
<config-file target="*-Info.plist" parent="CFBundleURLTypes"> | ||
<array> | ||
<dict> | ||
<key>PackageName</key> | ||
<string>$PACKAGE_NAME</string> | ||
</dict> | ||
</array> | ||
</config-file> | ||
#### target | ||
@@ -327,9 +345,14 @@ | ||
If this file does not exist, the tool should stop/reverse the installation process, warn the user, and exit with a non-zero code. | ||
The target can include a wildcard (`*`) element. In this case, plugman will recursively search through the project directory structure and use the first match. | ||
On iOS, the location of configuration files relative to the project directory root is not known. Specifying a target of `config.xml` will resolve to `cordova-ios-project/MyAppName/config.xml`. | ||
If the specified file does not exist, the tool will ignore the configuration change and continue installation. | ||
#### parent | ||
An absolute XPath selector pointing to the parent of the elements to be added to | ||
the config file. | ||
An XPath selector pointing to the parent of the elements to be added to the config file. If absolute selectors are used, you can use a wildcard (`*`) to specify the root element, e.g. `/*/plugins`. | ||
For plist files, the parent is used to determine under what parent key should the specified XML be inserted. | ||
If the selector does not resolve to a child of the specified document, the tool should stop/reverse the installation process, warn the user, and exit with a non-zero code. | ||
@@ -339,3 +362,3 @@ | ||
This is OUTDATED as it only applies to cordova-ios 2.2.0 and below. Use <config-file> tag (same as Android) for newer versions of Cordova. | ||
This is OUTDATED as it only applies to cordova-ios 2.2.0 and below. Use <config-file> tag for newer versions of Cordova. | ||
@@ -345,4 +368,3 @@ Example: | ||
<config-file target="config.xml" parent="/cordova/plugins"> | ||
<plugin name="ChildBrowser" | ||
value="ChildBrowserCommand"/> | ||
<plugin name="ChildBrowser" value="ChildBrowserCommand"/> | ||
</config-file> | ||
@@ -353,4 +375,3 @@ | ||
<plugins-plist key="Foo" | ||
string="CDVFoo" /> | ||
<plugins-plist key="Foo" string="CDVFoo" /> | ||
@@ -361,3 +382,3 @@ | ||
Like source files, but specifically for platforms that distinguish between | ||
source files, headers, and resources (iOS) | ||
source files, headers, and resources (iOS). | ||
@@ -423,9 +444,9 @@ Examples: | ||
<info> | ||
You need to install **Google Play Services** from the `Android Extras` section using the Android SDK manager (run `android`). | ||
<info> | ||
You need to install **Google Play Services** from the `Android Extras` section using the Android SDK manager (run `android`). | ||
You need to add the following line to your `local.properties` | ||
android.library.reference.1=PATH_TO_ANDROID_SDK/sdk/extras/google/google_play_services/libproject/google-play-services_lib | ||
</info> | ||
You need to add the following line to your `local.properties` | ||
android.library.reference.1=PATH_TO_ANDROID_SDK/sdk/extras/google/google_play_services/libproject/google-play-services_lib | ||
</info> | ||
@@ -473,30 +494,8 @@ ## Variables | ||
## Project Directory Structure | ||
TODO: show how the foo plugin example from above will have its files placed in a cordova project after running plugman | ||
## Authors | ||
* Andrew Lunny | ||
* Fil Maj | ||
* Mike Reinstein | ||
* Anis Kadri | ||
* Braden Shepherdson | ||
* Tim Kim | ||
## Contributors | ||
* Michael Brooks | ||
See the package.json file for attribution notes. | ||
## License | ||
Apache | ||
## TODO | ||
These apply to plugman as well as cordova-cli. Keep the two in step, they both have `future` branches. | ||
These are in rough order of priority, most urgent at the top. | ||
* Fix all the tests, including the www-only tests, which expect the old `www` platform that has been removed. Note that most of the tests will need some rewiring because of the separation of `--fetch` and `--install`. [CB-2814](http://issues.cordova.io/2814). Assigned to Tim. | ||
* Implement a `cordova watch` a la `grunt watch` that will re-run `cordova prepare` every time the installed plugins change (including those installed with `--link`). This is definitely a stretch goal, but it would be awesome. Not assigned but tracked at [CB-2819](http://issues.cordova.io/2819). | ||
Apache License 2.0 |
@@ -25,6 +25,6 @@ var fetch = require('../src/fetch'), | ||
}); | ||
it('should copy locally-available plugin to plugins directory when specified with a trailing slash', function() { | ||
fetch(test_plugin+'/', temp, false); | ||
expect(fs.existsSync(copied_plugin_path)).toBe(true); | ||
}); | ||
// it('should copy locally-available plugin to plugins directory when specified with a trailing slash', function() { | ||
// fetch(test_plugin+'/', temp, false); | ||
// expect(fs.existsSync(copied_plugin_path)).toBe(true); | ||
// }); | ||
it('should create a symlink if used with `link` param', function() { | ||
@@ -46,17 +46,3 @@ fetch(test_plugin, temp, true); | ||
}); | ||
it('should call getPluginInfo and clonePluginRepo for names', function() { | ||
var s1 = spyOn(plugins, 'getPluginInfo').andCallFake(function(plugin_name, callback) { | ||
callback(null, {url:"https://github.com/imhotep/ChildBrowser.git"}); | ||
}); | ||
var s2 = spyOn(plugins, 'clonePluginGitRepo'); | ||
fetch("ChildBrowser", temp, false, null); | ||
expect(s1).toHaveBeenCalled(); | ||
expect(s2).toHaveBeenCalledWith('https://github.com/imhotep/ChildBrowser.git', temp, null); | ||
}); | ||
it('should throw if used with name and `link` param', function() { | ||
expect(function() { | ||
fetch('ChildBrowser', temp, true); | ||
}).toThrow(); | ||
}); | ||
}); | ||
}); |
@@ -33,2 +33,3 @@ var install = require('../src/install'), | ||
describe('success', function() { | ||
@@ -56,3 +57,3 @@ var android_installer; | ||
expect(transactions.length).toEqual(3); | ||
expect(transactions.length).toEqual(1); | ||
expect(transactions[0].tag).toBe('source-file'); | ||
@@ -69,2 +70,5 @@ }); | ||
describe('failure', function() { | ||
describe('should revert web assets if an install error occurs', function() { | ||
}); | ||
it('should throw if platform is unrecognized', function() { | ||
@@ -71,0 +75,0 @@ expect(function() { |
@@ -8,2 +8,3 @@ var ios = require('../../src/platforms/ios'), | ||
os = require('osenv'), | ||
common = require('../../src/platforms/common'), | ||
xcode = require('xcode'), | ||
@@ -199,3 +200,3 @@ plist = require('plist'), | ||
}); | ||
it('should call into xcodeproj\'s addHeaderFile appropriately when element a no target-dir', function() { | ||
it('should call into xcodeproj\'s addHeaderFile appropriately when element a target-dir', function() { | ||
var headers = copyArray(valid_headers).filter(function(s) { return s.attrib['target-dir'] != undefined}); | ||
@@ -277,41 +278,41 @@ var spy = jasmine.createSpy(); | ||
}); | ||
describe('of <asset> elements', function() { | ||
beforeEach(function() { | ||
shell.cp('-rf', ios_config_xml_project, temp); | ||
}); | ||
it('should throw if asset src cannot be found', function() { | ||
var assets = copyArray(invalid_assets); | ||
expect(function() { | ||
ios.install(assets, faulty_id, temp, faultyplugin, {}); | ||
}).toThrow('"' + path.resolve(faultyplugin, 'www/main.js') + '" not found!'); | ||
}); | ||
it('should throw if asset target already exists', function() { | ||
var assets = copyArray(valid_assets); | ||
var target = path.join(temp, 'www', 'dummyplugin.js'); | ||
fs.writeFileSync(target, 'some bs', 'utf-8'); | ||
expect(function() { | ||
ios.install(assets, dummy_id, temp, dummyplugin, {}); | ||
}).toThrow('"'+ target + '" already exists!'); | ||
}); | ||
it('should cp the file and directory to the right target location', function() { | ||
var assets = copyArray(valid_assets); | ||
var spy_cp = spyOn(shell, 'cp').andCallThrough(); | ||
var spy_mkdir = spyOn(shell, 'mkdir').andCallThrough(); | ||
ios.install(assets, dummy_id, temp, dummyplugin, {}); | ||
expect(spy_mkdir).toHaveBeenCalledWith('-p', path.join(temp, 'www')); | ||
expect(spy_cp).toHaveBeenCalledWith(path.join(dummyplugin, 'www', 'dummyplugin.js'), path.join(temp, 'www', 'dummyplugin.js')); | ||
expect(spy_cp).toHaveBeenCalledWith('-R', path.join(dummyplugin, 'www', 'dummyplugin/*'), path.join(temp, 'www', 'dummyplugin')); | ||
// make sure the file and directory are properly copies | ||
expect(fs.existsSync(path.join(temp, 'www', 'dummyplugin.js'))).toBe(true); | ||
expect(fs.statSync(path.join(temp, 'www', 'dummyplugin.js')).isFile()).toBe(true); | ||
expect(fs.existsSync(path.join(temp, 'www', 'dummyplugin'))).toBe(true); | ||
expect(fs.statSync(path.join(temp, 'www', 'dummyplugin')).isDirectory()).toBe(true); | ||
expect(fs.existsSync(path.join(temp, 'www', 'dummyplugin', 'image.jpg'))).toBe(true); | ||
expect(fs.statSync(path.join(temp, 'www', 'dummyplugin', 'image.jpg')).isFile()).toBe(true); | ||
}); | ||
}); | ||
// TODO move this shit to install/uninstall | ||
// describe('of <asset> elements', function() { | ||
// beforeEach(function() { | ||
// shell.cp('-rf', ios_config_xml_project, temp); | ||
// }); | ||
// it('should throw if asset src cannot be found', function() { | ||
// var assets = copyArray(invalid_assets); | ||
// expect(function() { | ||
// ios.install(assets, faulty_id, temp, faultyplugin, {}); | ||
// }).toThrow('"' + path.resolve(faultyplugin, 'www/main.js') + '" not found!'); | ||
// }); | ||
// it('should throw if asset target already exists', function() { | ||
// var assets = copyArray(valid_assets); | ||
// var target = path.join(temp, 'www', 'dummyplugin.js'); | ||
// fs.writeFileSync(target, 'some bs', 'utf-8'); | ||
// expect(function() { | ||
// ios.install(assets, dummy_id, temp, dummyplugin, {}); | ||
// }).toThrow('"'+ target + '" already exists!'); | ||
// }); | ||
// it('should cp the file and directory to the right target location', function() { | ||
// var assets = copyArray(valid_assets); | ||
// var spy_cp = spyOn(shell, 'cp').andCallThrough(); | ||
// var spy_mkdir = spyOn(shell, 'mkdir').andCallThrough(); | ||
// ios.install(assets, dummy_id, temp, dummyplugin, {}); | ||
// expect(spy_mkdir).toHaveBeenCalledWith('-p', path.join(temp, 'www')); | ||
// expect(spy_cp).toHaveBeenCalledWith(path.join(dummyplugin, 'www', 'dummyplugin.js'), path.join(temp, 'www', 'dummyplugin.js')); | ||
// expect(spy_cp).toHaveBeenCalledWith('-R', path.join(dummyplugin, 'www', 'dummyplugin/*'), path.join(temp, 'www', 'dummyplugin')); | ||
// | ||
// // make sure the file and directory are properly copies | ||
// expect(fs.existsSync(path.join(temp, 'www', 'dummyplugin.js'))).toBe(true); | ||
// expect(fs.statSync(path.join(temp, 'www', 'dummyplugin.js')).isFile()).toBe(true); | ||
// | ||
// expect(fs.existsSync(path.join(temp, 'www', 'dummyplugin'))).toBe(true); | ||
// expect(fs.statSync(path.join(temp, 'www', 'dummyplugin')).isDirectory()).toBe(true); | ||
// | ||
// expect(fs.existsSync(path.join(temp, 'www', 'dummyplugin', 'image.jpg'))).toBe(true); | ||
// expect(fs.statSync(path.join(temp, 'www', 'dummyplugin', 'image.jpg')).isFile()).toBe(true); | ||
// }); | ||
// }); | ||
@@ -362,28 +363,59 @@ describe('of <framework> elements', function() { | ||
describe('of <source-file> elements', function() { | ||
it('should call into xcodeproj\'s removeSourceFile appropriately when element has no target-dir'); | ||
it('should call into xcodeproj\'s removeSourceFile appropriately when element a no target-dir'); | ||
it('should rm the file from the right target location when element has a target-dir'); | ||
it('should rm the file from the right target location when element has no target-dir'); | ||
}); | ||
describe('of <plugins-plist> elements', function() { | ||
it('should only be used in an applicably old cordova-ios project'); | ||
it('should not be used in an applicably old cordova-ios project'); | ||
it('should remove the <plugin> element in applicably new cordova-ios projects with old-style plugins using only <plugins-plist> elements', function() { | ||
it('should call into xcodeproj\'s removeSourceFile appropriately when element has no target-dir', function(){ | ||
var source = copyArray(valid_source).filter(function(s) { return s.attrib['target-dir'] == undefined}); | ||
shell.cp('-rf', ios_config_xml_project, temp); | ||
var spy = jasmine.createSpy(); | ||
spyOn(xcode, 'project').andReturn({ | ||
parseSync:function(){}, | ||
writeSync:function(){}, | ||
removeSourceFile:spy | ||
}); | ||
ios.uninstall(source, dummy_id, temp, dummyplugin); | ||
expect(spy).toHaveBeenCalledWith(path.join('Plugins', 'DummyPluginCommand.m')); | ||
}); | ||
it('should call into xcodeproj\'s removeSourceFile appropriately when element a target-dir', function(){ | ||
var source = copyArray(valid_source).filter(function(s) { return s.attrib['target-dir'] != undefined}); | ||
shell.cp('-rf', ios_config_xml_project, temp); | ||
var spy = spyOn(shell, 'rm'); | ||
ios.uninstall(source, dummy_id, temp, dummyplugin); | ||
expect(spy).toHaveBeenCalledWith('-rf', path.join(temp, 'SampleApp', 'Plugins', 'targetDir', 'TargetDirTest.m')); | ||
}); | ||
it('should rm the file from the right target location when element has no target-dir', function(){ | ||
var source = copyArray(valid_source).filter(function(s) { return s.attrib['target-dir'] == undefined}); | ||
shell.cp('-rf', ios_config_xml_project, temp); | ||
var spy = spyOn(shell, 'rm'); | ||
ios.uninstall(source, dummy_id, temp, dummyplugin); | ||
expect(spy).toHaveBeenCalledWith('-rf', path.join(temp, 'SampleApp', 'Plugins', 'DummyPluginCommand.m')); | ||
}); | ||
it('should rm the file from the right target location when element has a target-dir', function(){ | ||
var source = copyArray(valid_source).filter(function(s) { return s.attrib['target-dir'] != undefined}); | ||
shell.cp('-rf', ios_config_xml_project, temp); | ||
var spy = spyOn(shell, 'rm'); | ||
ios.uninstall(source, dummy_id, temp, dummyplugin); | ||
expect(spy).toHaveBeenCalledWith('-rf', path.join(temp, 'SampleApp', 'Plugins', 'targetDir', 'TargetDirTest.m')); | ||
}); | ||
}); | ||
describe('of <config-file> elements', function() { | ||
it('should only be used in applicably new cordova-ios projects'); | ||
it('should remove any applicable <plugin> elements in applicably new cordova-ios projects with old-style plugins using only <plugins-plist> elements'); | ||
it('should call xml_helpers\' pruneXML'); | ||
it('should write the new config file out after successfully pruning'); | ||
}); | ||
describe('of <asset> elements', function() { | ||
it('should call rm on specified asset', function() { | ||
beforeEach(function() { | ||
shell.cp('-rf', ios_config_xml_project, temp); | ||
}); | ||
it('should call rm on specified asset',function(){ | ||
var config = copyArray(valid_assets); | ||
var spy = spyOn(common, 'removeFile'); | ||
ios.uninstall(config, dummy_id, temp, dummyplugin); | ||
expect(spy).toHaveBeenCalledWith(path.join(temp, 'www'), 'dummyplugin.js'); | ||
expect(spy).toHaveBeenCalledWith(path.join(temp, 'www'), 'dummyplugin'); | ||
}); | ||
it('should call rm on the www/plugins/<plugin_id> folder', function() { | ||
it('should call rm on the www/plugins/<plugin_id> folder',function(){ | ||
var config = copyArray(valid_assets); | ||
var spy = spyOn(common, 'removeFile'); | ||
ios.uninstall(config, dummy_id, temp, dummyplugin); | ||
expect(spy).toHaveBeenCalledWith(path.join(temp, 'www', 'plugins'), dummy_id); | ||
}); | ||
@@ -393,16 +425,81 @@ }); | ||
describe('of <header-file> elements', function() { | ||
it('should call into xcodeproj\'s removeHeaderFile appropriately when element has no target-dir'); | ||
it('should call into xcodeproj\'s removeHeaderFile appropriately when element a no target-dir'); | ||
it('should rm the file from the right target location'); | ||
beforeEach(function() { | ||
shell.cp('-rf', ios_config_xml_project, temp); | ||
}); | ||
it('should call into xcodeproj\'s removeHeaderFile appropriately when element has no target-dir', function(){ | ||
var headers = copyArray(valid_headers).filter(function(s) { return s.attrib['target-dir'] == undefined}); | ||
var spy = jasmine.createSpy(); | ||
spyOn(xcode, 'project').andReturn({ | ||
parseSync:function(){}, | ||
writeSync:function(){}, | ||
removeHeaderFile:spy | ||
}); | ||
ios.uninstall(headers, dummy_id, temp, dummyplugin); | ||
expect(spy).toHaveBeenCalledWith(path.join('Plugins', 'DummyPluginCommand.h')); | ||
}); | ||
it('should call into xcodeproj\'s removeHeaderFile appropriately when element a target-dir', function(){ | ||
var headers = copyArray(valid_headers).filter(function(s) { return s.attrib['target-dir'] != undefined}); | ||
var spy = jasmine.createSpy(); | ||
spyOn(xcode, 'project').andReturn({ | ||
parseSync:function(){}, | ||
writeSync:function(){}, | ||
removeHeaderFile:spy | ||
}); | ||
ios.uninstall(headers, dummy_id, temp, dummyplugin); | ||
expect(spy).toHaveBeenCalledWith(path.join('Plugins', 'targetDir', 'TargetDirTest.h')); | ||
}); | ||
it('should rm the file from the right target location', function(){ | ||
var headers = copyArray(valid_headers).filter(function(s) { return s.attrib['target-dir'] != undefined}); | ||
var spy = spyOn(shell, 'rm'); | ||
ios.uninstall(headers, dummy_id, temp, dummyplugin); | ||
expect(spy).toHaveBeenCalledWith('-rf', path.join(temp, 'SampleApp', 'Plugins', 'targetDir', 'TargetDirTest.h')); | ||
}); | ||
}); | ||
describe('of <resource-file> elements', function() { | ||
it('should call into xcodeproj\'s removeResourceFile'); | ||
it('should rm the file from the right target location'); | ||
beforeEach(function() { | ||
shell.cp('-rf', ios_config_xml_project, temp); | ||
}); | ||
it('should call into xcodeproj\'s removeResourceFile', function(){ | ||
var resources = copyArray(valid_resources); | ||
var spy = jasmine.createSpy(); | ||
spyOn(xcode, 'project').andReturn({ | ||
parseSync:function(){}, | ||
writeSync:function(){}, | ||
removeResourceFile:spy | ||
}); | ||
ios.uninstall(resources, dummy_id, temp, dummyplugin); | ||
expect(spy).toHaveBeenCalledWith(path.join('Resources', 'DummyPlugin.bundle')); | ||
}); | ||
it('should rm the file from the right target location', function(){ | ||
var resources = copyArray(valid_resources); | ||
var spy = spyOn(shell, 'rm'); | ||
ios.uninstall(resources, dummy_id, temp, dummyplugin); | ||
expect(spy).toHaveBeenCalledWith('-rf', path.join(temp, 'SampleApp', 'Resources', 'DummyPlugin.bundle')); | ||
}); | ||
}); | ||
describe('of <framework> elements', function() { | ||
it('should call into xcodeproj\'s removeFramework'); | ||
beforeEach(function() { | ||
shell.cp('-rf', ios_config_xml_project, temp); | ||
}); | ||
it('should call into xcodeproj\'s removeFramework' ,function() { | ||
var frameworks = copyArray(valid_frameworks).filter(function(f) { return f.attrib.weak == undefined; }); | ||
var spy = jasmine.createSpy(); | ||
spyOn(xcode, 'project').andReturn({ | ||
parseSync:function(){}, | ||
writeSync:function(){}, | ||
removeFramework:spy | ||
}); | ||
ios.uninstall(frameworks, dummy_id, temp, dummyplugin); | ||
expect(spy).toHaveBeenCalledWith(path.join('src', 'ios', 'libsqlite3.dylib')); | ||
}); | ||
}); | ||
}); | ||
}); |
@@ -47,3 +47,3 @@ var uninstall = require('../src/uninstall'), | ||
expect(transactions.length).toEqual(6); | ||
expect(transactions.length).toEqual(1); | ||
expect(transactions[0].tag).toBe('source-file'); | ||
@@ -73,11 +73,6 @@ }); | ||
install('android', temp, 'DummyPlugin', plugins_dir, {}); | ||
// make uninstall fail by removing a js asset | ||
shell.rm(path.join(temp, 'assets', 'www', 'dummyplugin.js')); | ||
var s = spyOn(android, 'install'); | ||
// destroy android manifest /manifest/application so pruneXML fails | ||
var manifest_path = path.join(temp, 'AndroidManifest.xml'); | ||
var manifest = xml_helpers.parseElementtreeSync(manifest_path); | ||
var app_el = manifest.find('application'); | ||
manifest.getroot().remove(0, app_el); | ||
var output = manifest.write({indent:4}); | ||
fs.writeFileSync(manifest_path, output); | ||
uninstall('android', temp, 'DummyPlugin', plugins_dir, {}); | ||
@@ -84,0 +79,0 @@ var executed_txs = s.mostRecentCall.args[0]; |
@@ -11,6 +11,9 @@ var configChanges = require('../../src/util/config-changes'), | ||
dummyplugin = path.join(__dirname, '..', 'plugins', 'DummyPlugin'), | ||
cbplugin = path.join(__dirname, '..', 'plugins', 'ChildBrowser'), | ||
childrenplugin = path.join(__dirname, '..', 'plugins', 'multiple-children'), | ||
shareddepsplugin = path.join(__dirname, '..', 'plugins', 'shared-deps-multi-child'), | ||
configplugin = path.join(__dirname, '..', 'plugins', 'ConfigTestPlugin'), | ||
varplugin = path.join(__dirname, '..', 'plugins', 'VariablePlugin'), | ||
android_two_project = path.join(__dirname, '..', 'projects', 'android_two', '*'), | ||
android_two_no_perms_project = path.join(__dirname, '..', 'projects', 'android_two_no_perms', '*'), | ||
ios_plist_project = path.join(__dirname, '..', 'projects', 'ios-plist', '*'), | ||
@@ -135,3 +138,3 @@ ios_config_xml = path.join(__dirname, '..', 'projects', 'ios-config-xml', '*'), | ||
xdescribe('save_platform_json method', function() { | ||
describe('save_platform_json method', function() { | ||
it('should write out specified json', function() { | ||
@@ -148,4 +151,5 @@ var filepath = path.join(plugins_dir, 'android.json'); | ||
it('should return a flat config heirarchy for simple, one-off config changes', function() { | ||
shell.cp('-rf', android_two_project, temp); | ||
var xml; | ||
var munge = configChanges.generate_plugin_config_munge(dummyplugin, 'android'); | ||
var munge = configChanges.generate_plugin_config_munge(dummyplugin, 'android', temp, {}); | ||
expect(munge['AndroidManifest.xml']).toBeDefined(); | ||
@@ -168,3 +172,4 @@ expect(munge['AndroidManifest.xml']['/manifest/application']).toBeDefined(); | ||
it('should split out multiple children of config-file elements into individual leaves', function() { | ||
var munge = configChanges.generate_plugin_config_munge(childrenplugin, 'android'); | ||
shell.cp('-rf', android_two_project, temp); | ||
var munge = configChanges.generate_plugin_config_munge(childrenplugin, 'android', temp, {}); | ||
expect(munge['AndroidManifest.xml']).toBeDefined(); | ||
@@ -177,8 +182,9 @@ expect(munge['AndroidManifest.xml']['/manifest']).toBeDefined(); | ||
expect(munge['AndroidManifest.xml']['/manifest']['<uses-permission android:name="android.permission.WAKE_LOCK" />']).toBeDefined(); | ||
expect(munge['AndroidManifest.xml']['/manifest']['<permission android:name="$PACKAGE_NAME.permission.C2D_MESSAGE" android:protectionLevel="signature" />']).toBeDefined(); | ||
expect(munge['AndroidManifest.xml']['/manifest']['<uses-permission android:name="$PACKAGE_NAME.permission.C2D_MESSAGE" />']).toBeDefined(); | ||
expect(munge['AndroidManifest.xml']['/manifest']['<permission android:name="com.alunny.childapp.permission.C2D_MESSAGE" android:protectionLevel="signature" />']).toBeDefined(); | ||
expect(munge['AndroidManifest.xml']['/manifest']['<uses-permission android:name="com.alunny.childapp.permission.C2D_MESSAGE" />']).toBeDefined(); | ||
expect(munge['AndroidManifest.xml']['/manifest']['<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />']).toBeDefined(); | ||
}); | ||
it('should not use xml comments as config munge leaves', function() { | ||
var munge = configChanges.generate_plugin_config_munge(childrenplugin, 'android'); | ||
shell.cp('-rf', android_two_project, temp); | ||
var munge = configChanges.generate_plugin_config_munge(childrenplugin, 'android', temp, {}); | ||
expect(munge['AndroidManifest.xml']['/manifest']['<!--library-->']).not.toBeDefined(); | ||
@@ -188,11 +194,32 @@ expect(munge['AndroidManifest.xml']['/manifest']['<!-- GCM connects to Google Services. -->']).not.toBeDefined(); | ||
it('should increment config heirarchy leaves if dfferent config-file elements target the same file + selector + xml', function() { | ||
var munge = configChanges.generate_plugin_config_munge(configplugin, 'android'); | ||
shell.cp('-rf', android_two_project, temp); | ||
var munge = configChanges.generate_plugin_config_munge(configplugin, 'android', temp, {}); | ||
expect(munge['res/xml/config.xml']['/widget']['<poop />']).toEqual(2); | ||
}); | ||
it('should take into account interpolation variables', function() { | ||
var munge = configChanges.generate_plugin_config_munge(childrenplugin, 'android', {PACKAGE_NAME:'ca.filmaj.plugins'}); | ||
shell.cp('-rf', android_two_project, temp); | ||
var munge = configChanges.generate_plugin_config_munge(childrenplugin, 'android', temp, {PACKAGE_NAME:'ca.filmaj.plugins'}); | ||
expect(munge['AndroidManifest.xml']['/manifest']['<uses-permission android:name="ca.filmaj.plugins.permission.C2D_MESSAGE" />']).toBeDefined(); | ||
}); | ||
it('should create munges for platform-agnostic config.xml changes', function() { | ||
shell.cp('-rf', android_two_project, temp); | ||
var munge = configChanges.generate_plugin_config_munge(dummyplugin, 'android', temp, {}); | ||
expect(munge['config.xml']['/*']['<access origin="build.phonegap.com" />']).toBeDefined(); | ||
expect(munge['config.xml']['/*']['<access origin="s3.amazonaws.com" />']).toBeDefined(); | ||
}); | ||
it('should automatically add on ios bundle identifier as PACKAGE_NAME variable for ios config munges', function() { | ||
shell.cp('-rf', ios_config_xml, temp); | ||
var munge = configChanges.generate_plugin_config_munge(varplugin, 'ios', temp, {}); | ||
var expected_xml = '<cfbundleid>com.example.friendstring</cfbundleid>'; | ||
expect(munge['config.xml']['/widget'][expected_xml]).toBeDefined(); | ||
}); | ||
it('should automatically add on app java identifier as PACKAGE_NAME variable for android config munges', function() { | ||
shell.cp('-rf', android_two_project, temp); | ||
var munge = configChanges.generate_plugin_config_munge(varplugin, 'android', temp, {}); | ||
var expected_xml = '<package>com.alunny.childapp</package>'; | ||
expect(munge['AndroidManifest.xml']['/manifest'][expected_xml]).toBeDefined(); | ||
}); | ||
it('should special case plugins-plist elements into own property', function() { | ||
var munge = configChanges.generate_plugin_config_munge(dummyplugin, 'ios', {}); | ||
shell.cp('-rf', ios_config_xml, temp); | ||
var munge = configChanges.generate_plugin_config_munge(dummyplugin, 'ios', temp, {}); | ||
expect(munge['plugins-plist']).toBeDefined(); | ||
@@ -214,3 +241,3 @@ expect(munge['plugins-plist']['com.phonegap.plugins.dummyplugin']).toEqual('DummyPluginCommand'); | ||
configChanges.process(plugins_dir, temp, 'android'); | ||
expect(spy).toHaveBeenCalledWith(path.join(plugins_dir, 'DummyPlugin'), 'android', {}); | ||
expect(spy).toHaveBeenCalledWith(path.join(plugins_dir, 'DummyPlugin'), 'android', temp, {}); | ||
}); | ||
@@ -242,5 +269,7 @@ it('should get a reference to existing config munge by calling get_platform_json', function() { | ||
configChanges.process(plugins_dir, temp, 'android'); | ||
expect(spy.calls.length).toEqual(2); | ||
expect(spy.argsForCall[0][2]).toEqual('/manifest/application'); | ||
expect(spy.argsForCall[1][2]).toEqual('/cordova/plugins'); | ||
expect(spy.calls.length).toEqual(4); | ||
expect(spy.argsForCall[0][2]).toEqual('/*'); | ||
expect(spy.argsForCall[1][2]).toEqual('/*'); | ||
expect(spy.argsForCall[2][2]).toEqual('/manifest/application'); | ||
expect(spy.argsForCall[3][2]).toEqual('/cordova/plugins'); | ||
}); | ||
@@ -250,5 +279,3 @@ it('should not call graftXML for a config munge that already exists from another plugin', function() { | ||
shell.cp('-rf', configplugin, plugins_dir); | ||
var cfg = configChanges.get_platform_json(plugins_dir, 'android'); | ||
cfg.prepare_queue.installed = [{'plugin':'ConfigTestPlugin', 'vars':{}}]; | ||
configChanges.save_platform_json(cfg, plugins_dir, 'android'); | ||
configChanges.add_installed_plugin_to_prepare_queue(plugins_dir, 'ConfigTestPlugin', 'android', {}); | ||
@@ -261,5 +288,3 @@ var spy = spyOn(xml_helpers, 'graftXML').andReturn(true); | ||
shell.cp('-rf', android_two_project, temp); | ||
var cfg = configChanges.get_platform_json(plugins_dir, 'android'); | ||
cfg.prepare_queue.installed = [{'plugin':'DummyPlugin', 'vars':{}}]; | ||
configChanges.save_platform_json(cfg, plugins_dir, 'android'); | ||
configChanges.add_installed_plugin_to_prepare_queue(plugins_dir, 'DummyPlugin', 'android', {}); | ||
@@ -271,7 +296,15 @@ var spy = spyOn(fs, 'readFileSync').andCallThrough(); | ||
}); | ||
it('should resolve wildcard config-file targets to the project, if applicable', function() { | ||
shell.cp('-rf', ios_config_xml, temp); | ||
shell.cp('-rf', cbplugin, plugins_dir); | ||
configChanges.add_installed_plugin_to_prepare_queue(plugins_dir, 'ChildBrowser', 'ios', {}); | ||
var spy = spyOn(fs, 'readFileSync').andCallThrough(); | ||
configChanges.process(plugins_dir, temp, 'ios'); | ||
expect(spy).toHaveBeenCalledWith(path.join(temp, 'SampleApp', 'SampleApp-Info.plist'), 'utf8'); | ||
}); | ||
it('should move successfully installed plugins from queue to installed plugins section, and include/retain vars if applicable', function() { | ||
shell.cp('-rf', android_two_project, temp); | ||
shell.cp('-rf', varplugin, plugins_dir); | ||
var cfg = configChanges.get_platform_json(plugins_dir, 'android'); | ||
cfg.prepare_queue.installed = [{'plugin':'VariablePlugin', 'vars':{"API_KEY":"hi"}}]; | ||
configChanges.save_platform_json(cfg, plugins_dir, 'android'); | ||
configChanges.add_installed_plugin_to_prepare_queue(plugins_dir, 'VariablePlugin', 'android', {"API_KEY":"hi"}); | ||
@@ -288,5 +321,3 @@ configChanges.process(plugins_dir, temp, 'android'); | ||
shell.cp('-rf', varplugin, plugins_dir); | ||
var cfg = configChanges.get_platform_json(plugins_dir, 'android'); | ||
cfg.prepare_queue.installed = [{'plugin':'VariablePlugin', 'vars':{"API_KEY":"hi"}}]; | ||
configChanges.save_platform_json(cfg, plugins_dir, 'android'); | ||
configChanges.add_installed_plugin_to_prepare_queue(plugins_dir, 'VariablePlugin', 'android', {"API_KEY":"hi"}); | ||
@@ -314,14 +345,103 @@ var spy = spyOn(configChanges, 'save_platform_json'); | ||
it('should call pruneXML for every config munge it completely removes from the app (every leaf that is decremented to 0)', function() { | ||
shell.cp('-rf', android_two_project, temp); | ||
// Run through an "install" | ||
configChanges.add_installed_plugin_to_prepare_queue(plugins_dir, 'DummyPlugin', 'android', {}); | ||
configChanges.process(plugins_dir, temp, 'android'); | ||
// Now set up an uninstall and make sure prunexml is called properly | ||
configChanges.add_uninstalled_plugin_to_prepare_queue(plugins_dir, 'DummyPlugin', 'android'); | ||
var spy = spyOn(xml_helpers, 'pruneXML').andReturn(true); | ||
configChanges.process(plugins_dir, temp, 'android'); | ||
expect(spy.calls.length).toEqual(4); | ||
expect(spy.argsForCall[0][2]).toEqual('/*'); | ||
expect(spy.argsForCall[1][2]).toEqual('/*'); | ||
expect(spy.argsForCall[2][2]).toEqual('/manifest/application'); | ||
expect(spy.argsForCall[3][2]).toEqual('/cordova/plugins'); | ||
}); | ||
it('should call pruneXML with variables to interpolate if applicable', function() { | ||
it('should generate a config munge that interpolates variables into config changes, if applicable', function() { | ||
shell.cp('-rf', android_two_project, temp); | ||
shell.cp('-rf', varplugin, plugins_dir); | ||
// Run through an "install" | ||
configChanges.add_installed_plugin_to_prepare_queue(plugins_dir, 'VariablePlugin', 'android', {"API_KEY":"canucks"}); | ||
configChanges.process(plugins_dir, temp, 'android'); | ||
// Now set up an uninstall and make sure prunexml is called properly | ||
configChanges.add_uninstalled_plugin_to_prepare_queue(plugins_dir, 'VariablePlugin', 'android'); | ||
var spy = spyOn(configChanges, 'generate_plugin_config_munge').andReturn({}); | ||
configChanges.process(plugins_dir, temp, 'android'); | ||
var munge_params = spy.mostRecentCall.args; | ||
expect(munge_params[0]).toEqual(path.join(plugins_dir, 'VariablePlugin')); | ||
expect(munge_params[1]).toEqual('android'); | ||
expect(munge_params[2]).toEqual(temp); | ||
expect(munge_params[3]['API_KEY']).toEqual('canucks'); | ||
}); | ||
it('should not call pruneXML for a config munge that another plugin depends on', function() { | ||
shell.cp('-rf', android_two_no_perms_project, temp); | ||
shell.cp('-rf', childrenplugin, plugins_dir); | ||
shell.cp('-rf', shareddepsplugin, plugins_dir); | ||
// Run through and "install" two plugins (they share a permission for INTERNET) | ||
configChanges.add_installed_plugin_to_prepare_queue(plugins_dir, 'multiple-children', 'android', {}); | ||
configChanges.add_installed_plugin_to_prepare_queue(plugins_dir, 'shared-deps-multi-child', 'android', {}); | ||
configChanges.process(plugins_dir, temp, 'android'); | ||
// Now set up an uninstall for multi-child plugin | ||
configChanges.add_uninstalled_plugin_to_prepare_queue(plugins_dir, 'multiple-children', 'android'); | ||
configChanges.process(plugins_dir, temp, 'android'); | ||
var am_xml = new et.ElementTree(et.XML(fs.readFileSync(path.join(temp, 'AndroidManifest.xml'), 'utf-8'))); | ||
var permission = am_xml.find('./uses-permission'); | ||
expect(permission).toBeDefined(); | ||
expect(permission.attrib['android:name']).toEqual('android.permission.INTERNET'); | ||
}); | ||
it('should not call pruneXML for a config munge targeting a config file that does not exist', function() { | ||
shell.cp('-rf', android_two_project, temp); | ||
// install a plugin | ||
configChanges.add_installed_plugin_to_prepare_queue(plugins_dir, 'DummyPlugin', 'android', {}); | ||
configChanges.process(plugins_dir, temp, 'android'); | ||
// set up an uninstall for the same plugin | ||
configChanges.add_uninstalled_plugin_to_prepare_queue(plugins_dir, 'DummyPlugin', 'android', {}); | ||
var spy = spyOn(fs, 'readFileSync').andCallThrough(); | ||
configChanges.process(plugins_dir, temp, 'android'); | ||
expect(spy).not.toHaveBeenCalledWith(path.join(temp, 'res', 'xml', 'plugins.xml'), 'utf-8'); | ||
}); | ||
it('should remove uninstalled plugins from installed plugins list', function() { | ||
shell.cp('-rf', android_two_project, temp); | ||
shell.cp('-rf', varplugin, plugins_dir); | ||
// install the var plugin | ||
configChanges.add_installed_plugin_to_prepare_queue(plugins_dir, 'VariablePlugin', 'android', {"API_KEY":"eat my shorts"}); | ||
configChanges.process(plugins_dir, temp, 'android'); | ||
// queue up an uninstall for the same plugin | ||
configChanges.add_uninstalled_plugin_to_prepare_queue(plugins_dir, 'VariablePlugin', 'android'); | ||
configChanges.process(plugins_dir, temp, 'android'); | ||
var cfg = configChanges.get_platform_json(plugins_dir, 'android'); | ||
expect(cfg.prepare_queue.uninstalled.length).toEqual(0); | ||
expect(cfg.installed_plugins['com.adobe.vars']).not.toBeDefined(); | ||
}); | ||
it('should only parse + remove plist plugin entries in applicably old ios projects', function() { | ||
shell.cp('-rf', ios_plist_project, temp); | ||
// install plugin | ||
configChanges.add_installed_plugin_to_prepare_queue(plugins_dir, 'DummyPlugin', 'ios', {}); | ||
configChanges.process(plugins_dir, temp, 'ios'); | ||
// set up an uninstall | ||
configChanges.add_uninstalled_plugin_to_prepare_queue(plugins_dir, 'DummyPlugin', 'ios'); | ||
var spy = spyOn(plist, 'parseFileSync').andReturn({Plugins:{}}); | ||
configChanges.process(plugins_dir, temp, 'ios'); | ||
expect(spy).toHaveBeenCalledWith(path.join(temp, 'SampleApp', 'PhoneGap.plist')); | ||
}); | ||
it('should save changes to global config munge after completing an uninstall', function() { | ||
shell.cp('-rf', android_two_project, temp); | ||
shell.cp('-rf', varplugin, plugins_dir); | ||
// install a plugin | ||
configChanges.add_installed_plugin_to_prepare_queue(plugins_dir, 'VariablePlugin', 'android', {"API_KEY":"eat my shorts"}); | ||
configChanges.process(plugins_dir, temp, 'android'); | ||
// set up an uninstall for the plugin | ||
configChanges.add_uninstalled_plugin_to_prepare_queue(plugins_dir, 'VariablePlugin', 'android'); | ||
var spy = spyOn(configChanges, 'save_platform_json'); | ||
configChanges.process(plugins_dir, temp, 'android'); | ||
expect(spy).toHaveBeenCalled(); | ||
}); | ||
@@ -328,0 +448,0 @@ }); |
@@ -30,24 +30,2 @@ #!/usr/bin/env node | ||
describe('server', function(){ | ||
it('should receive the correct request when searching for a plugin', function(){ | ||
var mySpy = spyOn(http, 'get').andCallThrough(); | ||
// this clears the timeout in plugins.js | ||
var spyTimeout = spyOn(global, 'setTimeout'); | ||
plugins.getPluginInfo('ChildBrowser', function() {}); | ||
expect(mySpy).toHaveBeenCalled(); | ||
expect(mySpy.argsForCall[0][0]).toBe('http://plugins.cordova.io/cordova_plugins/_design/cordova_plugins/_view/by_name?key="ChildBrowser"'); | ||
}); | ||
it('should receive the correct request when searching for a list of plugins', function(){ | ||
var mySpy = spyOn(http, 'get').andCallThrough(); | ||
// this clears the timeout in plugins.js | ||
var spyTimeout = spyOn(global, 'setTimeout'); | ||
plugins.listAllPlugins(function(){}); | ||
expect(mySpy).toHaveBeenCalled(); | ||
expect(mySpy.argsForCall[0][0]).toBe('http://plugins.cordova.io/cordova_plugins/_design/cordova_plugins/_view/by_name'); | ||
}); | ||
it('should be able to receive the correct git clone arguments', function(){ | ||
@@ -54,0 +32,0 @@ var mySpy = spyOn(plugins, 'clonePluginGitRepo'); |
@@ -34,40 +34,96 @@ /* | ||
describe('xml-helpers', function(){ | ||
it('should return false for different tags', function(){ | ||
expect(xml_helpers.equalNodes(usesNetworkOne, title)).toBe(false); | ||
describe('equalNodes', function() { | ||
it('should return false for different tags', function(){ | ||
expect(xml_helpers.equalNodes(usesNetworkOne, title)).toBe(false); | ||
}); | ||
it('should return true for identical tags', function(){ | ||
expect(xml_helpers.equalNodes(usesNetworkOne, usesNetworkTwo)).toBe(true); | ||
}); | ||
it('should return false for different attributes', function(){ | ||
expect(xml_helpers.equalNodes(usesNetworkOne, usesReceive)).toBe(false); | ||
}); | ||
it('should distinguish between text', function(){ | ||
expect(xml_helpers.equalNodes(helloTagOne, goodbyeTag)).toBe(false); | ||
}); | ||
it('should ignore whitespace in text', function(){ | ||
expect(xml_helpers.equalNodes(helloTagOne, helloTagTwo)).toBe(true); | ||
}); | ||
describe('should compare children', function(){ | ||
it('by child quantity', function(){ | ||
var one = et.XML('<i><b>o</b></i>'), | ||
two = et.XML('<i><b>o</b><u></u></i>'); | ||
expect(xml_helpers.equalNodes(one, two)).toBe(false); | ||
}); | ||
it('by child equality', function(){ | ||
var one = et.XML('<i><b>o</b></i>'), | ||
two = et.XML('<i><u></u></i>'), | ||
uno = et.XML('<i>\n<b>o</b>\n</i>'); | ||
expect(xml_helpers.equalNodes(one, uno)).toBe(true); | ||
expect(xml_helpers.equalNodes(one, two)).toBe(false); | ||
}); | ||
}); | ||
}); | ||
describe('pruneXML', function() { | ||
var config_xml; | ||
it('should return true for identical tags', function(){ | ||
expect(xml_helpers.equalNodes(usesNetworkOne, usesNetworkTwo)).toBe(true); | ||
}); | ||
it('should return false for different attributes', function(){ | ||
expect(xml_helpers.equalNodes(usesNetworkOne, usesReceive)).toBe(false); | ||
}); | ||
it('should distinguish between text', function(){ | ||
expect(xml_helpers.equalNodes(helloTagOne, goodbyeTag)).toBe(false); | ||
}); | ||
it('should ignore whitespace in text', function(){ | ||
expect(xml_helpers.equalNodes(helloTagOne, helloTagTwo)).toBe(true); | ||
}); | ||
describe('should compare children', function(){ | ||
it('by child quantity', function(){ | ||
var one = et.XML('<i><b>o</b></i>'), | ||
two = et.XML('<i><b>o</b><u></u></i>'); | ||
expect(xml_helpers.equalNodes(one, two)).toBe(false); | ||
beforeEach(function() { | ||
config_xml = xml_helpers.parseElementtreeSync(path.join(__dirname, '..', 'projects', 'android_two', 'res', 'xml', 'config.xml')); | ||
}); | ||
it('by child equality', function(){ | ||
var one = et.XML('<i><b>o</b></i>'), | ||
two = et.XML('<i><u></u></i>'), | ||
uno = et.XML('<i>\n<b>o</b>\n</i>'); | ||
expect(xml_helpers.equalNodes(one, uno)).toBe(true); | ||
expect(xml_helpers.equalNodes(one, two)).toBe(false); | ||
it('should remove any children that match the specified selector', function() { | ||
var children = config_xml.findall('plugins/plugin'); | ||
xml_helpers.pruneXML(config_xml, children, 'plugins'); | ||
expect(config_xml.find('plugins').getchildren().length).toEqual(0); | ||
}); | ||
}); | ||
it('should do nothing if the children cannot be found', function() { | ||
var children = [title]; | ||
xml_helpers.pruneXML(config_xml, children, 'plugins'); | ||
expect(config_xml.find('plugins').getchildren().length).toEqual(17); | ||
}); | ||
it('should be able to handle absolute selectors', function() { | ||
var children = config_xml.findall('plugins/plugin'); | ||
xml_helpers.pruneXML(config_xml, children, '/cordova/plugins'); | ||
expect(config_xml.find('plugins').getchildren().length).toEqual(0); | ||
}); | ||
it('should be able to handle absolute selectors with wildcards', function() { | ||
var children = config_xml.findall('plugins/plugin'); | ||
xml_helpers.pruneXML(config_xml, children, '/*/plugins'); | ||
expect(config_xml.find('plugins').getchildren().length).toEqual(0); | ||
}); | ||
}); | ||
describe('graftXML', function() { | ||
var config_xml, plugin_xml; | ||
beforeEach(function() { | ||
config_xml = xml_helpers.parseElementtreeSync(path.join(__dirname, '..', 'projects', 'android_two', 'res', 'xml', 'config.xml')); | ||
plugin_xml = xml_helpers.parseElementtreeSync(path.join(__dirname, '..', 'plugins', 'ChildBrowser', 'plugin.xml')); | ||
}); | ||
it('should add children to the specified selector', function() { | ||
var children = plugin_xml.find('config-file').getchildren(); | ||
xml_helpers.graftXML(config_xml, children, 'plugins'); | ||
expect(config_xml.find('plugins').getchildren().length).toEqual(19); | ||
}); | ||
it('should be able to handle absolute selectors', function() { | ||
var children = plugin_xml.find('config-file').getchildren(); | ||
xml_helpers.graftXML(config_xml, children, '/cordova'); | ||
expect(config_xml.findall('access').length).toEqual(3); | ||
}); | ||
it('should be able to handle absolute selectors with wildcards', function() { | ||
var children = plugin_xml.find('config-file').getchildren(); | ||
xml_helpers.graftXML(config_xml, children, '/*'); | ||
expect(config_xml.findall('access').length).toEqual(3); | ||
}); | ||
}); | ||
}); |
@@ -19,12 +19,2 @@ var shell = require('shelljs'), | ||
} | ||
} else if(!fs.existsSync(plugin_dir)) { | ||
if (link) { | ||
var err = new Error('--link is not supported for name installations'); | ||
if (callback) callback(err); | ||
else throw err; | ||
} else { | ||
plugins.getPluginInfo(plugin_dir, function(err, plugin_info) { | ||
plugins.clonePluginGitRepo(plugin_info.url, plugins_dir, callback); | ||
}); | ||
} | ||
} else { | ||
@@ -38,4 +28,3 @@ // Copy from the local filesystem. | ||
} else { | ||
// XXX if you don't path.resolve(plugin_dir) and plugin_dir has a trailing slash shelljs shits itself. | ||
shell.cp('-R', path.resolve(plugin_dir), plugins_dir); // Yes, not dest. | ||
shell.cp('-R', plugin_dir, plugins_dir); // Yes, not dest. | ||
} | ||
@@ -42,0 +31,0 @@ |
@@ -83,4 +83,2 @@ var path = require('path'), | ||
} | ||
// TODO: handle asset elements | ||
// TODO: if plugin does not have platform tag but has platform-agnostic config changes, should we queue it up? | ||
@@ -92,4 +90,3 @@ var platformTag = plugin_et.find('./platform[@name="'+platform+'"]'); | ||
// should call prepare probably! | ||
require('./../plugman').prepare(project_dir, platform, plugins_dir); | ||
if (callback) callback(); | ||
finalizeInstall(project_dir, plugins_dir, platform, plugin_basename, filtered_variables, callback); | ||
return; | ||
@@ -109,8 +106,36 @@ } | ||
txs = txs.concat(sourceFiles, headerFiles, resourceFiles, frameworks, assets); | ||
// asset installation | ||
var installedAssets = []; | ||
var common = require('./platforms/common'); | ||
try { | ||
for(var i = 0, j = assets.length ; i < j ; i++) { | ||
var src = assets[i].attrib['src'], | ||
target = assets[i].attrib['target']; | ||
common.copyFile(plugin_dir, src, handler.www_dir(project_dir), target); | ||
installedAssets.push(assets[i]); | ||
} | ||
} catch(err) { | ||
var issue = 'asset installation failed\n'+err.stack+'\n'; | ||
try { | ||
// removing assets and reverting install | ||
for(var i = 0, j = installedAssets.length ; i < j ; i++) { | ||
common.removeFile(handler.www_dir(project_dir), installedAssets[i].target); | ||
} | ||
common.removeFileF(path.resolve(handler.www_dir(project_dir), 'plugins', plugin_id)); | ||
issue += 'but successfully reverted\n'; | ||
} catch(err2) { | ||
issue += 'and reversion failed :(\n' + err2.stack; | ||
} | ||
var error = new Error(issue); | ||
if (callback) callback(error); | ||
else throw error; | ||
} | ||
txs = txs.concat(sourceFiles, headerFiles, resourceFiles, frameworks); | ||
// pass platform-specific transactions into install | ||
handler.install(txs, plugin_id, project_dir, plugin_dir, filtered_variables, function(err) { | ||
if (err) { | ||
// FAIL | ||
// TODO revert assets at this point too | ||
if (err. transactions) { | ||
@@ -134,8 +159,4 @@ handler.uninstall(err.transactions.executed, plugin_id, project_dir, plugin_dir, function(superr) { | ||
} else { | ||
// WIN! | ||
// queue up the plugin so prepare knows what to do. | ||
config_changes.add_installed_plugin_to_prepare_queue(plugins_dir, path.basename(plugin_dir), platform, filtered_variables); | ||
// call prepare after a successful install | ||
require('./../plugman').prepare(project_dir, platform, plugins_dir); | ||
// Log out plugin INFO element contents in case additional install steps are necessary | ||
@@ -146,5 +167,15 @@ var info = platformTag.findall('./info'); | ||
} | ||
if (callback) callback(); | ||
finalizeInstall(project_dir, plugins_dir, platform, plugin_basename, filtered_variables, callback); | ||
} | ||
}); | ||
} | ||
function finalizeInstall(project_dir, plugins_dir, platform, plugin_name, variables, callback) { | ||
// queue up the plugin so prepare knows what to do. | ||
config_changes.add_installed_plugin_to_prepare_queue(plugins_dir, plugin_name, platform, variables); | ||
// call prepare after a successful install | ||
require('./../plugman').prepare(project_dir, platform, plugins_dir); | ||
if (callback) callback(); | ||
} |
@@ -37,2 +37,10 @@ /* | ||
return path.join(project_dir, 'assets', 'www'); | ||
}, | ||
// reads the package name out of the Android Manifest file | ||
// @param string project_dir the absolute path to the directory containing the project | ||
// @return string the name of the package | ||
package_name:function (project_dir) { | ||
var mDoc = xml_helpers.parseElementtreeSync(path.join(project_dir, 'AndroidManifest.xml')); | ||
return mDoc._root.attrib['package']; | ||
} | ||
@@ -42,3 +50,2 @@ }; | ||
function handlePlugin(action, plugin_id, txs, project_dir, plugin_dir, variables, callback) { | ||
var PACKAGE_NAME = androidPackageName(project_dir); | ||
variables = variables || {}; | ||
@@ -69,24 +76,2 @@ | ||
break; | ||
case 'config-file': | ||
// Only modify config files that exist. | ||
var config_file = path.resolve(project_dir, mod.attrib['target']); | ||
if (fs.existsSync(config_file)) { | ||
var xmlDoc = xml_helpers.parseElementtreeSync(config_file); | ||
var selector = mod.attrib["parent"]; | ||
var children = mod.findall('*'); | ||
if (action == 'install') { | ||
if (!xml_helpers.graftXML(xmlDoc, children, selector)) { | ||
throw new Error('failed to add config-file children to "' + selector + '" to "'+ config_file + '"'); | ||
} | ||
} else { | ||
if (!xml_helpers.pruneXML(xmlDoc, children, selector)) { | ||
throw new Error('failed to remove config-file children from "' + selector + '" from "' + config_file + '"'); | ||
} | ||
} | ||
var output = xmlDoc.write({indent: 4}); | ||
fs.writeFileSync(config_file, output); | ||
} | ||
break; | ||
case 'asset': | ||
@@ -119,20 +104,3 @@ var src = mod.attrib['src']; | ||
if (action == 'install') { | ||
variables['PACKAGE_NAME'] = androidPackageName(project_dir); | ||
var config_filename = path.resolve(project_dir, 'res', 'xml', 'config.xml'); | ||
if (!fs.existsSync(config_filename)) config_filename = path.resolve(project_dir, 'res', 'xml', 'plugins.xml'); | ||
plugins_module.searchAndReplace(config_filename, variables); | ||
plugins_module.searchAndReplace(path.resolve(project_dir, 'AndroidManifest.xml'), variables); | ||
} | ||
if (callback) callback(); | ||
} | ||
// reads the package name out of the Android Manifest file | ||
// @param string project_dir the absolute path to the directory containing the project | ||
// @return string the name of the package | ||
function androidPackageName(project_dir) { | ||
var mDoc = xml_helpers.parseElementtreeSync(path.resolve(project_dir, 'AndroidManifest.xml')); | ||
return mDoc._root.attrib['package']; | ||
} |
@@ -37,2 +37,7 @@ /* | ||
return path.join(project_dir, 'www'); | ||
}, | ||
package_name:function(project_dir) { | ||
var config_path = path.join(module.exports.www_dir(project_dir), 'config.xml'); | ||
var widget_doc = new et.ElementTree(et.XML(fs.readFileSync(config_path, 'utf-8'))); | ||
return widget_doc._root.attrib['id']; | ||
} | ||
@@ -56,24 +61,2 @@ }; | ||
break; | ||
case 'config-file': | ||
// Only modify config files that exist. | ||
var config_file = path.resolve(project_dir, mod.attrib['target']); | ||
if (fs.existsSync(config_file)) { | ||
var xmlDoc = xml_helpers.parseElementtreeSync(config_file); | ||
var selector = mod.attrib["parent"]; | ||
var children = mod.findall('*'); | ||
if (action == 'install') { | ||
if (!xml_helpers.graftXML(xmlDoc, children, selector)) { | ||
throw new Error('failed to add config-file children to "' + filename + '"'); | ||
} | ||
} else { | ||
if (!xml_helpers.pruneXML(xmlDoc, children, selector)) { | ||
throw new Error('failed to remove config-file children from "' + filename + '"'); | ||
} | ||
} | ||
var output = xmlDoc.write({indent: 4}); | ||
fs.writeFileSync(config_file, output); | ||
} | ||
break; | ||
case 'asset': | ||
@@ -80,0 +63,0 @@ var src = mod.attrib['src']; |
@@ -42,2 +42,6 @@ /* | ||
return path.join(project_dir, 'www'); | ||
}, | ||
package_name:function(project_dir) { | ||
var plist_file = glob.sync(path.join(project_dir, '**', '*-Info.plist'))[0]; | ||
return plist.parseFileSync(plist_file).CFBundleIdentifier; | ||
} | ||
@@ -102,3 +106,2 @@ }; | ||
var destFile = path.resolve(targetDir, path.basename(src)); | ||
if (action == 'install') { | ||
@@ -111,87 +114,10 @@ if (!fs.existsSync(srcFile)) throw new Error('cannot find "' + srcFile + '" ios <source-file>'); | ||
} else { | ||
xcodeproj.removeSourceFile(path.join('Plugins', path.basename(src))); | ||
xcodeproj.removeSourceFile(path.join('Plugins', path.relative(pluginsDir, destFile))); | ||
shell.rm('-rf', destFile); | ||
// TODO: is this right, should we check if dir is empty? | ||
shell.rm('-rf', targetDir); | ||
} | ||
break; | ||
case 'plugins-plist': | ||
var name = mod.attrib.key; | ||
var value = mod.attrib.string; | ||
// Tack on stuff into plist if this is an old-style project | ||
if (path.extname(config_file) == '.plist') { | ||
// determine if this is a binary or ascii plist and choose the parser | ||
// TODO: this is temporary until binary support is added to node-plist | ||
var pl = (isBinaryPlist(config_file) ? bplist : plist); | ||
var plistObj = pl.parseFileSync(config_file); | ||
if (action == 'install') { | ||
// TODO: move to prepare? | ||
// add hosts to whitelist (ExternalHosts) in plist | ||
/* | ||
hosts && hosts.forEach(function(host) { | ||
plistObj.ExternalHosts.push(host.attrib['origin']); | ||
}); | ||
*/ | ||
// add plugin to plist | ||
plistObj.Plugins[name] = value; | ||
} else { | ||
delete plistObj.Plugins[name]; | ||
if(fs.existsSync(targetDir) && fs.readdirSync(targetDir).length>0){ | ||
shell.rm('-rf', targetDir); | ||
} | ||
// write out plist | ||
fs.writeFileSync(config_file, plist.build(plistObj)); | ||
} else { | ||
// If it's a config.xml-based project, we should still add/remove plugin entry to config.xml | ||
var xmlDoc = xml_helpers.parseElementtreeSync(config_file); | ||
var pluginsEl = xmlDoc.find('plugins'); | ||
// Only add if it's not already there. | ||
if (pluginsEl.findall('./plugin[@name="' + name + '"]').length === 0) { | ||
if ( action == 'install') { | ||
var new_plugin = new et.Element('plugin'); | ||
new_plugin.attrib.name = name; | ||
new_plugin.attrib.value = value; | ||
pluginsEl.append(new_plugin); | ||
} else { | ||
var culprit = pluginsEl.find("plugin[@name='"+name+"']"); | ||
pluginsEl.remove(0, culprit); | ||
} | ||
var output = xmlDoc.write({indent: 4}); | ||
fs.writeFileSync(config_file, output); | ||
} | ||
} | ||
break; | ||
case 'config-file': | ||
// Only use config file appropriate for the current cordova-ios project | ||
if (mod.attrib['target'] == config_filename) { | ||
// edit configuration files | ||
var xmlDoc = xml_helpers.parseElementtreeSync(config_file); | ||
var selector = mod.attrib["parent"], | ||
children = mod.findall('*'); | ||
if( action == 'install') { | ||
// Throw error if plugin was already added. | ||
if (children.length == 1 && children[0].tag.toLowerCase() == 'plugin' && (xmlDoc.find('plugins').findall('./plugin[@name="' + children[0].attrib.name + '"]').length === 1) | ||
||(xmlDoc.find('plugins').findall('./plugin[@value="' + children[0].attrib.value + '"]').length === 1)){ | ||
throw new Error('faild to add '+children[0].attrib.name+' to config.xml because it already exists'); | ||
} | ||
if (!xml_helpers.graftXML(xmlDoc, children, selector)) { | ||
throw new Error('failed to add config-file children to xpath "' + selector + '" in "' + config_file + '" because xpath selector could not be resolved.'); | ||
} | ||
} else { | ||
// Ignore if plugin was already removed. | ||
if (children.length == 1 && children[0].tag.toLowerCase() == 'plugin' && xmlDoc.find('plugins').findall('./plugin[@name="' + children[0].attrib.name + '"]').length === 0) break; | ||
if (!xml_helpers.pruneXML(xmlDoc, children, selector)) { | ||
throw new Error('failed to remove config-file children from "' + selector + '" from "' + config_path + '"'); | ||
} | ||
} | ||
var output = xmlDoc.write({indent: 4}); | ||
fs.writeFileSync(config_file, output); | ||
} | ||
break; | ||
case 'asset': | ||
@@ -204,3 +130,3 @@ var src = mod.attrib['src']; | ||
common.removeFile(module.exports.www_dir(project_dir), target); | ||
common.removeFile(path.resolve(module.exports.www_dir(project_dir), 'plugins', plugin_id)); | ||
common.removeFile(path.join(module.exports.www_dir(project_dir), 'plugins'), plugin_id); | ||
} | ||
@@ -221,3 +147,3 @@ break; | ||
// TODO: doesnt preserve-dirs affect what the originally-added path to xcodeproj (see above) affect how we should call remove too? | ||
xcodeproj.removeHeaderFile(path.join('Plugins', path.basename(src))); | ||
xcodeproj.removeHeaderFile(path.join('Plugins', path.relative(pluginsDir, destFile))); | ||
shell.rm('-rf', destFile); | ||
@@ -254,3 +180,3 @@ // TODO: again.. is this right? same as source-file | ||
default: | ||
throw new Error('Unrecognized plugin.xml element/action in android installer: ' + mod.tag); | ||
throw new Error('Unrecognized plugin.xml element/action in ios installer: ' + mod.tag); | ||
break; | ||
@@ -270,12 +196,5 @@ } | ||
} | ||
// write out xcodeproj file | ||
fs.writeFileSync(pbxPath, xcodeproj.writeSync()); | ||
if (action == 'install') { | ||
variables['PACKAGE_NAME'] = plist.parseFileSync(projectPListPath).CFBundleIdentifier; | ||
searchAndReplace(pbxPath, variables); | ||
searchAndReplace(projectPListPath, variables); | ||
searchAndReplace(config_file, variables); | ||
} | ||
if (callback) callback(); | ||
@@ -282,0 +201,0 @@ } |
@@ -35,6 +35,3 @@ var path = require('path'), | ||
// TODO: remove any asset elements | ||
var platformTag = plugin_et.find('./platform[@name="'+platform+'"]'); | ||
var platformTag = plugin_et.find('./platform[@name="'+platform+'"]'); | ||
if (!platformTag) { | ||
@@ -56,9 +53,34 @@ // Either this plugin doesn't support this platform, or it's a JS-only plugin. | ||
assets = platformTag.findall('./asset'), | ||
frameworks = platformTag.findall('./framework'), | ||
pluginsPlist = platformTag.findall('./plugins-plist'), | ||
configChanges = platformTag.findall('./config-file'); | ||
frameworks = platformTag.findall('./framework'); | ||
assets = assets.concat(plugin_et.findall('./asset')); | ||
txs = txs.concat(sourceFiles, headerFiles, resourceFiles, frameworks, configChanges, assets, pluginsPlist); | ||
// asset uninstallation | ||
var uninstalledAssets = []; | ||
var common = require('./platforms/common'); | ||
try { | ||
for(var i = 0, j = assets.length ; i < j ; i++) { | ||
common.removeFile(handler.www_dir(project_dir), assets[i].attrib['target']); | ||
uninstalledAssets.push(assets[i]); | ||
} | ||
common.removeFileF(path.resolve(handler.www_dir(project_dir), 'plugins', plugin_id)); | ||
} catch(err) { | ||
var issue = 'asset uninstallation failed\n'+err.stack+'\n'; | ||
try { | ||
// adding assets back | ||
for(var i = 0, j = uninstalledAssets.length ; i < j ; i++) { | ||
var src = uninstalledAssets[i].attrib['src'], | ||
target = uninstalledAssets[i].attrib['target']; | ||
common.copyFile(plugin_dir, src, handler.www_dir(project_dir), target); | ||
} | ||
issue += 'but successfully reverted\n'; | ||
} catch(err2) { | ||
issue += 'and reversion failed :(\n' + err2.stack; | ||
} | ||
var error = new Error(issue); | ||
if (callback) callback(error); | ||
else throw error; | ||
} | ||
txs = txs.concat(sourceFiles, headerFiles, resourceFiles, frameworks); | ||
// pass platform-specific transactions into uninstall | ||
@@ -68,2 +90,3 @@ handler.uninstall(txs, plugin_id, project_dir, plugin_dir, function(err) { | ||
// FAIL | ||
// TODO revert assets here too | ||
if (err. transactions) { | ||
@@ -70,0 +93,0 @@ handler.install(err.transactions.executed, plugin_id, project_dir, plugin_dir, cli_variables, function(superr) { |
@@ -27,3 +27,4 @@ /* | ||
platforms = require('./../platforms'), | ||
xml_helpers = require('./../util/xml-helpers'); | ||
xml_helpers = require('./../util/xml-helpers'), | ||
plist_helpers = require('./../util/plist-helpers'); | ||
@@ -72,5 +73,12 @@ function checkPlatform(platform) { | ||
}, | ||
generate_plugin_config_munge:function(plugin_dir, platform, vars) { | ||
generate_plugin_config_munge:function(plugin_dir, platform, project_dir, vars) { | ||
checkPlatform(platform); | ||
vars = vars || {}; | ||
var platform_handler = platforms[platform]; | ||
// Add PACKAGE_NAME variable into vars | ||
if (!vars['PACKAGE_NAME']) { | ||
vars['PACKAGE_NAME'] = platform_handler.package_name(project_dir); | ||
} | ||
var munge = {}; | ||
@@ -80,23 +88,31 @@ var plugin_xml = new et.ElementTree(et.XML(fs.readFileSync(path.join(plugin_dir, 'plugin.xml'), 'utf-8'))); | ||
var platformTag = plugin_xml.find('platform[@name="' + platform + '"]'); | ||
var changes = platformTag.findall('config-file'); | ||
// note down plugins-plist munges in special section of munge obj | ||
var plugins_plist = platformTag.findall('plugins-plist'); | ||
plugins_plist.forEach(function(pl) { | ||
if (!munge['plugins-plist']) { | ||
munge['plugins-plist'] = {}; | ||
} | ||
var key = pl.attrib['key']; | ||
var value = pl.attrib['string']; | ||
if (!munge['plugins-plist'][key]) { | ||
munge['plugins-plist'][key] = value; | ||
} | ||
}); | ||
var changes = []; | ||
// add platform-agnostic config changes | ||
changes = changes.concat(plugin_xml.findall('config-file')); | ||
if (platformTag) { | ||
// add platform-specific config changes if they exist | ||
changes = changes.concat(platformTag.findall('config-file')); | ||
// note down plugins-plist munges in special section of munge obj | ||
var plugins_plist = platformTag.findall('plugins-plist'); | ||
plugins_plist.forEach(function(pl) { | ||
if (!munge['plugins-plist']) { | ||
munge['plugins-plist'] = {}; | ||
} | ||
var key = pl.attrib['key']; | ||
var value = pl.attrib['string']; | ||
if (!munge['plugins-plist'][key]) { | ||
munge['plugins-plist'][key] = value; | ||
} | ||
}); | ||
} | ||
changes.forEach(function(change) { | ||
var target = change.attrib['target']; | ||
var parent = change.attrib['parent']; | ||
if (!munge[target]) { | ||
munge[target] = {}; | ||
} | ||
if (!munge[target][change.attrib['parent']]) { | ||
munge[target][change.attrib['parent']] = {}; | ||
if (!munge[target][parent]) { | ||
munge[target][parent] = {}; | ||
} | ||
@@ -113,6 +129,6 @@ var xmls = change.getchildren(); | ||
// 2. add into munge | ||
if (!munge[target][change.attrib['parent']][stringified]) { | ||
munge[target][change.attrib['parent']][stringified] = 0; | ||
if (!munge[target][parent][stringified]) { | ||
munge[target][parent][stringified] = 0; | ||
} | ||
munge[target][change.attrib['parent']][stringified] += 1; | ||
munge[target][parent][stringified] += 1; | ||
}); | ||
@@ -130,6 +146,6 @@ }); | ||
var plugin_id = u.id; | ||
var plugin_vars = platform_config.installed_plugins[plugin_id].vars; | ||
var plugin_vars = platform_config.installed_plugins[plugin_id]; | ||
// get config munge, aka how did this plugin change various config files | ||
var config_munge = module.exports.generate_plugin_config_munge(plugin_dir, platform, plugin_vars); | ||
var config_munge = module.exports.generate_plugin_config_munge(plugin_dir, platform, project_dir, plugin_vars); | ||
// global munge looks at all plugins' changes to config files | ||
@@ -170,10 +186,21 @@ var global_munge = platform_config.config_munge; | ||
if (fs.existsSync(filepath)) { | ||
var doc = new et.ElementTree(et.XML(fs.readFileSync(filepath, 'utf-8'))); | ||
var xml_to_prune = [new et.ElementTree(et.XML(xml_child)).getroot()]; | ||
if (xml_helpers.pruneXML(doc, xml_to_prune, selector)) { | ||
// were good, write out the file! | ||
fs.writeFileSync(filepath, doc.write(), 'utf-8'); | ||
if (path.extname(filepath) == '.xml') { | ||
var xml_to_prune = [et.XML(xml_child)]; | ||
var doc = new et.ElementTree(et.XML(fs.readFileSync(filepath, 'utf-8'))); | ||
if (xml_helpers.pruneXML(doc, xml_to_prune, selector)) { | ||
// were good, write out the file! | ||
fs.writeFileSync(filepath, doc.write(), 'utf-8'); | ||
} else { | ||
// uh oh | ||
throw new Error('pruning xml at selector "' + selector + '" from "' + filepath + '" during config uninstall went bad :('); | ||
} | ||
} else { | ||
// uh oh | ||
throw new Error('pruning xml during config uninstall went bad :('); | ||
// plist file | ||
var pl = (isBinaryPlist(filepath) ? bplist : plist); | ||
var plistObj = pl.parseFileSync(filepath); | ||
if (plist_helpers.prunePLIST(plistObj, xml_child, selector)) { | ||
fs.writeFileSync(filepath, plist.build(plistObj)); | ||
} else { | ||
throw new Error('grafting to plist "' + filepath + '" during config install went bad :('); | ||
} | ||
} | ||
@@ -205,3 +232,3 @@ } | ||
// get config munge, aka how should this plugin change various config files | ||
var config_munge = module.exports.generate_plugin_config_munge(plugin_dir, platform, plugin_vars); | ||
var config_munge = module.exports.generate_plugin_config_munge(plugin_dir, platform, project_dir, plugin_vars); | ||
// global munge looks at all plugins' changes to config files | ||
@@ -246,10 +273,22 @@ var global_munge = platform_config.config_munge; | ||
if (fs.existsSync(filepath)) { | ||
var doc = new et.ElementTree(et.XML(fs.readFileSync(filepath, 'utf-8'))); | ||
var xml_to_graft = [new et.ElementTree(et.XML(xml_child)).getroot()]; | ||
if (xml_helpers.graftXML(doc, xml_to_graft, selector)) { | ||
// were good, write out the file! | ||
fs.writeFileSync(filepath, doc.write(), 'utf-8'); | ||
// look at ext and do proper config change based on file type | ||
if (path.extname(filepath) == '.xml') { | ||
var xml_to_graft = [et.XML(xml_child)]; | ||
var doc = new et.ElementTree(et.XML(fs.readFileSync(filepath, 'utf-8'))); | ||
if (xml_helpers.graftXML(doc, xml_to_graft, selector)) { | ||
// were good, write out the file! | ||
fs.writeFileSync(filepath, doc.write(), 'utf-8'); | ||
} else { | ||
// uh oh | ||
throw new Error('grafting xml at selector "' + selector + '" from "' + filepath + '" during config install went bad :('); | ||
} | ||
} else { | ||
// uh oh | ||
throw new Error('grafting xml during config install went bad :('); | ||
// plist file | ||
var pl = (isBinaryPlist(filepath) ? bplist : plist); | ||
var plistObj = pl.parseFileSync(filepath); | ||
if (plist_helpers.graftPLIST(plistObj, xml_child, selector)) { | ||
fs.writeFileSync(filepath, plist.build(plistObj)); | ||
} else { | ||
throw new Error('grafting to plist "' + filepath + '" during config install went bad :('); | ||
} | ||
} | ||
@@ -265,3 +304,3 @@ } | ||
// Move to installed_plugins | ||
platform_config.installed_plugins[plugin_id] = plugin_vars; | ||
platform_config.installed_plugins[plugin_id] = plugin_vars || {}; | ||
}); | ||
@@ -286,8 +325,17 @@ | ||
// Some config-file target attributes are not qualified with a full leading directory, or contain wildcards. resolve to a real path in this function | ||
function resolveConfigFilePath(project_dir, platform, file) { | ||
var filepath = path.join(project_dir, file); | ||
if (platform == 'ios' && file == 'config.xml') { | ||
filepath = glob.sync(path.join(project_dir, '**', 'config.xml'))[0]; | ||
if (file.indexOf('*') > -1) { | ||
// handle wildcards in targets using glob. | ||
var matches = glob.sync(path.join(project_dir, '**', file)); | ||
if (matches.length) filepath = matches[0]; | ||
} else { | ||
// special-case config.xml target that is just "config.xml". this should be resolved to the real location of the file. | ||
if (file == 'config.xml') { | ||
var matches = glob.sync(path.join(project_dir, '**', 'config.xml')); | ||
if (matches.length) filepath = matches[0]; | ||
} | ||
} | ||
return filepath; | ||
} |
@@ -26,23 +26,22 @@ /* | ||
// adds node to doc at selector | ||
exports.graftPLIST = function (doc, nodes, selector) { | ||
var text = et.tostring(nodes, { xml_declaration: false }); | ||
obj = plist.parseStringSync("<plist>"+text+"</plist>"); | ||
module.exports = { | ||
graftPLIST:function (doc, xml, selector) { | ||
var obj = plist.parseStringSync("<plist>"+xml+"</plist>"); | ||
var node = doc[selector]; | ||
if (node && Array.isArray(node) && Array.isArray(obj)) | ||
doc[selector] = node.concat(obj); | ||
else | ||
doc[selector] = obj; | ||
var node = doc[selector]; | ||
if (node && Array.isArray(node) && Array.isArray(obj)) | ||
doc[selector] = node.concat(obj); | ||
else | ||
doc[selector] = obj; | ||
return true; | ||
} | ||
return true; | ||
}, | ||
// removes node from doc at selector | ||
prunePLIST:function(doc, xml, selector) { | ||
var obj = plist.parseStringSync("<plist>"+xml+"</plist>"); | ||
pruneOBJECT(doc, selector, obj); | ||
// removes node from doc at selector | ||
exports.prunePLIST = function(doc, nodes, selector) { | ||
var text = et.tostring(nodes, { xml_declaration: false }), | ||
obj = plist.parseStringSync("<plist>"+text+"</plist>"); | ||
pruneOBJECT(doc, selector, obj); | ||
return true; | ||
return true; | ||
} | ||
} | ||
@@ -91,2 +90,1 @@ | ||
}; | ||
@@ -22,8 +22,7 @@ #!/usr/bin/env node | ||
var http = require('http'), | ||
osenv = require('osenv'), | ||
os = require('os'), | ||
path = require('path'), | ||
fs = require('fs'), | ||
util = require('util'), | ||
shell = require('shelljs'), | ||
remote = require(path.join(__dirname, '..', '..', 'config', 'remote')); | ||
shell = require('shelljs'); | ||
@@ -33,47 +32,2 @@ module.exports = { | ||
// Fetches plugin information from remote server | ||
getPluginInfo:function(plugin_name, callback) { | ||
var responded = false; | ||
http.get(remote.url + util.format(remote.query_path, plugin_name), function(res) { | ||
var str = ''; | ||
res.on('data', function (chunk) { | ||
str += chunk; | ||
}); | ||
res.on('end', function () { | ||
responded = true; | ||
var response, plugin_info; | ||
if((response = JSON.parse(str)).rows.length == 1) { | ||
plugin_info = response.rows[0].value; | ||
callback(null, plugin_info); | ||
} else { | ||
callback("Could not find information on "+plugin_name+" plugin"); | ||
} | ||
}); | ||
}).on('error', function(e) { | ||
callback(e); | ||
}); | ||
setTimeout(function() { | ||
if (!responded) { | ||
console.log('timed out'); | ||
callback('timed out') | ||
} | ||
}, 3000); | ||
}, | ||
listAllPlugins:function(success, error) { | ||
http.get(remote.url + remote.list_path, function(res) { | ||
var str = ''; | ||
res.on('data', function (chunk) { | ||
str += chunk; | ||
}); | ||
res.on('end', function () { | ||
var plugins = (JSON.parse(str)).rows; | ||
success(plugins); | ||
}); | ||
}).on('error', function(e) { | ||
console.log("Got error: " + e.message); | ||
error(e.message); | ||
}); | ||
}, | ||
clonePluginGitRepo:function(plugin_git_url, plugins_dir, callback) { | ||
@@ -85,11 +39,4 @@ if(!shell.which('git')) { | ||
} | ||
// use osenv to get a temp directory in a portable way | ||
var lastSlash = plugin_git_url.lastIndexOf('/'); | ||
var basename = plugin_git_url.substring(lastSlash+1); | ||
var dotGitIndex = basename.lastIndexOf('.git'); | ||
if (dotGitIndex >= 0) { | ||
basename = basename.substring(0, dotGitIndex); | ||
} | ||
var plugin_dir = path.join(plugins_dir, basename); | ||
var plugin_dir = path.join(plugins_dir, path.basename(plugin_git_url).replace(path.extname(plugin_git_url), '')); | ||
@@ -111,18 +58,4 @@ // trash it if it already exists (something went wrong before probably) | ||
}); | ||
} | ||
// TODO add method for archives and other formats | ||
// extractArchive:function(plugin_dir) { | ||
// } | ||
// TODO add method to publish plugin from cli | ||
// publishPlugin:function(plugin_dir) { | ||
// } | ||
// TODO add method to unpublish plugin from cli | ||
// unpublishPlugin:function(plugin_dir) { | ||
// } | ||
}, | ||
}; | ||
@@ -76,24 +76,5 @@ /* | ||
graftXML: function(doc, nodes, selector) { | ||
var ROOT = /^\/([^\/]*)/ | ||
, ABSOLUTE = /^\/([^\/]*)\/(.*)/ | ||
, parent, tagName, subSelector; | ||
var parent = resolveParent(doc, selector); | ||
if (!parent) return false; | ||
// handle absolute selector (which elementtree doesn't like) | ||
if (ROOT.test(selector)) { | ||
tagName = selector.match(ROOT)[1]; | ||
if (tagName === doc._root.tag) { | ||
parent = doc._root; | ||
// could be an absolute path, but not selecting the root | ||
if (ABSOLUTE.test(selector)) { | ||
subSelector = selector.match(ABSOLUTE)[2]; | ||
parent = parent.find(subSelector) | ||
} | ||
} else { | ||
return false; | ||
} | ||
} else { | ||
parent = doc.find(selector) | ||
} | ||
nodes.forEach(function (node) { | ||
@@ -111,23 +92,5 @@ // check if child is unique first | ||
pruneXML: function(doc, nodes, selector) { | ||
var ROOT = /^\/([^\/]*)/ | ||
, ABSOLUTE = /^\/([^\/]*)\/(.*)/ | ||
, parent, tagName, subSelector; | ||
var parent = resolveParent(doc, selector); | ||
if (!parent) return false; | ||
// handle absolute selector (which elementtree doesn't like) | ||
if (ROOT.test(selector)) { | ||
tagName = selector.match(ROOT)[1]; | ||
if (tagName === doc._root.tag) { | ||
parent = doc._root; | ||
// could be an absolute path, but not selecting the root | ||
if (ABSOLUTE.test(selector)) { | ||
subSelector = selector.match(ABSOLUTE)[2]; | ||
parent = parent.find(subSelector) | ||
} | ||
} else { | ||
return false; | ||
} | ||
} else { | ||
parent = doc.find(selector) | ||
} | ||
nodes.forEach(function (node) { | ||
@@ -156,3 +119,3 @@ var matchingKid = null; | ||
for (i = 0, j = matchingKids.length ; i < j ; i++) { | ||
if (exports.equalNodes(node, matchingKids[i])) { | ||
if (module.exports.equalNodes(node, matchingKids[i])) { | ||
return matchingKids[i]; | ||
@@ -180,1 +143,26 @@ } | ||
var ROOT = /^\/([^\/]*)/, | ||
ABSOLUTE = /^\/([^\/]*)\/(.*)/; | ||
function resolveParent(doc, selector) { | ||
var parent, tagName, subSelector; | ||
// handle absolute selector (which elementtree doesn't like) | ||
if (ROOT.test(selector)) { | ||
tagName = selector.match(ROOT)[1]; | ||
// test for wildcard "any-tag" root selector | ||
if (tagName == '*' || tagName === doc._root.tag) { | ||
parent = doc._root; | ||
// could be an absolute path, but not selecting the root | ||
if (ABSOLUTE.test(selector)) { | ||
subSelector = selector.match(ABSOLUTE)[2]; | ||
parent = parent.find(subSelector) | ||
} | ||
} else { | ||
return false; | ||
} | ||
} else { | ||
parent = doc.find(selector) | ||
} | ||
return parent; | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
3895169
167
3984
34
14
485