├── .bowerrc ├── .envrc.android-sample ├── .envrc.ios-sample ├── .gitignore ├── .gitmodules ├── .npmignore ├── LICENSE ├── README.md ├── app ├── css │ ├── app.scss │ └── ionic.scss ├── index.jade ├── js │ ├── common │ │ ├── factories │ │ │ ├── form_factory.coffee │ │ │ ├── observer_factory.coffee │ │ │ └── promise_factory.coffee │ │ └── services │ │ │ └── auth.coffee │ ├── config │ │ ├── 001-globals.coffee │ │ ├── 005-window_utils.coffee │ │ ├── 006-weinre.coffee │ │ ├── 009-livereload.coffee │ │ ├── 010-initialize_app.coffee │ │ ├── 011-set_globals_as_app_constants.coffee │ │ ├── 012-app_run.coffee │ │ ├── google_analytics.coffee │ │ ├── http.coffee │ │ ├── ionic_config.coffee │ │ ├── logger.coffee │ │ ├── rollbar.coffee │ │ ├── weinre.coffee │ │ └── window_injector.coffee │ ├── features │ │ └── pets │ │ │ ├── pet_detail_ctrl.coffee │ │ │ ├── pet_index_ctrl.coffee │ │ │ └── pet_service.coffee │ └── routes.coffee └── templates │ ├── about.jade │ ├── adopt.jade │ ├── pet-detail.jade │ ├── pet-index.jade │ └── tabs.jade ├── assets ├── config.xml.ejs ├── img │ ├── favicon.png │ ├── logo-square.png │ ├── splash-landscape.png │ └── splash-portrait.png └── js │ └── rollbar.js ├── bower.json ├── gulp ├── config.coffee ├── main.coffee ├── tasks.coffee └── tasks │ ├── base │ ├── bower-install.coffee │ └── clean.coffee │ ├── build │ ├── assets.coffee │ ├── build.coffee │ ├── minify.coffee │ ├── scripts.coffee │ ├── styles.coffee │ ├── templates.coffee │ └── views.coffee │ ├── cordova.coffee │ ├── default.coffee │ ├── deploy.coffee │ ├── help.coffee │ ├── release.coffee │ ├── server │ ├── serve.coffee │ ├── watch.coffee │ └── weinre.coffee │ └── test │ ├── e2e.coffee │ └── unit.coffee ├── gulpfile.js ├── hooks ├── after_compile │ └── 090-copy_cordova_platform_assets_to_www.sh ├── after_platform_add │ ├── 010_configure_android_keys.sh │ └── 030_install_plugins.sh ├── after_prepare │ └── 050_generate_res_assets.sh ├── before_compile │ └── 010_configure_ios_keys.sh └── before_platform_add │ └── 010_clear_current_platform.sh ├── ionic.project ├── keys ├── android │ └── .gitkeep └── ios │ └── .gitkeep ├── package.json ├── platforms └── .gitignore ├── plugins └── .gitignore ├── test ├── e2e │ ├── app_test.coffee │ ├── protractor.config.js │ └── test_helper.coffee └── unit │ ├── factories │ └── user.coffee │ ├── helpers │ ├── app_helpers.coffee │ ├── factory_girl.coffee │ ├── factory_girl_test.coffee │ └── test_helpers.coffee │ ├── js │ └── factories │ │ └── promise_factory_test.coffee │ ├── karma.conf.coffee │ └── tests-config.coffee └── utils ├── testfairy-upload-android.sh └── testfairy-upload-ios.sh /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "assets/components" 3 | } 4 | -------------------------------------------------------------------------------- /.envrc.android-sample: -------------------------------------------------------------------------------- 1 | # Copy this file as '.envrc' and fill in the variables. 2 | 3 | export ANDROID_KEYSTORE_NAME="ionicstarter" 4 | export ANDROID_KEYSTORE_PASSWORD="123" 5 | export ANDROID_ALIAS_NAME="ionicstarter" 6 | export ANDROID_ALIAS_PASSWORD="456" 7 | -------------------------------------------------------------------------------- /.envrc.ios-sample: -------------------------------------------------------------------------------- 1 | # Copy this file as '.envrc' and fill in the variables. 2 | # 3 | # If you are on Mac OSX, and you want to be able to compile both platforms: 4 | # - no problem! 5 | # Just merge this file with '.envrc.android-sample' and rename it to '.envrc'. 6 | 7 | # Find the name by `security find-identity | sed -n 's/.*\("[^"]*"\).*/\1/p' | grep 'iPhone'` 8 | export IOS_CODE_SIGN_IDENTITY='"iPhone Distribution: Adam Kowalsky Organization (123JASD82J)"' 9 | 10 | # Content of your iPhone Distribution key. 11 | # As you can see, it can be generated automatically. 12 | export IOS_SIGN_KEY=`security find-identity | grep "$IOS_CODE_SIGN_IDENTITY" | awk '{print $2}' | head -n 1` 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /platforms/^.* 2 | /keys 3 | 4 | /node_modules 5 | /assets/components 6 | 7 | /www 8 | 9 | /.envrc 10 | /npm-debug.log 11 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "vendor/plugins/ionic-plugins-keyboard"] 2 | path = vendor/plugins/ionic-plugins-keyboard 3 | url = https://github.com/driftyco/ionic-plugins-keyboard 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /app 2 | /assets 3 | /keys 4 | /merges 5 | /test 6 | 7 | /.bowerrc 8 | /.envrc* 9 | /ionic.project 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | License of ionic-angular-cordova-seed, from which we have started this project: 2 | 3 | The MIT License (MIT) 4 | 5 | Copyright (c) 2014 Drifty 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy of 8 | this software and associated documentation files (the "Software"), to deal in 9 | the Software without restriction, including without limitation the rights to 10 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 11 | the Software, and to permit persons to whom the Software is furnished to do so, 12 | subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 19 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 20 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 21 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 22 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Features 2 | 3 | * Application can be run in a local http server, or emulated/released to Android/iOS 4 | * A lot of useful gulp tasks, like: 5 | * `gulp` - watch for changes + browser-sync (http server with livereload) + weinre debugger 6 | * `gulp cordova:emulate:ios` - run application in iOS emulator 7 | * `gulp cordova:run:android` - run application on Android's devise 8 | * Run `gulp help` or see `gulp/tasks.coffee` for more information about all tasks 9 | * Useful hooks and tweaks, which allow you to deploy your cordova app out-of-the-box 10 | * SASS + CoffeeScript + Jade combo 11 | * Support for multiple environments, like *development, staging, production* (configuration available in `gulpfile.coffee`) 12 | * Tests configured and working: unit (karma + mocha) and end to end (protractor) 13 | * Rollbar support (configured, working in angular functions and support for uploading the sourcemaps to Rollbar server) 14 | 15 | # Requirements 16 | 17 | * NodeJS 18 | * Cordova 4.2+ 19 | * Android or iOS SDK installed and [configured](http://docs.phonegap.com/en/4.0.0/guide_platforms_index.md.html#Platform%20Guides) (required only if you want to deploy the app to native mobile platforms - you can run `gulp` server without that) 20 | 21 | 22 | # How to install 23 | 24 | ``` 25 | git clone jtomaszewski/ionicstarter-mobile 26 | cd ionicstarter-mobile 27 | git submodule update --init --recursive 28 | 29 | # install dependencies 30 | npm install 31 | npm install -g gulp 32 | bower install 33 | brew install imagemagick # or `apt-get install imagemagick`, if you're on linux 34 | 35 | gulp # build www/ directory and run http server on 4440 port 36 | ``` 37 | 38 | If you get "too many files" error, try: `ulimit -n 10000`. You may want to add this line to your .bashrc / .zshrc / config.fish. 39 | 40 | 41 | ## What does the `gulp build` do? 42 | 43 | More or less: 44 | 45 | * All .scss, .coffee, .jade files from `app/` will be compiled and copied to `www/` 46 | * All `.ejs` files from `assets/` will be compiled to `www/`. 47 | * All other files from `assets/` will be copied to `www/`. 48 | 49 | For detailed description, see `gulpfile.coffee`. 50 | 51 | P.S. `www/` is like `dist/` directory for Cordova. That's why it's not included in this repository, as it's fully generated with `gulp`. 52 | 53 | 54 | ## Testing 55 | 56 | Requirements: installed PhantomJS and configured [selenium standalone webdriver](https://github.com/angular/protractor/blob/master/docs/getting-started.md#setup-and-config). 57 | 58 | #### Unit tests (karma & PhantomJS/Chrome) 59 | 60 | ``` 61 | gulp test:unit # using PhantomJS 62 | gulp test:unit --browsers Chrome # or using Google Chrome 63 | ``` 64 | 65 | #### e2e tests (protractor & selenium) 66 | 67 | ``` 68 | gulp # your www/ directory should be built and served at :4400 port 69 | node_modules/.bin/webdriver-manager start & # run selenium server in the background 70 | 71 | gulp test:e2e # finally, run e2e tests 72 | ``` 73 | 74 | 75 | # How to run on mobile? 76 | 77 | I recommend [tmux](http://tmux.sourceforge.net/) for handling multiple terminal tabs/windows ;) 78 | 79 | 1. Copy `.envrc.android-sample` or `.envrc.ios-sample` to `.envrc` and configure it. 80 | 81 | * Ofcourse, if you're a Mac user and you can compile both Android and iOS on the same machine, you can include all the variables from both of these files in only one `.envrc` . 82 | 83 | * Also, make sure you have all the keys and certificates needed stored in `keys/android/` and `keys/ios/`: 84 | 85 | * `keys/android/ionicstarter.keystore` 86 | * `keys/ios/ionicstarter_staging.mobileprovision` 87 | * `keys/ios/ionicstarter_production.mobileprovision` 88 | 89 | 2. Ensure, you have [configured ios/android platform with Cordova](http://cordova.apache.org/docs/en/edge/guide_cli_index.md.html), f.e. by running `gulp cordova:platform-add:[ios|android]`. 90 | 91 | 3. Run `gulp cordova:emulate:[ios|android]` or `gulp cordova:run:[ios|android]`. 92 | 93 | # Releasing to appstores 94 | 95 | First, generate the certificate keys: 96 | 97 | #### Android 98 | 99 | 1. [Generate .keystore file](http://developer.android.com/tools/publishing/app-signing.html): 100 | `keytool -genkey -v -keystore keys/android/$ANDROID_KEYSTORE_NAME.keystore -alias $ANDROID_ALIAS_NAME -keyalg RSA -keysize 2048 -validity 10000` 101 | 102 | #### iPhone 103 | 104 | 1. Create a certificate and a provisioning profile, as it's described [here](http://docs.build.phonegap.com/en_US/3.3.0/signing_signing-ios.md.html#iOS%20Signing). 105 | 106 | 2. Download the provisioning profile and copy it into `keys/ios/`, so it will match the `IOS_PROVISIONING_PROFILE` file set up in the `gulpfile.coffee`. 107 | 108 | Then, generate the application and deploy it to the webserver with: 109 | 110 | ``` 111 | gulp release:[ios|android] --env=[staging|production] 112 | ``` 113 | -------------------------------------------------------------------------------- /app/css/app.scss: -------------------------------------------------------------------------------- 1 | @import "ionic.css"; 2 | 3 | body { 4 | background-color: gray; 5 | } 6 | -------------------------------------------------------------------------------- /app/css/ionic.scss: -------------------------------------------------------------------------------- 1 | // Ionicons 2 | $ionicons-font-path: "../components/ionic/release/fonts"; 3 | @import "../../assets/components/ionic/scss/ionicons/ionicons.scss"; 4 | 5 | // Variables 6 | @import "../../assets/components/ionic/scss/mixins"; 7 | @import "../../assets/components/ionic/scss/variables"; 8 | 9 | // Base 10 | @import "../../assets/components/ionic/scss/reset"; 11 | @import "../../assets/components/ionic/scss/scaffolding"; 12 | @import "../../assets/components/ionic/scss/type"; 13 | 14 | // Components 15 | @import "../../assets/components/ionic/scss/action-sheet"; 16 | @import "../../assets/components/ionic/scss/backdrop"; 17 | @import "../../assets/components/ionic/scss/bar"; 18 | @import "../../assets/components/ionic/scss/tabs"; 19 | @import "../../assets/components/ionic/scss/menu"; 20 | @import "../../assets/components/ionic/scss/modal"; 21 | @import "../../assets/components/ionic/scss/popover"; 22 | @import "../../assets/components/ionic/scss/popup"; 23 | @import "../../assets/components/ionic/scss/loading"; 24 | @import "../../assets/components/ionic/scss/items"; 25 | @import "../../assets/components/ionic/scss/list"; 26 | @import "../../assets/components/ionic/scss/badge"; 27 | @import "../../assets/components/ionic/scss/slide-box"; 28 | 29 | // Forms 30 | @import "../../assets/components/ionic/scss/form"; 31 | @import "../../assets/components/ionic/scss/checkbox"; 32 | @import "../../assets/components/ionic/scss/toggle"; 33 | @import "../../assets/components/ionic/scss/radio"; 34 | @import "../../assets/components/ionic/scss/range"; 35 | @import "../../assets/components/ionic/scss/select"; 36 | @import "../../assets/components/ionic/scss/progress"; 37 | 38 | // Buttons 39 | @import "../../assets/components/ionic/scss/button"; 40 | @import "../../assets/components/ionic/scss/button-bar"; 41 | 42 | // Util 43 | @import "../../assets/components/ionic/scss/grid"; 44 | @import "../../assets/components/ionic/scss/util"; 45 | @import "../../assets/components/ionic/scss/platform"; 46 | 47 | // Animations 48 | @import "../../assets/components/ionic/scss/animations"; 49 | @import "../../assets/components/ionic/scss/transitions"; 50 | -------------------------------------------------------------------------------- /app/index.jade: -------------------------------------------------------------------------------- 1 | doctype html 2 | html 3 | head 4 | meta(charset='utf-8') 5 | meta(name='viewport', content='initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no, width=device-width, minimal-ui') 6 | title #{GLOBALS.BUNDLE_NAME} 7 | 8 | //- fix: see: 9 | //- http://stackoverflow.com/questions/19110144/ios7-issues-with-webview-focus-when-using-keyboard-html/19182670#19182670 10 | //- https://developer.apple.com/library/safari/documentation/AppleApplications/Reference/SafariHTMLRef/Articles/MetaTags.html 11 | //- This seems however like it did worked only on iOS pre-7 12 | //- http://blogs.telerik.com/appbuilder/posts/13-11-07/everything-hybrid-web-apps-need-to-know-about-the-status-bar-in-ios7 13 | meta(name="apple-mobile-web-app-capable" content="yes") 14 | meta(name="apple-mobile-web-app-status-bar-style" content="white-translucent") 15 | 16 | //- This policy allows everything (eg CSS, AJAX, object, frame, media, etc) except that 17 | //- * CSS only from the same origin and inline styles, 18 | //- * scripts only from the same origin and inline styles, and eval() 19 | meta(http-equiv="Content-Security-Policy" content="default-src *; style-src 'self' 'unsafe-inline'; script-src * 'unsafe-inline' 'unsafe-eval'; img-src * data: blob: filesystem:") 20 | 21 | link(href='css/app.css?v=#{GLOBALS.DEPLOY_TIME}', rel='stylesheet') 22 | 23 | link(rel="icon", type="image/png", href="img/favicon.png") 24 | link(rel="apple-touch-icon", href="img/favicon.png" ) 25 | 26 | //- notify about all errors 27 | -if (GLOBALS.ROLLBAR_CLIENT_ACCESS_TOKEN) 28 | script(type="text/javascript"). 29 | var _rollbarConfig = { 30 | accessToken: "#{GLOBALS.ROLLBAR_CLIENT_ACCESS_TOKEN}", 31 | captureUncaught: true, 32 | payload: { 33 | environment: "#{GLOBALS.ENV}", 34 | client: { 35 | javascript: { 36 | source_map_enabled: true, 37 | code_version: "#{GLOBALS.CODE_VERSION}", 38 | // Optionally have Rollbar guess which frames the error was thrown from 39 | // when the browser does not provide line and column numbers. 40 | guess_uncaught_frames: true 41 | } 42 | } 43 | } 44 | }; 45 | script(src="js/rollbar.js") 46 | 47 | //- Add google analytics, if available. 48 | //- NOTE: to make it work on Cordova, 49 | //- you will also need angulartics and cordova google-analytics-plugin. 50 | -if (GLOBALS.GOOGLE_ANALYTICS_ID) 51 | script(type="text/javascript"). 52 | (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ 53 | (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), 54 | m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) 55 | })(window,document,'script','https://www.google-analytics.com/analytics.js','ga'); 56 | 57 | ga('create', '#{GLOBALS.GOOGLE_ANALYTICS_ID}', '#{GLOBALS.GOOGLE_ANALYTICS_HOST}'); 58 | 59 | //- define required JS GLOBALS 60 | script. 61 | GLOBALS = !{JSON.stringify(GLOBALS)}; 62 | 63 | script(src="js/vendor.js?v=#{GLOBALS.DEPLOY_TIME}") 64 | script(src="js/app_templates.js?v=#{GLOBALS.DEPLOY_TIME}") 65 | script(src="js/app.js?v=#{GLOBALS.DEPLOY_TIME}") 66 | 67 | body 68 | // 69 | The nav bar that will be updated as we navigate between views 70 | Additional attributes set its look, nav-bar animation and icons 71 | Icons provided by Ionicons: http://ionicons.com/ 72 | 73 | ion-nav-bar.bar-positive 74 | // 75 | The views will be rendered in the directive below 76 | Templates are in the /templates folder (but you could also 77 | have templates inline in this html file if you'd like). 78 | 79 | ion-nav-view 80 | -------------------------------------------------------------------------------- /app/js/common/factories/form_factory.coffee: -------------------------------------------------------------------------------- 1 | angular.module("ionicstarter") 2 | 3 | .factory 'FormFactory', ($q) -> 4 | 5 | ### 6 | Basic form class that you can extend in your actual forms. 7 | 8 | Object attributes: 9 | - loading[Boolean] - is the request waiting for response? 10 | - message[String] - after response, success message 11 | - errors[String[]] - after response, error messages 12 | 13 | Options: 14 | - submitPromise[function] (REQUIRED) - creates a form request promise 15 | - onSuccess[function] - will be called on succeded promise 16 | - onFailure[function] - will be called on failed promise 17 | ### 18 | class FormFactory 19 | constructor: (@options = {}) -> 20 | @loading = false 21 | 22 | submit: -> 23 | @_handleRequestPromise @_createSubmitPromise() unless @loading 24 | 25 | _onSuccess: (response) -> 26 | @message = response.message || response.success 27 | response 28 | 29 | _onFailure: (response) -> 30 | @errors = response.data?.data?.errors || response.data?.errors || [response.data?.error || response.error || response.data?.message || response.message || "Something has failed. Try again."] 31 | $q.reject(response) 32 | 33 | _createSubmitPromise: -> 34 | @options.submitPromise() 35 | 36 | _handleRequestPromise: (@$promise, onSuccess, onFailure) -> 37 | @loading = true 38 | @submitted = false 39 | @message = null 40 | @errors = [] 41 | 42 | @$promise 43 | .then (response) => 44 | @errors = [] 45 | @submitted = true 46 | response 47 | 48 | .then(_.bind @_onSuccess, @) 49 | .then(onSuccess || @options.onSuccess) 50 | 51 | .catch(_.bind @_onFailure, @) 52 | .catch(onFailure || @options.onFailure) 53 | 54 | .finally => 55 | @loading = false 56 | 57 | @$promise 58 | -------------------------------------------------------------------------------- /app/js/common/factories/observer_factory.coffee: -------------------------------------------------------------------------------- 1 | angular.module("ionicstarter") 2 | 3 | .factory 'ObserverFactory', ($rootScope) -> 4 | class ObserverFactory 5 | on: (eventName, listener) -> 6 | @listeners ?= {} 7 | @listeners[eventName] ?= [] 8 | @listeners[eventName].push listener 9 | 10 | once: (eventName, listener) -> 11 | listener.__once__ = true 12 | @on eventName, listener 13 | 14 | off: (eventName, listener) -> 15 | return unless @listeners?[eventName] 16 | return delete @listeners[eventName] if !listener 17 | 18 | for i, v in @listeners[eventName] 19 | if @listeners[eventName] == listener 20 | @listeners.splice i, 1 21 | break 22 | 23 | fireEvent: (eventName, params...) -> 24 | return unless @listeners?[eventName]?.length 25 | 26 | for v in @listeners[eventName] 27 | v.apply(@, params) 28 | @off eventName, v if v.__once__ 29 | 30 | $rootScope.$apply() unless $rootScope.$$phase 31 | 32 | 33 | -------------------------------------------------------------------------------- /app/js/common/factories/promise_factory.coffee: -------------------------------------------------------------------------------- 1 | angular.module("ionicstarter") 2 | 3 | # Wraps the value in Promise if it's not. 4 | # This allows you to call `Promise(varIDontKnow).then ->` whenever you can. 5 | .factory 'PromiseFactory', ($q) -> 6 | constructor = (value, resolve = true) -> 7 | if value? && typeof value?.then == 'function' 8 | value 9 | else 10 | deferred = $q.defer() 11 | if resolve 12 | deferred.resolve(value) 13 | else 14 | deferred.reject(value) 15 | deferred.promise 16 | 17 | -------------------------------------------------------------------------------- /app/js/common/services/auth.coffee: -------------------------------------------------------------------------------- 1 | angular.module("ionicstarter") 2 | 3 | .service 'Auth', ($http, PromiseFactory) -> 4 | USER_EMAIL_CACHE_KEY = "user_email" 5 | USER_TOKEN_CACHE_KEY = "user_token" 6 | 7 | new class Auth 8 | constructor: -> 9 | @setAuthToken(localStorage.getItem(USER_EMAIL_CACHE_KEY), localStorage.getItem(USER_TOKEN_CACHE_KEY)) 10 | 11 | setAuthToken: (@email = null, @token = null, user) -> 12 | if @email && @token 13 | $http.defaults.headers.common["X-User-Email"] = @email 14 | $http.defaults.headers.common["X-User-Token"] = @token 15 | localStorage.setItem(USER_EMAIL_CACHE_KEY, @email) 16 | localStorage.setItem(USER_TOKEN_CACHE_KEY, @token) 17 | else 18 | delete $http.defaults.headers.common["X-User-Email"] 19 | delete $http.defaults.headers.common["X-User-Token"] 20 | localStorage.removeItem(USER_EMAIL_CACHE_KEY) 21 | localStorage.removeItem(USER_TOKEN_CACHE_KEY) 22 | @refreshUser(user) 23 | 24 | refreshUser: (user = null) -> 25 | @user = if user 26 | user.$promise = PromiseFactory(user) 27 | user.$resolved = true 28 | user 29 | else if @email && @token 30 | # AccountResource.getProfile({email: @email}) 31 | else 32 | null 33 | 34 | isSignedIn: -> 35 | !!@token 36 | 37 | resetSession: -> 38 | # AccountResource.signOut() 39 | @setAuthToken(null, null) 40 | -------------------------------------------------------------------------------- /app/js/config/001-globals.coffee: -------------------------------------------------------------------------------- 1 | # It will equal to the root path of the directory where index.html is located. 2 | # f.e. "file:///android_assets/www/" or "http://localhost/" 3 | GLOBALS.APP_ROOT = (location.origin + location.pathname).replace("index.html", "") 4 | -------------------------------------------------------------------------------- /app/js/config/005-window_utils.coffee: -------------------------------------------------------------------------------- 1 | # ==> App-wide global useful functions 2 | # Generate a DOM element and append it to container. 3 | # Usage example: 4 | # ```coffee 5 | # addElement document, "script", id: "facebook-jssdk", src: "facebook-js-sdk.js" 6 | # ``` 7 | window.addElement = (container, tagName, attrs = {}) -> 8 | return container.getElementById(attrs.id) if attrs.id && container.getElementById(attrs.id) 9 | 10 | fjs = container.getElementsByTagName(tagName)[0] 11 | tag = container.createElement(tagName) 12 | for k, v of attrs 13 | tag[k] = v 14 | fjs.parentNode.insertBefore tag, fjs 15 | tag 16 | 17 | 18 | # Debug helper 19 | window.log = -> console.log arguments 20 | 21 | 22 | # ==> Add setObject and getObject methods to Storage prototype (used f.e. by localStorage). 23 | Storage::setObject = (key, value) -> 24 | @setItem(key, JSON.stringify(value)) 25 | 26 | Storage::getObject = (key) -> 27 | return unless value = @getItem(key) 28 | JSON.parse(value) 29 | -------------------------------------------------------------------------------- /app/js/config/006-weinre.coffee: -------------------------------------------------------------------------------- 1 | # To debug, go to http://localhost:31173/client/#anonymous 2 | if GLOBALS.WEINRE_ADDRESS && (ionic.Platform.isAndroid() || ionic.Platform.isIOS()) && !navigator.platform.match(/MacIntel/i) 3 | window.addElement document, "script", id: "weinre-js", src: "http://#{GLOBALS.WEINRE_ADDRESS}/target/target-script-min.js#anonymous" 4 | -------------------------------------------------------------------------------- /app/js/config/009-livereload.coffee: -------------------------------------------------------------------------------- 1 | window.liveReloadTools = 2 | checkServerConnection: (origin) -> 3 | console.debug "Calling liveReloadTools.checkServerConnection for #{origin}" 4 | new Promise (resolve, reject) -> 5 | request = new XMLHttpRequest() 6 | request.addEventListener "load", (event) -> 7 | if event.loaded 8 | resolve(event) 9 | else 10 | reject(event) 11 | request.addEventListener "error", reject 12 | request.addEventListener "abort", reject 13 | request.open('GET', "#{origin}/config.xml?_t=#{Math.random()}") 14 | request.send() 15 | 16 | redirectToServer: (origin) -> 17 | console.debug "Calling liveReloadTools.redirectToServer for #{origin}" 18 | return if GLOBALS.APP_ROOT == "#{origin}/" 19 | 20 | liveReloadTools.checkServerConnection(origin) 21 | .then -> 22 | console.debug "BrowserSync server has been found! (#{origin})" 23 | window.location.href = "#{origin}/index.html?referer=#{encodeURIComponent(GLOBALS.APP_ROOT)}" 24 | .catch -> 25 | 26 | referer = URI(window.location.href).query(true)?.referer || '' 27 | 28 | # document.addEventListener "DOMContentLoaded", -> 29 | # if window.location.href.indexOf("?referer=") != -1 30 | # console.debug "'?referer=' query parameter found. Altering all .cordova-script script tags ..." 31 | # referer = URI(window.location.href).query(true).referer 32 | 33 | if window.location.protocol == "file:" || referer.indexOf("file:") == 0 34 | console.debug "'file://' protocol found in location.href . Adding cordova.js script to ..." 35 | scriptElement = document.createElement("script") 36 | scriptElement.id = "cordova-js" 37 | scriptElement.src = "cordova.js" 38 | document.querySelector("head").appendChild(scriptElement) 39 | 40 | GLOBALS.HTTP_SERVER_ORIGIN = "http://#{GLOBALS.HTTP_SERVER_IP}:#{GLOBALS.HTTP_SERVER_PORT}" if GLOBALS.HTTP_SERVER_IP && GLOBALS.HTTP_SERVER_PORT 41 | liveReloadTools.redirectToServer(GLOBALS.HTTP_SERVER_ORIGIN) if GLOBALS.HTTP_SERVER_ORIGIN 42 | -------------------------------------------------------------------------------- /app/js/config/010-initialize_app.coffee: -------------------------------------------------------------------------------- 1 | # Initialize angular's app. 2 | 3 | app = angular.module(GLOBALS.ANGULAR_APP_NAME, [ 4 | "#{GLOBALS.ANGULAR_APP_NAME}.templates" 5 | "ionic" 6 | "angulartics.google.analytics" 7 | "angulartics.google.analytics.cordova" 8 | ]) 9 | -------------------------------------------------------------------------------- /app/js/config/011-set_globals_as_app_constants.coffee: -------------------------------------------------------------------------------- 1 | app = angular.module(GLOBALS.ANGULAR_APP_NAME) 2 | 3 | for k, v of GLOBALS 4 | app.constant k, v 5 | 6 | # Make GLOBALS visible in every scope. 7 | app.run ($rootScope) -> 8 | $rootScope.GLOBALS = GLOBALS 9 | -------------------------------------------------------------------------------- /app/js/config/012-app_run.coffee: -------------------------------------------------------------------------------- 1 | app = angular.module(GLOBALS.ANGULAR_APP_NAME) 2 | 3 | 4 | # Run the app only after cordova has been initialized 5 | # (this is why we don't include ng-app in the index.jade) 6 | ionic.Platform.ready -> 7 | console.log 'ionic.Platform is ready! Running `angular.bootstrap()`...' unless GLOBALS.ENV == "test" 8 | angular.bootstrap document, [GLOBALS.ANGULAR_APP_NAME] 9 | 10 | 11 | app.run ($log, $timeout) -> 12 | $log.debug "Ionic app \"#{GLOBALS.ANGULAR_APP_NAME}\" has just started (app.run)!" unless GLOBALS.ENV == "test" 13 | 14 | # Finally, let's show the app, by hiding the splashscreen 15 | # (it should be visible up until this moment) 16 | $timeout -> 17 | navigator.splashscreen?.hide() 18 | -------------------------------------------------------------------------------- /app/js/config/google_analytics.coffee: -------------------------------------------------------------------------------- 1 | return unless GLOBALS.CORDOVA_GOOGLE_ANALYTICS_ID 2 | app = angular.module(GLOBALS.ANGULAR_APP_NAME) 3 | 4 | 5 | ionic.Platform.ready -> 6 | app.config (googleAnalyticsCordovaProvider) -> 7 | googleAnalyticsCordovaProvider.debug = GLOBALS.ENV != 'production' 8 | googleAnalyticsCordovaProvider.trackingId = GLOBALS.CORDOVA_GOOGLE_ANALYTICS_ID 9 | -------------------------------------------------------------------------------- /app/js/config/http.coffee: -------------------------------------------------------------------------------- 1 | app = angular.module(GLOBALS.ANGULAR_APP_NAME) 2 | 3 | 4 | app.config ($httpProvider) -> 5 | # Combine multiple $http requests into one $applyAsync (boosts performance) 6 | $httpProvider.useApplyAsync(true) 7 | 8 | # Add support for PATCH requests 9 | $httpProvider.defaults.headers.patch ||= {} 10 | $httpProvider.defaults.headers.patch['Content-Type'] = 'application/json' 11 | 12 | # Send API version code in header (might be useful in future) 13 | $httpProvider.defaults.headers.common["X-Api-Version"] = "1.0" 14 | 15 | $httpProvider.interceptors.push ($injector, $q, $log, $location) -> 16 | responseError: (response) -> 17 | $log.debug "httperror: ", response.status unless GLOBALS.ENV == "test" 18 | 19 | # Sign out current user if we receive a 401 status. 20 | if response.status == 401 21 | $injector.invoke (Auth) -> 22 | Auth.setAuthToken(null, null) 23 | $location.path("/") 24 | 25 | $q.reject(response) 26 | -------------------------------------------------------------------------------- /app/js/config/ionic_config.coffee: -------------------------------------------------------------------------------- 1 | app = angular.module(GLOBALS.ANGULAR_APP_NAME) 2 | 3 | 4 | app.config ($ionicConfigProvider) -> 5 | # Customize navigation look 6 | # $ionicConfigProvider.backButton.icon('ion-ios7-arrow-back') 7 | # $ionicConfigProvider.backButton.text($translateProvider.instant("GO_BACK")) 8 | # $ionicConfigProvider.backButton.previousTitleText(false) 9 | # $ionicConfigProvider.navBar.alignTitle('center') 10 | # $ionicConfigProvider.navBar.positionPrimaryButtons('left') 11 | # $ionicConfigProvider.navBar.positionSecondaryButtons('right') 12 | 13 | # Change views cache limit from 10 to 4 14 | $ionicConfigProvider.views.maxCache(4) 15 | 16 | # Don't prefetch any templates - we use $templateCache for that 17 | $ionicConfigProvider.templates.maxPrefetch(false) 18 | 19 | # Turn off animations on Android and iOS 6 devices. 20 | # More info: http://ionicframework.com/docs/nightly/api/provider/%24ionicConfigProvider/ 21 | unless ionic.Platform.grade == "a" 22 | $ionicConfigProvider.views.transition("none") 23 | $ionicConfigProvider.views.maxCache(2) 24 | -------------------------------------------------------------------------------- /app/js/config/logger.coffee: -------------------------------------------------------------------------------- 1 | app = angular.module(GLOBALS.ANGULAR_APP_NAME) 2 | 3 | 4 | # Turn off $log.debug on production 5 | app.config ($logProvider, $compileProvider) -> 6 | if GLOBALS.ENV == "production" 7 | $logProvider.debugEnabled(false) 8 | $compileProvider.debugInfoEnabled(false) 9 | -------------------------------------------------------------------------------- /app/js/config/rollbar.coffee: -------------------------------------------------------------------------------- 1 | return unless window.Rollbar? 2 | app = angular.module(GLOBALS.ANGULAR_APP_NAME) 3 | 4 | 5 | app.factory '$exceptionHandler', ($log) -> 6 | (e, cause) -> 7 | $log.error e.message 8 | Rollbar.error(e) 9 | 10 | 11 | Rollbar.configure 12 | payload: 13 | deploy_time: GLOBALS.DEPLOY_TIME 14 | deploy_date: moment(GLOBALS.DEPLOY_TIME).format() 15 | bundle_name: GLOBALS.BUNDLE_NAME 16 | bundle_version: GLOBALS.BUNDLE_VERSION 17 | 18 | transform: (payload) -> 19 | if frames = payload.data?.body?.trace?.frames 20 | for frame in frames 21 | frame.filename = frame.filename.replace(GLOBALS.APP_ROOT, "#{GLOBALS.ROLLBAR_SOURCEMAPS_URL_PREFIX}/") 22 | 23 | 24 | app.run (Auth) -> 25 | Auth.on "user.updated", (user) -> 26 | Rollbar.configure 27 | payload: 28 | person: ({ 29 | id: user.id 30 | email: user.email 31 | } if user) 32 | 33 | 34 | app.run (onRouteChangeCallback) -> 35 | onRouteChangeCallback (state) -> 36 | Rollbar.configure 37 | payload: 38 | context: state.name 39 | -------------------------------------------------------------------------------- /app/js/config/weinre.coffee: -------------------------------------------------------------------------------- 1 | # To debug, go to http://localhost:31173/client/#anonymous 2 | if GLOBALS.WEINRE_ADDRESS && (ionic.Platform.isAndroid() || ionic.Platform.isIOS()) && !navigator.platform.match(/MacIntel/i) 3 | window.addElement document, "script", id: "weinre-js", src: "http://#{GLOBALS.WEINRE_ADDRESS}/target/target-script-min.js#anonymous" 4 | -------------------------------------------------------------------------------- /app/js/config/window_injector.coffee: -------------------------------------------------------------------------------- 1 | app = angular.module(GLOBALS.ANGULAR_APP_NAME) 2 | 3 | 4 | # Useful for debugging, like `$a("$rootScope")` 5 | app.run ($window, $injector) -> 6 | $window.$a = $injector.get 7 | -------------------------------------------------------------------------------- /app/js/features/pets/pet_detail_ctrl.coffee: -------------------------------------------------------------------------------- 1 | angular.module("ionicstarter") 2 | 3 | # A simple controller that shows a tapped item's data 4 | .controller "PetDetailCtrl", ($scope, $stateParams, PetService) -> 5 | $scope.pet = PetService.get($stateParams.petId) 6 | 7 | -------------------------------------------------------------------------------- /app/js/features/pets/pet_index_ctrl.coffee: -------------------------------------------------------------------------------- 1 | angular.module("ionicstarter") 2 | 3 | # A simple controller that fetches a list of data from a service 4 | .controller "PetIndexCtrl", ($scope, PetService) -> 5 | $scope.pets = PetService.all() 6 | 7 | -------------------------------------------------------------------------------- /app/js/features/pets/pet_service.coffee: -------------------------------------------------------------------------------- 1 | ### 2 | A simple example service that returns some data. 3 | ### 4 | angular.module("ionicstarter") 5 | 6 | .factory "PetService", -> 7 | 8 | # Might use a resource here that returns a JSON array 9 | 10 | # Some fake testing data 11 | pets = [ 12 | { 13 | id: 0 14 | title: "Cats" 15 | description: "Furry little creatures. Obsessed with plotting assassination, but never following through on it." 16 | } 17 | { 18 | id: 1 19 | title: "Dogs" 20 | description: "Lovable. Loyal almost to a fault. Smarter than they let on." 21 | } 22 | { 23 | id: 2 24 | title: "Turtles" 25 | description: "Everyone likes turtles." 26 | } 27 | { 28 | id: 3 29 | title: "Sharks" 30 | description: "An advanced pet. Needs millions of gallons of salt water. Will happily eat you." 31 | } 32 | ] 33 | all: -> 34 | pets 35 | 36 | get: (petId) -> 37 | 38 | # Simple index lookup 39 | pets[petId] 40 | 41 | -------------------------------------------------------------------------------- /app/js/routes.coffee: -------------------------------------------------------------------------------- 1 | angular.module("ionicstarter") 2 | 3 | .config ($stateProvider, $urlRouterProvider) -> 4 | 5 | # Ionic uses AngularUI Router which uses the concept of states 6 | # Learn more here: https://github.com/angular-ui/ui-router 7 | # Set up the various states which the app can be in. 8 | 9 | # the pet tab has its own child nav-view and history 10 | $stateProvider 11 | 12 | .state "tab", 13 | url: "/tab" 14 | abstract: true 15 | templateUrl: "templates/tabs.html" 16 | 17 | .state "tab.pet-index", 18 | url: "/pets" 19 | views: 20 | "pets-tab": 21 | templateUrl: "templates/pet-index.html" 22 | controller: "PetIndexCtrl" 23 | 24 | .state "tab.pet-detail", 25 | url: "/pet/:petId" 26 | views: 27 | "pets-tab": 28 | templateUrl: "templates/pet-detail.html" 29 | controller: "PetDetailCtrl" 30 | 31 | .state "tab.adopt", 32 | url: "/adopt" 33 | views: 34 | "adopt-tab": 35 | templateUrl: "templates/adopt.html" 36 | 37 | .state "tab.about", 38 | url: "/about" 39 | views: 40 | "about-tab": 41 | templateUrl: "templates/about.html" 42 | 43 | 44 | # if none of the above states are matched, use this as the fallback 45 | $urlRouterProvider.otherwise "/tab/pets" 46 | -------------------------------------------------------------------------------- /app/templates/about.jade: -------------------------------------------------------------------------------- 1 | html 2 | body 3 | // 4 | This template loads for the 'tab.about' state (app.js) 5 | ion-view(view-title='About Ionic') 6 | ion-content(has-header='true', has-tabs='true', padding='true') 7 | img.ionic-logo(src='img/ionic.png') 8 | p 9 | | This is a sample seed project for the Ionic Framework. Please cut it up and make it your own. 10 | | Check out the 11 | a(href='http://ionicframework.com/docs/', target='_blank') docs 12 | | for more info. 13 | p 14 | | Questions? Hit up the 15 | a(href='http://forum.ionicframework.com/', target='_blank') forum 16 | | . 17 | p 18 | | Find a bug? Create an 19 | a(href='https://github.com/driftyco/ionic/issues?state=open', target='_blank') issue 20 | | . 21 | p 22 | | What to help improve Ionic? 23 | a(href='http://ionicframework.com/contribute/', target='_blank') Contribute 24 | | . 25 | p 26 | | Stay up-to-date with the Ionic 27 | a(href='http://ionicframework.com/subscribe/', target='_blank') newsletter 28 | | and 29 | a(href='https://twitter.com/Ionicframework', target='_blank') twitter 30 | | account. 31 | p 32 | | MIT Licensed. Happy coding. 33 | -------------------------------------------------------------------------------- /app/templates/adopt.jade: -------------------------------------------------------------------------------- 1 | html 2 | body 3 | // 4 | This template loads for the 'tab.adopt' state (app.js) 5 | ion-view(view-title='Adopt A Pet') 6 | ion-content(has-header='true', has-tabs='true') 7 | .list.list-inset 8 | label.item.item-input 9 | span.input-label Your name 10 | input(type='text') 11 | label.item.item-input 12 | span.input-label Your email 13 | input(type='email') 14 | ion-toggle Subscribe To Newsletter 15 | button.button.button-positive.button-block Adopt 16 | -------------------------------------------------------------------------------- /app/templates/pet-detail.jade: -------------------------------------------------------------------------------- 1 | html 2 | body 3 | // 4 | This template loads for the 'tab.pet-detail' state (app.js) 5 | 'pet' is a $scope variable created in the PetCtrl controller (controllers.js) 6 | The PetCtrl pulls data from the Pets service (service.js) 7 | The Pets service returns an array of pet data 8 | ion-view(view-title='{{pet.title}}') 9 | ion-content(has-header='true', padding='true') 10 | p {{ pet.description }} 11 | p 12 | a.button.button-small.icon.ion-arrow-left-b(href='#/tab/pets') All Pets 13 | -------------------------------------------------------------------------------- /app/templates/pet-index.jade: -------------------------------------------------------------------------------- 1 | html 2 | body 3 | // 4 | 5 | This template gets placed in the Pet tab's directive. 6 | It was wired up in the app config (app.js) 7 | The 'pets' data comes from its $scope within PetIndexCtrl (controller.js) 8 | ion-view(view-title='Pet Information') 9 | ion-content(has-header='true', has-tabs='true') 10 | ion-list 11 | ion-item(ng-repeat='pet in pets', type='item-text-wrap', href='#/tab/pet/{{pet.id}}') 12 | h3 {{pet.title}} 13 | p {{pet.description}} 14 | -------------------------------------------------------------------------------- /app/templates/tabs.jade: -------------------------------------------------------------------------------- 1 | html 2 | body 3 | // 4 | 5 | Create tabs with an icon and label, using the tabs-positive style. 6 | Each tab's child directive will have its own 7 | navigation history that also transitions its views in and out. 8 | ion-tabs.tabs-icon-top.tabs-default.tabs-positive 9 | // Pets Tab 10 | ion-tab(title='Pets', icon='icon ion-home', href='#/tab/pets') 11 | ion-nav-view(name='pets-tab') 12 | // Adopt Tab 13 | ion-tab(title='Adopt', icon='icon ion-heart', href='#/tab/adopt') 14 | ion-nav-view(name='adopt-tab') 15 | // About Tab 16 | ion-tab(title='About', icon='icon ion-search', href='#/tab/about') 17 | ion-nav-view(name='about-tab') 18 | -------------------------------------------------------------------------------- /assets/config.xml.ejs: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | <%= BUNDLE_NAME %> 10 | 11 | A sample ionic-cordova application. 12 | 13 | 14 | Jacek Tomaszewski 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | <% if(HTTP_SERVER_IP && HTTP_SERVER_PORT) { %> 25 | 26 | <% } %> 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /assets/img/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jtomaszewski/ionic-cordova-gulp-seed/e6b68f83d6c8bb960ba515c6258ac03ca33e7934/assets/img/favicon.png -------------------------------------------------------------------------------- /assets/img/logo-square.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jtomaszewski/ionic-cordova-gulp-seed/e6b68f83d6c8bb960ba515c6258ac03ca33e7934/assets/img/logo-square.png -------------------------------------------------------------------------------- /assets/img/splash-landscape.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jtomaszewski/ionic-cordova-gulp-seed/e6b68f83d6c8bb960ba515c6258ac03ca33e7934/assets/img/splash-landscape.png -------------------------------------------------------------------------------- /assets/img/splash-portrait.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jtomaszewski/ionic-cordova-gulp-seed/e6b68f83d6c8bb960ba515c6258ac03ca33e7934/assets/img/splash-portrait.png -------------------------------------------------------------------------------- /assets/js/rollbar.js: -------------------------------------------------------------------------------- 1 | !function(a,b){function c(b){this.shimId=++h,this.notifier=null,this.parentShim=b,this.logger=function(){},a.console&&void 0===a.console.shimId&&(this.logger=a.console.log)}function d(b,c,d){!d[4]&&a._rollbarWrappedError&&(d[4]=a._rollbarWrappedError,a._rollbarWrappedError=null),b.uncaughtError.apply(b,d),c&&c.apply(a,d)}function e(b){var d=c;return g(function(){if(this.notifier)return this.notifier[b].apply(this.notifier,arguments);var c=this,e="scope"===b;e&&(c=new d(this));var f=Array.prototype.slice.call(arguments,0),g={shim:c,method:b,args:f,ts:new Date};return a._rollbarShimQueue.push(g),e?c:void 0})}function f(a,b){if(b.hasOwnProperty&&b.hasOwnProperty("addEventListener")){var c=b.addEventListener;b.addEventListener=function(b,d,e){c.call(this,b,a.wrap(d),e)};var d=b.removeEventListener;b.removeEventListener=function(a,b,c){d.call(this,a,b&&b._wrapped?b._wrapped:b,c)}}}function g(a,b){return b=b||this.logger,function(){try{return a.apply(this,arguments)}catch(c){b("Rollbar internal error:",c)}}}var h=0;c.init=function(a,b){var e=b.globalAlias||"Rollbar";if("object"==typeof a[e])return a[e];a._rollbarShimQueue=[],a._rollbarWrappedError=null,b=b||{};var h=new c;return g(function(){if(h.configure(b),b.captureUncaught){var c=a.onerror;a.onerror=function(){var a=Array.prototype.slice.call(arguments,0);d(h,c,a)};var g,i,j=["EventTarget","Window","Node","ApplicationCache","AudioTrackList","ChannelMergerNode","CryptoOperation","EventSource","FileReader","HTMLUnknownElement","IDBDatabase","IDBRequest","IDBTransaction","KeyOperation","MediaController","MessagePort","ModalWindow","Notification","SVGElementInstance","Screen","TextTrack","TextTrackCue","TextTrackList","WebSocket","WebSocketWorker","Worker","XMLHttpRequest","XMLHttpRequestEventTarget","XMLHttpRequestUpload"];for(g=0;g", 6 | "driftyco" 7 | ], 8 | "description": "Ionic & Cordova & Gulp seed with organized code, tests, bower support and some other stuff. Originated from ionic-angular-cordova-seed.", 9 | "license": "MIT", 10 | "private": false, 11 | "ignore": [ 12 | "**/.*", 13 | "node_modules" 14 | ], 15 | "dependencies": { 16 | "ionic": "v1.0.0-beta.14", 17 | "angulartics": "~0.17.2", 18 | "urijs": "~1.17.0" 19 | }, 20 | "devDependencies": { 21 | "angular-mocks": "~1.3.6" 22 | }, 23 | "resolutions": { 24 | "angular": "1.3.6" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /gulp/config.coffee: -------------------------------------------------------------------------------- 1 | gulp = require("gulp") 2 | gutil = require("gulp-util") 3 | extend = require("extend") 4 | exec = require("sync-exec") 5 | os = require("os") 6 | 7 | module.exports = new class GulpConfig 8 | constructor: -> 9 | @_GLOBALS_DEFAULTS = { 10 | defaults: { 11 | # If true, then we'll mark this app release as a debug version 12 | # (for example, we'll add ".dbg" to BUNDLE_NAME, 13 | # if we're running/emulating a --debug version of the cordova app) 14 | SET_AS_DEBUG: null 15 | 16 | DEBUG: false 17 | 18 | BUNDLE_VERSION: "1.0.0" 19 | 20 | AVAILABLE_PLATFORMS: ["ios", "android"] 21 | 22 | # The name of your angular app you're going to use in `angular.module("")` 23 | ANGULAR_APP_NAME: "ionicstarter" 24 | 25 | # Base path to this project's directory. Generated automatically. 26 | APP_ROOT: exec("pwd").stdout.trim() + "/" 27 | 28 | # By default, we compile all html/css/js files into www/ directory. 29 | BUILD_DIR: (GLOBALS) -> 30 | if !!+GLOBALS.TMP_BUILD_DIR 31 | "#{os.tmpdir()}/ionic#{GLOBALS.DEPLOY_TIME}/" 32 | else 33 | "www" 34 | 35 | # However, you can set this flag to true, and we'll change BUILD_DIR to a random directory. 36 | # Could be useful f.e. in unit/e2e tests 37 | TMP_BUILD_DIR: false 38 | 39 | # Should we compress assets (.css and .js files, soon also images)? 40 | COMPRESS_ASSETS: false 41 | 42 | # Marks the current code version. Used in uploading sourcemaps to Rollbar. 43 | # By default: sha-code of the recent git commit. 44 | CODE_VERSION: exec("git rev-parse HEAD").stdout.trim() 45 | 46 | # This is only for convenience. 47 | # It will equal to "android" or "ios", 48 | # while the gulp is running a task building the app for given platform 49 | CORDOVA_PLATFORM: null 50 | 51 | # Current timestamp, used to get rid of unwanted http 304 requests. 52 | DEPLOY_TIME: Date.now() 53 | 54 | # Used in server/weinre tasks as an actual IP for the server. 55 | HTTP_SERVER_IP: (-> 56 | # Try to detect IP address in user's network. 57 | # If not, fallback to 127.0.0.1 . 58 | localIp = exec("(ifconfig wlan 2>/dev/null || ifconfig en0) | grep inet | grep -v inet6 | awk '{print $2}' | sed 's/addr://g'").stdout.trim() 59 | localIp = "127.0.0.1" unless parseInt(localIp) > 0 60 | localIp 61 | )() 62 | 63 | # By default, application runs on :4400 port. 64 | HTTP_SERVER_PORT: 4400 65 | 66 | REMOTE_LIVERELOAD: true 67 | 68 | # Optionally, you can define a proxy server: 69 | # all requests to "http://localhost:4400/api/xxx" 70 | # will be redirected to "https://ionic.starter.com/xxx" 71 | # PROXY_ADDRESS: "https://ionic.starter.com" 72 | # PROXY_ROUTE: "/api" 73 | PROXY_ADDRESS: null 74 | PROXY_ROUTE: null 75 | 76 | # If true, we'll open the app in the browser after running the server. 77 | OPEN_IN_BROWSER: true 78 | 79 | # Report errors to Rollbar (rollbar.com) 80 | ROLLBAR_CLIENT_ACCESS_TOKEN: null # "aaa" 81 | ROLLBAR_SERVER_ACCESS_TOKEN: null # "bbb" 82 | 83 | # If you want to upload sourcemaps to Rollbar, just set a random URL prefix 84 | # (we'll modify payloads on iOS/Android so the URL to js scripts will be always the same) 85 | ROLLBAR_SOURCEMAPS_URL_PREFIX: "https://ionicstarter.com" 86 | 87 | # Important: leave it as false, even if you want to have the sourcemaps uploaded. 88 | # gulp.task("deploy:rollbar-sourcemaps") automatically sets it as true - only when it's needed. 89 | # (you want to upload them only on release tasks, don't you?) 90 | UPLOAD_SOURCEMAPS_TO_ROLLBAR: false 91 | 92 | # If defined, we'll deploy the app to testfairy after compiling the release. 93 | # TESTFAIRY_API_KEY: "123" 94 | # TESTFAIRY_TESTER_GROUPS: "IonicStarterTesters" 95 | TESTFAIRY_API_KEY: null 96 | TESTFAIRY_TESTER_GROUPS: null 97 | }, 98 | 99 | development: { 100 | ENV: "development" 101 | 102 | BUNDLE_ID: "com.jtomaszewski.ionicstarter.development" 103 | BUNDLE_NAME: "IonicStarterDev" 104 | 105 | # Automatically connect to weinre on application's startup 106 | # (this way you can debug your application on your PC even if it's running from mobile ;) ) 107 | WEINRE_ADDRESS: (GLOBALS) -> 108 | "#{GLOBALS.HTTP_SERVER_IP}:31173" 109 | }, 110 | 111 | production: { 112 | ENV: "production" 113 | 114 | BUNDLE_ID: "com.jtomaszewski.ionicstarter.production" 115 | BUNDLE_NAME: "IonicStarter" 116 | 117 | COMPRESS_ASSETS: true 118 | 119 | # If those 2 variables are defined, the app will be deployed to the remote server after compiling the release. 120 | # ANDROID_DEPLOY_APPBIN_PATH: "deploy@ionicstarter.com:/u/apps/ionicstarter/shared/public/uploads/ionicstarter-production.apk" 121 | # ANDROID_DEPLOY_APPBIN_URL: "http://ionicstarter.com/uploads/ionicstarter-production.apk" 122 | 123 | # If those 2 variables are defined, the app will be deployed to the remote server after compiling the release. 124 | # IOS_DEPLOY_APPBIN_PATH: "deploy@ionicstarter.com:/u/apps/ionicstarter/shared/public/uploads/ionicstarter-production.ipa" 125 | # IOS_DEPLOY_APPBIN_URL: "http://ionicstarter.com/uploads/ionicstarter-production.ipa" 126 | 127 | # Required for the release to be signed with correct certificate. 128 | IOS_PROVISIONING_PROFILE: "keys/ios/ionicstarterstaging.mobileprovision" 129 | 130 | # CORDOVA_GOOGLE_ANALYTICS_ID: "UA-123123-2" 131 | # GOOGLE_ANALYTICS_ID: "UA-123123-1" 132 | # GOOGLE_ANALYTICS_HOST: "ionicstarter.com" 133 | } 134 | } 135 | 136 | # _PUBLIC_GLOBALS_KEYS defines which @GLOBALS 137 | # will be actually passed into the frontend's application. 138 | # (rest of globals are visible only in gulp and shell scripts) 139 | # 140 | # The filtered globals will be available under GulpConfig.PUBLIC_GLOBALS. 141 | @_PUBLIC_GLOBALS_KEYS = [ 142 | "ANGULAR_APP_NAME" 143 | "BUNDLE_NAME" 144 | "BUNDLE_VERSION" 145 | "CODE_VERSION" 146 | "CORDOVA_GOOGLE_ANALYTICS_ID" 147 | "DEPLOY_TIME" 148 | "ENV" 149 | "GOOGLE_ANALYTICS_HOST" 150 | "GOOGLE_ANALYTICS_ID" 151 | "ROLLBAR_CLIENT_ACCESS_TOKEN" 152 | "ROLLBAR_SOURCEMAPS_URL_PREFIX" 153 | "WEINRE_ADDRESS" 154 | ] 155 | 156 | @_PUBLIC_GLOBALS_KEYS = @_PUBLIC_GLOBALS_KEYS.concat([ 157 | "HTTP_SERVER_IP" 158 | "HTTP_SERVER_PORT" 159 | ]) unless gulp.env.appstore || gulp.env.release 160 | 161 | # _SHELL_GLOBALS_KEYS defines which @GLOBALS 162 | # will be passed to some of the shell tasks (f.e. those in tasks/cordova.coffee). 163 | # 164 | # The filtered globals will be available under GulpConfig.SHELL_GLOBALS. 165 | @_SHELL_GLOBALS_KEYS = [ 166 | "BUNDLE_ID" 167 | "BUNDLE_NAME" 168 | "BUNDLE_VERSION" 169 | "IOS_PROVISIONING_PROFILE" 170 | ] 171 | 172 | @_regenerateGlobals() 173 | 174 | @PATHS = { 175 | assets: ['assets/**', '!assets/**/*.ejs'] 176 | assets_ejs: ['assets/**/*.ejs'] 177 | watched_assets: ['assets/fonts/**', 'assets/images/**', 'assets/js/**', '!assets/*.ejs'] 178 | styles: ['app/css/**/*.scss'] 179 | scripts: 180 | vendor: [ 181 | "assets/components/urijs/src/URI.js" 182 | 183 | "assets/components/ionic/release/js/ionic.js" 184 | "assets/components/angular/angular.js" 185 | "assets/components/angular-animate/angular-animate.js" 186 | "assets/components/angular-sanitize/angular-sanitize.js" 187 | "assets/components/angular-ui-router/release/angular-ui-router.js" 188 | "assets/components/ionic/release/js/ionic-angular.js" 189 | 190 | # Here add any vendor files that should be included in vendor.js 191 | # (f.e. bower components) 192 | 193 | # Google Analytics support (for both in-browser and Cordova app) 194 | "assets/components/angulartics/src/angulartics.js" 195 | "assets/components/angulartics/src/angulartics-ga.js" 196 | "assets/components/angulartics/src/angulartics-ga-cordova.js" 197 | ] 198 | app: [ 199 | 'app/js/config/**/*.coffee' # initialize & configure the angular's app 200 | 'app/js/*/**/*.coffee' # include all angular submodules (like controllers, directives, services) 201 | 'app/js/routes.coffee' # app.config - set routes 202 | ] 203 | tests: 204 | e2e: [ 205 | 'test/e2e/*_test.coffee' 206 | ] 207 | templates: ['app/templates/**/*.jade'] 208 | views: ['app/**/*.jade', '!app/templates/**/*.jade'] 209 | } 210 | 211 | @DESTINATIONS = { 212 | assets: "#{@GLOBALS.BUILD_DIR}" 213 | styles: "#{@GLOBALS.BUILD_DIR}/css" 214 | scripts: "#{@GLOBALS.BUILD_DIR}/js" 215 | views: "#{@GLOBALS.BUILD_DIR}" 216 | livereload: [ 217 | "#{@GLOBALS.BUILD_DIR}/**/*" 218 | "!#{@GLOBALS.BUILD_DIR}/**/*.map" 219 | "!#{@GLOBALS.BUILD_DIR}/config.xml" 220 | ] 221 | unnecessary_assets: [ 222 | "#{@GLOBALS.BUILD_DIR}/**/*.map" 223 | ] 224 | } 225 | 226 | 227 | updateGlobalsDefaults: (GLOBALS_DEFAULTS) -> 228 | extend true, @_GLOBALS_DEFAULTS, GLOBALS_DEFAULTS 229 | @_regenerateGlobals() 230 | 231 | 232 | filterPublicGlobals: (@_PUBLIC_GLOBALS_KEYS) -> 233 | @PUBLIC_GLOBALS = {} 234 | for key in @_PUBLIC_GLOBALS_KEYS 235 | @PUBLIC_GLOBALS[key] = @GLOBALS[key] if @GLOBALS[key]? 236 | 237 | 238 | filterShellGlobals: (@_SHELL_GLOBALS_KEYS) -> 239 | @SHELL_GLOBALS = {} 240 | for key in @_SHELL_GLOBALS_KEYS 241 | @SHELL_GLOBALS[key] = @GLOBALS[key] if @GLOBALS[key]? 242 | 243 | 244 | _regenerateGlobals: -> 245 | @GLOBALS = require('extend') true, {}, @_GLOBALS_DEFAULTS.defaults, (@_GLOBALS_DEFAULTS[gutil.env.env || "development"] || {}) 246 | 247 | Object.keys(@GLOBALS).forEach (k) => 248 | # If a @GLOBALS[k] is a function, 249 | # then let's use it as a dynamic getter 250 | # (More information: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty) 251 | if typeof @GLOBALS[k] == "function" 252 | getter = @GLOBALS[k] 253 | Object.defineProperty @GLOBALS, k, get: => getter(@GLOBALS) 254 | 255 | # You can replace any of @GLOBALS by defining ENV variable in your command line, 256 | # f.e. `BUNDLE_ID="com.different.bundleid" gulp` 257 | @GLOBALS[k] = process.env[k] if process.env[k]? 258 | 259 | # You can also do this in this way: 260 | # `gulp --BUNDLE_ID="com.different.bundleid"` 261 | @GLOBALS[k] = gulp.env[k] if gulp.env[k]? 262 | 263 | @filterPublicGlobals(@_PUBLIC_GLOBALS_KEYS) 264 | @filterShellGlobals(@_SHELL_GLOBALS_KEYS) 265 | -------------------------------------------------------------------------------- /gulp/main.coffee: -------------------------------------------------------------------------------- 1 | # Configure GulpConfig 2 | require "./config" 3 | 4 | # Load all tasks 5 | require "./tasks" 6 | -------------------------------------------------------------------------------- /gulp/tasks.coffee: -------------------------------------------------------------------------------- 1 | require "./tasks/base/clean" 2 | require "./tasks/base/bower-install" 3 | 4 | require "./tasks/build/assets" 5 | require "./tasks/build/styles" 6 | require "./tasks/build/scripts" 7 | require "./tasks/build/templates" 8 | require "./tasks/build/views" 9 | require "./tasks/build/minify" 10 | require "./tasks/build/build" 11 | 12 | require "./tasks/test/e2e" 13 | require "./tasks/test/unit" 14 | 15 | require "./tasks/server/watch" 16 | require "./tasks/server/serve" 17 | require "./tasks/server/weinre" 18 | 19 | require "./tasks/cordova" 20 | 21 | require "./tasks/deploy" 22 | require "./tasks/release" 23 | 24 | require "./tasks/help" 25 | require "./tasks/default" 26 | -------------------------------------------------------------------------------- /gulp/tasks/base/bower-install.coffee: -------------------------------------------------------------------------------- 1 | gulp = require('gulp-help')(require('gulp')) 2 | shell = require 'gulp-shell' 3 | 4 | gulp.task 'bower:install', "Run `bower install` to ensure bower packages are up-to-date", shell.task('bower install') 5 | -------------------------------------------------------------------------------- /gulp/tasks/base/clean.coffee: -------------------------------------------------------------------------------- 1 | gulp = require('gulp-help')(require('gulp')) 2 | clean = require 'gulp-clean' 3 | 4 | {GLOBALS, PUBLIC_GLOBALS, PATHS, DESTINATIONS} = require "../../config" 5 | 6 | gulp.task 'clean', "Clean contents of ./#{GLOBALS.BUILD_DIR}/", -> 7 | gulp.src(GLOBALS.BUILD_DIR, read: false) 8 | .pipe(clean(force: true)) 9 | -------------------------------------------------------------------------------- /gulp/tasks/build/assets.coffee: -------------------------------------------------------------------------------- 1 | gulp = require('gulp-help')(require('gulp')) 2 | gutil = require 'gulp-util' 3 | plumber = require 'gulp-plumber' 4 | changed = require 'gulp-changed' 5 | ejs = require 'gulp-ejs' 6 | 7 | {GLOBALS, PUBLIC_GLOBALS, PATHS, DESTINATIONS} = require "../../config" 8 | 9 | 10 | gulp.task 'assets:ejs', "Compile assets/*.ejs files and copy them to ./#{GLOBALS.BUILD_DIR}/", -> 11 | gulp.src(PATHS.assets_ejs) 12 | .pipe((plumber (error) -> 13 | gutil.log gutil.colors.red(error.message) 14 | @emit('end') 15 | )) 16 | .pipe(ejs(GLOBALS, ext: '')) 17 | .pipe(gulp.dest(DESTINATIONS.assets)) 18 | 19 | gulp.task 'assets:others', "Copy assets/* files to ./#{GLOBALS.BUILD_DIR}/", -> 20 | gulp.src(PATHS.assets, base: "assets") 21 | .pipe(changed(DESTINATIONS.assets)) 22 | .pipe(gulp.dest(DESTINATIONS.assets)) 23 | 24 | gulp.task 'assets', "Copy assets files to ./#{GLOBALS.BUILD_DIR}/", ['assets:ejs', 'assets:others'] 25 | -------------------------------------------------------------------------------- /gulp/tasks/build/build.coffee: -------------------------------------------------------------------------------- 1 | gulp = require('gulp-help')(require('gulp')) 2 | runSequence = require 'run-sequence' 3 | 4 | {GLOBALS, PUBLIC_GLOBALS, PATHS, DESTINATIONS} = require "../../config" 5 | 6 | 7 | # Run 'set-as-debug' as the first task, to enable debug version. 8 | # Example: `gulp set-as-debug cordova:run:android` 9 | gulp.task "set-as-debug", false, -> 10 | unless gulp.env.appstore || gulp.env.release 11 | GLOBALS.SET_AS_DEBUG ?= true 12 | 13 | gulp.task "set-debug", false, -> 14 | unless gulp.env.appstore || gulp.env.release 15 | if !!+GLOBALS.SET_AS_DEBUG 16 | GLOBALS.DEBUG = true 17 | 18 | if GLOBALS.DEBUG 19 | if GLOBALS.BUNDLE_ID.indexOf(".debug") == -1 20 | console.log ">> set-debug gulp task >> Adding .debug to the GLOBALS.BUNDLE_ID" 21 | GLOBALS.BUNDLE_ID += ".debug" 22 | GLOBALS.BUNDLE_NAME += "Dbg" 23 | 24 | 25 | gulp.task "build-debug", false, ["set-as-debug", "set-debug", "build"] 26 | 27 | 28 | gulp.task "build", "Compile all the contents of ./#{GLOBALS.BUILD_DIR}/", (cb) -> 29 | runSequence ["clean", "bower:install"], 30 | [ 31 | "assets" 32 | "styles" 33 | "scripts" 34 | "templates" 35 | "views" 36 | ], cb 37 | 38 | 39 | gulp.task "build-release", false, (cb) -> 40 | runSequence "build", "build:minify", cb 41 | -------------------------------------------------------------------------------- /gulp/tasks/build/minify.coffee: -------------------------------------------------------------------------------- 1 | gulp = require('gulp-help')(require('gulp')) 2 | clean = require 'gulp-clean' 3 | 4 | {GLOBALS, PUBLIC_GLOBALS, PATHS, DESTINATIONS} = require "../../config" 5 | 6 | 7 | gulp.task 'build:minify', 8 | "Minify ./#{GLOBALS.BUILD_DIR}/ files size by compressing them and removing unnecessary ones.", 9 | ['build:remove-unnecessary-assets'] 10 | 11 | 12 | gulp.task 'build:remove-unnecessary-assets', "Remove unnecessary assets from ./#{GLOBALS.BUILD_DIR}/.", -> 13 | gulp.src(DESTINATIONS.unnecessary_assets, read: false) 14 | .pipe(clean(force: true)) 15 | -------------------------------------------------------------------------------- /gulp/tasks/build/scripts.coffee: -------------------------------------------------------------------------------- 1 | gulp = require('gulp-help')(require('gulp')) 2 | gutil = require 'gulp-util' 3 | plumber = require 'gulp-plumber' 4 | coffee = require 'gulp-coffee' 5 | concat = require 'gulp-concat' 6 | sourcemaps = require 'gulp-sourcemaps' 7 | rollbar = require 'gulp-rollbar' 8 | gulpIf = require 'gulp-if' 9 | uglify = require 'gulp-uglify' 10 | 11 | {GLOBALS, PUBLIC_GLOBALS, PATHS, DESTINATIONS} = require "../../config" 12 | 13 | 14 | uploadSourcemapsToRollbar = -> 15 | shouldUploadRollbarSourcemaps = !!+GLOBALS.UPLOAD_SOURCEMAPS_TO_ROLLBAR && !!GLOBALS.ROLLBAR_SERVER_ACCESS_TOKEN 16 | gulpIf(shouldUploadRollbarSourcemaps, rollbar({ 17 | accessToken: (GLOBALS.ROLLBAR_SERVER_ACCESS_TOKEN || "none") 18 | version: GLOBALS.CODE_VERSION 19 | sourceMappingURLPrefix: GLOBALS.ROLLBAR_SOURCEMAPS_URL_PREFIX + "/js" 20 | })) 21 | 22 | 23 | gulp.task 'scripts:vendor', "Compile vendor js scripts to the ./#{GLOBALS.BUILD_DIR}/js/vendor.js file", -> 24 | gulp.src(PATHS.scripts.vendor) 25 | 26 | .pipe(sourcemaps.init()) 27 | .pipe(concat('vendor.js')) 28 | .pipe(gulpIf(!!+GLOBALS.COMPRESS_ASSETS, uglify(mangle: false))) 29 | .pipe(uploadSourcemapsToRollbar()) 30 | .pipe(sourcemaps.write('./')) 31 | 32 | .pipe(gulp.dest(DESTINATIONS.scripts)) 33 | 34 | 35 | gulp.task "scripts:app", "Compile ./app/js/*.js scripts to the ./#{GLOBALS.BUILD_DIR}/js/app.js file", -> 36 | gulp.src(PATHS.scripts.app) 37 | .pipe((plumber (error) -> 38 | gutil.log gutil.colors.red(error.message) 39 | @emit('end') 40 | )) 41 | 42 | .pipe(sourcemaps.init()) 43 | .pipe(coffee()) 44 | .pipe(concat("app.js")) 45 | .pipe(gulpIf(!!+GLOBALS.COMPRESS_ASSETS, uglify(mangle: false))) 46 | .pipe(uploadSourcemapsToRollbar()) 47 | .pipe(sourcemaps.write('./')) 48 | 49 | .pipe(gulp.dest(DESTINATIONS.scripts)) 50 | 51 | 52 | gulp.task 'scripts', "Compile ./#{GLOBALS.BUILD_DIR}/js/*.js scripts", ['scripts:vendor', 'scripts:app'] 53 | 54 | 55 | if !!GLOBALS.ROLLBAR_SERVER_ACCESS_TOKEN 56 | # Run this as a first task, to enable uploading sourcemaps to rollbar. 57 | # By default it's being run in the "release" task. 58 | gulp.task "deploy:rollbar-sourcemaps:enable", "Turn on uploading of scripts' sourcemaps to Rollbar (during the scripts:* tasks)", -> 59 | GLOBALS.UPLOAD_SOURCEMAPS_TO_ROLLBAR = true 60 | 61 | gulp.task "deploy:rollbar-sourcemaps", "Upload scripts' sourcemaps to Rollbar", ["deploy:rollbar-sourcemaps:enable", "scripts"] 62 | -------------------------------------------------------------------------------- /gulp/tasks/build/styles.coffee: -------------------------------------------------------------------------------- 1 | gulp = require('gulp-help')(require('gulp')) 2 | gutil = require 'gulp-util' 3 | plumber = require 'gulp-plumber' 4 | changed = require 'gulp-changed' 5 | notify = require 'gulp-notify' 6 | sass = require 'gulp-sass' 7 | sourcemaps = require 'gulp-sourcemaps' 8 | gulpIf = require 'gulp-if' 9 | minifyCSS = require 'gulp-minify-css' 10 | 11 | {GLOBALS, PUBLIC_GLOBALS, PATHS, DESTINATIONS} = require "../../config" 12 | 13 | gulp.task 'styles', "Compile ./app/css/*.sass stylesheets to ./#{GLOBALS.BUILD_DIR}/css/*.css", -> 14 | gulp.src(PATHS.styles) 15 | .pipe(changed(DESTINATIONS.styles, extension: '.css')) 16 | .pipe((plumber (error) -> 17 | gutil.log gutil.colors.red(error.message) 18 | @emit('end') 19 | )) 20 | 21 | .pipe(sourcemaps.init()) 22 | .pipe(sass()) 23 | .pipe(gulpIf(!!+GLOBALS.COMPRESS_ASSETS, minifyCSS(processImport: false))) 24 | .pipe(sourcemaps.write('./')) 25 | 26 | .on('error', notify.onError((error) -> error.message)) 27 | .pipe(gulp.dest(DESTINATIONS.styles)) 28 | -------------------------------------------------------------------------------- /gulp/tasks/build/templates.coffee: -------------------------------------------------------------------------------- 1 | gulp = require('gulp-help')(require('gulp')) 2 | gutil = require 'gulp-util' 3 | plumber = require 'gulp-plumber' 4 | notify = require 'gulp-notify' 5 | jade = require 'gulp-jade' 6 | templateCache = require 'gulp-angular-templatecache' 7 | path = require 'path' 8 | 9 | {GLOBALS, PUBLIC_GLOBALS, PATHS, DESTINATIONS} = require "../../config" 10 | 11 | gulp.task 'templates', "Compile ./app/templates/*.jade templates to a ./#{GLOBALS.BUILD_DIR}/js/app_templates.js file", -> 12 | gulp.src(PATHS.templates) 13 | .pipe((plumber (error) -> 14 | gutil.log gutil.colors.red(error.message) 15 | @emit('end') 16 | )) 17 | .pipe(jade({ 18 | locals: 19 | GLOBALS: PUBLIC_GLOBALS 20 | pretty: true 21 | })) 22 | .on('error', notify.onError((error) -> error.message)) 23 | 24 | .pipe(templateCache("app_templates.js", { 25 | standalone: true 26 | module: "#{GLOBALS.ANGULAR_APP_NAME}.templates" 27 | base: (file) -> 28 | file.path 29 | .replace(path.resolve("./"), "") 30 | .replace("/app/", "") 31 | })) 32 | .pipe(gulp.dest(DESTINATIONS.scripts)) 33 | -------------------------------------------------------------------------------- /gulp/tasks/build/views.coffee: -------------------------------------------------------------------------------- 1 | gulp = require('gulp-help')(require('gulp')) 2 | gutil = require 'gulp-util' 3 | plumber = require 'gulp-plumber' 4 | notify = require 'gulp-notify' 5 | jade = require 'gulp-jade' 6 | 7 | {GLOBALS, PUBLIC_GLOBALS, PATHS, DESTINATIONS} = require "../../config" 8 | 9 | gulp.task 'views', "Compile ./app/*.jade views to ./#{GLOBALS.BUILD_DIR}/*.html", -> 10 | gulp.src(PATHS.views) 11 | .pipe((plumber (error) -> 12 | gutil.log gutil.colors.red(error.message) 13 | @emit('end') 14 | )) 15 | .pipe(jade({ 16 | locals: 17 | GLOBALS: PUBLIC_GLOBALS 18 | pretty: true 19 | })) 20 | .on('error', notify.onError((error) -> error.message)) 21 | .pipe(gulp.dest(DESTINATIONS.views)) 22 | -------------------------------------------------------------------------------- /gulp/tasks/cordova.coffee: -------------------------------------------------------------------------------- 1 | gulp = require('gulp-help')(require('gulp')) 2 | shell = require 'gulp-shell' 3 | 4 | {GLOBALS, PUBLIC_GLOBALS, SHELL_GLOBALS, PATHS, DESTINATIONS} = require "../config" 5 | 6 | 7 | # Adds all SHELL_GLOBALS as ENV variables. 8 | # Returns string like `env BUNDLE_ID="com.jtomaszewski.ionicstarter.development" BUNDLE_NAME="IonicStarterDev" ` 9 | generateEnvCommand = -> 10 | cmd = "env " 11 | for k, v of SHELL_GLOBALS 12 | cmd += "#{k}=\"#{v}\" " 13 | cmd 14 | 15 | 16 | # Clean all cordova platforms, so they will need to be generated again. 17 | gulp.task "cordova:clear", "Reset built apps (clears platforms/* directories)", shell.task('rm -rf plugins/* platforms/*') 18 | 19 | 20 | # Create cordova platform. 21 | GLOBALS.AVAILABLE_PLATFORMS.forEach (platform) -> 22 | gulp.task "set-platform-global:#{platform}", 23 | false, 24 | -> 25 | GLOBALS.CORDOVA_PLATFORM = platform 26 | 27 | gulp.task "cordova:platform-add:#{platform}", 28 | "Prepares platforms/#{platform} project (runs `cordova platform add #{platform}`)", 29 | ["set-platform-global:#{platform}", "build"], 30 | -> 31 | shell.task(generateEnvCommand() + "node_modules/.bin/cordova platform add #{platform}", ignoreErrors: true)() 32 | 33 | # Build and emulate. 34 | gulp.task "cordova:emulate:#{platform}", 35 | "Emulates the app on #{platform} (runs `cordova emulate #{platform}`)", 36 | ["cordova:platform-add:#{platform}", "build-debug"].concat(if GLOBALS.REMOTE_LIVERELOAD then ["watch", "serve"] else []), 37 | -> 38 | shell.task(generateEnvCommand() + "node_modules/.bin/cordova emulate #{platform}")() 39 | 40 | # Build and run on connected device. 41 | gulp.task "cordova:run:#{platform}", 42 | "Runs the app debug version on #{platform} (runs `cordova run #{platform} --device`)", 43 | ["cordova:platform-add:#{platform}", "build-debug"].concat(if GLOBALS.REMOTE_LIVERELOAD then ["watch", "serve"] else []), 44 | -> 45 | shell.task(generateEnvCommand() + "node_modules/.bin/cordova run #{platform} --device")() 46 | 47 | # Same as cordova:run, but use release version, not debug. 48 | gulp.task "cordova:run-release:#{platform}", 49 | "Runs the app release version on #{platform} (runs `cordova run #{platform} --device --release`)", 50 | ["cordova:platform-add:#{platform}", "build-release"].concat(if GLOBALS.REMOTE_LIVERELOAD then ["watch", "serve"] else []), 51 | -> 52 | shell.task(generateEnvCommand() + "node_modules/.bin/cordova run #{platform} --device --release")() 53 | 54 | # Build a release. 55 | gulp.task "cordova:build-release:#{platform}", 56 | "Builds the app release version for #{platform} (runs `cordova build #{platform} --release`)", 57 | ["cordova:platform-add:#{platform}", "build-release"], 58 | -> 59 | shell.task(generateEnvCommand() + "node_modules/.bin/cordova build #{platform} --release" + ((" --device" if platform == "ios") || ""))() 60 | 61 | # Sign the release. 62 | # NOTE no longer needed since xcode 8 uses automatic code signing (cordova ios 4.3.0) 63 | if platform == "ios" 64 | if GLOBALS.IOS_PROVISIONING_PROFILE 65 | gulp.task "cordova:sign-release:ios", 66 | "Signs the release of iOS app with '#{GLOBALS.IOS_PROVISIONING_PROFILE}' and compiles it to 'platforms/ios/#{GLOBALS.BUNDLE_NAME}.ipa' binary file" 67 | -> 68 | shell.task("xcrun -sdk iphoneos PackageApplication \ 69 | -v \"platforms/ios/build/device/#{GLOBALS.BUNDLE_NAME}.app\" \ 70 | -o \"#{GLOBALS.APP_ROOT}platforms/ios/#{GLOBALS.BUNDLE_NAME}.ipa\" \ 71 | --embed \"#{GLOBALS.IOS_PROVISIONING_PROFILE}\"")() 72 | else 73 | gulp.task "cordova:sign-release:#{platform}", false, [] 74 | -------------------------------------------------------------------------------- /gulp/tasks/default.coffee: -------------------------------------------------------------------------------- 1 | gulp = require('gulp-help')(require('gulp')) 2 | runSequence = require 'run-sequence' 3 | 4 | {GLOBALS, PATHS, DESTINATIONS} = require "../config" 5 | 6 | 7 | gulp.task "default", "Build ./#{GLOBALS.BUILD_DIR}/ contents, run browser-sync server and watch for changes (and rebuild & livereload, when something changes).", (cb) -> 8 | runSequence "build", ["watch", "serve", "weinre"], cb 9 | -------------------------------------------------------------------------------- /gulp/tasks/deploy.coffee: -------------------------------------------------------------------------------- 1 | gulp = require('gulp-help')(require('gulp')) 2 | shell = require 'gulp-shell' 3 | 4 | {GLOBALS, PUBLIC_GLOBALS, PATHS, DESTINATIONS} = require "../config" 5 | 6 | 7 | # Deploy the release's binary to webserver. 8 | open_qrcode_cmd = (url) -> 9 | "curl -s --include \ 10 | --request GET 'https://pierre2106j-qrcode.p.mashape.com/api?type=text&text=#{encodeURIComponent(url)}&ecl=L%20%7C%20M%7C%20Q%20%7C%20H&pixel=8&forecolor=000000&backcolor=ffffff' \ 11 | --header \"X-Mashape-Authorization: xWzeUXHELgVCXp9L4iK3epFzvsTECUai\" | tail -n 1 | xargs open" 12 | 13 | 14 | deploy_release_cmd = (from, to, to_url) -> 15 | "scp \ 16 | #{from} #{to} \ 17 | && echo \"App has been deployed to #{to_url} .\"\ 18 | " + (if +GLOBALS.OPEN_IN_BROWSER then " && #{open_qrcode_cmd(to_url)}" else "") 19 | 20 | 21 | # Android deployment 22 | androidDeployReleaseTasks = [] 23 | androidReleaseFile = "platforms/android/build/outputs/apk/android-release.apk" 24 | 25 | 26 | if GLOBALS.TESTFAIRY_API_KEY && GLOBALS.TESTFAIRY_TESTER_GROUPS 27 | gulp.task "deploy:testfairy:android", "Deploy the .apk binary file to TestFairy", shell.task(""" 28 | env \ 29 | TESTFAIRY_API_KEY='#{GLOBALS.TESTFAIRY_API_KEY}' \ 30 | TESTER_GROUPS='#{GLOBALS.TESTFAIRY_TESTER_GROUPS}' \ 31 | utils/testfairy-upload-android.sh \"#{androidReleaseFile}\" 32 | """) 33 | androidDeployReleaseTasks.push "deploy:testfairy:android" 34 | 35 | 36 | if GLOBALS.ANDROID_DEPLOY_APPBIN_PATH 37 | cmd = deploy_release_cmd androidReleaseFile, GLOBALS.ANDROID_DEPLOY_APPBIN_PATH, GLOBALS.ANDROID_DEPLOY_APPBIN_URL 38 | gulp.task "open-deploy:server:android", shell.task(open_qrcode_cmd(GLOBALS.ANDROID_DEPLOY_APPBIN_URL)) 39 | gulp.task "deploy:server:android", "Deploy the .apk binary to #{GLOBALS.ANDROID_DEPLOY_APPBIN_PATH}", shell.task(cmd) 40 | androidDeployReleaseTasks.push "deploy:server:android" 41 | 42 | 43 | gulp.task "deploy:release:android", "Deploy the Android app", androidDeployReleaseTasks 44 | 45 | 46 | 47 | # IOS deployment 48 | iosDeployReleaseTasks = [] 49 | iosReleaseFile = "platforms/ios/build/device/#{GLOBALS.BUNDLE_NAME}.ipa" 50 | 51 | 52 | if GLOBALS.TESTFAIRY_API_KEY && GLOBALS.TESTFAIRY_TESTER_GROUPS 53 | gulp.task "deploy:testfairy:ios", "Deploy the .ipa binary file to TestFairy", shell.task(""" 54 | env \ 55 | TESTFAIRY_API_KEY='#{GLOBALS.TESTFAIRY_API_KEY}' \ 56 | TESTER_GROUPS='#{GLOBALS.TESTFAIRY_TESTER_GROUPS}' \ 57 | utils/testfairy-upload-ios.sh \"#{iosReleaseFile}\" 58 | """) 59 | iosDeployReleaseTasks.push "deploy:testfairy:ios" 60 | 61 | 62 | if GLOBALS.IOS_DEPLOY_APPBIN_PATH 63 | cmd = deploy_release_cmd iosReleaseFile, GLOBALS.IOS_DEPLOY_APPBIN_PATH, GLOBALS.IOS_DEPLOY_APPBIN_URL 64 | gulp.task "deploy:server:ios", "Deploy the .ipa binary file to #{GLOBALS.IOS_DEPLOY_APPBIN_PATH}", shell.task(cmd) 65 | iosDeployReleaseTasks.push "deploy:server:ios" 66 | 67 | gulp.task "deploy:release:ios", "Deploy the iOS app", iosDeployReleaseTasks 68 | -------------------------------------------------------------------------------- /gulp/tasks/help.coffee: -------------------------------------------------------------------------------- 1 | {GLOBALS, PUBLIC_GLOBALS, _GLOBALS_DEFAULTS, SHELL_GLOBALS, PATHS, DESTINATIONS} = require "../config" 2 | 3 | 4 | truncate = (string, length = 50, delimiter = '...') -> 5 | if !string || string.toString().length <= length 6 | string 7 | else 8 | string.toString().slice(0, length - delimiter.length) + delimiter 9 | 10 | 11 | opt = {} 12 | opt["env=#{Object.keys(_GLOBALS_DEFAULTS).slice(1).join("|")}"] = "Defines the environment for all gulp tasks and generated GLOBALS variables" 13 | for k,v of GLOBALS 14 | opt[truncate("#{k}=#{v}", 55)] = "a GLOBAL variable" 15 | 16 | 17 | # Configure `gulp help` task 18 | gulp = require('gulp-help')(require('gulp'), { 19 | description: """ 20 | Display this help text. NOTE: All argument options described below are applied to all of gulp tasks. 21 | """ 22 | options: opt 23 | }) 24 | -------------------------------------------------------------------------------- /gulp/tasks/release.coffee: -------------------------------------------------------------------------------- 1 | gulp = require('gulp-help')(require('gulp')) 2 | runSequence = require 'run-sequence' 3 | 4 | {GLOBALS, PUBLIC_GLOBALS, PATHS, DESTINATIONS} = require "../config" 5 | 6 | 7 | GLOBALS.AVAILABLE_PLATFORMS.forEach (platform) -> 8 | # Build the release and deploy it. 9 | gulp.task "release:#{platform}", 10 | "Release the #{platform} app", 11 | (cb) -> 12 | runSequence "cordova:build-release:#{platform}", 13 | "deploy:release:#{platform}", 14 | cb 15 | 16 | 17 | releaseTasks = ["deploy:rollbar-sourcemaps:enable", "release:android", "release:ios"] 18 | .filter (taskName) -> !!gulp.tasks[taskName] 19 | 20 | if releaseTasks.length > 0 21 | gulp.task "release", "Release the app - deploy it to both Android & iOS", ["build-release"], -> 22 | runSequence.apply(runSequence, releaseTasks) 23 | 24 | -------------------------------------------------------------------------------- /gulp/tasks/server/serve.coffee: -------------------------------------------------------------------------------- 1 | gulp = require('gulp-help')(require('gulp')) 2 | gutil = require 'gulp-util' 3 | browserSync = require 'browser-sync' 4 | proxy = require "proxy-middleware" 5 | url = require "url" 6 | 7 | {GLOBALS, PUBLIC_GLOBALS, PATHS, DESTINATIONS} = require "../../config" 8 | 9 | gulp.task 'serve', "Run browser-sync server with livereload in ./#{GLOBALS.BUILD_DIR}/ directory", -> 10 | middlewares = [] 11 | 12 | if GLOBALS.PROXY_ADDRESS 13 | proxyOptions = url.parse(GLOBALS.PROXY_ADDRESS) 14 | proxyOptions.route = GLOBALS.PROXY_ROUTE 15 | middlewares.push proxy(proxyOptions) 16 | 17 | browserSync({ 18 | server: 19 | baseDir: GLOBALS.BUILD_DIR 20 | middleware: middlewares 21 | port: GLOBALS.HTTP_SERVER_PORT 22 | open: !!+GLOBALS.OPEN_IN_BROWSER 23 | files: DESTINATIONS.livereload 24 | }) 25 | -------------------------------------------------------------------------------- /gulp/tasks/server/watch.coffee: -------------------------------------------------------------------------------- 1 | gulp = require('gulp-help')(require('gulp')) 2 | 3 | {GLOBALS, PUBLIC_GLOBALS, PATHS, DESTINATIONS} = require "../../config" 4 | 5 | gulp.task 'watch', "Watch contents of app, assets, vendor files and recompile them to ./#{GLOBALS.BUILD_DIR}/ when changed", -> 6 | if process.env.GULP_WATCH_ASSETS # this makes some bug with descriptors on mac, so let's enable it only when specified ENV is defined 7 | gulp.watch(PATHS.assets, ['assets']) 8 | else 9 | gulp.watch(PATHS.watched_assets, ['assets']) 10 | gulp.watch(PATHS.assets_ejs, ['assets:ejs']) 11 | gulp.watch(PATHS.scripts.app, ['scripts:app']) 12 | gulp.watch(PATHS.scripts.vendor, ['scripts:vendor']) 13 | gulp.watch(PATHS.styles, ['styles']) 14 | gulp.watch(PATHS.templates, ['templates']) 15 | gulp.watch(PATHS.views, ['views']) 16 | -------------------------------------------------------------------------------- /gulp/tasks/server/weinre.coffee: -------------------------------------------------------------------------------- 1 | gulp = require('gulp-help')(require('gulp')) 2 | gutil = require 'gulp-util' 3 | open = require 'open' 4 | childProcess = require 'child_process' 5 | 6 | {GLOBALS, PUBLIC_GLOBALS, PATHS, DESTINATIONS} = require "../../config" 7 | 8 | gulp.task "weinre", "Run a Weinre debugger server", -> 9 | return unless GLOBALS.WEINRE_ADDRESS 10 | [weinreHost, weinrePort] = GLOBALS.WEINRE_ADDRESS.split(":") 11 | 12 | args = ["--httpPort=#{weinrePort}", "--boundHost=#{weinreHost}"] 13 | child = childProcess.spawn "node_modules/.bin/weinre", args, 14 | stdio: "inherit" 15 | # .on "exit", (code) -> 16 | # child.kill() if child 17 | # cb(code) 18 | 19 | if +GLOBALS.OPEN_IN_BROWSER 20 | open("http://#{weinreHost}:#{weinrePort}/client/#anonymous") 21 | gutil.log gutil.colors.blue "Opening weinre debugger in the browser..." 22 | -------------------------------------------------------------------------------- /gulp/tasks/test/e2e.coffee: -------------------------------------------------------------------------------- 1 | gulp = require('gulp-help')(require('gulp')) 2 | gutil = require 'gulp-util' 3 | notify = require 'gulp-notify' 4 | protractor = require 'gulp-protractor' 5 | Q = require 'q' 6 | childProcess = require 'child_process' 7 | 8 | {GLOBALS, PUBLIC_GLOBALS, PATHS, DESTINATIONS} = require "../../config" 9 | 10 | phantomChild = null 11 | phantomDefer = null 12 | 13 | # standalone test server which runs in the background. 14 | # doesnt work atm - instead, run `webdriver-manager start` 15 | gulp.task 'test:e2e:server', "Run e2e server", (cb) -> 16 | return cb() if phantomDefer 17 | phantomDefer = Q.defer() 18 | 19 | phantomChild = childProcess.spawn('phantomjs', ['--webdriver=4444'], { 20 | }) 21 | phantomChild.stdout.on 'data', (data) -> 22 | gutil.log gutil.colors.yellow data.toString() 23 | if data.toString().match 'running on port ' 24 | phantomDefer.resolve() 25 | 26 | phantomChild.once 'close', -> 27 | gutil.log "phantomChild closed" 28 | phantomChild.kill() if phantomChild 29 | phantomDefer.reject() 30 | 31 | phantomChild.on 'exit', (code) -> 32 | gutil.log "phantomChild exitted" 33 | phantomChild.kill() if phantomChild 34 | 35 | phantomDefer.promise 36 | 37 | # You can run it like this: 38 | # `gulp test:e2e` - runs all e2e tests 39 | # `gulp test:e2e --debug --specs test/e2e/intro_test.coffee` - runs only one test, in debug mode 40 | gulp.task 'test:e2e', "Run e2e tests (e2e server must be running)", -> 41 | args = ['--baseUrl', "http://localhost:#{GLOBALS.HTTP_SERVER_PORT}"] 42 | args.push 'debug' if gulp.env.debug 43 | 44 | protractorTests = PATHS.scripts.tests.e2e 45 | protractorTests = gulp.env.specs.split(',') if gulp.env.specs 46 | 47 | gulp.src(protractorTests) 48 | .pipe(protractor.protractor({ 49 | configFile: "test/e2e/protractor.config.js", 50 | args: args 51 | })) 52 | .on('error', (notify.onError((error) -> error.message))) 53 | , { 54 | options: 55 | "debug=0": "(passed directly to Protractor)" 56 | "specs=path_to_e2e_test.coffee": "(passed directly to Protractor)" 57 | } 58 | -------------------------------------------------------------------------------- /gulp/tasks/test/unit.coffee: -------------------------------------------------------------------------------- 1 | gulp = require('gulp-help')(require('gulp')) 2 | childProcess = require 'child_process' 3 | extend = require("extend") 4 | runSequence = require 'run-sequence' 5 | os = require 'os' 6 | 7 | {GLOBALS, PUBLIC_GLOBALS, PATHS, DESTINATIONS} = require "../../config" 8 | 9 | 10 | gulp.task "build-test", false, (cb) -> 11 | runSequence ["clean"], 12 | [ 13 | "scripts" 14 | "templates" 15 | ], cb 16 | 17 | 18 | gulp.task 'watch-test', false, -> 19 | gulp.watch(PATHS.scripts.app, ['scripts:app']) 20 | gulp.watch(PATHS.scripts.vendor, ['scripts:vendor']) 21 | gulp.watch(PATHS.templates, ['templates']) 22 | 23 | 24 | # Runs unit tests using karma. 25 | # You can run it simply using `gulp test:unit`. 26 | # You can also pass some karma arguments like this: `gulp test:unit --browsers Chrome`. 27 | # 28 | # NOTE if you want to run simultaneously `gulp` and `gulp test:unit` in 2 terminals, 29 | # you can have some bugs with karma turning off its' watchers when `gulp` cleans the build directory. 30 | # To solve this, instead run `gulp test:unit --TMP_BUILD_DIR=1`, 31 | # so karma will be building its' files into a different, temporary directory. 32 | 33 | gulp.task 'test:unit', 34 | "Run unit tests", 35 | ["build-test", "watch-test"], 36 | -> 37 | args = ['start', 'test/unit/karma.conf.coffee'] 38 | for name in ['browsers', 'reporters'] 39 | args.push "--#{name}", "#{gulp.env[name]}" if gulp.env.hasOwnProperty(name) 40 | 41 | childProcess.spawn "node_modules/.bin/karma", args, 42 | stdio: 'inherit' 43 | env: extend({}, process.env, BUILD_DIR: GLOBALS.BUILD_DIR) 44 | , { 45 | options: 46 | "browsers=chrome,PhantomJS": "(passed directly to Karma)" 47 | "reporters=osx,progress": "(passed directly to Karma)" 48 | } 49 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | require('coffee-script/register'); 2 | require('./gulp/main.coffee'); 3 | -------------------------------------------------------------------------------- /hooks/after_compile/090-copy_cordova_platform_assets_to_www.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if test ! -d "hooks"; then 4 | echo "HOOK >> You must run it in main directory of cordova project." 5 | exit 1 6 | fi 7 | 8 | if [[ $CORDOVA_PLATFORMS == *"android"* ]]; then 9 | ASSETS_FOLDER="platforms/android/assets/www" 10 | cp -r $ASSETS_FOLDER/cordova* $ASSETS_FOLDER/plugins* www/ 11 | fi 12 | 13 | if [[ $CORDOVA_PLATFORMS == *"ios"* ]]; then 14 | ASSETS_FOLDER="platforms/ios/www" 15 | cp -r $ASSETS_FOLDER/cordova* $ASSETS_FOLDER/plugins* www/ 16 | fi 17 | 18 | -------------------------------------------------------------------------------- /hooks/after_platform_add/010_configure_android_keys.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # More information: http://stackoverflow.com/questions/30106468/specify-signing-config-for-gradle-and-cordova-5 4 | 5 | # Exit, if there's no android here. 6 | [[ $CORDOVA_PLATFORMS == *"android"* ]] || exit 0 7 | 8 | if test ! -f "keys/android/$ANDROID_KEYSTORE_NAME.keystore"; then 9 | echo "HOOK-configure_android_keys >> Android key's file \"keys/android/$ANDROID_KEYSTORE_NAME.keystore\" doesn't exist!" 10 | exit 1 11 | fi 12 | 13 | if test ! -n "$ANDROID_KEYSTORE_PASSWORD"; then 14 | echo "HOOK-configure_android_keys >> Enter key.store.password:\t" 15 | read ANDROID_KEYSTORE_PASSWORD 16 | fi 17 | 18 | if test ! -n "$ANDROID_ALIAS_PASSWORD"; then 19 | echo "HOOK-configure_android_keys >> Enter key.alias.password:\t" 20 | read ANDROID_ALIAS_PASSWORD 21 | fi 22 | 23 | echo "storeFile=../../keys/android/$ANDROID_KEYSTORE_NAME.keystore" > platforms/android/release-signing.properties 24 | echo "storeType=jks" >> platforms/android/release-signing.properties 25 | echo "keyAlias=$ANDROID_ALIAS_NAME" >> platforms/android/release-signing.properties 26 | echo "" >> platforms/android/release-signing.properties 27 | echo "# If you want, put also key passwords here" >> platforms/android/release-signing.properties 28 | echo "storePassword=$ANDROID_KEYSTORE_PASSWORD" >> platforms/android/release-signing.properties 29 | echo "keyPassword=$ANDROID_ALIAS_PASSWORD" >> platforms/android/release-signing.properties 30 | -------------------------------------------------------------------------------- /hooks/after_platform_add/030_install_plugins.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | function install_plugin { 4 | # Possible values: 'android', 'ios', 'android ios' 5 | PLATFORMS=$1 6 | 7 | # NAME can be a cordova's plugin name, local path to plugin or a link to git repository 8 | NAME=$2 9 | 10 | # Additional args passed to plugman after --plugin (f.e. variables) 11 | ARGS=$3 12 | 13 | for PLATFORM in $PLATFORMS; do 14 | # Is this the platform currently issued by cordova's command? 15 | [[ $CORDOVA_PLATFORMS == *"$PLATFORM"* ]] || continue 16 | 17 | plugman install --plugins_dir plugins --project platforms/$PLATFORM --platform $PLATFORM --plugin $NAME $ARGS || (echo "HOOK-install_plugins >> Installation of plugin $NAME on $PLATFORM has failed." && exit 1) 18 | done 19 | } 20 | 21 | # NOW, LET'S INSTALL PLUGINS 22 | 23 | # Some basic cordova plugins 24 | # install_plugin "android ios" cordova-plugin-console || exit $? 25 | # install_plugin "android ios" cordova-plugin-device || exit $? 26 | # install_plugin "android ios" cordova-plugin-device-orientation || exit $? 27 | # install_plugin "android ios" cordova-plugin-geolocation || exit $? 28 | # install_plugin "android ios" cordova-plugin-inappbrowser || exit $? 29 | # install_plugin "android ios" cordova-plugin-network-information || exit $? 30 | install_plugin "android ios" cordova-plugin-splashscreen || exit $? 31 | install_plugin "android ios" cordova-plugin-whitelist || exit $? 32 | 33 | # Created by authors of Ionic, fixes keyboard issues on iOS 34 | install_plugin "android ios" vendor/plugins/ionic-plugins-keyboard || exit $? 35 | 36 | # Use native SDK of google analytics (see angulartics-ga-cordova plugin) 37 | # install_plugin "android ios" vendor/plugins/GAPlugin || exit $? 38 | 39 | 40 | echo "HOOK-install_plugins >> Plugins have been installed." 41 | -------------------------------------------------------------------------------- /hooks/after_prepare/050_generate_res_assets.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Compile image resources (images and splashscreens) from assets/img/ to assets/img/res/ dasrectory. 4 | 5 | if test ! -d "hooks"; then 6 | echo "HOOK-generate_res_assets >> You must run it in main directory of cordova project." 7 | exit 1 8 | fi 9 | 10 | LOGO="assets/img/logo-square.png" 11 | PORTRAIT_SPLASH="assets/img/splash-portrait.png" 12 | LANDSCAPE_SPLASH="assets/img/splash-landscape.png" 13 | 14 | if [[ $CORDOVA_PLATFORMS == *"android"* ]]; then 15 | convert $LOGO -resize "96x96" "platforms/android/res/drawable/icon.png" 16 | convert $LOGO -resize "36x36" "platforms/android/res/drawable-ldpi/icon.png" 17 | convert $LOGO -resize "48x48" "platforms/android/res/drawable-mdpi/icon.png" 18 | convert $LOGO -resize "72x72" "platforms/android/res/drawable-hdpi/icon.png" 19 | convert $LOGO -resize "96x96" "platforms/android/res/drawable-xhdpi/icon.png" 20 | 21 | convert $PORTRAIT_SPLASH -resize "720x1280^" \ 22 | -gravity center -crop 720x1280+0+0 +repage\ 23 | "platforms/android/res/drawable/screen.png" 24 | convert $PORTRAIT_SPLASH -resize "200x320^" \ 25 | -gravity center -crop 200x320+0+0 +repage\ 26 | "platforms/android/res/drawable-ldpi/screen.png" 27 | convert $PORTRAIT_SPLASH -resize "320x480^" \ 28 | -gravity center -crop 320x480+0+0 +repage\ 29 | "platforms/android/res/drawable-mdpi/screen.png" 30 | convert $PORTRAIT_SPLASH -resize "480x800^" \ 31 | -gravity center -crop 480x800+0+0 +repage\ 32 | "platforms/android/res/drawable-hdpi/screen.png" 33 | convert $PORTRAIT_SPLASH -resize "720x1280^" \ 34 | -gravity center -crop 720x1280+0+0 +repage\ 35 | "platforms/android/res/drawable-xhdpi/screen.png" 36 | 37 | convert $LANDSCAPE_SPLASH -resize "1280x720^" \ 38 | -gravity center -crop 1280x720+0+0 +repage\ 39 | "platforms/android/res/drawable/screen_landscape.png" 40 | convert $LANDSCAPE_SPLASH -resize "320x200^" \ 41 | -gravity center -crop 320x200+0+0 +repage\ 42 | "platforms/android/res/drawable-ldpi/screen_landscape.png" 43 | convert $LANDSCAPE_SPLASH -resize "480x320^" \ 44 | -gravity center -crop 480x320+0+0 +repage\ 45 | "platforms/android/res/drawable-mdpi/screen_landscape.png" 46 | convert $LANDSCAPE_SPLASH -resize "800x480^" \ 47 | -gravity center -crop 800x480+0+0 +repage\ 48 | "platforms/android/res/drawable-hdpi/screen_landscape.png" 49 | convert $LANDSCAPE_SPLASH -resize "1280x720^" \ 50 | -gravity center -crop 1280x720+0+0 +repage\ 51 | "platforms/android/res/drawable-xhdpi/screen_landscape.png" 52 | 53 | # Copy to new file paths which are supported since Cordova 3.5. 54 | cp platforms/android/res/drawable-ldpi/screen.png platforms/android/res/drawable-port-ldpi/screen.png 55 | cp platforms/android/res/drawable-ldpi/screen_landscape.png platforms/android/res/drawable-land-ldpi/screen_landscape.png 56 | cp platforms/android/res/drawable-ldpi/screen_landscape.png platforms/android/res/drawable-land-ldpi/screen.png 57 | 58 | cp platforms/android/res/drawable-mdpi/screen.png platforms/android/res/drawable-port-mdpi/screen.png 59 | cp platforms/android/res/drawable-mdpi/screen_landscape.png platforms/android/res/drawable-land-mdpi/screen_landscape.png 60 | cp platforms/android/res/drawable-mdpi/screen_landscape.png platforms/android/res/drawable-land-mdpi/screen.png 61 | 62 | cp platforms/android/res/drawable-hdpi/screen.png platforms/android/res/drawable-port-hdpi/screen.png 63 | cp platforms/android/res/drawable-hdpi/screen_landscape.png platforms/android/res/drawable-land-hdpi/screen_landscape.png 64 | cp platforms/android/res/drawable-hdpi/screen_landscape.png platforms/android/res/drawable-land-hdpi/screen.png 65 | 66 | cp platforms/android/res/drawable-xhdpi/screen.png platforms/android/res/drawable-port-xhdpi/screen.png 67 | cp platforms/android/res/drawable-xhdpi/screen_landscape.png platforms/android/res/drawable-land-xhdpi/screen_landscape.png 68 | cp platforms/android/res/drawable-xhdpi/screen_landscape.png platforms/android/res/drawable-land-xhdpi/screen.png 69 | 70 | echo "HOOK-generate_res_assets >> Android res images have been generated." 71 | fi 72 | 73 | if test -d "platforms/ios"; then 74 | BUNDLE_PATH=$(find platforms/ios/*.xcodeproj -type d | head -n 1 | sed 's/\.xcodeproj//g') 75 | 76 | convert $LOGO -resize "57x57" "$BUNDLE_PATH/Resources/icons/icon.png" 77 | convert $LOGO -resize "114x114" "$BUNDLE_PATH/Resources/icons/icon@2x.png" 78 | convert $LOGO -resize "120x120" "$BUNDLE_PATH/Resources/icons/icon-60@2x.png" 79 | convert $LOGO -resize "72x72" "$BUNDLE_PATH/Resources/icons/icon-72.png" 80 | convert $LOGO -resize "144x144" "$BUNDLE_PATH/Resources/icons/icon-72@2x.png" 81 | convert $LOGO -resize "76x76" "$BUNDLE_PATH/Resources/icons/icon-76.png" 82 | convert $LOGO -resize "152x152" "$BUNDLE_PATH/Resources/icons/icon-76@2x.png" 83 | 84 | convert $LOGO -resize "29x29" "$BUNDLE_PATH/Resources/icons/icon-small.png" 85 | convert $LOGO -resize "58x58" "$BUNDLE_PATH/Resources/icons/icon-small@2x.png" 86 | convert $LOGO -resize "40x40" "$BUNDLE_PATH/Resources/icons/icon-40.png" 87 | convert $LOGO -resize "80x80" "$BUNDLE_PATH/Resources/icons/icon-40@2x.png" 88 | convert $LOGO -resize "50x50" "$BUNDLE_PATH/Resources/icons/icon-50.png" 89 | convert $LOGO -resize "100x100" "$BUNDLE_PATH/Resources/icons/icon-50@2x.png" 90 | convert $LOGO -resize "60x60" "$BUNDLE_PATH/Resources/icons/icon-60.png" 91 | 92 | convert $PORTRAIT_SPLASH -resize "640x1136^" \ 93 | -gravity center -crop 640x1136+0+0 +repage\ 94 | "$BUNDLE_PATH/Resources/splash/Default-568h@2x~iphone.png" 95 | convert $LANDSCAPE_SPLASH -resize "2048x1496^" \ 96 | -gravity center -crop 2048x1496+0+0 +repage\ 97 | "$BUNDLE_PATH/Resources/splash/Default-Landscape@2x~ipad.png" 98 | convert $LANDSCAPE_SPLASH -resize "1024x748^" \ 99 | -gravity center -crop 1024x748+0+0 +repage\ 100 | "$BUNDLE_PATH/Resources/splash/Default-Landscape~ipad.png" 101 | convert $PORTRAIT_SPLASH -resize "1536x2008^" \ 102 | -gravity center -crop 1536x2008+0+0 +repage\ 103 | "$BUNDLE_PATH/Resources/splash/Default-Portrait@2x~ipad.png" 104 | convert $PORTRAIT_SPLASH -resize "768x1004^" \ 105 | -gravity center -crop 768x1004+0+0 +repage\ 106 | "$BUNDLE_PATH/Resources/splash/Default-Portrait~ipad.png" 107 | convert $PORTRAIT_SPLASH -resize "640x960^" \ 108 | -gravity center -crop 640x960+0+0 +repage\ 109 | "$BUNDLE_PATH/Resources/splash/Default@2x~iphone.png" 110 | convert $PORTRAIT_SPLASH -resize "320x480^" \ 111 | -gravity center -crop 320x480+0+0 +repage\ 112 | "$BUNDLE_PATH/Resources/splash/Default~iphone.png" 113 | 114 | echo "HOOK-generate_res_assets >> iOS res images have been generated." 115 | fi 116 | 117 | -------------------------------------------------------------------------------- /hooks/before_compile/010_configure_ios_keys.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Exit, if there's no ios here. 4 | [[ $CORDOVA_PLATFORMS == *"ios"* ]] || exit 0 5 | 6 | 7 | # 8 | # CODE_SIGN_IDENTITY 9 | # 10 | 11 | echo "CODE_SIGN_IDENTITY = $IOS_CODE_SIGN_IDENTITY" > platforms/ios/cordova/build.xcconfig 12 | # to list all installed iOS identities, run: 13 | # security find-identity | sed -n 's/.*\("[^"]*"\).*/\1/p' | grep 'iPhone' 14 | # 15 | # generic 'iPhone Developer' (no quotes) will match the right Identity with the right Provisioning Profile plus Certificate, based on the app bundle id 16 | 17 | 18 | # 19 | # IOS_PROVISIONING_PROFILE 20 | # 21 | 22 | if test ! -n "$IOS_PROVISIONING_PROFILE" && test ! -f "$IOS_PROVISIONING_PROFILE"; then 23 | echo "HOOK-configure_ios_keys >> Provisioning profile's file is missing! (path: $IOS_PROVISIONING_PROFILE) Xcode will find matching profile on his own." 24 | else 25 | PROVISIONING_PROFILE_UUID=$(grep UUID -A1 -a $IOS_PROVISIONING_PROFILE | grep -o "[-A-z0-9]\{36\}") 26 | echo "PROVISIONING_PROFILE = $PROVISIONING_PROFILE_UUID" >> platforms/ios/cordova/build.xcconfig 27 | fi 28 | 29 | 30 | echo "HOOK-configure_ios_keys >> iOS platform has been configured to sign the app with key '$IOS_CODE_SIGN_IDENTITY.'" 31 | -------------------------------------------------------------------------------- /hooks/before_platform_add/010_clear_current_platform.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | clear_platform() { 4 | PLATFORM=$1 5 | rm -rf "platforms/$PLATFORM" "plugins/$PLATFORM.json" "plugins/com.phonegap.plugins.facebookconnect" 6 | } 7 | 8 | if [[ $CORDOVA_PLATFORMS == *"android"* ]]; then 9 | if ! cat "platforms/android/assets/www/config.xml" | grep -q "id=\"$BUNDLE_ID\""; then 10 | echo "HOOK-clear_current_platform >> Current android app isn't named as $BUNDLE_ID, clearing it..." 11 | clear_platform android 12 | fi 13 | fi 14 | 15 | if [[ $CORDOVA_PLATFORMS == *"ios"* ]]; then 16 | if ! cat "platforms/ios/www/config.xml" | grep -q "id=\"$BUNDLE_ID\""; then 17 | echo "HOOK-clear_current_platform >> Current ios app isn't named as $BUNDLE_ID, clearing it..." 18 | clear_platform ios 19 | fi 20 | fi 21 | -------------------------------------------------------------------------------- /ionic.project: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ionicstarter", 3 | "app_id": "", 4 | "gulpStartupTasks": [ 5 | "watch" 6 | ], 7 | "watchPatterns": [ 8 | "www/assets/**", 9 | "www/css/**", 10 | "www/fonts/**", 11 | "www/img/**", 12 | "www/js/**", 13 | "www/*.html" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /keys/android/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jtomaszewski/ionic-cordova-gulp-seed/e6b68f83d6c8bb960ba515c6258ac03ca33e7934/keys/android/.gitkeep -------------------------------------------------------------------------------- /keys/ios/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jtomaszewski/ionic-cordova-gulp-seed/e6b68f83d6c8bb960ba515c6258ac03ca33e7934/keys/ios/.gitkeep -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ionic-cordova-gulp-seed", 3 | "version": "2.6.1", 4 | "description": "Ionic & Cordova & Gulp seed with organized code, tests, bower support and some other stuff. Originated from ionic-angular-cordova-seed.", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/jtomaszewski/ionic-cordova-gulp-seed" 8 | }, 9 | "author": "jacek@jtom.me", 10 | "keywords": [ 11 | "seed", 12 | "ionic", 13 | "gulp", 14 | "cordova" 15 | ], 16 | "license": "MIT", 17 | "devDependencies": { 18 | "browser-sync": "^2.0.1", 19 | "chai": "^1.9.1", 20 | "coffee-script": "^1.7.1", 21 | "cordova": "~5.4.0", 22 | "ecstatic": "~0.5.8", 23 | "extend": "^1.2.1", 24 | "gulp": "^3.8.6", 25 | "gulp-angular-templatecache": "^1.3.0", 26 | "gulp-changed": "~0.2.0", 27 | "gulp-clean": "~0.2.4", 28 | "gulp-coffee": "^2.2.0", 29 | "gulp-concat": "^2.4.3", 30 | "gulp-ejs": "^0.2.1", 31 | "gulp-help": "~1.3.3", 32 | "gulp-if": "^1.2.5", 33 | "gulp-jade": "~0.4.2", 34 | "gulp-minify-css": "~0.4.5", 35 | "gulp-notify": "~0.6.2", 36 | "gulp-plumber": "^0.6.3", 37 | "gulp-protractor": "0.0.12", 38 | "gulp-rollbar": "^0.1.1", 39 | "gulp-sass": "^2.3.2", 40 | "gulp-shell": "^0.2.5", 41 | "gulp-sourcemaps": "^1.3.0", 42 | "gulp-uglify": "~1.1.0", 43 | "gulp-util": "~2.2.14", 44 | "jade": "^1.3.0", 45 | "karma": "^0.12.1", 46 | "karma-chai": "^0.1.0", 47 | "karma-chai-plugins": "^0.2.0", 48 | "karma-chrome-launcher": "^0.1.2", 49 | "karma-cli": "0.0.4", 50 | "karma-coffee-preprocessor": "^0.2.1", 51 | "karma-mocha": "^0.1.3", 52 | "karma-osx-reporter": "^0.1.0", 53 | "karma-phantomjs-launcher": "^0.1.2", 54 | "karma-ubuntu-reporter": "0.0.3", 55 | "mocha": "^1.18.2", 56 | "node-sass": "^3.8.0", 57 | "open": "0.0.4", 58 | "plugman": "~0.23.1", 59 | "protractor": "^1.6.1", 60 | "protractor-coffee-preprocessor": "0.0.1", 61 | "proxy-middleware": "^0.11.1", 62 | "q": "^1.0.0", 63 | "run-sequence": "~0.3.6", 64 | "sync-exec": "^0.5.0", 65 | "url": "~0.10.2", 66 | "weinre": "^2.0.0-pre-I0Z7U9OV" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /platforms/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore everything in this directory 2 | * 3 | # Except this file 4 | !.gitignore 5 | -------------------------------------------------------------------------------- /plugins/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore everything in this directory 2 | * 3 | # Except this file 4 | !.gitignore 5 | -------------------------------------------------------------------------------- /test/e2e/app_test.coffee: -------------------------------------------------------------------------------- 1 | TestHelper = require './test_helper' 2 | 3 | describe 'app init', -> 4 | 5 | it 'should land on the pets page', -> 6 | expect(browser.getLocationAbsUrl()).toMatch '/tab/pets' 7 | -------------------------------------------------------------------------------- /test/e2e/protractor.config.js: -------------------------------------------------------------------------------- 1 | require('coffee-script/register'); 2 | TestHelper = require('./test_helper'); 3 | 4 | exports.config = { 5 | baseUrl: 'http://localhost:4400', 6 | 7 | seleniumAddress: 'http://localhost:4444/wd/hub', 8 | 9 | capabilities: { 10 | 'browserName': 'chrome' 11 | }, 12 | 13 | specs: ['./**/*_test.coffee'], 14 | 15 | jasmineNodeOpts: { 16 | showColors: true, 17 | }, 18 | 19 | onPrepare: function() { 20 | beforeEach(function(){ 21 | browser.get('/'); 22 | }) 23 | 24 | afterEach(function(){ 25 | browser.manage().deleteAllCookies(); 26 | TestHelper.localStorage.clear(); 27 | }); 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /test/e2e/test_helper.coffee: -------------------------------------------------------------------------------- 1 | module.exports = 2 | 3 | # Shortcut-helper to automatically make the user signed in the application. 4 | signIn: -> 5 | @localStorage.setItem "user_email", "test@domain.com" 6 | @localStorage.setItem "user_token", "123" 7 | 8 | # localStorage helpers. 9 | localStorage: 10 | setItem: (key, value) -> 11 | browser.executeScript "window.localStorage.setItem(#{JSON.stringify(key)}, #{JSON.stringify(value)});" 12 | 13 | getItem: (key) -> 14 | browser.executeScript "window.localStorage.getItem(#{JSON.stringify(key)});" 15 | 16 | removeItem: (key) -> 17 | browser.executeScript "window.localStorage.removeItem(#{JSON.stringify(key)});" 18 | 19 | clear: -> 20 | browser.executeScript "window.localStorage.clear();" 21 | 22 | setObject: (key, value) -> 23 | @setItem(key, JSON.stringify(value)) 24 | 25 | getObject: (key) -> 26 | JSON.parse(@getItem(key)) 27 | -------------------------------------------------------------------------------- /test/unit/factories/user.coffee: -------------------------------------------------------------------------------- 1 | FactoryGirl.define 'user', 2 | id: -> ~~(Math.random() * 1000) 3 | username: 'testuser' 4 | email: 'testuser@test.com' 5 | -------------------------------------------------------------------------------- /test/unit/helpers/app_helpers.coffee: -------------------------------------------------------------------------------- 1 | # Use it like this: 2 | # beforeEach(signIn) 3 | @signIn = -> 4 | inject (Auth, $httpBackend) -> 5 | current_user = FactoryGirl.create 'user', id: 1337 6 | 7 | $httpBackend 8 | .whenGET "/account.json?email=#{current_user.email}" 9 | .respond current_user 10 | 11 | Auth.setAuthToken current_user.email, '123' 12 | -------------------------------------------------------------------------------- /test/unit/helpers/factory_girl.coffee: -------------------------------------------------------------------------------- 1 | @FactoryGirl = new class FactoryGirl 2 | constructor: -> 3 | @_factories = {} 4 | 5 | create: (name, attributes = {}) -> 6 | name = name.toString() 7 | throw "Factory #{name} doesn't exist!" unless @_factories.hasOwnProperty name 8 | 9 | model = {} 10 | angular.extend model, @_factories[name], attributes 11 | 12 | for k, v of model 13 | model[k] = v() if typeof v == 'function' 14 | 15 | model 16 | 17 | createList: (name, count, attributes = {}) -> 18 | throw "Uncorrect count '#{count}'!" unless typeof count == 'number' 19 | count = ~~ +count 20 | @create(name, attributes) for i in [0...count] 21 | 22 | define: (name, attributes) -> 23 | @_factories[name] = attributes 24 | 25 | undefine: (name) -> 26 | delete @_factories[name] 27 | -------------------------------------------------------------------------------- /test/unit/helpers/factory_girl_test.coffee: -------------------------------------------------------------------------------- 1 | describe 'FactoryGirl', -> 2 | beforeEach -> 3 | id = 1 4 | 5 | FactoryGirl.define 'testcar', 6 | id: -> id++ 7 | name: 'bmw' 8 | type: 'car' 9 | 10 | afterEach -> 11 | FactoryGirl.undefine 'testcar' 12 | 13 | describe '#create', -> 14 | it "creates a object, assigning default attributes and evaluating lazy functions", -> 15 | car = FactoryGirl.create 'testcar' 16 | 17 | expect( car.id ).to.equal 1 18 | expect( car.name ).to.equal 'bmw' 19 | 20 | car = FactoryGirl.create 'testcar' 21 | 22 | expect( car.id ).to.equal 2 23 | expect( car.name ).to.equal 'bmw' 24 | 25 | it "replaces/adds attributes from passed arguments", -> 26 | car = FactoryGirl.create 'testcar', 27 | id: -> 6 28 | name: 'mercedes' 29 | klass: 's' 30 | 31 | expect( car.id ).to.equal 6 # replaced evaluated function with also another evaluated function 32 | expect( car.name ).to.equal 'mercedes' # replaced static attribute 33 | expect( car.klass ).to.equal 's' # added custom attribute 34 | expect( car.type ).to.equal 'car' # default attribute 35 | 36 | describe '#createList', -> 37 | it "creates multiple objects with passed attributes", -> 38 | cars = FactoryGirl.createList 'testcar', 2, name: 'mercedes' 39 | 40 | expect( cars.length ).to.equal 2 41 | expect( cars[0].id ).to.equal 1 42 | expect( cars[1].id ).to.equal 2 43 | expect( car.name ).to.equal 'mercedes' for car in cars 44 | 45 | -------------------------------------------------------------------------------- /test/unit/helpers/test_helpers.coffee: -------------------------------------------------------------------------------- 1 | 2 | @$it = (text, testFn) -> 3 | if testFn.length > 0 4 | it text, (done) -> 5 | inject ($rootScope) -> 6 | hasFinished = false 7 | finish = (err) -> 8 | hasFinished = true 9 | done(err) 10 | 11 | testFn(finish) 12 | # until hasFinished 13 | # $rootScope.$digest() 14 | $rootScope.$digest() 15 | else 16 | it text, -> 17 | inject ($rootScope) -> 18 | value = testFn() 19 | $rootScope.$digest() 20 | value 21 | 22 | -------------------------------------------------------------------------------- /test/unit/js/factories/promise_factory_test.coffee: -------------------------------------------------------------------------------- 1 | describe "PromiseFactory", -> 2 | 3 | it "given a promise, returns the promise", -> 4 | inject ($q, PromiseFactory) -> 5 | some_promise = $q.defer().promise 6 | expect(PromiseFactory(some_promise)).to.equal some_promise 7 | 8 | $it 'given a value, it returns resolved promise', (done) -> 9 | inject (PromiseFactory) -> 10 | promise = PromiseFactory('123') 11 | expect(promise).to.eventually.equal('123').notify(done) 12 | 13 | $it 'given a value and resolve=false, returns rejected promise', (done) -> 14 | inject (PromiseFactory) -> 15 | promise = PromiseFactory('123', false) 16 | expect(promise).to.be.rejectedWith('123').notify(done) 17 | -------------------------------------------------------------------------------- /test/unit/karma.conf.coffee: -------------------------------------------------------------------------------- 1 | BUILD_DIR = process.env.BUILD_DIR ? "www" 2 | 3 | module.exports = (config) -> 4 | config.set 5 | basePath: '../../' 6 | frameworks: ['mocha', 'chai', 'chai-as-promised', 'sinon-chai', 'chai-things'] 7 | 8 | # list of files / patterns to load in the browser 9 | files: [ 10 | "#{BUILD_DIR}/js/vendor.js" 11 | "assets/components/angular-mocks/angular-mocks.js" 12 | 13 | "test/unit/tests-config.coffee" 14 | 15 | # "#{BUILD_DIR}/js/app.js" 16 | # This is a concatenated list of all scripts from gulpfile.coffee 17 | # (we need to keep it up to date with it). 18 | "#{BUILD_DIR}/js/app_templates.js" 19 | 'app/js/config/**/*.coffee' 20 | 'app/js/*/**/*.coffee' 21 | 'app/js/routes.coffee' 22 | 23 | "test/unit/helpers/**/*.coffee" 24 | "test/unit/**/*.coffee" 25 | ] 26 | 27 | exclude: [ 28 | "test/unit/karma.conf.coffee" 29 | ] 30 | 31 | # use dots reporter, as travis terminal does not support escaping sequences 32 | # possible values: 'dots', 'progress', 'osx', 'ubuntu' 33 | # CLI --reporters progress 34 | reporters: ['osx', 'dots'] 35 | 36 | autoWatch: true 37 | 38 | # f.e. Chrome, PhantomJS 39 | browsers: ['PhantomJS'] 40 | 41 | preprocessors: 42 | '**/*.coffee': ['coffee'] 43 | 44 | coffeePreprocessor: 45 | options: 46 | bare: false 47 | sourceMap: true 48 | 49 | # transformPath: (path) -> 50 | # path.replace(/\.coffee$/, '.js') 51 | -------------------------------------------------------------------------------- /test/unit/tests-config.coffee: -------------------------------------------------------------------------------- 1 | # mocha.setup 2 | # bail: false 3 | # ignoreLeaks: true 4 | 5 | window.GLOBALS = 6 | ANGULAR_APP_NAME: "ionicstarter" 7 | ENV: 'test' 8 | 9 | beforeEach module(GLOBALS.ANGULAR_APP_NAME) 10 | 11 | afterEach -> 12 | inject ($httpBackend) -> 13 | $httpBackend.verifyNoOutstandingRequest() 14 | $httpBackend.verifyNoOutstandingExpectation() 15 | 16 | sessionStorage.clear() 17 | localStorage.clear() 18 | 19 | -------------------------------------------------------------------------------- /utils/testfairy-upload-android.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | UPLOADER_VERSION=1.09 4 | 5 | # Put your TestFairy API_KEY here. Find it in your TestFairy account settings. 6 | # TESTFAIRY_API_KEY= 7 | 8 | # Your Keystore, Storepass and Alias, the ones you use to sign your app. 9 | KEYSTORE=keys/android/$ANDROID_KEYSTORE_NAME.keystore 10 | STOREPASS=$ANDROID_KEYSTORE_PASSWORD 11 | ALIAS=$ANDROID_ALIAS_NAME 12 | 13 | # Tester Groups that will be notified when the app is ready. Setup groups in your TestFairy account testers page. 14 | # This parameter is optional, leave empty if not required 15 | # TESTER_GROUPS= 16 | 17 | # Should email testers about new version. Set to "off" to disable email notifications. 18 | NOTIFY="on" 19 | 20 | # If AUTO_UPDATE is "on" all users will be prompt to update to this build next time they run the app 21 | AUTO_UPDATE="on" 22 | 23 | # The maximum recording duration for every test. 24 | MAX_DURATION="5m" 25 | 26 | # Is video recording enabled for this build 27 | VIDEO="off" 28 | 29 | # Add a TestFairy watermark to the application icon? 30 | ICON_WATERMARK="on" 31 | 32 | # Comment text will be included in the email sent to testers 33 | COMMENT="This build was uploaded via the upload API" 34 | 35 | # locations of various tools 36 | CURL=curl 37 | ZIP=zip 38 | KEYTOOL=keytool 39 | ZIPALIGN=zipalign 40 | JARSIGNER=jarsigner 41 | 42 | SERVER_ENDPOINT=http://app.testfairy.com 43 | 44 | usage() { 45 | echo "Usage: testfairy-upload.sh APK_FILENAME" 46 | echo 47 | } 48 | 49 | verify_tools() { 50 | 51 | # Windows users: this script requires zip, curl and sed. If not installed please get from http://cygwin.com/ 52 | 53 | # Check 'zip' tool 54 | ${ZIP} -h >/dev/null 55 | if [ $? -ne 0 ]; then 56 | echo "Could not run zip tool, please check settings" 57 | exit 1 58 | fi 59 | 60 | # Check 'curl' tool 61 | ${CURL} --help >/dev/null 62 | if [ $? -ne 0 ]; then 63 | echo "Could not run curl tool, please check settings" 64 | exit 1 65 | fi 66 | 67 | OUTPUT=$( ${JARSIGNER} -help 2>&1 | grep "verify" ) 68 | if [ $? -ne 0 ]; then 69 | echo "Could not run jarsigner tool, please check settings" 70 | exit 1 71 | fi 72 | 73 | # Check 'zipalign' tool 74 | OUTPUT=$( ${ZIPALIGN} 2>&1 | grep -i "Zip alignment" ) 75 | if [ $? -ne 0 ]; then 76 | echo "Could not run zipalign tool, please check settings" 77 | exit 1 78 | fi 79 | 80 | OUTPUT=$( ${KEYTOOL} -help 2>&1 | grep "keypasswd" ) 81 | if [ $? -ne 0 ]; then 82 | echo "Could not run keytool tool, please check settings" 83 | exit 1 84 | fi 85 | } 86 | 87 | verify_settings() { 88 | if [ -z "${TESTFAIRY_API_KEY}" ]; then 89 | usage 90 | echo "Please update API_KEY with your private API key, as noted in the Settings page" 91 | exit 1 92 | fi 93 | 94 | if [ -z "${KEYSTORE}" -o -z "${STOREPASS}" -o -z "{$ALIAS}" ]; then 95 | usage 96 | echo "Please update KEYSTORE, STOREPASS and ALIAS with your jar signing credentials" 97 | exit 1 98 | fi 99 | 100 | # verify KEYSTORE, STOREPASS and ALIAS at once 101 | OUTPUT=$( ${KEYTOOL} -list -keystore "${KEYSTORE}" -storepass "${STOREPASS}" -alias "${ALIAS}" 2>&1 ) 102 | if [ $? -ne 0 ]; then 103 | usage 104 | echo "Please check keystore credentials; keytool failed to verify storepass and alias" 105 | exit 1 106 | fi 107 | } 108 | 109 | if [ $# -ne 1 ]; then 110 | usage 111 | exit 1 112 | fi 113 | 114 | # before even going on, make sure all tools work 115 | verify_tools 116 | verify_settings 117 | 118 | APK_FILENAME=$1 119 | if [ ! -f "${APK_FILENAME}" ]; then 120 | usage 121 | echo "Can't find file: ${APK_FILENAME}" 122 | exit 2 123 | fi 124 | 125 | # temporary file paths 126 | DATE=`date` 127 | TMP_FILENAME=/tmp/testfairy.upload.apk 128 | ZIPALIGNED_FILENAME=/tmp/testfairy.zipalign.apk 129 | rm -f "${TMP_FILENAME}" "${ZIPALIGNED_FILENAME}" 130 | 131 | /bin/echo -n "Uploading ${APK_FILENAME} to TestFairy.. " 132 | JSON=$( ${CURL} -s ${SERVER_ENDPOINT}/api/upload -F api_key=${TESTFAIRY_API_KEY} -F apk_file="@${APK_FILENAME}" -F icon-watermark="${ICON_WATERMARK}" -F video="${VIDEO}" -F max-duration="${MAX_DURATION}" -F comment="${COMMENT}" -A "TestFairy Command Line Uploader ${UPLOADER_VERSION}" ) 133 | 134 | URL=$( echo ${JSON} | sed 's/\\\//\//g' | sed -n 's/.*"instrumented_url"\s*:\s*"\([^"]*\)".*/\1/p' ) 135 | if [ -z "${URL}" ]; then 136 | echo "FAILED!" 137 | echo 138 | echo "Upload failed, please check your settings" 139 | exit 1 140 | fi 141 | 142 | URL="${URL}?api_key=${TESTFAIRY_API_KEY}" 143 | 144 | echo "OK!" 145 | /bin/echo -n "Downloading instrumented APK.. " 146 | ${CURL} -L -o ${TMP_FILENAME} -s ${URL} 147 | 148 | if [ ! -f "${TMP_FILENAME}" ]; then 149 | echo "FAILED!" 150 | echo 151 | echo "Could not download APK back from server, please contact support@testfairy.com" 152 | exit 1 153 | fi 154 | 155 | echo "OK!" 156 | 157 | /bin/echo -n "Re-signing APK file.. " 158 | ${ZIP} -qd ${TMP_FILENAME} 'META-INF/*' 159 | ${JARSIGNER} -keystore "${KEYSTORE}" -storepass "${STOREPASS}" -digestalg SHA1 -sigalg MD5withRSA ${TMP_FILENAME} "${ALIAS}" 160 | ${JARSIGNER} -verify ${TMP_FILENAME} >/dev/null 161 | if [ $? -ne 0 ]; then 162 | echo "FAILED!" 163 | echo 164 | echo "Jarsigner failed to verify, please check parameters and try again" 165 | exit 1 166 | fi 167 | 168 | ${ZIPALIGN} -f 4 ${TMP_FILENAME} ${ZIPALIGNED_FILENAME} 169 | rm -f ${TMP_FILENAME} 170 | echo "OK!" 171 | 172 | /bin/echo -n "Uploading signed APK to TestFairy.. " 173 | JSON=$( ${CURL} -s ${SERVER_ENDPOINT}/api/upload-signed -F api_key=${TESTFAIRY_API_KEY} -F apk_file=@${ZIPALIGNED_FILENAME} -F testers-groups="${TESTER_GROUPS}" -F auto-update="${AUTO_UPDATE}" -F notify="${NOTIFY}") 174 | rm -f ${ZIPALIGNED_FILENAME} 175 | 176 | URL=$( echo ${JSON} | sed 's/\\\//\//g' | sed -n 's/.*"build_url"\s*:\s*"\([^"]*\)".*/\1/p' ) 177 | if [ -z "$URL" ]; then 178 | echo "FAILED!" 179 | echo 180 | echo "Build uploaded, but no reply from server. Please contact support@testfairy.com" 181 | exit 1 182 | fi 183 | 184 | echo "OK!" 185 | echo 186 | echo "Build was successfully uploaded to TestFairy and is available at:" 187 | echo ${URL} 188 | -------------------------------------------------------------------------------- /utils/testfairy-upload-ios.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | UPLOADER_VERSION=1.09 4 | 5 | # Put your TestFairy API_KEY here. Find it in your TestFairy account settings. 6 | # TESTFAIRY_API_KEY= 7 | 8 | # Tester Groups that will be notified when the app is ready. Setup groups in your TestFairy account testers page. 9 | # This parameter is optional, leave empty if not required 10 | # TESTER_GROUPS= 11 | 12 | # Should email testers about new version. Set to "off" to disable email notifications. 13 | NOTIFY="on" 14 | 15 | # If AUTO_UPDATE is "on" all users will be prompt to update to this build next time they run the app 16 | AUTO_UPDATE="on" 17 | 18 | # The maximum recording duration for every test. 19 | MAX_DURATION="5m" 20 | 21 | # Is video recording enabled for this build. valid values: "on", "off", "wifi" 22 | VIDEO="off" 23 | 24 | # Comment text will be included in the email sent to testers 25 | COMMENT="This build was uploaded via the upload API" 26 | 27 | # locations of various tools 28 | CURL=curl 29 | 30 | SERVER_ENDPOINT=http://app.testfairy.com 31 | 32 | usage() { 33 | echo "Usage: testfairy-upload-ios.sh IPA_FILENAME" 34 | echo 35 | } 36 | 37 | verify_tools() { 38 | 39 | # Windows users: this script requires curl. If not installed please get from http://cygwin.com/ 40 | 41 | # Check 'curl' tool 42 | ${CURL} --help >/dev/null 43 | if [ $? -ne 0 ]; then 44 | echo "Could not run curl tool, please check settings" 45 | exit 1 46 | fi 47 | } 48 | 49 | verify_settings() { 50 | if [ -z "${TESTFAIRY_API_KEY}" ]; then 51 | usage 52 | echo "Please update API_KEY with your private API key, as noted in the Settings page" 53 | exit 1 54 | fi 55 | } 56 | 57 | if [ $# -ne 1 ]; then 58 | usage 59 | exit 1 60 | fi 61 | 62 | # before even going on, make sure all tools work 63 | verify_tools 64 | verify_settings 65 | 66 | IPA_FILENAME=$1 67 | if [ ! -f "${IPA_FILENAME}" ]; then 68 | usage 69 | echo "Can't find file: ${IPA_FILENAME}" 70 | exit 2 71 | fi 72 | 73 | # temporary file paths 74 | DATE=`date` 75 | 76 | /bin/echo -n "Uploading ${IPA_FILENAME} to TestFairy.. " 77 | JSON=$( ${CURL} -s ${SERVER_ENDPOINT}/api/upload -F api_key=${TESTFAIRY_API_KEY} -F file="@${IPA_FILENAME}" -F video="${VIDEO}" -F max-duration="${MAX_DURATION}" -F comment="${COMMENT}" -F testers-groups="${TESTER_GROUPS}" -F auto-update="${AUTO_UPDATE}" -F notify="${NOTIFY}" -A "TestFairy iOS Command Line Uploader ${UPLOADER_VERSION}" ) 78 | 79 | URL=$( echo ${JSON} | sed 's/\\\//\//g' | sed -n 's/.*"build_url"\s*:\s*"\([^"]*\)".*/\1/p' ) 80 | if [ -z "$URL" ]; then 81 | echo "FAILED!" 82 | echo 83 | echo "Build uploaded, but no reply from server. Please contact support@testfairy.com" 84 | exit 1 85 | fi 86 | 87 | echo "OK!" 88 | echo 89 | echo "Build was successfully uploaded to TestFairy and is available at:" 90 | echo ${URL} 91 | --------------------------------------------------------------------------------