├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── TODO.md ├── bin └── zfdeploy.php ├── box.json ├── composer.json ├── config ├── routes.php └── zpk │ ├── deployment.xml │ ├── logo │ ├── apigility-logo.png │ └── zf2-logo.png │ └── schema.xsd ├── src ├── Deploy.php └── SelfUpdate.php └── zfdeploy.phar /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file, in reverse chronological order by release. 4 | 5 | ## 1.3.1 - TBD 6 | 7 | ### Added 8 | 9 | - Nothing. 10 | 11 | ### Changed 12 | 13 | - Nothing. 14 | 15 | ### Deprecated 16 | 17 | - Nothing. 18 | 19 | ### Removed 20 | 21 | - Nothing. 22 | 23 | ### Fixed 24 | 25 | - Nothing. 26 | 27 | ## 1.3.0 - 2018-05-07 28 | 29 | ### Added 30 | 31 | - [#50](https://github.com/zfcampus/zf-deploy/pull/50) adds support for PHP 7.1 and 7.2. 32 | 33 | ### Changed 34 | 35 | - Nothing. 36 | 37 | ### Deprecated 38 | 39 | - Nothing. 40 | 41 | ### Removed 42 | 43 | - [#50](https://github.com/zfcampus/zf-deploy/pull/50) removes support for HHVM. 44 | 45 | ### Fixed 46 | 47 | - Nothing. 48 | 49 | ## 1.2.0 - 2016-07-12 50 | 51 | ### Added 52 | 53 | - [#47](https://github.com/zfcampus/zf-deploy/pull/47) adds support for v3 54 | releases of Zend Framework components, and remains compatible with their v2 55 | releases. 56 | 57 | ### Deprecated 58 | 59 | - Nothing. 60 | 61 | ### Removed 62 | 63 | - [#47](https://github.com/zfcampus/zf-deploy/pull/47) removes support for PHP 5.5. 64 | 65 | ### Fixed 66 | 67 | - Nothing. 68 | 69 | ## 1.1.1 - 2016-07-12 70 | 71 | ### Added 72 | 73 | - Nothing. 74 | 75 | ### Deprecated 76 | 77 | - Nothing. 78 | 79 | ### Removed 80 | 81 | - Nothing. 82 | 83 | ### Fixed 84 | 85 | - [#46](https://github.com/zfcampus/zf-deploy/pull/46) ensures that the vendor 86 | binary can always find the routing configuration, whether installed standalone 87 | or via Composer. 88 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014-2018, Zend Technologies USA, Inc. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | - Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | - Redistributions in binary form must reproduce the above copyright notice, this 11 | list of conditions and the following disclaimer in the documentation and/or 12 | other materials provided with the distribution. 13 | 14 | - Neither the name of Zend Technologies USA, Inc. nor the names of its 15 | contributors may be used to endorse or promote products derived from this 16 | software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ZFDeploy - deploy ZF applications 2 | 3 | > ## Repository abandoned 2019-12-31 4 | > 5 | > This repository is abandoned, and has no replacement provided. 6 | 7 | [![Build Status](https://secure.travis-ci.org/zfcampus/zf-deploy.svg?branch=master)](https://secure.travis-ci.org/zfcampus/zf-deploy) 8 | [![Coverage Status](https://coveralls.io/repos/github/zfcampus/zf-deploy/badge.svg?branch=master)](https://coveralls.io/github/zfcampus/zf-deploy?branch=master) 9 | 10 | ## Introduction 11 | 12 | **ZFDeploy** is a command line tool to deploy [Zend Framework](http://framework.zend.com) applications. 13 | 14 | This tool produces a file package ready to be deployed. The tool supports the following format: 15 | ZIP, TAR, TGZ (.TAR.GZ), .ZPK (the deployment file format of [Zend Server 6](http://files.zend.com/help/Zend-Server/zend-server.htm#understanding_the_package_structure.htm)). 16 | 17 | ## Requirements 18 | 19 | Please see the [composer.json](composer.json) file. 20 | 21 | ## Installation 22 | 23 | ZFDeploy may be installed in two ways: as a standalone, updatable `phar` file, 24 | or via Composer. 25 | 26 | ### Standalone PHAR installation 27 | 28 | The standalone `phar` file is available at: 29 | 30 | - https://packages.zendframework.com/zf-deploy/zfdeploy.phar 31 | 32 | You can retrieve it using any of the following commands. 33 | 34 | Via `curl`: 35 | 36 | ```console 37 | $ curl -o zfdeploy.phar https://packages.zendframework.com/zf-deploy/zfdeploy.phar 38 | ``` 39 | 40 | Via `wget`: 41 | 42 | ```console 43 | $ wget https://packages.zendframework.com/zf-deploy/zfdeploy.phar 44 | ``` 45 | 46 | Or using your installed PHP binary: 47 | 48 | ```console 49 | $ php -r "file_put_contents('zfdeploy.phar', file_get_contents('https://packages.zendframework.com/zf-deploy/zfdeploy.phar'));" 50 | ``` 51 | 52 | Once you have the file, make it executable; in Unix-like systems: 53 | 54 | ```console 55 | $ chmod 755 zfdeploy.phar 56 | ``` 57 | 58 | You can update the `phar` file periodically to the latest version using the `self-update` command: 59 | 60 | ```console 61 | $ zfdeploy.phar self-update 62 | ``` 63 | 64 | ### Composer installation 65 | 66 | Run the following `composer` command: 67 | 68 | ```console 69 | $ composer require "zfcampus/zf-deploy:~1.0-dev" 70 | ``` 71 | 72 | Alternately, manually add the following to your `composer.json`, in the `require` section: 73 | 74 | ```javascript 75 | "require": { 76 | "zfcampus/zf-deploy": "~1.0-dev" 77 | } 78 | ``` 79 | 80 | And then run `composer update` to ensure the module is installed. 81 | 82 | If installed via composer, the script lives in `vendor/bin/zfdeploy.php` of your application. 83 | 84 | ## Usage 85 | 86 | > ### Note 87 | > 88 | > If you clone this project standalone, the script is located in `bin/zfdeploy.php`. If you install 89 | > this repository as a Composer dependency of your project, the script is located in 90 | > `vendor/bin/zfdeploy.php`. If you install using the `phar` file, you will either need to put it on 91 | > your path or provide the full path to the `phar` file; the script name then is `zfdeploy.phar`. 92 | > 93 | > Depending on your environment, you may need to execute the `phar` file or `php` script using your 94 | > `php` executable: 95 | > 96 | > ```console 97 | > $ php bin/zfdeploy.php 98 | > $ php vendor/bin/zfdeploy.php 99 | > $ php zfdeploy.phar 100 | > ``` 101 | > 102 | > In most Unix-like systems, if you have `/usr/bin/env` available, both the script and `phar` file 103 | > should be self-executable. 104 | > 105 | > For our examples, we will reference the script as `zfdeploy`, regardless of how you installed it 106 | > or how you determine you will need to execute it. 107 | 108 | The command line tool can be executed using the following command: 109 | 110 | ```console 111 | $ zfdeploy build 112 | ``` 113 | 114 | where `` is the filename of the output package to produce. When run with no other 115 | arguments, it assumes the current directory should be packaged; if you want to specify a different 116 | directory for packaging, use the `--target` flag: 117 | 118 | ```console 119 | $ zfdeploy build --target path/to/application 120 | ``` 121 | 122 | You can specify the file format directly in the `` using the proper extension (e.g. 123 | `application.zip` will create a ZIP file). 124 | 125 | `zfdeploy` includes the following commands: 126 | 127 | ```console 128 | $ zfdeploy 129 | ZFDeploy, version 0.3.0-dev 130 | 131 | Available commands: 132 | 133 | build Build a deployment package 134 | help Get help for individual commands 135 | self-update Updates zfdeploy.phar to the latest version 136 | version Display the version of the script 137 | ``` 138 | 139 | The full syntax of the `build` command includes: 140 | 141 | ```console 142 | Usage: 143 | build [--target=] [--modules=] [--vendor|-v]:vendor [--composer=] [--gitignore=] [--deploymentxml=] [--zpkdata=] [--version=] 144 | 145 | Arguments: 146 | Name of the package file to create; suffix must be .zip, .tar, .tar.gz, .tgz, or .zpk 147 | --target The target directory of the application to package; defaults to current working directory 148 | --modules Comma-separated list of modules to include in build 149 | --vendor|-v Whether or not to include the vendor directory (disabled by default) 150 | --composer Whether or not to execute composer; "on" or "off" ("on" by default) 151 | --gitignore Whether or not to parse the .gitignore file to determine what files/folders to exclude; "on" or "off" ("on" by default) 152 | --configs Path to directory containing application config files to include in the package 153 | --deploymentxmlPath to a custom deployment.xml to use when building a ZPK package 154 | --zpkdata Path to a directory containing ZPK package assets (deployment.xml, logo, scripts, etc.) 155 | --version Specific application version to use for a ZPK package 156 | ``` 157 | 158 | This deployment tool takes care of the local configuration files, related to the specific 159 | environment, using the `.gitignore` file. If your applications use the `.gitignore` file to exclude 160 | local configuration files, for instance the `local.php` file in the `/config/autoload` folder, 161 | **ZFdeploy** will not include these files in the deployment package. You can disable the usage of 162 | the `.gitignore` file using the `--gitignore off` option. 163 | 164 | > ### NOTE: if you disable the .gitignore usage 165 | > 166 | > If you disable the `.gitignore` using the `--gitignore off` option, all the files of the ZF 167 | > application will be included in the package. **That means local configuration files, including 168 | > sensitive information like database credentials, are deployed in production!!!** Please consider 169 | > this behaviour before switching off the gitignore option. 170 | 171 | Another important part of the deployment of a ZF application is the usage of 172 | [composer](https://getcomposer.org). 173 | 174 | **ZFDeploy** executes the following composer command during the creation of the deployment package: 175 | 176 | ```bash 177 | $ php composer.phar install --no-dev --prefer-dist --optimize-autoloader 178 | ``` 179 | 180 | The `--no-dev` flag ensures that development packages are not installed in the production 181 | environment. The `--prefer-dist` option tell composer to install from dist if possible. This can 182 | speed up installs substantially on build servers and other use cases where you typically do not run 183 | updates of the vendors The `--optimize-autoloader` flag makes Composer's autoloader more performant 184 | by building a "class map". 185 | 186 | For more information about Composer, you can read the [Documentation](https://getcomposer.org/doc/) 187 | page of the project. 188 | 189 | > ### Note: production configuration 190 | > 191 | > Zend Framework applications often include `{,*.}local.php` files in `config\autoload/`, which 192 | > are used to provide environment specific configuration. (In Apigility, this may include database 193 | > configuration, Authentication configuration, etc.). These files are omitted from version control 194 | > via `.gitignore` directives -- and, by default, from packaging. 195 | > 196 | > The settings you want for production will often differ from those in your development environment, 197 | > and you may push them to production in a variety of ways -- via Chef, Puppet, Ansible, etc. 198 | > Another option is to use the `--configs` flag when building your package. You can pass a directory 199 | > containing production configuration files, and these will then be included in your deployment 200 | > package. 201 | 202 | ## Getting help 203 | 204 | The `help` command can list both the available commands, as well as provide the syntax for each 205 | command: 206 | 207 | - `zfdeploy help` will list all commands available. 208 | - `zfdeploy help ` will show usage for the named command. 209 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | TODO 2 | ==== 3 | 4 | - ~~Check the .zpk package produced by zfdeploy using Zend Server 6.3~~ 5 | - Potentially also provide functionality for actually _pushing_ the package to Zend Server via the server API? 6 | - ~~Added the -version option to specify the version for the .zpk package~~ 7 | - ~~Added the icon support with auto-detection for ZF2 or Apigility applications~~ 8 | - ~~Deploy an Apigility application produced by the Admin UI (tested with this PR: https://github.com/zfcampus/zf-apigility-skeleton/pull/53)~~ 9 | - ~~Check if the development mode is disabled in production for Apigility~~ 10 | - Write the tests 11 | - ~~Test the script in a Windows environment~~ 12 | - ~~Remove the /vendor/*/*/test folders in the deploy package for optimization~~ 13 | -------------------------------------------------------------------------------- /bin/zfdeploy.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | getDispatcher()->map('self-update', new SelfUpdate(VERSION)); 55 | 56 | $exit = $application->run(); 57 | exit($exit); 58 | -------------------------------------------------------------------------------- /box.json: -------------------------------------------------------------------------------- 1 | { 2 | "chmod": "0755", 3 | "directories": [ 4 | "config", 5 | "src" 6 | ], 7 | "files": [ 8 | "LICENSE.md", 9 | "vendor/herrera-io/phar-update/res/schema.json" 10 | ], 11 | "finder": [ 12 | { 13 | "name": "*.php", 14 | "exclude": ["tests"], 15 | "in": "vendor" 16 | } 17 | ], 18 | "git-version": "1.3.0dev", 19 | "main": "bin/zfdeploy.php", 20 | "output": "zfdeploy.phar", 21 | "stub": true 22 | } 23 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "zfcampus/zf-deploy", 3 | "description": "Deployment tool for Zend Framework applications", 4 | "license": "BSD-3-Clause", 5 | "keywords": [ 6 | "zf", 7 | "zendframework", 8 | "apigility" 9 | ], 10 | "support": { 11 | "issues": "https://github.com/zfcampus/zf-deploy/issues", 12 | "source": "https://github.com/zfcampus/zf-deploy", 13 | "rss": "https://github.com/zfcampus/zf-deploy/releases.atom", 14 | "chat": "https://zendframework-slack.herokuapp.com", 15 | "forum": "https://discourse.zendframework.com/c/questions/apigility" 16 | }, 17 | "require": { 18 | "php": "^5.6 || ^7.0", 19 | "herrera-io/phar-update": "~1.0", 20 | "zendframework/zend-filter": "^2.7.1", 21 | "zfcampus/zf-console": "^1.3" 22 | }, 23 | "require-dev": { 24 | "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.5", 25 | "zendframework/zend-coding-standard": "~1.0.0" 26 | }, 27 | "autoload": { 28 | "psr-4": { 29 | "ZF\\Deploy\\": "src/" 30 | } 31 | }, 32 | "autoload-dev": { 33 | "psr-4": { 34 | "ZFTest\\Deploy\\": "test/" 35 | } 36 | }, 37 | "config": { 38 | "sort-packages": true 39 | }, 40 | "extra": { 41 | "branch-alias": { 42 | "dev-master": "1.3.x-dev", 43 | "dev-develop": "1.4.x-dev" 44 | } 45 | }, 46 | "bin": [ 47 | "bin/zfdeploy.php" 48 | ], 49 | "scripts": { 50 | "check": [ 51 | "@cs-check", 52 | "@test" 53 | ], 54 | "cs-check": "phpcs", 55 | "cs-fix": "phpcbf", 56 | "test": "phpunit --colors=always", 57 | "test-coverage": "phpunit --colors=always --coverage-clover clover.xml" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /config/routes.php: -------------------------------------------------------------------------------- 1 | 'self-update', 24 | 'description' => 'The self-update command checks packages.zendframework.com for a newer 25 | version, and, if found, downloads and installs the latest.', 26 | 'short_description' => 'Updates zfdeploy.phar to the latest version', 27 | 'defaults' => [ 28 | 'self-update' => true, 29 | ], 30 | ], 31 | [ 32 | 'name' => 'build', 33 | 'route' => 'build [--target=] [--modules=] [--vendor|-v]:vendor [--composer=] [--gitignore=] [--configs=] [--deploymentxml=] [--zpkdata=] [--version=]', 34 | 'description' => 'Create a deployment package named based on the provided target directory.', 35 | 'short_description' => 'Build a deployment package', 36 | 'options_descriptions' => [ 37 | '' => 'Name of the package file to create; suffix must be .zip, .tar, .tar.gz, .tgz, or .zpk', 38 | '--target' => 'The target directory of the application to package; defaults to current working directory', 39 | '--modules' => 'Comma-separated list of modules to include in build', 40 | '--vendor|-v' => 'Whether or not to include the vendor directory (disabled by default)', 41 | '--composer' => 'Whether or not to execute composer; "on" or "off" ("on" by default)', 42 | '--gitignore' => 'Whether or not to parse the .gitignore file to determine what files/folders to exclude; "on" or "off" ("on" by default)', 43 | '--configs' => 'Path to directory containing application config files to include in the package', 44 | '--deploymentxml' => 'Path to a custom deployment.xml to use when building a ZPK package', 45 | '--zpkdata' => 'Path to a directory containing ZPK package assets (deployment.xml, logo, scripts, etc.)', 46 | '--version' => 'Specific application version to use for a ZPK package', 47 | ], 48 | 'constraints' => [ 49 | 'package' => '#\.(' . implode('|', $extensions) . ')$#', 50 | 'composer' => '/^(on|off)$/', 51 | 'gitignore' => '/^(on|off)$/', 52 | ], 53 | 'defaults' => [ 54 | 'build' => true, 55 | 'composer' => true, 56 | 'configs' => false, 57 | 'deploymentxml' => null, 58 | 'gitignore' => true, 59 | 'modules' => [], 60 | 'target' => getcwd(), 61 | 'vendor' => false, 62 | 'version' => date('Y-m-d_H:i'), 63 | 'zpkdata' => null, 64 | ], 65 | 'filters' => [ 66 | 'composer' => $booleanFilter, 67 | 'gitignore' => $booleanFilter, 68 | 'modules' => new ExplodeFilter(), 69 | ], 70 | 'handler' => 'ZF\Deploy\Deploy', 71 | ], 72 | ]; 73 | -------------------------------------------------------------------------------- /config/zpk/deployment.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {NAME} 5 | 6 | {VERSION} 7 | 8 | {LOGO} 9 | data 10 | data/public 11 | 12 | -------------------------------------------------------------------------------- /config/zpk/logo/apigility-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfcampus/zf-deploy/09f962d1d825e65285cb111a2176c187ea627992/config/zpk/logo/apigility-logo.png -------------------------------------------------------------------------------- /config/zpk/logo/zf2-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfcampus/zf-deploy/09f962d1d825e65285cb111a2176c187ea627992/config/zpk/logo/zf2-logo.png -------------------------------------------------------------------------------- /config/zpk/schema.xsd: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | Package descriptor format version 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | Application version 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | Application dependencies block 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | PHP version dependencies block 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | PHP extensions dependencies block 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | PHP directives dependencies block 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | Zend Server dependencies block 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | Zend Server components dependencies block 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | Zend Framework dependencies block 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | Zend Framework 2 dependencies block 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | Deployment Libraries dependencies block 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | Package parameters 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | Use to check equality with other parameter 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | Environment variables for the deployment scripts 259 | 260 | 261 | 262 | 263 | 264 | Single environment variable with a name and a 265 | string value. 266 | 267 | 268 | 269 | 270 | Environment variable name. 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | Environment variable value. 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | Persistent resources to be kept during upgrade 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | -------------------------------------------------------------------------------- /src/Deploy.php: -------------------------------------------------------------------------------- 1 | resetStateForExecution($console); 99 | 100 | $opts = (object) $route->getMatches(); 101 | 102 | if (! $this->validatePackage($opts->package, $opts)) { 103 | return $this->exitCode; 104 | } 105 | 106 | if (! $this->validateApplicationPath($opts->target, $opts)) { 107 | return $this->exitCode; 108 | } 109 | 110 | if (! $this->validateModules($opts->modules, $opts->target)) { 111 | return $this->exitCode; 112 | } 113 | 114 | $console->writeLine(sprintf('Creating package "%s"...', $opts->package), Color::BLUE); 115 | 116 | $tmpDir = $this->createTmpDir(); 117 | if (false === $tmpDir) { 118 | return $this->exitCode; 119 | } 120 | 121 | $tmpDir = $this->prepareZpk( 122 | $tmpDir, 123 | basename($opts->package, '.' . $opts->format), 124 | $opts->version, 125 | $opts->format, 126 | $opts->deploymentxml, 127 | $opts->zpkdata, 128 | $opts->appConfigPath 129 | ); 130 | 131 | if (false === $tmpDir) { 132 | return $this->exitCode; 133 | } 134 | 135 | $this->cloneApplication( 136 | $opts->target, 137 | $tmpDir, 138 | $opts->gitignore, 139 | $opts->vendor, 140 | $opts->modules, 141 | $opts->configs 142 | ); 143 | $this->copyModules($opts->modules, $opts->target, $tmpDir); 144 | 145 | if (false === $this->executeComposer($opts->vendor, $opts->composer, $tmpDir)) { 146 | return $this->exitCode; 147 | } 148 | 149 | $this->removeTestDir($tmpDir . '/vendor'); 150 | 151 | if (false === $this->createPackage($opts->package, $tmpDir, $opts->format)) { 152 | return $this->exitCode; 153 | } 154 | 155 | self::recursiveDelete($opts->format === 'zpk' ? dirname($tmpDir) : $tmpDir); 156 | 157 | $this->console->writeLine(sprintf( 158 | '[DONE] Package %s successfully created (%d bytes)', 159 | $opts->package, 160 | filesize($opts->package) 161 | ), Color::GREEN); 162 | 163 | return self::INFO_NO_ERROR; 164 | } 165 | 166 | /** 167 | * Report an error 168 | * 169 | * Allows passing in a specific color to use when emitting the error 170 | * message; defaults to red. 171 | * 172 | * @param string $message 173 | * @param string $color 174 | * @return false 175 | */ 176 | protected function reportError($message, $color = Color::RED) 177 | { 178 | $this->console->writeLine($message, $color); 179 | 180 | return false; 181 | } 182 | 183 | /** 184 | * Validate a deployment XML file against a schema 185 | * 186 | * @param string $file 187 | * @param string $schema 188 | * @return bool 189 | */ 190 | protected function validateXml($file, $schema) 191 | { 192 | if (! file_exists($file)) { 193 | return $this->reportError(sprintf('The XML file "%s" does not exist.', $file)); 194 | } 195 | if (! file_exists($schema)) { 196 | return $this->reportError(sprintf('Error: The XML schema file "%s" does not exist.', $schema)); 197 | } 198 | 199 | // Copy schema to temp file; fixes portability problem with Windows 200 | $tmpSchema = tempnam(sys_get_temp_dir(), 'zfd'); 201 | copy($schema, $tmpSchema); 202 | 203 | // Validate the deployment XML file 204 | $dom = new DOMDocument(); 205 | $dom->loadXML(file_get_contents($file)); 206 | if (! $dom->schemaValidate($tmpSchema)) { 207 | unlink($tmpSchema); 208 | return $this->reportError(sprintf( 209 | 'The XML file "%s" does not validate against the schema "%s".', 210 | $file, 211 | $schema 212 | )); 213 | } 214 | 215 | unlink($tmpSchema); 216 | return true; 217 | } 218 | 219 | /** 220 | * Validate the package file argument 221 | * 222 | * Determines the format, and, if the package file is valid, sets the 223 | * format for this invocation. 224 | * 225 | * @param string $package 226 | * @param object $opts All options 227 | * @return bool 228 | */ 229 | protected function validatePackage($package, $opts) 230 | { 231 | // Does the file already exist? (if so, error!) 232 | if (file_exists($package)) { 233 | $this->exitCode = self::ERROR_PACKAGE_EXISTS; 234 | return $this->reportError(sprintf('Error: package file "%s" already exists', $package)); 235 | } 236 | 237 | preg_match('#\.(?Ptar.gz|tar|tgz|zip|zpk)$#', $package, $matches); 238 | $format = $matches['format']; 239 | 240 | // Do we have the PHP extension necessary for the file format? (if not, error!) 241 | switch ($format) { 242 | case 'zip': 243 | case 'zpk': 244 | if (! extension_loaded('zip')) { 245 | $this->exitCode = self::ERROR_MISSING_ZIP_EXTENSION; 246 | return $this->reportError('Error: the ZIP extension of PHP is not loaded.'); 247 | } 248 | break; 249 | 250 | case 'tar': 251 | case 'tar.gz': 252 | case 'tgz': 253 | if (! class_exists('PharData')) { 254 | $this->exitCode = self::ERROR_MISSING_PHAR_EXTENSION; 255 | return $this->reportError('Error: the Phar extension of PHP is not loaded.'); 256 | } 257 | break; 258 | } 259 | 260 | $opts->format = $format; 261 | 262 | return true; 263 | } 264 | 265 | /** 266 | * Validate the application path 267 | * 268 | * If valid, also sets the $appConfigPath property in $opts. 269 | * 270 | * @param string $target 271 | * @param object $opts All options 272 | * @return bool 273 | */ 274 | protected function validateApplicationPath($target, $opts) 275 | { 276 | // Is it a directory? (if not, error!) 277 | if (! is_dir($target)) { 278 | $this->exitCode = self::ERROR_WRONG_APP_DIR; 279 | return $this->reportError(sprintf('Error: the application path "%s" is not valid', $target)); 280 | } 281 | 282 | // Is it a valid ZF2 app? (if not, error!) 283 | $appConfigPath = $target . '/config/application.config.php'; 284 | if (! file_exists($appConfigPath)) { 285 | $this->exitCode = self::ERROR_MISSING_APP_CONFIG; 286 | return $this->reportError(sprintf( 287 | 'Error: the folder "%s" does not contain a standard ZF2 application', 288 | $target 289 | )); 290 | } 291 | $config = require $appConfigPath; 292 | if (! isset($config['modules'])) { 293 | $this->exitCode = self::ERROR_MISSING_APP_MODULES; 294 | return $this->reportError(sprintf( 295 | 'Error: the folder "%s" does not contain a standard ZF2 application', 296 | $target 297 | )); 298 | } 299 | 300 | // Set $this->appConfigPath when done 301 | $opts->appConfigPath = $appConfigPath; 302 | 303 | return true; 304 | } 305 | 306 | /** 307 | * Validate the modules list 308 | * 309 | * @param array $modules 310 | * @param string $target 311 | * @return bool 312 | */ 313 | protected function validateModules(array $modules, $target) 314 | { 315 | // If empty, done 316 | if (empty($modules)) { 317 | return true; 318 | } 319 | 320 | // Validate each module 321 | foreach ($modules as $module) { 322 | $normalized = str_replace('\\', '/', $module); 323 | if (! is_dir($target . '/module/' . $normalized)) { 324 | $this->exitCode = self::ERROR_MISSING_MODULE; 325 | return $this->reportError(sprintf('Error: the module "%s" does not exist in %s', $module, $target)); 326 | } 327 | } 328 | 329 | return true; 330 | } 331 | 332 | /** 333 | * Validate a ZPK data directory 334 | * 335 | * @param string $dir 336 | * @return bool 337 | */ 338 | protected function validateZpkDataDir($dir) 339 | { 340 | // No ZPK data dir passed, nothing to do 341 | if (empty($dir)) { 342 | return true; 343 | } 344 | 345 | // Does the directory exist? (if not, error!) 346 | if (! file_exists($dir) || ! is_dir($dir)) { 347 | $this->exitCode = self::ERROR_WRONG_DATA_DIR; 348 | return $this->reportError(sprintf('Error: The specified ZPK data directory "%s" does not exist', $dir)); 349 | } 350 | 351 | // Does the directory contain a deployment.xml file? (if not, error!) 352 | if (! file_exists($dir . '/deployment.xml')) { 353 | $this->exitCode = self::ERROR_MISSING_DEPLOYMENT_FILE; 354 | return $this->reportError(sprintf( 355 | 'Error: The specified ZPK data directory "%s" does not contain a deployment.xml file', 356 | $dir 357 | )); 358 | } 359 | 360 | return true; 361 | } 362 | 363 | /** 364 | * Create a temporary directory for packaging 365 | * 366 | * Returns the directory name on success. 367 | * 368 | * @return string|false 369 | */ 370 | protected function createTmpDir() 371 | { 372 | $count = 0; 373 | do { 374 | $tmpDir = sys_get_temp_dir() . '/' . uniqid("ZFDeploy_"); 375 | $count++; 376 | } while ($count < 3 && file_exists($tmpDir)); 377 | 378 | if ($count >= 3) { 379 | $this->exitCode = self::ERROR_COULD_NOT_SELECT_TEMP_DIR; 380 | return $this->reportError('Error: Cannot select a temporary directory in %s', sys_get_temp_dir()); 381 | } 382 | 383 | if (false === mkdir($tmpDir)) { 384 | $this->exitCode = self::ERROR_COULD_NOT_CREATE_TEMP_DIR; 385 | return $this->reportError('Error: Cannot create a temporary directory %s', $tmpDir); 386 | } 387 | 388 | return $tmpDir; 389 | } 390 | 391 | /** 392 | * Prepare ZPK files 393 | * 394 | * Sets up the required directory structure for a ZPK, including adding 395 | * any desired scripts, the deployment.xml, and the logo. 396 | * 397 | * Returns the path to the data directory on completion. 398 | * 399 | * If the $format is not zpk, returns $tmpDir. 400 | * 401 | * @param string $tmpDir 402 | * @param string $appname 403 | * @param string $version 404 | * @param string $format 405 | * @param string $deploymentXml 406 | * @param string $zpkDataDir 407 | * @param array $zpkDataDir 408 | * @return string|false 409 | */ 410 | protected function prepareZpk($tmpDir, $appname, $version, $format, $deploymentXml, $zpkDataDir, $appConfigPath) 411 | { 412 | if ('zpk' !== $format) { 413 | return $tmpDir; 414 | } 415 | 416 | $logo = ''; 417 | 418 | // ZPK data path provided; sync it in 419 | if (! $this->validateZpkDataDir($zpkDataDir)) { 420 | return false; 421 | } 422 | 423 | if ($zpkDataDir) { 424 | $deploymentXml = $zpkDataDir . '/deployment.xml'; 425 | self::recursiveCopy($zpkDataDir, $tmpDir); 426 | } 427 | 428 | // Create the data directory, if it doesn't exist 429 | if (! is_dir($tmpDir . '/data')) { 430 | mkdir($tmpDir . '/data'); 431 | } 432 | 433 | // ZPK data path NOT provided; sync in defaults 434 | if (! $zpkDataDir) { 435 | mkdir($tmpDir . '/scripts'); 436 | foreach (glob(__DIR__ . '/../config/zpk/scripts/*.php') as $script) { 437 | copy($script, $tmpDir . '/scripts/' . basename($script)); 438 | } 439 | } 440 | 441 | // No deployment.xml provided; use defaults 442 | if (! $deploymentXml) { 443 | $logo = $this->copyLogo($tmpDir, $appConfigPath); 444 | $deploymentXml = __DIR__ . '/../config/zpk/deployment.xml'; 445 | } 446 | 447 | // Prepare deployment.xml 448 | if (false === $this->prepareDeploymentXml($deploymentXml, $tmpDir, $appname, $logo, $version, $format)) { 449 | return false; 450 | } 451 | 452 | return $tmpDir .= '/data'; 453 | } 454 | 455 | /** 456 | * Copy the logo into the ZPK directory 457 | * 458 | * Determines whether to use a ZF2 or Apigility logo. 459 | * 460 | * @param string $tmpDir 461 | * @param array $appConfigPath Application configuration path 462 | * @return string The logo file name 463 | */ 464 | protected function copyLogo($tmpDir, $appConfigPath) 465 | { 466 | $logoFile = __DIR__ . '/../config/zpk/logo/zf2-logo.png'; 467 | $logo = 'zf2-logo.png'; 468 | 469 | $ns = preg_quote('\\'); 470 | $appConfig = file_get_contents($appConfigPath); 471 | if (preg_match( 472 | '/\'modules\'\s*=>\s*array\s*\(\s+[\'a-z0-9\s_' . $ns . ',]*?\'zf' . $ns . '+apigility\'/is', 473 | $appConfig 474 | )) { 475 | $logoFile = __DIR__ . '/../config/zpk/logo/apigility-logo.png'; 476 | $logo = 'apigility-logo.png'; 477 | } 478 | 479 | copy($logoFile, $tmpDir . '/' . $logo); 480 | 481 | return $logo; 482 | } 483 | 484 | /** 485 | * Prepares the default deployment XML 486 | * 487 | * Injects the application name, logo, and version, and then validates it 488 | * before returning. 489 | * 490 | * @param string $tmpDir 491 | * @param string $appname 492 | * @param string $logo 493 | * @param string $version 494 | * @param string $format 495 | * @return bool 496 | */ 497 | protected function prepareDeploymentXml($deploymentXml, $tmpDir, $appname, $logo, $version, $format) 498 | { 499 | $deployString = file_get_contents($deploymentXml); 500 | 501 | $deployString = str_replace('{NAME}', $appname, $deployString); 502 | $deployString = str_replace('{VERSION}', $version, $deployString); 503 | $deployString = str_replace('{LOGO}', $logo, $deployString); 504 | 505 | $packageLocation = $tmpDir . '/deployment.xml'; 506 | file_put_contents($packageLocation, $deployString); 507 | 508 | if (! $this->validateXml($packageLocation, __DIR__ . '/../config/zpk/schema.xsd')) { 509 | $this->exitCode = self::ERROR_WRONG_DEPLOYMENT_FILE; 510 | return $this->reportError(sprintf( 511 | 'Error: Wrong "%s" file content', 512 | $packageLocation 513 | )); 514 | } 515 | 516 | return true; 517 | } 518 | 519 | /** 520 | * Clone the application into the build directory 521 | * 522 | * @param array $applicationPath 523 | * @param array $tmpDir 524 | * @param bool $gitignore 525 | * @param bool $useVendor 526 | * @param array $modules 527 | * @param false|string $configsPath 528 | */ 529 | protected function cloneApplication($applicationPath, $tmpDir, $gitignore, $useVendor, $modules, $configsPath) 530 | { 531 | $exclude = []; 532 | if (! $useVendor) { 533 | $exclude[$applicationPath . '/composer.lock'] = true; 534 | $exclude[$applicationPath . '/vendor'] = true; 535 | } 536 | 537 | if (is_array($modules) && count($modules) > 0) { 538 | $exclude[$applicationPath . '/module'] = true; 539 | } 540 | 541 | self::recursiveCopy($applicationPath, $tmpDir, $exclude, $gitignore); 542 | 543 | if ($configsPath && is_dir($configsPath)) { 544 | $tmpConfigPath = $tmpDir . '/config/autoload/'; 545 | foreach (glob($configsPath . '/*.php') as $config) { 546 | copy($config, $tmpConfigPath . basename($config)); 547 | } 548 | } 549 | } 550 | 551 | /** 552 | * Copy modules into the build directory 553 | * 554 | * Only if specific modules were specified via the CLI arguments. 555 | * 556 | * @param array $modules 557 | * @param string $applicationPath 558 | * @param string $tmpDir 559 | */ 560 | protected function copyModules(array $modules, $applicationPath, $tmpDir) 561 | { 562 | if (empty($modules)) { 563 | return; 564 | } 565 | 566 | // copy modules 567 | foreach ($modules as $module) { 568 | $normalized = str_replace('\\', '/', $module); 569 | self::recursiveCopy($applicationPath . '/module/' . $normalized, $tmpDir . '/module/' . $normalized); 570 | } 571 | 572 | // enable only the selected modules in the config/application.config.php 573 | if (file_exists($tmpDir . '/config/application.config.php')) { 574 | $config = include $tmpDir . '/config/application.config.php'; 575 | // Remove a module if not present in $modules 576 | $tot = count($config['modules']); 577 | for ($i = 0; $i < $tot; $i++) { 578 | $normalized = str_replace('\\', '/', $config['modules'][$i]); 579 | if (is_dir($applicationPath . '/module/' . $normalized) 580 | && ! in_array($config['modules'][$i], $modules) 581 | ) { 582 | unset($config['modules'][$i]); 583 | } 584 | } 585 | file_put_contents( 586 | $tmpDir . '/config/application.config.php', 587 | 'getComposerExecutable($tmpDir); 719 | $command = sprintf('%s install --no-dev --prefer-dist --optimize-autoloader 2>&1', $composer); 720 | 721 | if ($composer !== 'composer') { 722 | $command = $this->prependPhpBinaryPath($command); 723 | } 724 | 725 | $this->console->write('Executing ', Color::BLUE); 726 | $this->console->writeLine($command); 727 | 728 | $curDir = getcwd(); 729 | chdir($tmpDir); 730 | $result = exec($command, $output, $exitCode); 731 | chdir($curDir); 732 | 733 | if ($this->downloadedComposer) { 734 | @unlink($this->downloadedComposer); 735 | unset($this->downloadedComposer); 736 | } 737 | 738 | if ($exitCode !== 0) { 739 | $this->exitCode = self::ERROR_COMPOSER_ERROR; 740 | return $this->reportError( 741 | 'Composer error during install command (exit code: ' . $exitCode . ') ' . implode('; ', $output) 742 | ); 743 | } 744 | } 745 | 746 | /** 747 | * Determine the Composer executable 748 | * 749 | * If 'composer' command is available on the path, use it. 750 | * If a 'composer.phar' exists in $tmpDir, perform a self-update, and use it. 751 | * Otherwise, download 'composer.phar' from getcomposer.org, and use it. 752 | * 753 | * @param mixed $tmpDir 754 | * @return string 755 | */ 756 | protected function getComposerExecutable($tmpDir) 757 | { 758 | exec('composer 2>&1', $output, $exitCode); 759 | if ($exitCode === 0) { 760 | return 'composer'; 761 | } 762 | 763 | $phar = $tmpDir . '/composer.phar'; 764 | if (file_exists($phar)) { 765 | $this->console->writeLine('composer.phar exists in temp dir ' . $tmpDir, Color::LIGHT_YELLOW); 766 | $this->updateComposerPhar($phar); 767 | return $phar; 768 | } 769 | 770 | $this->downloadComposerPhar($phar); 771 | return $phar; 772 | } 773 | 774 | /** 775 | * Create the package file 776 | * 777 | * @param string $package 778 | * @param string $dir 779 | * @param string $format 780 | * @return bool 781 | */ 782 | protected function createPackage($package, $dir, $format) 783 | { 784 | $this->console->writeLine('Creating package...', Color::BLUE); 785 | 786 | switch ($format) { 787 | case 'zpk': 788 | $dir = dirname($dir); 789 | // intentionally fall through; zpk is a zip archive 790 | case 'zip': 791 | $packager = new ZipArchive; 792 | $packager->open($package, ZipArchive::CREATE); 793 | break; 794 | case 'tar': 795 | $pharFile = $package; 796 | $packager = new PharData($pharFile); 797 | break; 798 | case 'tar.gz': 799 | $pharFile = dirname($package) . '/' . basename($package, '.tar.gz') . '.tar'; 800 | $packager = new PharData($pharFile); 801 | break; 802 | case 'tgz': 803 | $pharFile = dirname($package) . '/' . basename($package, '.tgz') . '.tar'; 804 | $packager = new PharData($pharFile); 805 | break; 806 | default: 807 | $this->exitCode = self::ERROR_UNKNOWN_ARCHIVE_FORMAT; 808 | return $this->reportError(sprintf('Unknown package format "%s"', $format)); 809 | } 810 | 811 | // Create recursive directory iterator 812 | $files = new RecursiveIteratorIterator( 813 | new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS), 814 | RecursiveIteratorIterator::LEAVES_ONLY 815 | ); 816 | 817 | $this->console->writeLine('Writing files...', Color::BLUE); 818 | // Remove the relative path 819 | $dirPos = strlen($dir) + 1; 820 | switch ($format) { 821 | case 'zip': 822 | case 'zpk': 823 | foreach ($files as $name => $file) { 824 | $normalizedFile = str_replace('\\', '/', $file); 825 | $packager->addFile($normalizedFile, substr($normalizedFile, $dirPos)); 826 | } 827 | break; 828 | case 'tar': 829 | case 'tar.gz': 830 | case 'tgz': 831 | $packager->buildFromIterator($files, $dir); 832 | break; 833 | } 834 | 835 | // Close and finalize the archive 836 | $this->console->writeLine('Closing package...', Color::BLUE); 837 | switch ($format) { 838 | case 'zip': 839 | case 'zpk': 840 | $packager->close(); 841 | break; 842 | case 'tar': 843 | unset($packager); 844 | break; 845 | case 'tar.gz': 846 | $packager->compress(Phar::GZ, '.tar.gz'); 847 | unset($packager); 848 | unlink($pharFile); 849 | break; 850 | case 'tgz': 851 | $packager->compress(Phar::GZ, '.tgz'); 852 | unset($packager); 853 | unlink($pharFile); 854 | break; 855 | } 856 | 857 | return true; 858 | } 859 | 860 | /** 861 | * Reset internal state for a new execution cycle 862 | */ 863 | protected function resetStateForExecution(Console $console) 864 | { 865 | $this->console = $console; 866 | $this->downloadedComposer = null; 867 | $this->exitCode = self::INFO_NO_ERROR; 868 | } 869 | 870 | /** 871 | * Prepends the PHP binary path to the given command. 872 | * 873 | * This is particularly useful when executing PHARs and ensures that they 874 | * execute successfully even if the PHAR is not executable or there no PHP 875 | * executable available in the environment. 876 | * 877 | * The prepended PHP path is the one of the PHP executing the current 878 | * process. 879 | * 880 | * @param string $command a command line 881 | * @return string The modified command line 882 | */ 883 | protected function prependPhpBinaryPath($command) 884 | { 885 | if (defined('PHP_BINARY')) { 886 | // Since PHP 5.4 887 | $command = '"' . PHP_BINARY . '" ' . $command; 888 | } 889 | return $command; 890 | } 891 | 892 | /** 893 | * Update an existing composer.phar 894 | * 895 | * @param string $phar 896 | */ 897 | protected function updateComposerPhar($phar) 898 | { 899 | $updateCommand = sprintf('%s self-update 2>&1', $phar); 900 | $updateCommand = $this->prependPhpBinaryPath($updateCommand); 901 | exec($updateCommand); 902 | } 903 | 904 | /** 905 | * Download composer from getcomposer.org 906 | * 907 | * @param string $path 908 | */ 909 | protected function downloadComposerPhar($path) 910 | { 911 | // Try to download it 912 | file_put_contents($path, fopen('https://getcomposer.org/composer.phar', '-r')); 913 | 914 | // Remember it is downloaded - to delete it afterwards 915 | $this->downloadedComposer = $path; 916 | } 917 | } 918 | -------------------------------------------------------------------------------- /src/SelfUpdate.php: -------------------------------------------------------------------------------- 1 | version = $version; 31 | } 32 | 33 | /** 34 | * Perform a self-update on the phar file 35 | * 36 | * @param Route $route 37 | * @param Console $console 38 | * @return int 39 | */ 40 | public function __invoke(Route $route, Console $console) 41 | { 42 | $manifest = UpdateManifest::loadFile(self::MANIFEST_FILE); 43 | $manager = new UpdateManager($manifest); 44 | 45 | if (! $manager->update($this->version, true, true)) { 46 | $console->writeLine('No updates available.', Color::YELLOW); 47 | return 0; 48 | } 49 | 50 | $version = new Version($this->version); 51 | $update = $manifest->findRecent($version); 52 | 53 | $console->write('Updated to version '); 54 | $console->writeLine($update->getVersion(), Color::GREEN); 55 | return 0; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /zfdeploy.phar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfcampus/zf-deploy/09f962d1d825e65285cb111a2176c187ea627992/zfdeploy.phar --------------------------------------------------------------------------------