diff --git a/Gruntfile.js b/Gruntfile.js index d380ee4a24f..34baec79984 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -3,6 +3,42 @@ module.exports = function( grunt ) { 'use strict'; require( 'dotenv' ).config(); + + // Root paths to include in the plugin build ZIP when running `npm run build`. + const productionIncludedRootFiles = [ + 'LICENSE', + 'amp.php', + 'assets', + 'back-compat', + 'includes', + 'readme.txt', + 'templates', + 'vendor', + ]; + + // These patterns paths will be excluded from among the above directory. + const productionExcludedPathPatterns = [ + /.*\/src\/.*/, + /.*images\/stories-editor\/.*\.svg/, + ]; + + // These will be removed from the vendor directory after installing but prior to creating a ZIP. + // ⚠️ Warning: These paths are passed straight to rm command in the shell, without any escaping. + const productionVendorExcludedFilePatterns = [ + 'composer.*', + 'vendor/*/*/.editorconfig', + 'vendor/*/*/.gitignore', + 'vendor/*/*/composer.*', + 'vendor/*/*/Doxyfile', + 'vendor/*/*/LICENSE', + 'vendor/*/*/phpunit.*', + 'vendor/*/*/*.md', + 'vendor/*/*/*.txt', + 'vendor/*/*/*.yml', + 'vendor/*/*/.*.yml', + 'vendor/*/*/tests', + ]; + grunt.initConfig( { pkg: grunt.file.readJSON( 'package.json' ), @@ -32,6 +68,9 @@ module.exports = function( grunt ) { verify_matching_versions: { command: 'php bin/verify-version-consistency.php', }, + composer_install: { + command: 'if [ ! -e build ]; then echo "Run grunt build first."; exit 1; fi; cd build; composer install --no-dev -o && composer remove cweagans/composer-patches --update-no-dev -o && rm -r ' + productionVendorExcludedFilePatterns.join( ' ' ), + }, create_build_zip: { command: 'if [ ! -e build ]; then echo "Run grunt build first."; exit 1; fi; if [ -e amp.zip ]; then rm amp.zip; fi; cd build; zip -r ../amp.zip .; cd ..; echo; echo "ZIP of build: $(pwd)/amp.zip"', }, @@ -95,17 +134,24 @@ module.exports = function( grunt ) { const versionAppend = new Date().toISOString().replace( /\.\d+/, '' ).replace( /-|:/g, '' ) + '-' + commitHash; const paths = lsOutput.trim().split( /\n/ ).filter( function( file ) { - return ! /^(blocks|\.|bin|([^/]+)+\.(md|json|xml)|Gruntfile\.js|tests|wp-assets|readme\.md|composer\..*|patches|webpack.*|assets\/images\/stories-editor\/.*\.svg|assets\/src|assets\/css\/src|docker-compose\.yml|.*\.config\.js|codecov\.yml|example\.env)/.test( file ); + const topSegment = file.replace( /\/.*/, '' ); + if ( ! productionIncludedRootFiles.includes( topSegment ) ) { + return false; + } + + for ( const productionExcludedPathPattern of productionExcludedPathPatterns ) { + if ( productionExcludedPathPattern.test( file ) ) { + return false; + } + } + + return true; } ); - paths.push( 'vendor/autoload.php' ); + paths.push( 'composer.*' ); // Copy in order to be able to do run composer_install. paths.push( 'assets/js/*.js' ); paths.push( 'assets/js/*.deps.json' ); paths.push( 'assets/css/*.css' ); - paths.push( 'vendor/composer/**' ); - paths.push( 'vendor/sabberworm/php-css-parser/lib/**' ); - paths.push( 'vendor/fasterimage/fasterimage/src/**' ); - paths.push( 'vendor/willwashburn/stream/src/**' ); grunt.config.set( 'copy', { build: { @@ -138,6 +184,7 @@ module.exports = function( grunt ) { } ); grunt.task.run( 'readme' ); grunt.task.run( 'copy' ); + grunt.task.run( 'shell:composer_install' ); done(); } diff --git a/bin/build-plugin-zip.sh b/bin/build-plugin-zip.sh new file mode 100755 index 00000000000..8f87aa70d2a --- /dev/null +++ b/bin/build-plugin-zip.sh @@ -0,0 +1,56 @@ +#!/bin/bash + +# Exit if any command fails. +set -e + +# Change to the expected directory. +cd "$(dirname "$0")" +cd .. +PLUGIN_DIR=$(pwd) + +# Enable nicer messaging for build status. +BLUE_BOLD='\033[1;34m'; +GREEN_BOLD='\033[1;32m'; +RED_BOLD='\033[1;31m'; +YELLOW_BOLD='\033[1;33m'; +COLOR_RESET='\033[0m'; +error () { + echo -e "\n${RED_BOLD}$1${COLOR_RESET}\n" +} +status () { + echo -e "\n${BLUE_BOLD}$1${COLOR_RESET}\n" +} +success () { + echo -e "\n${GREEN_BOLD}$1${COLOR_RESET}\n" +} +warning () { + echo -e "\n${YELLOW_BOLD}$1${COLOR_RESET}\n" +} + +status "Time to release AMP ⚡️" + +status "Setting up a fresh build environment in a temporary folder. ✨" + +# Create a fresh temporary folder in a way that works across platforms. +BUILD_DIR=$(mktemp -d 2>/dev/null || mktemp -d -t 'amp-production-build') + +# Do a local clone to move the current files across. +git clone -l . "$BUILD_DIR" +cd "$BUILD_DIR" + +# Run the build. +status "Installing dependencies... 📦" +composer install +PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true npm install + +status "Generating build... ⚙️" +npm run build + +status "Copying the ZIP file back over... ↩️" +rm -f "$PLUGIN_DIR/amp.zip" +cp "$BUILD_DIR/amp.zip" "$PLUGIN_DIR/amp.zip" + +status "Removing the temporary folder again... 🗑️" +rm -rf "$BUILD_DIR" + +success "You've built AMP! 🎉 \nThe ZIP file can be found in the following location:\n$PLUGIN_DIR/amp.zip" diff --git a/composer.json b/composer.json index 11c023d160d..34c92dfe974 100644 --- a/composer.json +++ b/composer.json @@ -39,7 +39,7 @@ "extra": { "patches": { "sabberworm/php-css-parser": { - "PHP-CSS-Parser: Fix parsing CSS selectors which contain commas ": "patches/php-css-parser-mods.diff" + "PHP-CSS-Parser: Fix parsing CSS selectors which contain commas ": "https://github.com/sabberworm/PHP-CSS-Parser/commit/fa139f65c5b098ae652c970b25e6eb03fc495eb4.diff" } } } diff --git a/composer.lock b/composer.lock index 4655d4d0917..34fd1a2cb70 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "a3c997851b69a8a961f47dd70a9d2c59", + "content-hash": "da3403f1f44ade0351beef9a5a5be1b9", "packages": [ { "name": "cweagans/composer-patches", @@ -127,6 +127,11 @@ "phpunit/phpunit": "~4.8" }, "type": "library", + "extra": { + "patches_applied": { + "PHP-CSS-Parser: Fix parsing CSS selectors which contain commas ": "https://github.com/sabberworm/PHP-CSS-Parser/commit/fa139f65c5b098ae652c970b25e6eb03fc495eb4.diff" + } + }, "autoload": { "psr-0": { "Sabberworm\\CSS": "lib/" @@ -239,9 +244,9 @@ "authors": [ { "name": "Franck Nijhof", + "role": "Developer / IT Manager", "email": "franck.nijhof@dealerdirect.com", - "homepage": "http://www.frenck.nl", - "role": "Developer / IT Manager" + "homepage": "http://www.frenck.nl" } ], "description": "PHP_CodeSniffer Standards Composer Installer Plugin", @@ -421,6 +426,17 @@ { "name": "roave/security-advisories", "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/Roave/SecurityAdvisories.git", + "reference": "ea693fa060702164985511acc3ceb5389c9ac761" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/ea693fa060702164985511acc3ceb5389c9ac761", + "reference": "ea693fa060702164985511acc3ceb5389c9ac761", + "shasum": "" + }, "conflict": { "3f/pygmentize": "<1.2", "adodb/adodb-php": "<5.20.12", diff --git a/patches/php-css-parser-mods.diff b/patches/php-css-parser-mods.diff deleted file mode 100644 index d1df4bd5960..00000000000 --- a/patches/php-css-parser-mods.diff +++ /dev/null @@ -1,109 +0,0 @@ -diff --git a/lib/Sabberworm/CSS/RuleSet/DeclarationBlock.php b/lib/Sabberworm/CSS/RuleSet/DeclarationBlock.php -index 6614b1d..4f06d3d 100644 ---- a/lib/Sabberworm/CSS/RuleSet/DeclarationBlock.php -+++ b/lib/Sabberworm/CSS/RuleSet/DeclarationBlock.php -@@ -39,7 +39,19 @@ class DeclarationBlock extends RuleSet { - if (is_array($mSelector)) { - $this->aSelectors = $mSelector; - } else { -- $this->aSelectors = explode(',', $mSelector); -+ list( $sSelectors, $aPlaceholders ) = $this->addSelectorExpressionPlaceholders( $mSelector ); -+ if ( empty( $aPlaceholders ) ) { -+ $this->aSelectors = explode(',', $sSelectors); -+ } else { -+ $aSearches = array_keys( $aPlaceholders ); -+ $aReplaces = array_values( $aPlaceholders ); -+ $this->aSelectors = array_map( -+ function( $sSelector ) use ( $aSearches, $aReplaces ) { -+ return str_replace( $aSearches, $aReplaces, $sSelector ); -+ }, -+ explode(',', $sSelectors) -+ ); -+ } - } - foreach ($this->aSelectors as $iKey => $mSelector) { - if (!($mSelector instanceof Selector)) { -@@ -48,6 +60,52 @@ class DeclarationBlock extends RuleSet { - } - } - -+ /** -+ * Add placeholders for parenthetical/bracketed expressions in selectors which may contain commas that break exploding. -+ * -+ * This prevents a single selector like `.widget:not(.foo, .bar)` from erroneously getting parsed in setSelectors as -+ * two selectors `.widget:not(.foo` and `.bar)`. -+ * -+ * @param string $sSelectors Selectors. -+ * @return array First array value is the selectors with placeholders, and second value is the array of placeholders mapped to the original expressions. -+ */ -+ private function addSelectorExpressionPlaceholders( $sSelectors ) { -+ $iOffset = 0; -+ $aPlaceholders = array(); -+ -+ while ( preg_match( '/\(|\[/', $sSelectors, $aMatches, PREG_OFFSET_CAPTURE, $iOffset ) ) { -+ $sMatchString = $aMatches[0][0]; -+ $iMatchOffset = $aMatches[0][1]; -+ $iStyleLength = strlen( $sSelectors ); -+ $iOpenParens = 1; -+ $iStartOffset = $iMatchOffset + strlen( $sMatchString ); -+ $iFinalOffset = $iStartOffset; -+ for ( ; $iFinalOffset < $iStyleLength; $iFinalOffset++ ) { -+ if ( '(' === $sSelectors[ $iFinalOffset ] || '[' === $sSelectors[ $iFinalOffset ] ) { -+ $iOpenParens++; -+ } elseif ( ')' === $sSelectors[ $iFinalOffset ] || ']' === $sSelectors[ $iFinalOffset ] ) { -+ $iOpenParens--; -+ } -+ -+ // Found the end of the expression, so replace it with a placeholder. -+ if ( 0 === $iOpenParens ) { -+ $sMatchedExpr = substr( $sSelectors, $iMatchOffset, $iFinalOffset - $iMatchOffset + 1 ); -+ $sPlaceholder = sprintf( '{placeholder:%d}', count( $aPlaceholders ) + 1 ); -+ $aPlaceholders[ $sPlaceholder ] = $sMatchedExpr; -+ -+ // Update the CSS to replace the matched calc() with the placeholder function. -+ $sSelectors = substr( $sSelectors, 0, $iMatchOffset ) . $sPlaceholder . substr( $sSelectors, $iFinalOffset + 1 ); -+ // Update offset based on difference of length of placeholder vs original matched calc(). -+ $iFinalOffset += strlen( $sPlaceholder ) - strlen( $sMatchedExpr ); -+ break; -+ } -+ } -+ // Start matching at the next byte after the match. -+ $iOffset = $iFinalOffset + 1; -+ } -+ return array( $sSelectors, $aPlaceholders ); -+ } -+ - // remove one of the selector of the block - public function removeSelector($mSelector) { - if($mSelector instanceof Selector) { -diff --git a/tests/Sabberworm/CSS/ParserTest.php b/tests/Sabberworm/CSS/ParserTest.php -index d9f3ec6..5f2fb60 100644 ---- a/tests/Sabberworm/CSS/ParserTest.php -+++ b/tests/Sabberworm/CSS/ParserTest.php -@@ -154,6 +154,12 @@ class ParserTest extends \PHPUnit_Framework_TestCase { - case "li.green": - $this->assertSame(11, $oSelector->getSpecificity()); - break; -+ case "div:not(.foo[title=\"a,b\"], .bar)": -+ $this->assertSame(31, $oSelector->getSpecificity()); -+ break; -+ case "div[title=\"a,b\"]": -+ $this->assertSame(11, $oSelector->getSpecificity()); -+ break; - default: - $this->fail("specificity: untested selector " . $oSelector->getSelector()); - } -diff --git a/tests/files/specificity.css b/tests/files/specificity.css -index 82a2939..df03ff0 100644 ---- a/tests/files/specificity.css -+++ b/tests/files/specificity.css -@@ -2,6 +2,8 @@ - #file, - .help:hover, - li.green, --ol li::before { -+ol li::before, -+div:not(.foo[title="a,b"], .bar), -+div[title="a,b"] { - font-family: Helvetica; - }