30 |
31 | > **Advanced workflows for building rock-solid Ionic apps**: develop, prototype, test, build and deliver high quality apps with Yeoman, Gulp, Bower, Angular, Cordova and of course Ionic. All in one sexy generator.
32 |
33 | **[Read more ... ](https://github.com/mwaylabs/generator-m-ionic/tree/master/docs/intro/why_you_need_it.md)**
34 |
35 | ### What's in the box
36 |
37 |
38 |
78 |
79 |
80 |
81 | **[Read more ...](https://github.com/mwaylabs/generator-m-ionic/tree/master/docs/intro/whats_in_the_box.md)**
82 |
83 | ### What's new
84 | 1.9.0
85 | - **Livereload** for the device! Wohoo! See [how it's done](https://github.com/mwaylabs/generator-m-ionic/blob/master/docs/guides/development_intro.md#run-on-device-or-emulator-with-livereload).
86 | - **Testing workflow** improvements
87 | - **Precommit hooks** and others in a new [Husky Guide](https://github.com/mwaylabs/generator-m-ionic/tree/master/docs/guides/testing_workflow.md)
88 | - **gulp protractor** now returns, allowing it to be used with husky, travis, jenkins, ...
89 | - **Questions** the generator asks are documented and explained in the [Questions](https://github.com/mwaylabs/generator-m-ionic/tree/master/docs/guides/questions.md) document
90 | - [read more ...](https://github.com/mwaylabs/generator-m-ionic/releases/tag/1.9.0)
91 |
92 | ## Quick Start
93 | - [Quick Start](https://github.com/mwaylabs/generator-m-ionic/tree/master/docs/intro/quick_start.md) for the experienced developer.
94 | - [Try the demo](https://github.com/mwaylabs/generator-m-ionic-demo). Get a quick impression by cloning the sample project generated with the latest version of Generator-M-Ionic.
95 |
96 |
97 | ## Guides
98 | ##### Setup
99 | - [Installation and Prerequisites](https://github.com/mwaylabs/generator-m-ionic/tree/master/docs/guides/installation_prerequisites.md)
100 | - [Questions](https://github.com/mwaylabs/generator-m-ionic/tree/master/docs/guides/questions.md) the generator will ask and what they mean
101 |
102 | ##### Basics
103 | - [Development Introduction](https://github.com/mwaylabs/generator-m-ionic/tree/master/docs/guides/development_intro.md)
104 | - [File structure](https://github.com/mwaylabs/generator-m-ionic/tree/master/docs/guides/file_structure.md)
105 | - [Sub-generators](https://github.com/mwaylabs/generator-m-ionic/tree/master/docs/guides/sub_generators.md) for adding new components.
106 | - [Git integration](https://github.com/mwaylabs/generator-m-ionic/tree/master/docs/guides/git_integration.md), see how it's done.
107 | - [Sass integration](https://github.com/mwaylabs/generator-m-ionic/tree/master/docs/guides/sass_integration.md) in our module concept.
108 | - [Bower component usage](https://github.com/mwaylabs/generator-m-ionic/tree/master/docs/guides/bower_component_usage.md) in our module concept.
109 | - [Ionic style source](https://github.com/mwaylabs/generator-m-ionic/tree/master/docs/guides/ionic_style_source.md), change it from CSS to Sass or vice versa.
110 |
111 | ##### Quality
112 | - [ESLint](https://github.com/mwaylabs/generator-m-ionic/tree/master/docs/guides/eslint.md) code style checks and setting up your IDE/Editor.
113 | - [Testing](https://github.com/mwaylabs/generator-m-ionic/tree/master/docs/guides/testing.md) with our testing setup.
114 | - [Husky hooks](https://github.com/mwaylabs/generator-m-ionic/tree/master/docs/guides/testing_workflow.md), automatically run linting and tests before you commit.
115 |
116 | ##### Advanced
117 | - [CORS & Proxying](https://github.com/mwaylabs/generator-m-ionic/tree/master/docs/guides/cors_proxy.md), how to cope with CORS issues.
118 | - [App Icons and splash screens](https://github.com/mwaylabs/generator-m-ionic/tree/master/docs/guides/icons_splash_screens.md), a simple setup or different sets for different builds - all is possible.
119 | - [Use Environments](https://github.com/mwaylabs/generator-m-ionic/tree/master/docs/guides/environments.md) manage different API Endpoints and much more with just a single parameter.
120 | - [Gulp defaults](https://github.com/mwaylabs/generator-m-ionic/tree/master/docs/guides/gulp_defaults.md), spare yourself some tedious typing on the command line.
121 | - [Generator Update (experimental)](https://github.com/mwaylabs/generator-m-ionic/tree/master/docs/guides/generator_update.md) can help you update to a new generator version.
122 |
123 |
124 | ##### Building & Continuous Integration
125 | - [Build Vars](https://github.com/mwaylabs/generator-m-ionic/tree/master/docs/guides/build_vars.md), inject vars into your app at build time.
126 | - [Programmatically change the `config.xml`](https://github.com/mwaylabs/generator-m-ionic/tree/master/docs/guides/programmatically_change_configxml.md), an essential part for a successful continuous integration setup. Add environments and build vars for a full blown continuous integration use case!
127 |
128 | ##### Ecosystems
129 | - [Ionic Platform](https://github.com/mwaylabs/generator-m-ionic/tree/master/docs/ecosystems/ionic_platform.md) (beta) - A cloud platform for managing and scaling cross-platform mobile apps
130 | - [Appmobi](https://github.com/mwaylabs/generator-m-ionic/tree/master/docs/ecosystems/appmobi.md) - Secure Mobile Development Platform
131 | - [ApiOmat](https://github.com/mwaylabs/generator-m-ionic/tree/master/docs/ecosystems/apiomat.md) (beta) - Enterprise Backend as a Service
132 |
133 | ## Generator Insights
134 | We've published 3 blog articles on our company blog delivering deep insights into the why and how of the generator:
135 | - September 2015: [Generator-M-Ionic and the search for the holy grail](http://blog.mwaysolutions.com/2015/09/21/generator-m-ionic-and-the-search-for-the-holy-grail/)
136 | - rather **technical comparison** between the generator and similar tools as well as technical insights to the **decisions and motivation** behind the generator
137 | - September 2015: [Generator-M-Ionic: HTML5 mobile app development evolved](http://blog.mwaysolutions.com/2015/09/10/generator-m-ionic-html5-mobile-app-development-evolved/)
138 | - provides insight to the **technology choices and ecosystem** and the **benefits of using the generator**
139 | - March 2015: [Generator-M: the state of HTML5 mobile app development at M-Way](http://blog.mwaysolutions.com/2015/03/26/generator-m-the-state-of-html5-mobile-app-development-at-m-way/)
140 | - the **origins** of the generator development and **company strategy**
141 |
142 |
143 | ## Questions, issues? Talk to us!
144 | Do the following:
145 | 1. check out our [Issue Guidelines](https://github.com/mwaylabs/generator-m-ionic/tree/master/docs/contribute/issue_guide.md) and [issues](https://github.com/mwaylabs/generator-m-ionic/issues) to see if there already is a solution or answer.
146 | 2. [](https://gitter.im/mwaylabs/generator-m-ionic?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) - Get in touch with other developers and our core team.
147 | 3. If all fails, make sure you have read the [Issue Guidelines](https://github.com/mwaylabs/generator-m-ionic/tree/master/docs/contribute/issue_guide.md) **first** and then [open a new issue](https://github.com/mwaylabs/generator-m-ionic/issues/new).
148 |
149 | ## Want to contribute ideas, code?
150 | Start by reading our:
151 |
152 | 1. [Mission Statement](https://github.com/mwaylabs/generator-m-ionic/tree/master/docs/contribute/mission_statement.md)
153 | 2. [Contribution Guide](https://github.com/mwaylabs/generator-m-ionic/tree/master/docs/contribute/contribution_guide.md)
154 |
155 |
156 | ## License
157 | Code licensed under MIT. Docs under Apache 2. PhoneGap is a trademark of Adobe.
158 |
--------------------------------------------------------------------------------
/mobile-app/hooks/after_prepare/update_platform_config.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | 'use strict';
3 |
4 | // taken from https://github.com/diegonetto/generator-ionic/blob/master/templates/hooks/after_prepare/update_platform_config.js
5 |
6 | /** This hook updates platform configuration files based on preferences and config-file data defined in config.xml.
7 | Currently only the AndroidManifest.xml and IOS *-Info.plist file are supported.
8 |
9 | Preferences:
10 | 1. Preferences defined outside of the platform element will apply to all platforms
11 | 2. Preferences defined inside a platform element will apply only to the specified platform
12 | 3. Platform preferences take precedence over common preferences
13 | 4. The preferenceMappingData object contains all of the possible custom preferences to date including the
14 | target file they belong to, parent element, and destination element or attribute
15 |
16 | Config Files
17 | 1. config-file elements MUST be defined inside a platform element, otherwise they will be ignored.
18 | 2. config-file target attributes specify the target file to update. (AndroidManifest.xml or *-Info.plist)
19 | 3. config-file parent attributes specify the parent element (AndroidManifest.xml) or parent key (*-Info.plist)
20 | that the child data will replace or be appended to.
21 | 4. config-file elements are uniquely indexed by target AND parent for each platform.
22 | 5. If there are multiple config-file's defined with the same target AND parent, the last config-file will be used
23 | 6. Elements defined WITHIN a config-file will replace or be appended to the same elements relative to the parent element
24 | 7. If a unique config-file contains multiples of the same elements (other than uses-permssion elements which are
25 | selected by by the uses-permission name attribute), the last defined element will be retrieved.
26 |
27 | Examples:
28 |
29 | AndroidManifest.xml
30 | NOTE: For possible manifest values see http://developer.android.com/guide/topics/manifest/manifest-intro.html
31 |
32 |
33 | //These preferences are actually available in Cordova by default although not currently documented
34 |
35 |
36 |
37 |
38 | //custom preferences examples
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
52 |
53 |
54 |
55 |
56 |
57 |
58 | *-Info.plist
59 |
60 |
61 |
62 |
63 | UIInterfaceOrientationLandscapeOmg
64 |
65 |
66 |
67 |
68 | someValue
69 |
70 |
71 |
72 | NOTE: Currently, items aren't removed from the platform config files if you remove them from config.xml.
73 | For example, if you add a custom permission, build the remove it, it will still be in the manifest.
74 | If you make a mistake, for example adding an element to the wrong parent, you may need to remove and add your platform,
75 | or revert to your previous manifest/plist file.
76 |
77 | TODO: We may need to capture all default manifest/plist elements/keys created by Cordova along with any plugin elements/keys to compare against custom elements to remove.
78 | */
79 |
80 | // global vars
81 | var fs = require('fs');
82 | var path = require('path');
83 | var _ = require('lodash');
84 | var et = require('elementtree');
85 | var plist = require('plist');
86 |
87 | var rootdir = path.resolve(__dirname, '../../');
88 |
89 | var platformConfig = (function () {
90 | /* Global object that defines the available custom preferences for each platform.
91 | Maps a config.xml preference to a specific target file, parent element, and destination attribute or element
92 | */
93 | var preferenceMappingData = {
94 | 'android': {
95 | 'android-manifest-hardwareAccelerated': {
96 | target: 'AndroidManifest.xml',
97 | parent: './',
98 | destination: 'android:hardwareAccelerated'
99 | },
100 | 'android-installLocation': {
101 | target: 'AndroidManifest.xml',
102 | parent: './',
103 | destination: 'android:installLocation'
104 | },
105 | 'android-activity-hardwareAccelerated': {
106 | target: 'AndroidManifest.xml',
107 | parent: 'application',
108 | destination: 'android:hardwareAccelerated'
109 | },
110 | 'android-configChanges': {
111 | target: 'AndroidManifest.xml',
112 | parent: 'application/activity[@android:name=\'CordovaApp\']',
113 | destination: 'android:configChanges'
114 | },
115 | 'android-launchMode': {
116 | target: 'AndroidManifest.xml',
117 | parent: 'application/activity[@android:name=\'CordovaApp\']',
118 | destination: 'android:launchMode'
119 | },
120 | 'android-theme': {
121 | target: 'AndroidManifest.xml',
122 | parent: 'application/activity[@android:name=\'CordovaApp\']',
123 | destination: 'android:theme'
124 | },
125 | 'android-windowSoftInputMode': {
126 | target: 'AndroidManifest.xml',
127 | parent: 'application/activity[@android:name=\'CordovaApp\']',
128 | destination: 'android:windowSoftInputMode'
129 | }
130 | },
131 | 'ios': {}
132 | };
133 | var configXmlData, preferencesData;
134 |
135 | return {
136 | // Parses a given file into an elementtree object
137 | parseElementtreeSync: function (filename) {
138 | var contents = fs.readFileSync(filename, 'utf-8');
139 | if (contents) {
140 | //Windows is the BOM. Skip the Byte Order Mark.
141 | contents = contents.substring(contents.indexOf('<'));
142 | }
143 | return new et.ElementTree(et.XML(contents));
144 | },
145 |
146 | // Converts an elementtree object to an xml string. Since this is used for plist values, we don't care about attributes
147 | eltreeToXmlString: function (data) {
148 | var tag = data.tag;
149 | var el = '<' + tag + '>';
150 |
151 | if (data.text && data.text.trim()) {
152 | el += data.text.trim();
153 | } else {
154 | _.each(data.getchildren(), function (child) {
155 | el += platformConfig.eltreeToXmlString(child);
156 | });
157 | }
158 |
159 | el += '' + tag + '>';
160 | return el;
161 | },
162 |
163 | // Parses the config.xml into an elementtree object and stores in the config object
164 | getConfigXml: function () {
165 | if (!configXmlData) {
166 | configXmlData = this.parseElementtreeSync(path.join(rootdir, 'config.xml'));
167 | }
168 |
169 | return configXmlData;
170 | },
171 |
172 | /* Retrieves all from config.xml and returns a map of preferences with platform as the key.
173 | If a platform is supplied, common prefs + platform prefs will be returned, otherwise just common prefs are returned.
174 | */
175 | getPreferences: function (platform) {
176 | var configXml = this.getConfigXml();
177 |
178 | //init common config.xml prefs if we haven't already
179 | if (!preferencesData) {
180 | preferencesData = {
181 | common: configXml.findall('preference')
182 | };
183 | }
184 |
185 | var prefs = preferencesData.common || [];
186 | if (platform) {
187 | if (!preferencesData[platform]) {
188 | preferencesData[platform] = configXml.findall('platform[@name=\'' + platform + '\']/preference');
189 | }
190 | prefs = prefs.concat(preferencesData[platform]);
191 | }
192 |
193 | return prefs;
194 | },
195 |
196 | /* Retrieves all configured xml for a specific platform/target/parent element nested inside a platforms config-file
197 | element within the config.xml. The config-file elements are then indexed by target|parent so if there are
198 | any config-file elements per platform that have the same target and parent, the last config-file element is used.
199 | */
200 | getConfigFilesByTargetAndParent: function (platform) {
201 | var configFileData = this.getConfigXml().findall('platform[@name=\'' + platform + '\']/config-file');
202 |
203 | return _.keyBy(configFileData, function (item) {
204 | var parent = item.attrib.parent;
205 | //if parent attribute is undefined /* or */, set parent to top level elementree selector
206 | if (!parent || parent === '/*' || parent === '*/') {
207 | parent = './';
208 | }
209 | return item.attrib.target + '|' + parent;
210 | });
211 | },
212 |
213 | // Parses the config.xml's preferences and config-file elements for a given platform
214 | parseConfigXml: function (platform) {
215 | var configData = {};
216 | this.parsePreferences(configData, platform);
217 | this.parseConfigFiles(configData, platform);
218 |
219 | return configData;
220 | },
221 |
222 | // Retrieves the config.xml's pereferences for a given platform and parses them into JSON data
223 | parsePreferences: function (configData, platform) {
224 | var preferences = this.getPreferences(platform),
225 | type = 'preference';
226 |
227 | _.each(preferences, function (preference) {
228 | var prefMappingData = preferenceMappingData[platform][preference.attrib.name],
229 | target,
230 | prefData;
231 |
232 | if (prefMappingData) {
233 | prefData = {
234 | parent: prefMappingData.parent,
235 | type: type,
236 | destination: prefMappingData.destination,
237 | data: preference
238 | };
239 |
240 | target = prefMappingData.target;
241 | if (!configData[target]) {
242 | configData[target] = [];
243 | }
244 | configData[target].push(prefData);
245 | }
246 | });
247 | },
248 |
249 | // Retrieves the config.xml's config-file elements for a given platform and parses them into JSON data
250 | parseConfigFiles: function (configData, platform) {
251 | var configFiles = this.getConfigFilesByTargetAndParent(platform),
252 | type = 'configFile';
253 |
254 | _.each(configFiles, function (configFile, key) {
255 | var keyParts = key.split('|');
256 | var target = keyParts[0];
257 | var parent = keyParts[1];
258 | var items = configData[target] || [];
259 |
260 | _.each(configFile.getchildren(), function (element) {
261 | items.push({
262 | parent: parent,
263 | type: type,
264 | destination: element.tag,
265 | data: element
266 | });
267 | });
268 |
269 | configData[target] = items;
270 | });
271 | },
272 |
273 | // Parses config.xml data, and update each target file for a specified platform
274 | updatePlatformConfig: function (platform) {
275 | var configData = this.parseConfigXml(platform),
276 | platformPath = path.join(rootdir, 'platforms', platform);
277 |
278 | _.each(configData, function (configItems, targetFileName) {
279 | var projectName, targetFile;
280 |
281 | if (platform === 'ios' && targetFileName.indexOf('Info.plist') > -1) {
282 | projectName = platformConfig.getConfigXml().findtext('name');
283 | targetFile = path.join(platformPath, projectName, projectName + '-Info.plist');
284 | platformConfig.updateIosPlist(targetFile, configItems);
285 | } else if (platform === 'android' && targetFileName === 'AndroidManifest.xml') {
286 | targetFile = path.join(platformPath, targetFileName);
287 | platformConfig.updateAndroidManifest(targetFile, configItems);
288 | }
289 | });
290 | },
291 |
292 | // Updates the AndroidManifest.xml target file with data from config.xml
293 | updateAndroidManifest: function (targetFile, configItems) {
294 | var tempManifest = platformConfig.parseElementtreeSync(targetFile),
295 | root = tempManifest.getroot();
296 |
297 | _.each(configItems, function (item) {
298 | // if parent is not found on the root, child/grandchild nodes are searched
299 | var parentEl = root.find(item.parent) || root.find('*/' + item.parent),
300 | data = item.data,
301 | childSelector = item.destination,
302 | childEl;
303 |
304 | if (!parentEl) {
305 | return;
306 | }
307 |
308 | if (item.type === 'preference') {
309 | parentEl.attrib[childSelector] = data.attrib.value;
310 | } else {
311 | // since there can be multiple uses-permission elements, we need to select them by unique name
312 | if (childSelector === 'uses-permission') {
313 | childSelector += '[@android:name=\'' + data.attrib['android:name'] + '\']';
314 | }
315 |
316 | childEl = parentEl.find(childSelector);
317 | // if child element doesnt exist, create new element
318 | if (!childEl) {
319 | childEl = new et.Element(item.destination);
320 | parentEl.append(childEl);
321 | }
322 |
323 | // copy all config.xml data except for the generated _id property
324 | _.each(data, function (prop, propName) {
325 | if (propName !== '_id') {
326 | childEl[propName] = prop;
327 | }
328 | });
329 | }
330 | });
331 |
332 | fs.writeFileSync(targetFile, tempManifest.write({
333 | indent: 4
334 | }), 'utf-8');
335 | },
336 |
337 | /* Updates the *-Info.plist file with data from config.xml by parsing to an xml string, then using the plist
338 | module to convert the data to a map. The config.xml data is then replaced or appended to the original plist file
339 | */
340 | updateIosPlist: function (targetFile, configItems) {
341 | var infoPlist = plist.parse(fs.readFileSync(targetFile, 'utf-8')),
342 | tempInfoPlist;
343 |
344 | _.each(configItems, function (item) {
345 | var key = item.parent;
346 | var plistXml = '' + key + '';
347 | plistXml += platformConfig.eltreeToXmlString(item.data) + '';
348 |
349 | var configPlistObj = plist.parse(plistXml);
350 | infoPlist[key] = configPlistObj[key];
351 | });
352 |
353 | tempInfoPlist = plist.build(infoPlist);
354 | tempInfoPlist = tempInfoPlist.replace(/[\s\r\n]*<\/string>/g, '');
355 | fs.writeFileSync(targetFile, tempInfoPlist, 'utf-8');
356 | }
357 | };
358 | })();
359 |
360 | // Main
361 | (function () {
362 | if (rootdir) {
363 | // go through each of the platform directories that have been prepared
364 | var platforms = _.filter(fs.readdirSync('platforms'), function (file) {
365 | return fs.statSync(path.resolve('platforms', file)).isDirectory();
366 | });
367 |
368 | _.each(platforms, function (platform) {
369 | try {
370 | platform = platform.trim().toLowerCase();
371 | platformConfig.updatePlatformConfig(platform);
372 | } catch (e) {
373 | process.stdout.write(e);
374 | }
375 | });
376 | }
377 | })();
378 |
--------------------------------------------------------------------------------