diff --git a/FORK_CHANGELOG.md b/FORK_CHANGELOG.md index 53004c766..9e047180c 100644 --- a/FORK_CHANGELOG.md +++ b/FORK_CHANGELOG.md @@ -4,16 +4,53 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). -## [Unreleased] 1.15.1-atlassian-1 +## [1.15.1-atlassian-4] - 2024-12-06 ### Added - - [DCA11Y-1145]: Added support for using already installed node from default install directory of node version manager (fnm, mise, asdf, nvm, nvs). Use of version manager is enabled by default and can be controlled with `useNodeVersionManager` configuration property. + +### Changed +- [DCA11Y-1145]: `nodeVersion` property is not required any more, if `useNodeVersionManager` is not set to false + +## [1.15.1-atlassian-3] - 2024-11-29 + +### Fixed + +- [DCA11Y-1274]: Null arguments for mojos would fail the build +- [DCA11Y-1274]: Fix incremental with Yarn berry by fixing runtime detection +- [DCA11Y-1274]: Corepack Mojo incremental works +- [DCA11Y-1274]: Fix updating of digest versions without clean install +- [DCA11Y-1274]: Download dev metrics now correctly report PAC +- [DCA11Y-1145]: Fixed the legacy "downloadRoot" argument for PNPM & NPM installation + +### Added + +- [DCA11Y-1274]: ".flattened-pom.xml" & ".git" & ".node" to the excluded filenames list after finding it in Jira +- [DCA11Y-1274]: Log message indicating how much time is saved + +## [1.15.1-atlassian-2] - 2024-11-26 + +### Added + +- [DCA11Y-1274]: Incremental builds for Yarn, Corepack and NPM goals + +## [1.15.1-atlassian-1] - 2024-11-25 + - [DCA11Y-1145]: Automatic version detection of the Node version from `.tool-versions`, `.node-version`, and `.nvmrc` files - [DCA11Y-1145]: The configuration property `nodeVersionFile` to specify a file that can be read in `install-node-and-npm`, `install-node-and-pnpm`, and `install-node-and-yarn` ### Changed -- [DCA11Y-1145]: `nodeVersion` property is not required any more, if `useNodeVersionManager` is not set to false -- [DCA11Y-1145]: Now tolerant of `v` missing or present at the start of a Node version. \ No newline at end of file +- [DCA11Y-1145]: Now tolerant of `v` missing or present at the start of a Node version + + + +[DCA11Y-1274]: https://hello.jira.atlassian.cloud/browse/DCA11Y-1274 +[DCA11Y-1145]: https://hello.jira.atlassian.cloud/browse/DCA11Y-1145 + +[unreleased]: https://github.com/atlassian-forks/frontend-maven-plugin/compare/frontend-plugins-1.15.1-atlassian-3...HEAD +[1.15.1-atlassian-3]: https://github.com/atlassian-forks/frontend-maven-plugin/compare/frontend-plugins-1.15.1-atlassian-2...frontend-plugins-1.15.1-atlassian-3 +[1.15.1-atlassian-2]: https://github.com/atlassian-forks/frontend-maven-plugin/compare/frontend-plugins-1.15.1-atlassian-1...frontend-plugins-1.15.1-atlassian-2 +[1.15.1-atlassian-1]: https://github.com/atlassian-forks/frontend-maven-plugin/compare/frontend-plugins-1.15.1-atlassian-1-16519678...frontend-plugins-1.15.1-atlassian-1 +[1.15.1-atlassian-1-16519678]: https://github.com/atlassian-forks/frontend-maven-plugin/compare/frontend-plugins-1.15.1...frontend-plugins-1.15.1-atlassian-1-16519678 diff --git a/frontend-maven-plugin/pom.xml b/frontend-maven-plugin/pom.xml index 59b027204..cfc2a6c9a 100644 --- a/frontend-maven-plugin/pom.xml +++ b/frontend-maven-plugin/pom.xml @@ -4,7 +4,7 @@ frontend-plugins com.github.eirslett - 1.15.1-atlassian-1-SNAPSHOT + 1.15.1-atlassian-4-SNAPSHOT frontend-maven-plugin diff --git a/frontend-maven-plugin/src/it/incremental-build-diff-ids/.gitignore b/frontend-maven-plugin/src/it/incremental-build-diff-ids/.gitignore new file mode 100644 index 000000000..3c3629e64 --- /dev/null +++ b/frontend-maven-plugin/src/it/incremental-build-diff-ids/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/frontend-maven-plugin/src/it/incremental-build-diff-ids/package-lock.json b/frontend-maven-plugin/src/it/incremental-build-diff-ids/package-lock.json new file mode 100644 index 000000000..f16fd6f1e --- /dev/null +++ b/frontend-maven-plugin/src/it/incremental-build-diff-ids/package-lock.json @@ -0,0 +1,856 @@ +{ + "name": "incremental-build", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "incremental-build", + "version": "0.0.1", + "dependencies": { + "mocha": "^10.2.0" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browser-stdout": { + "version": "1.3.1", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "license": "ISC" + }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.3.7", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "4.0.0", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/diff": { + "version": "5.2.0", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "license": "BSD-3-Clause", + "bin": { + "flat": "cli.js" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/glob": { + "version": "8.1.0", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mocha": { + "version": "10.8.2", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/mocha/-/mocha-10.8.2.tgz", + "integrity": "sha512-VZlYo/WE8t1tstuRmqgeyBgCbJc/lEdopaa+axcKzTBJ+UIdlAB9XnmvTCAH4pwR4ElNInaedhEBmZD8iCSVEg==", + "license": "MIT", + "dependencies": { + "ansi-colors": "^4.1.3", + "browser-stdout": "^1.3.1", + "chokidar": "^3.5.3", + "debug": "^4.3.5", + "diff": "^5.2.0", + "escape-string-regexp": "^4.0.0", + "find-up": "^5.0.0", + "glob": "^8.1.0", + "he": "^1.2.0", + "js-yaml": "^4.1.0", + "log-symbols": "^4.1.0", + "minimatch": "^5.1.6", + "ms": "^2.1.3", + "serialize-javascript": "^6.0.2", + "strip-json-comments": "^3.1.1", + "supports-color": "^8.1.1", + "workerpool": "^6.5.1", + "yargs": "^16.2.0", + "yargs-parser": "^20.2.9", + "yargs-unparser": "^2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha.js" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "license": "BSD-3-Clause", + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/workerpool": { + "version": "6.5.1", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/workerpool/-/workerpool-6.5.1.tgz", + "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==", + "license": "Apache-2.0" + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "license": "MIT", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-unparser": { + "version": "2.0.0", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "license": "MIT", + "dependencies": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/frontend-maven-plugin/src/it/incremental-build-diff-ids/package.json b/frontend-maven-plugin/src/it/incremental-build-diff-ids/package.json new file mode 100644 index 000000000..b7c600513 --- /dev/null +++ b/frontend-maven-plugin/src/it/incremental-build-diff-ids/package.json @@ -0,0 +1,7 @@ +{ + "name": "incremental-build", + "version": "0.0.1", + "dependencies": { + "mocha": "^10.2.0" + } +} diff --git a/frontend-maven-plugin/src/it/incremental-build-diff-ids/pom.xml b/frontend-maven-plugin/src/it/incremental-build-diff-ids/pom.xml new file mode 100644 index 000000000..b4f3146ab --- /dev/null +++ b/frontend-maven-plugin/src/it/incremental-build-diff-ids/pom.xml @@ -0,0 +1,52 @@ + + + 4.0.0 + + com.atlassian.project + incremental-build-diff-ids + 0 + pom + + + + + com.github.eirslett + frontend-maven-plugin + + @project.version@ + + + target + v22.11.0 + + + + + install node and npm + + install-node-and-npm + + + + npm-install-1 + + npm + + + ci + + + + npm-install-2 + + npm + + + ci + + + + + + + diff --git a/frontend-maven-plugin/src/it/incremental-build-diff-ids/verify.groovy b/frontend-maven-plugin/src/it/incremental-build-diff-ids/verify.groovy new file mode 100644 index 000000000..e85500212 --- /dev/null +++ b/frontend-maven-plugin/src/it/incremental-build-diff-ids/verify.groovy @@ -0,0 +1,6 @@ +String buildLog = new File(basedir, 'build.log').text +assert buildLog.contains('npm (npm-install-1) @ incremental-build-diff-ids ---') : "Invalid test, didn't run the first execution" +assert buildLog.contains('npm (npm-install-2) @ incremental-build-diff-ids ---') : "Invalid test, didn't run the second execution" +assert new File(basedir, 'node_modules').exists() : "Invalid test, didn't install node modules"; + +assert buildLog.findAll(/\[INFO\] Running 'npm ci' in/).size() == 2 : "Should've executed twice since it's a different id" diff --git a/frontend-maven-plugin/src/main/java/com/github/eirslett/maven/plugins/frontend/mojo/AbstractFrontendMojo.java b/frontend-maven-plugin/src/main/java/com/github/eirslett/maven/plugins/frontend/mojo/AbstractFrontendMojo.java index b5129175c..f3695810e 100644 --- a/frontend-maven-plugin/src/main/java/com/github/eirslett/maven/plugins/frontend/mojo/AbstractFrontendMojo.java +++ b/frontend-maven-plugin/src/main/java/com/github/eirslett/maven/plugins/frontend/mojo/AbstractFrontendMojo.java @@ -153,4 +153,8 @@ protected Map getHttpHeaders(Server server) { String getFrontendMavenPluginVersion() { return pluginDescriptor.getVersion(); } + + File getTargetDir() { + return new File(project.getBuild().getDirectory()); + } } diff --git a/frontend-maven-plugin/src/main/java/com/github/eirslett/maven/plugins/frontend/mojo/CorepackMojo.java b/frontend-maven-plugin/src/main/java/com/github/eirslett/maven/plugins/frontend/mojo/CorepackMojo.java index fe9d57b29..7b17e7421 100644 --- a/frontend-maven-plugin/src/main/java/com/github/eirslett/maven/plugins/frontend/mojo/CorepackMojo.java +++ b/frontend-maven-plugin/src/main/java/com/github/eirslett/maven/plugins/frontend/mojo/CorepackMojo.java @@ -1,6 +1,9 @@ package com.github.eirslett.maven.plugins.frontend.mojo; +import com.github.eirslett.maven.plugins.frontend.lib.CorepackRunner; import com.github.eirslett.maven.plugins.frontend.lib.FrontendPluginFactory; +import com.github.eirslett.maven.plugins.frontend.lib.IncrementalBuildExecutionDigest.ExecutionCoordinates; +import com.github.eirslett.maven.plugins.frontend.lib.IncrementalMojoHelper; import org.apache.maven.execution.MavenSession; import org.apache.maven.plugins.annotations.Component; import org.apache.maven.plugins.annotations.LifecyclePhase; @@ -10,10 +13,11 @@ import org.sonatype.plexus.build.incremental.BuildContext; import java.io.File; +import java.util.Set; import static com.github.eirslett.maven.plugins.frontend.lib.AtlassianDevMetricsReporter.Goal.COREPACK; import static com.github.eirslett.maven.plugins.frontend.lib.AtlassianDevMetricsReporter.incrementExecutionCount; -import static com.github.eirslett.maven.plugins.frontend.mojo.MojoUtils.incrementalBuildEnabled; +import static com.github.eirslett.maven.plugins.frontend.lib.IncrementalMojoHelper.DEFAULT_EXCLUDED_FILENAMES; @Mojo(name="corepack", defaultPhase = LifecyclePhase.GENERATE_RESOURCES, threadSafe = true) public final class CorepackMojo extends AbstractFrontendMojo { @@ -24,12 +28,33 @@ public final class CorepackMojo extends AbstractFrontendMojo { @Parameter(defaultValue = "enable", property = "frontend.corepack.arguments", required = false) private String arguments; + /** + * Enable or disable incremental builds, on by default + */ + @Parameter(defaultValue = "true", property = "frontend.incremental", required = false) + private String frontendIncremental; + @Parameter(property = "session", defaultValue = "${session}", readonly = true) private MavenSession session; @Component private BuildContext buildContext; + /** + * Files that should be checked for changes for incremental builds in addition + * to the defaults in {@link IncrementalMojoHelper}. Directories will be searched. + */ + @Parameter(property = "triggerFiles", required = false) + private Set triggerFiles; + + /** + * Files that should NOT be checked for changes for incremental builds in addition + * to the defaults in {@link IncrementalMojoHelper}. Whole directories will be + * excluded. + */ + @Parameter(property = "excludedFilenames", required = false, defaultValue = DEFAULT_EXCLUDED_FILENAMES) + private Set excludedFilenames; + @Component(role = SettingsDecrypter.class) private SettingsDecrypter decrypter; @@ -46,19 +71,22 @@ protected boolean skipExecution() { @Override public synchronized void execute(FrontendPluginFactory factory) throws Exception { - File packageJson = new File(workingDirectory, "package.json"); + CorepackRunner runner = factory.getCorepackRunner(); - boolean incrementalEnabled = incrementalBuildEnabled(buildContext); - boolean willBeIncremental = incrementalEnabled && buildContext.hasDelta(packageJson); + ExecutionCoordinates coordinates = new ExecutionCoordinates(execution.getGoal(), execution.getExecutionId(), execution.getLifecyclePhase()); + IncrementalMojoHelper incrementalHelper = new IncrementalMojoHelper(frontendIncremental, coordinates, getTargetDir(), workingDirectory, triggerFiles, excludedFilenames); - incrementExecutionCount(project.getArtifactId(), arguments, COREPACK, getFrontendMavenPluginVersion(), incrementalEnabled, willBeIncremental, () -> { + boolean incrementalEnabled = incrementalHelper.incrementalEnabled(); + boolean isIncremental = incrementalEnabled && incrementalHelper.canBeSkipped(arguments, runner.getRuntime(), environmentVariables, project.getArtifactId(), getFrontendMavenPluginVersion()); - if (!willBeIncremental) { - factory.getCorepackRunner().execute(arguments, environmentVariables); - } else { - getLog().info("Skipping corepack install as package.json unchanged"); - } + incrementExecutionCount(project.getArtifactId(), arguments, COREPACK, getFrontendMavenPluginVersion(), incrementalEnabled, isIncremental, () -> { + if (isIncremental) { + getLog().info("Skipping corepack execution as no modified files in " + workingDirectory); + } else { + runner.execute(arguments, environmentVariables); + incrementalHelper.acceptIncrementalBuildDigest(); + } }); } } diff --git a/frontend-maven-plugin/src/main/java/com/github/eirslett/maven/plugins/frontend/mojo/NodeAndNpmMojo.java b/frontend-maven-plugin/src/main/java/com/github/eirslett/maven/plugins/frontend/mojo/NodeAndNpmMojo.java index 473cff778..3e55ea2b6 100644 --- a/frontend-maven-plugin/src/main/java/com/github/eirslett/maven/plugins/frontend/mojo/NodeAndNpmMojo.java +++ b/frontend-maven-plugin/src/main/java/com/github/eirslett/maven/plugins/frontend/mojo/NodeAndNpmMojo.java @@ -92,8 +92,8 @@ public void executeWithVerifiedNodeVersion(FrontendPluginFactory factory, String boolean failed = false; Timer timer = new Timer(); - final String nodeDownloadRoot = getNodeDownloadRoot(); - final String npmDownloadRoot = getNpmDownloadRoot(); + String nodeDownloadRoot = getNodeDownloadRoot(); + String npmDownloadRoot = getNpmDownloadRoot(); try { if (isAtlassianProject(project) && @@ -105,10 +105,10 @@ public void executeWithVerifiedNodeVersion(FrontendPluginFactory factory, String getLog().info("Atlassian project detected, going to use the internal mirrors (requires VPN)"); serverId = "maven-atlassian-com"; + nodeDownloadRoot = isBlank(nodeDownloadRoot) ? ATLASSIAN_NODE_DOWNLOAD_ROOT : nodeDownloadRoot; + npmDownloadRoot = isBlank(npmDownloadRoot) ? ATLASSIAN_NPM_DOWNLOAD_ROOT : npmDownloadRoot; try { - install(factory, validNodeVersion, - isBlank(nodeDownloadRoot) ? ATLASSIAN_NODE_DOWNLOAD_ROOT : nodeDownloadRoot, - isBlank(npmDownloadRoot) ? ATLASSIAN_NPM_DOWNLOAD_ROOT : npmDownloadRoot); + install(factory, validNodeVersion, nodeDownloadRoot, npmDownloadRoot); return; } catch (InstallationException exception) { // Ignore as many filesystem exceptions unrelated to the mirror easily @@ -119,7 +119,9 @@ public void executeWithVerifiedNodeVersion(FrontendPluginFactory factory, String pacAttemptFailed = true; getLog().warn("Oh no couldn't use the internal mirrors! Falling back to upstream mirrors"); getLog().debug("Using internal mirrors failed because: ", exception); - } finally { + + nodeDownloadRoot = getNodeDownloadRoot(); + npmDownloadRoot = getNpmDownloadRoot(); serverId = null; } } @@ -133,6 +135,8 @@ public void executeWithVerifiedNodeVersion(FrontendPluginFactory factory, String boolean finalFailed = failed; boolean finalPacAttemptFailed = pacAttemptFailed; boolean finalTriedToUsePac = triedToUsePac; + String finalNodeDownloadRoot = nodeDownloadRoot; + String finalNpmDownloadRoot = npmDownloadRoot; timer.stop( "runtime.download", project.getArtifactId(), @@ -142,8 +146,8 @@ public void executeWithVerifiedNodeVersion(FrontendPluginFactory factory, String put("installation", "npm"); put("installation-work-runtime", runtimeWork.toString()); put("installation-work-package-manager", packageManagerWork.toString()); - put("runtime-host", getHostForMetric(nodeDownloadRoot, NODEJS_ORG, finalTriedToUsePac, finalPacAttemptFailed)); - put("package-manager-host", getHostForMetric(npmDownloadRoot, DEFAULT_NPM_DOWNLOAD_ROOT, finalTriedToUsePac, finalPacAttemptFailed)); + put("runtime-host", getHostForMetric(finalNodeDownloadRoot, NODEJS_ORG, finalTriedToUsePac, finalPacAttemptFailed)); + put("package-manager-host", getHostForMetric(finalNpmDownloadRoot, DEFAULT_NPM_DOWNLOAD_ROOT, finalTriedToUsePac, finalPacAttemptFailed)); put("failed", Boolean.toString(finalFailed)); put("pac-attempted-failed", Boolean.toString(finalPacAttemptFailed)); put("tried-to-use-pac", Boolean.toString(finalTriedToUsePac)); @@ -199,7 +203,7 @@ private String getNodeDownloadRoot() { } private String getNpmDownloadRoot() { - if (downloadRoot != null && !"".equals(downloadRoot) && NPMInstaller.DEFAULT_NPM_DOWNLOAD_ROOT.equals(npmDownloadRoot)) { + if (downloadRoot != null && !"".equals(downloadRoot) && isBlank(npmDownloadRoot)) { return downloadRoot; } return npmDownloadRoot; diff --git a/frontend-maven-plugin/src/main/java/com/github/eirslett/maven/plugins/frontend/mojo/NodeAndPnpmMojo.java b/frontend-maven-plugin/src/main/java/com/github/eirslett/maven/plugins/frontend/mojo/NodeAndPnpmMojo.java index 85ebd8c7a..8921cdffb 100644 --- a/frontend-maven-plugin/src/main/java/com/github/eirslett/maven/plugins/frontend/mojo/NodeAndPnpmMojo.java +++ b/frontend-maven-plugin/src/main/java/com/github/eirslett/maven/plugins/frontend/mojo/NodeAndPnpmMojo.java @@ -109,10 +109,10 @@ public void executeWithVerifiedNodeVersion(FrontendPluginFactory factory, String getLog().info("Atlassian project detected, going to use the internal mirrors (requires VPN)"); serverId = "maven-atlassian-com"; + resolvedNodeDownloadRoot = isBlank(resolvedNodeDownloadRoot) ? ATLASSIAN_NODE_DOWNLOAD_ROOT : resolvedNodeDownloadRoot; + resolvedPnpmDownloadRoot = isBlank(resolvedPnpmDownloadRoot) ? ATLASSIAN_NPM_DOWNLOAD_ROOT : resolvedPnpmDownloadRoot; try { - install(factory, validNodeVersion, - isBlank(resolvedNodeDownloadRoot) ? ATLASSIAN_NODE_DOWNLOAD_ROOT : resolvedNodeDownloadRoot, - isBlank(resolvedPnpmDownloadRoot) ? ATLASSIAN_NPM_DOWNLOAD_ROOT : resolvedPnpmDownloadRoot); + install(factory, validNodeVersion, resolvedNodeDownloadRoot, resolvedPnpmDownloadRoot); return; } catch (InstallationException exception) { // Ignore as many filesystem exceptions unrelated to the mirror easily @@ -123,7 +123,9 @@ public void executeWithVerifiedNodeVersion(FrontendPluginFactory factory, String pacAttemptFailed = true; getLog().warn("Oh no couldn't use the internal mirrors! Falling back to upstream mirrors"); getLog().debug("Using internal mirrors failed because: ", exception); - } finally { + + resolvedNodeDownloadRoot = getNodeDownloadRoot(); + resolvedPnpmDownloadRoot = getPnpmDownloadRoot(); serverId = null; } } @@ -137,6 +139,8 @@ public void executeWithVerifiedNodeVersion(FrontendPluginFactory factory, String boolean finalFailed = failed; boolean finalPacAttemptFailed = pacAttemptFailed; boolean finalTriedToUsePac = triedToUsePac; + String finalResolvedPnpmDownloadRoot = resolvedPnpmDownloadRoot; + String finalResolvedNodeDownloadRoot = resolvedNodeDownloadRoot; timer.stop( "runtime.download", project.getArtifactId(), @@ -146,8 +150,8 @@ public void executeWithVerifiedNodeVersion(FrontendPluginFactory factory, String put("installation", "pnpm"); put("installation-work-runtime", runtimeWork.toString()); put("installation-work-package-manager", packageManagerWork.toString()); - put("runtime-host", getHostForMetric(resolvedPnpmDownloadRoot, NODEJS_ORG, finalTriedToUsePac, finalPacAttemptFailed)); - put("package-manager-host", getHostForMetric(resolvedPnpmDownloadRoot, DEFAULT_PNPM_DOWNLOAD_ROOT, finalTriedToUsePac, finalPacAttemptFailed)); + put("runtime-host", getHostForMetric(finalResolvedNodeDownloadRoot, NODEJS_ORG, finalTriedToUsePac, finalPacAttemptFailed)); + put("package-manager-host", getHostForMetric(finalResolvedPnpmDownloadRoot, DEFAULT_PNPM_DOWNLOAD_ROOT, finalTriedToUsePac, finalPacAttemptFailed)); put("failed", Boolean.toString(finalFailed)); put("pac-attempted-failed", Boolean.toString(finalPacAttemptFailed)); put("tried-to-use-pac", Boolean.toString(finalTriedToUsePac)); @@ -199,7 +203,7 @@ private String getNodeDownloadRoot() { } private String getPnpmDownloadRoot() { - if (downloadRoot != null && !"".equals(downloadRoot) && PnpmInstaller.DEFAULT_PNPM_DOWNLOAD_ROOT.equals(pnpmDownloadRoot)) { + if (downloadRoot != null && !"".equals(downloadRoot) && isBlank(pnpmDownloadRoot)) { return downloadRoot; } return pnpmDownloadRoot; diff --git a/frontend-maven-plugin/src/main/java/com/github/eirslett/maven/plugins/frontend/mojo/NodeAndYarnMojo.java b/frontend-maven-plugin/src/main/java/com/github/eirslett/maven/plugins/frontend/mojo/NodeAndYarnMojo.java index e4e1c8e74..bc8ef1ffa 100644 --- a/frontend-maven-plugin/src/main/java/com/github/eirslett/maven/plugins/frontend/mojo/NodeAndYarnMojo.java +++ b/frontend-maven-plugin/src/main/java/com/github/eirslett/maven/plugins/frontend/mojo/NodeAndYarnMojo.java @@ -117,7 +117,7 @@ public void executeWithVerifiedNodeVersion(FrontendPluginFactory factory, String pacAttemptFailed = true; getLog().warn("Oh no couldn't use the internal mirrors! Falling back to upstream mirrors"); getLog().debug("Using internal mirrors failed because: ", exception); - } finally { + nodeDownloadRoot = userSetNodeDownloadRoot; yarnDownloadRoot = userSetYarnDownloadRoot; serverId = null; diff --git a/frontend-maven-plugin/src/main/java/com/github/eirslett/maven/plugins/frontend/mojo/NpmMojo.java b/frontend-maven-plugin/src/main/java/com/github/eirslett/maven/plugins/frontend/mojo/NpmMojo.java index aecedd3fa..b7678c035 100644 --- a/frontend-maven-plugin/src/main/java/com/github/eirslett/maven/plugins/frontend/mojo/NpmMojo.java +++ b/frontend-maven-plugin/src/main/java/com/github/eirslett/maven/plugins/frontend/mojo/NpmMojo.java @@ -1,6 +1,9 @@ package com.github.eirslett.maven.plugins.frontend.mojo; import com.github.eirslett.maven.plugins.frontend.lib.FrontendPluginFactory; +import com.github.eirslett.maven.plugins.frontend.lib.IncrementalBuildExecutionDigest.ExecutionCoordinates; +import com.github.eirslett.maven.plugins.frontend.lib.IncrementalMojoHelper; +import com.github.eirslett.maven.plugins.frontend.lib.NpmRunner; import com.github.eirslett.maven.plugins.frontend.lib.NodeVersionDetector; import com.github.eirslett.maven.plugins.frontend.lib.ProxyConfig; import org.apache.maven.execution.MavenSession; @@ -13,10 +16,11 @@ import java.io.File; import java.util.Collections; +import java.util.Set; import static com.github.eirslett.maven.plugins.frontend.lib.AtlassianDevMetricsReporter.Goal.NPM; import static com.github.eirslett.maven.plugins.frontend.lib.AtlassianDevMetricsReporter.incrementExecutionCount; -import static com.github.eirslett.maven.plugins.frontend.mojo.MojoUtils.incrementalBuildEnabled; +import static com.github.eirslett.maven.plugins.frontend.lib.IncrementalMojoHelper.DEFAULT_EXCLUDED_FILENAMES; @Mojo(name="npm", defaultPhase = LifecyclePhase.GENERATE_RESOURCES, threadSafe = true) public final class NpmMojo extends AbstractNodeMojo { @@ -29,6 +33,12 @@ public final class NpmMojo extends AbstractNodeMojo { @Parameter(defaultValue = "install", property = "frontend.npm.arguments", required = false) private String arguments; + /** + * Enable or disable incremental builds, on by default + */ + @Parameter(defaultValue = "true", property = "frontend.incremental", required = false) + private String frontendIncremental; + @Parameter(property = "frontend.npm.npmInheritsProxyConfigFromMaven", required = false, defaultValue = "true") private boolean npmInheritsProxyConfigFromMaven; @@ -41,6 +51,21 @@ public final class NpmMojo extends AbstractNodeMojo { @Parameter(property = "session", defaultValue = "${session}", readonly = true) private MavenSession session; + /** + * Files that should be checked for changes for incremental builds in addition + * to the defaults in {@link IncrementalMojoHelper}. Directories will be searched. + */ + @Parameter(property = "triggerFiles", required = false) + private Set triggerFiles; + + /** + * Files that should NOT be checked for changes for incremental builds in addition + * to the defaults in {@link IncrementalMojoHelper}. Whole directories will be + * excluded. + */ + @Parameter(property = "excludedFilenames", defaultValue = DEFAULT_EXCLUDED_FILENAMES, required = false) + private Set excludedFilenames; + @Component private BuildContext buildContext; @@ -60,21 +85,22 @@ protected boolean skipExecution() { @Override public synchronized void executeWithVerifiedNodeVersion(FrontendPluginFactory factory, String nodeVersion) throws Exception { - File packageJson = new File(workingDirectory, "package.json"); + NpmRunner runner = factory.getNpmRunner(getProxyConfig(), getRegistryUrl()); - boolean incrementalEnabled = incrementalBuildEnabled(buildContext); - boolean willBeIncremental = incrementalEnabled && buildContext.hasDelta(packageJson); + ExecutionCoordinates coordinates = new ExecutionCoordinates(execution.getGoal(), execution.getExecutionId(), execution.getLifecyclePhase()); + IncrementalMojoHelper incrementalHelper = new IncrementalMojoHelper(frontendIncremental, coordinates, getTargetDir(), workingDirectory, triggerFiles, excludedFilenames); - incrementExecutionCount(project.getArtifactId(), arguments, NPM, getFrontendMavenPluginVersion(), incrementalEnabled, willBeIncremental, () -> { + boolean incrementalEnabled = incrementalHelper.incrementalEnabled(); + boolean isIncremental = incrementalEnabled && incrementalHelper.canBeSkipped(arguments, runner.getRuntime(), environmentVariables, project.getArtifactId(), getFrontendMavenPluginVersion()); - if (!willBeIncremental) { - ProxyConfig proxyConfig = getProxyConfig(); - - factory.getNpmRunner(proxyConfig, getRegistryUrl()).execute(arguments, environmentVariables); + incrementExecutionCount(project.getArtifactId(), arguments, NPM, getFrontendMavenPluginVersion(), incrementalEnabled, isIncremental, () -> { + if (isIncremental) { + getLog().info("Skipping npm execution as no modified files in " + workingDirectory); } else { - getLog().info("Skipping npm install as package.json unchanged"); - } + runner.execute(arguments, environmentVariables); + incrementalHelper.acceptIncrementalBuildDigest(); + } }); } diff --git a/frontend-maven-plugin/src/main/java/com/github/eirslett/maven/plugins/frontend/mojo/YarnMojo.java b/frontend-maven-plugin/src/main/java/com/github/eirslett/maven/plugins/frontend/mojo/YarnMojo.java index efd403b38..2f33f3f32 100644 --- a/frontend-maven-plugin/src/main/java/com/github/eirslett/maven/plugins/frontend/mojo/YarnMojo.java +++ b/frontend-maven-plugin/src/main/java/com/github/eirslett/maven/plugins/frontend/mojo/YarnMojo.java @@ -1,7 +1,10 @@ package com.github.eirslett.maven.plugins.frontend.mojo; import com.github.eirslett.maven.plugins.frontend.lib.FrontendPluginFactory; +import com.github.eirslett.maven.plugins.frontend.lib.IncrementalBuildExecutionDigest.ExecutionCoordinates; +import com.github.eirslett.maven.plugins.frontend.lib.IncrementalMojoHelper; import com.github.eirslett.maven.plugins.frontend.lib.ProxyConfig; +import com.github.eirslett.maven.plugins.frontend.lib.YarnRunner; import org.apache.maven.execution.MavenSession; import org.apache.maven.plugins.annotations.Component; import org.apache.maven.plugins.annotations.LifecyclePhase; @@ -12,10 +15,11 @@ import java.io.File; import java.util.Collections; +import java.util.Set; import static com.github.eirslett.maven.plugins.frontend.lib.AtlassianDevMetricsReporter.Goal.YARN; import static com.github.eirslett.maven.plugins.frontend.lib.AtlassianDevMetricsReporter.incrementExecutionCount; -import static com.github.eirslett.maven.plugins.frontend.mojo.MojoUtils.incrementalBuildEnabled; +import static com.github.eirslett.maven.plugins.frontend.lib.IncrementalMojoHelper.DEFAULT_EXCLUDED_FILENAMES; import static com.github.eirslett.maven.plugins.frontend.mojo.YarnUtils.isYarnrcYamlFilePresent; @Mojo(name = "yarn", defaultPhase = LifecyclePhase.GENERATE_RESOURCES, threadSafe = true) @@ -29,6 +33,12 @@ public final class YarnMojo extends AbstractFrontendMojo { @Parameter(defaultValue = "", property = "frontend.yarn.arguments", required = false) private String arguments; + /** + * Enable or disable incremental builds, on by default + */ + @Parameter(defaultValue = "true", property = "frontend.incremental", required = false) + private String frontendIncremental; + @Parameter(property = "frontend.yarn.yarnInheritsProxyConfigFromMaven", required = false, defaultValue = "true") private boolean yarnInheritsProxyConfigFromMaven; @@ -42,6 +52,21 @@ public final class YarnMojo extends AbstractFrontendMojo { @Parameter(property = "session", defaultValue = "${session}", readonly = true) private MavenSession session; + /** + * Files that should be checked for changes for incremental builds in addition + * to the defaults in {@link IncrementalMojoHelper}. Directories will be searched. + */ + @Parameter(property = "triggerFiles", required = false) + private Set triggerFiles; + + /** + * Files that should NOT be checked for changes for incremental builds in addition + * to the defaults in {@link IncrementalMojoHelper}. Whole directories will be + * excluded. + */ + @Parameter(property = "excludedFilenames", required = false, defaultValue = DEFAULT_EXCLUDED_FILENAMES) + private Set excludedFilenames; + @Component private BuildContext buildContext; @@ -61,22 +86,23 @@ protected boolean skipExecution() { @Override public synchronized void execute(FrontendPluginFactory factory) throws Exception { - File packageJson = new File(this.workingDirectory, "package.json"); + boolean isYarnBerry = isYarnrcYamlFilePresent(this.session, this.workingDirectory); + YarnRunner runner = factory.getYarnRunner(getProxyConfig(), getRegistryUrl(), isYarnBerry); - boolean incrementalEnabled = incrementalBuildEnabled(buildContext); - boolean willBeIncremental = incrementalEnabled && buildContext.hasDelta(packageJson); + ExecutionCoordinates coordinates = new ExecutionCoordinates(execution.getGoal(), execution.getExecutionId(), execution.getLifecyclePhase()); + IncrementalMojoHelper incrementalHelper = new IncrementalMojoHelper(frontendIncremental, coordinates, getTargetDir(), workingDirectory, triggerFiles, excludedFilenames); - incrementExecutionCount(project.getArtifactId(), arguments, YARN, getFrontendMavenPluginVersion(), incrementalEnabled, willBeIncremental, () -> { + boolean incrementalEnabled = incrementalHelper.incrementalEnabled(); + boolean isIncremental = incrementalEnabled && incrementalHelper.canBeSkipped(arguments, runner.getRuntime(), environmentVariables, project.getArtifactId(), getFrontendMavenPluginVersion()); - if (!willBeIncremental) { - ProxyConfig proxyConfig = getProxyConfig(); - boolean isYarnBerry = isYarnrcYamlFilePresent(this.session, this.workingDirectory); - factory.getYarnRunner(proxyConfig, getRegistryUrl(), isYarnBerry).execute(this.arguments, - this.environmentVariables); - } else { - getLog().info("Skipping yarn install as package.json unchanged"); - } + incrementExecutionCount(project.getArtifactId(), arguments, YARN, getFrontendMavenPluginVersion(), incrementalEnabled, isIncremental, () -> { + if (isIncremental) { + getLog().info("Skipping yarn execution as no modified files in " + workingDirectory); + } else { + runner.execute(this.arguments, this.environmentVariables); + incrementalHelper.acceptIncrementalBuildDigest(); + } }); } diff --git a/frontend-plugin-core/pom.xml b/frontend-plugin-core/pom.xml index 010aab3eb..d9485a3af 100644 --- a/frontend-plugin-core/pom.xml +++ b/frontend-plugin-core/pom.xml @@ -3,7 +3,7 @@ frontend-plugins com.github.eirslett - 1.15.1-atlassian-1-SNAPSHOT + 1.15.1-atlassian-4-SNAPSHOT 4.0.0 @@ -29,6 +29,13 @@ 1.21 + + commons-codec + commons-codec + 1.17.1 + compile + + commons-io commons-io diff --git a/frontend-plugin-core/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/AtlassianDevMetricsReporter.java b/frontend-plugin-core/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/AtlassianDevMetricsReporter.java index 38f9d71b7..55e91ca8c 100644 --- a/frontend-plugin-core/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/AtlassianDevMetricsReporter.java +++ b/frontend-plugin-core/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/AtlassianDevMetricsReporter.java @@ -27,12 +27,12 @@ import static com.github.eirslett.maven.plugins.frontend.lib.AtlassianDevMetricsIncremental.NOT_ENABLED; import static com.github.eirslett.maven.plugins.frontend.lib.AtlassianDevMetricsIncremental.REBUILDING_SKIPPED; import static com.github.eirslett.maven.plugins.frontend.lib.Utils.isBlank; -import static com.google.common.primitives.Ints.checkedCast; import static java.lang.Boolean.getBoolean; import static java.lang.Runtime.getRuntime; import static java.lang.System.getProperty; import static java.lang.System.getenv; import static java.time.Instant.now; +import static java.util.Objects.isNull; import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor; import static java.util.concurrent.TimeUnit.SECONDS; import static org.apache.http.entity.ContentType.APPLICATION_JSON; @@ -141,8 +141,10 @@ public static void incrementExecutionCount( : BUILT : NOT_ENABLED; + boolean finalFailed = failed; incrementCount("execute", artifactId, forkVersion, new HashMap() {{ put("goal", goal.toString()); + put("failed", Boolean.toString(finalFailed)); put("script", getScriptFromArguments(arguments)); put("incremental", incremental.toString()); }}); @@ -261,6 +263,10 @@ public static String getHostForMetric(String hostSetting, String defaultHost, bo * perfect */ static String getScriptFromArguments(String arguments) { + if (isNull(arguments)) { + arguments = ""; + } + if (arguments.contains("test") || arguments.contains("check") || arguments.contains("visreg") || arguments.contains("jest") || arguments.contains("storybook")) { return "test"; } diff --git a/frontend-plugin-core/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/BunRunner.java b/frontend-plugin-core/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/BunRunner.java index 5954b56d4..9e2624d6a 100644 --- a/frontend-plugin-core/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/BunRunner.java +++ b/frontend-plugin-core/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/BunRunner.java @@ -1,9 +1,15 @@ package com.github.eirslett.maven.plugins.frontend.lib; +import com.github.eirslett.maven.plugins.frontend.lib.IncrementalBuildExecutionDigest.Execution.Runtime; import com.github.eirslett.maven.plugins.frontend.lib.ProxyConfig.Proxy; import java.util.ArrayList; import java.util.List; +import java.util.Optional; + +import static java.util.Collections.emptyMap; +import static java.util.Collections.singletonList; +import static java.util.Optional.empty; public interface BunRunner extends NodeTaskRunner { } @@ -44,4 +50,14 @@ private static List buildArguments(ProxyConfig proxyConfig, String npmRe return arguments; } + public Optional getRuntime() { + try { + String version = new BunExecutor(config, singletonList(" --version"), emptyMap()) + .executeAndGetResult(logger); + return Optional.of(new Runtime("bun", version)); + } catch (Exception exception) { + logger.debug("Failed to get Bun version, because: ", exception); + return empty(); + } + } } diff --git a/frontend-plugin-core/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/BunTaskExecutor.java b/frontend-plugin-core/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/BunTaskExecutor.java index 7ba02aae7..8cdb93f15 100644 --- a/frontend-plugin-core/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/BunTaskExecutor.java +++ b/frontend-plugin-core/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/BunTaskExecutor.java @@ -15,13 +15,13 @@ abstract class BunTaskExecutor { private static final String AT = "@"; - private final Logger logger; + final Logger logger; private final String taskName; private final ArgumentsParser argumentsParser; - private final BunExecutorConfig config; + final BunExecutorConfig config; public BunTaskExecutor(BunExecutorConfig config, String taskLocation) { this(config, taskLocation, Collections.emptyList()); diff --git a/frontend-plugin-core/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/IncrementalBuildExecutionDigest.java b/frontend-plugin-core/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/IncrementalBuildExecutionDigest.java new file mode 100644 index 000000000..ebe85bd5c --- /dev/null +++ b/frontend-plugin-core/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/IncrementalBuildExecutionDigest.java @@ -0,0 +1,232 @@ +package com.github.eirslett.maven.plugins.frontend.lib; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.KeyDeserializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.Arrays.asList; +import static java.util.Objects.requireNonNull; + +public class IncrementalBuildExecutionDigest { + + /** + * This should be incremented as soon as the digest schema or semantics change + */ + public static final Long CURRENT_DIGEST_VERSION = 2L; + + private static final String SERIALIZATION_CHARSET = UTF_8.toString(); + /** + * Must be something that would be encoded otherwise we could end up with a set of + * parts we don't expect. + */ + static final String SERIALIZATION_SEPARATOR = ";"; + + public Long digestVersion; + + @JsonDeserialize(keyUsing = ExecutionCoordinatesDeserializer.class) + @JsonSerialize(keyUsing = ExecutionCoordinatesSerializer.class) + public Map executions; + + public IncrementalBuildExecutionDigest() { + // for Jackson + } + + public IncrementalBuildExecutionDigest(Long digestVersion, Map executions) { + this.digestVersion = digestVersion; + this.executions = executions; + } + + public static class ExecutionCoordinates { + public String goal; + public String id; + public String lifecyclePhase; + + public ExecutionCoordinates() { + // for Jackson + } + + public ExecutionCoordinates(String goal, String id, String lifecyclePhase) { + this.goal = goal; + this.id = id; + this.lifecyclePhase = lifecyclePhase; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof ExecutionCoordinates)) return false; + ExecutionCoordinates that = (ExecutionCoordinates) o; + return Objects.equals(goal, that.goal) && Objects.equals(id, that.id) && Objects.equals(lifecyclePhase, that.lifecyclePhase); + } + + @Override + public int hashCode() { + return Objects.hash(goal, id, lifecyclePhase); + } + } + + public static class Execution { + public String arguments; + public Map environmentVariables; + public Set files; + public Runtime runtime; + public Long millisecondsSaved = 0L; + + public Execution() { + // for Jackson + } + + public Execution(String arguments, Map environmentVariables, Set files, Runtime runtime) { + this.files = files; + this.environmentVariables = environmentVariables; + this.arguments = arguments; + this.runtime = runtime; + } + + public static class File { + public String filename; + public Integer byteLength; + public String hash; + + public File() { + // for Jackson + } + + public File(String filename, Integer byteLength, String hash) { + this.filename = filename; + this.byteLength = byteLength; + this.hash = hash; + } + + @Override + public String toString() { + return "File{" + + "filename='" + filename + '\'' + + ", byteLength=" + byteLength + + ", hash='" + hash + '\'' + + '}'; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof File)) return false; + File file = (File) o; + return Objects.equals(filename, file.filename) && Objects.equals(byteLength, file.byteLength) && Objects.equals(hash, file.hash); + } + + @Override + public int hashCode() { + return Objects.hash(filename, byteLength, hash); + } + } + + public static class Runtime { + public String runtime; + public String runtimeVersion; + + public Runtime() { + // for Jackson + } + + public Runtime(String runtime, String runtimeVersion) { + this.runtime = runtime; + this.runtimeVersion = runtimeVersion; + } + + @Override + public String toString() { + return "Runtime{" + + "runtime='" + runtime + '\'' + + ", runtimeVersion='" + runtimeVersion + '\'' + + '}'; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof Runtime)) return false; + Runtime runtime = (Runtime) o; + return Objects.equals(this.runtime, runtime.runtime) && Objects.equals(runtimeVersion, runtime.runtimeVersion); + } + + @Override + public int hashCode() { + return Objects.hash(runtime, runtimeVersion); + } + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof Execution)) return false; + Execution execution = (Execution) o; + return Objects.equals(files, execution.files) && Objects.equals(environmentVariables, execution.environmentVariables) && Objects.equals(arguments, execution.arguments) && Objects.equals(runtime, execution.runtime) && Objects.equals(millisecondsSaved, execution.millisecondsSaved); + } + + @Override + public int hashCode() { + return Objects.hash(files, environmentVariables, arguments, runtime, millisecondsSaved); + } + } + + public static class ExecutionCoordinatesSerializer extends StdSerializer { + + public ExecutionCoordinatesSerializer() { + super(IncrementalBuildExecutionDigest.ExecutionCoordinates.class); + } + + @Override + public void serialize(IncrementalBuildExecutionDigest.ExecutionCoordinates value, JsonGenerator gen, SerializerProvider provider) throws IOException { + final String encoded = URLEncoder.encode(value.goal, SERIALIZATION_CHARSET) + + SERIALIZATION_SEPARATOR + URLEncoder.encode(value.id, SERIALIZATION_CHARSET) + + SERIALIZATION_SEPARATOR + URLEncoder.encode(value.lifecyclePhase, SERIALIZATION_CHARSET); + gen.writeFieldName(encoded); + } + } + + public static class ExecutionCoordinatesDeserializer extends KeyDeserializer { + + public ExecutionCoordinatesDeserializer() { + super(); + } + + @Override + public IncrementalBuildExecutionDigest.ExecutionCoordinates deserializeKey(String key, DeserializationContext context) throws UnsupportedEncodingException { + requireNonNull(key, "ExecutionCoordinates string cannot be null"); + List keyParts = asList(key.split(SERIALIZATION_SEPARATOR)); + if (keyParts.size() != 3) { + throw new IllegalArgumentException("Supplied ExecutionCoordinates key didn't have three parts, was: " + key); + } + + final ExecutionCoordinates executionCoordinates = new ExecutionCoordinates(); + executionCoordinates.goal = URLDecoder.decode(keyParts.get(0), SERIALIZATION_CHARSET); + executionCoordinates.id = URLDecoder.decode(keyParts.get(1), SERIALIZATION_CHARSET); + executionCoordinates.lifecyclePhase = URLDecoder.decode(keyParts.get(2), SERIALIZATION_CHARSET); + + return executionCoordinates; + } + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof IncrementalBuildExecutionDigest)) return false; + IncrementalBuildExecutionDigest that = (IncrementalBuildExecutionDigest) o; + return Objects.equals(digestVersion, that.digestVersion) && Objects.equals(executions, that.executions); + } + + @Override + public int hashCode() { + return Objects.hash(digestVersion, executions); + } +} diff --git a/frontend-plugin-core/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/IncrementalMojoHelper.java b/frontend-plugin-core/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/IncrementalMojoHelper.java new file mode 100644 index 000000000..50744aaae --- /dev/null +++ b/frontend-plugin-core/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/IncrementalMojoHelper.java @@ -0,0 +1,459 @@ +package com.github.eirslett.maven.plugins.frontend.lib; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.eirslett.maven.plugins.frontend.lib.AtlassianDevMetricsReporter.Timer; +import com.github.eirslett.maven.plugins.frontend.lib.IncrementalBuildExecutionDigest.Execution; +import com.github.eirslett.maven.plugins.frontend.lib.IncrementalBuildExecutionDigest.Execution.Runtime; +import com.github.eirslett.maven.plugins.frontend.lib.IncrementalBuildExecutionDigest.ExecutionCoordinates; +import org.apache.commons.codec.digest.MurmurHash3; +import org.codehaus.plexus.util.StringUtils; +import org.slf4j.Logger; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.time.Duration; +import java.time.Instant; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; + +import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES; +import static com.fasterxml.jackson.databind.SerializationFeature.INDENT_OUTPUT; +import static com.github.eirslett.maven.plugins.frontend.lib.IncrementalBuildExecutionDigest.CURRENT_DIGEST_VERSION; +import static java.lang.String.format; +import static java.time.Instant.now; +import static java.util.Arrays.asList; +import static java.util.Collections.emptySet; +import static java.util.Objects.isNull; +import static java.util.Objects.requireNonNull; +import static java.util.Optional.empty; +import static org.slf4j.LoggerFactory.getLogger; + +public class IncrementalMojoHelper { + private static final String MVN_LIST_SEPARATOR = ","; + public static final String DEFAULT_EXCLUDED_FILENAMES = + ".node" + MVN_LIST_SEPARATOR + + "node_modules" + MVN_LIST_SEPARATOR + + "lcov-report" + MVN_LIST_SEPARATOR + + "coverage" + MVN_LIST_SEPARATOR + + "screenshots" + MVN_LIST_SEPARATOR + + "build" + MVN_LIST_SEPARATOR + + "dist" + MVN_LIST_SEPARATOR + + "target" + MVN_LIST_SEPARATOR + + ".idea" + MVN_LIST_SEPARATOR + + ".history" + MVN_LIST_SEPARATOR + + "tmp" + MVN_LIST_SEPARATOR + + ".settings" + MVN_LIST_SEPARATOR + + ".vscode" + MVN_LIST_SEPARATOR + + ".git" + MVN_LIST_SEPARATOR + + "dependency-reduced-pom.xml" + MVN_LIST_SEPARATOR + + ".flattened-pom.xml"; + + private static final Logger log = getLogger(IncrementalMojoHelper.class); + private static final String SEE_DEBUG_LOGS_MSG = " See the Maven debug logs (run with -X) for more info"; + + private static ObjectMapper objectMapper; + + private final ExecutionCoordinates coordinates; + private final File targetDirectory; + private final File workingDirectory; + private final boolean isActive; + private final Set triggerFiles; + private final Set excludedFilenames; + + private IncrementalBuildExecutionDigest digest; + private Optional startTimeForSavedTimeUpdate = empty(); + + public IncrementalMojoHelper(String activationFlag, ExecutionCoordinates coordinates, File targetDirectory, File workingDirectory, Set triggerFiles, Set excludedFilenames) { + this.coordinates = requireNonNull(coordinates, "coordinates"); + this.targetDirectory = requireNonNull(targetDirectory, "targetDirectory"); + this.workingDirectory = requireNonNull(workingDirectory, "workingDirectory"); + this.triggerFiles = isNull(triggerFiles) ? emptySet() : triggerFiles; + this.excludedFilenames = isNull(excludedFilenames) ? emptySet() : excludedFilenames; + + this.isActive = "true".equals(activationFlag); + } + + public boolean incrementalEnabled() { + return isActive; + } + + public boolean canBeSkipped(String arguments, Optional runtime, Map suppliedEnvVars, String artifactId, String forkVersion) { + Timer timer = new Timer(); + boolean failed = false; + + if (!isActive) { + return false; + } + + if (!runtime.isPresent()) { + log.warn("Failed to do incremental compilation because the runtime version couldn't be fetched." + SEE_DEBUG_LOGS_MSG); + return false; + } + + try { + try { + File digestFileLocation = getDigestFile(); + if (digestFileLocation.exists()) { + digest = readDigest(digestFileLocation); + } + } catch (FileNotFoundException exception) { + log.debug("No existing digest file", exception); + } + + if (isNull(digest)) { + digest = new IncrementalBuildExecutionDigest(CURRENT_DIGEST_VERSION, new HashMap<>()); + } + + boolean digestVersionsMatch = Objects.equals(digest.digestVersion, CURRENT_DIGEST_VERSION); + + Execution thisExecution = new Execution( + arguments, + getAllEnvVars(suppliedEnvVars), + createFilesDigest(), + runtime.get()); + + // The clock starts now + startTimeForSavedTimeUpdate = Optional.of(now()); + + boolean canSkipExecution = false; + Execution previousExecution = digest.executions.get(coordinates); + if (digestVersionsMatch && previousExecution != null) { + // patch it forward so we don't lose it and so the equality check works + thisExecution.millisecondsSaved = previousExecution.millisecondsSaved; + canSkipExecution = previousExecution.equals(thisExecution); + if (canSkipExecution) { + // Clear the time, we're about to skip execution so it'd report lower than it actually would save + startTimeForSavedTimeUpdate = Optional.empty(); + + log.info("Saving {} by skipping execution of frontend-maven-plugin! No changes were detected. If it should " + + "have executed, adjust the triggerFiles and excludedFilenames in the configuration.", Duration.ofMillis(previousExecution.millisecondsSaved)); + } else { + log.info("Didn't do incremental compilation because a change was detected for executionId: {} in artifactId: {}" + SEE_DEBUG_LOGS_MSG, coordinates.id, artifactId); + + if (log.isDebugEnabled()) { + String argumentsDifference = StringUtils.difference(previousExecution.arguments, thisExecution.arguments); + String envVarDifference = StringUtils.difference(previousExecution.environmentVariables.toString(), thisExecution.environmentVariables.toString()); + String runtimeDifference = StringUtils.difference(previousExecution.runtime.toString(), thisExecution.runtime.toString()); + + Set newFiles = new HashSet<>(thisExecution.files); + newFiles.removeAll(previousExecution.files); + Set goneFiles = new HashSet<>(previousExecution.files); + goneFiles.removeAll(thisExecution.files); + + if (!argumentsDifference.trim().isEmpty()) { + log.debug("Difference in arguments was: <{}> previously: <{}>, currently <{}>", argumentsDifference, previousExecution.arguments, thisExecution.arguments); + } + if (!envVarDifference.trim().isEmpty()) { + log.debug("Difference in environment variables was: <{}> previously: <{}>, currently <{}>", envVarDifference, previousExecution.environmentVariables, thisExecution.environmentVariables); + } + if (!runtimeDifference.trim().isEmpty()) { + log.debug("Difference in runtime was: <{}> previously: <{}>, currently <{}>", runtimeDifference, previousExecution.runtime, thisExecution.runtime); + } + if (!newFiles.isEmpty()) { + log.debug("Some files are \"new\" (may have changed meta-data), there were: {}", newFiles); + } + if (!goneFiles.isEmpty()) { + log.debug("Some files are \"gone\" (may have changed meta-data), there were: {}", goneFiles); + } + } + } + } + + digest.digestVersion = CURRENT_DIGEST_VERSION; + digest.executions.put(coordinates, thisExecution); + + return canSkipExecution; + } catch (Exception exception) { + log.error("Failure while determining if an incremental build is possible." + SEE_DEBUG_LOGS_MSG); + log.debug("Failure while determining if an incremental build is possible, because: ", exception); + return false; + } finally { + timer.stop("execute.incremental.check", artifactId, forkVersion, "", + new HashMap() {{ + put("failed", Boolean.toString(failed)); + }}); + } + } + + public void acceptIncrementalBuildDigest() { + if (!isActive) { + return; + } + + startTimeForSavedTimeUpdate.ifPresent(instant -> + digest.executions.get(coordinates).millisecondsSaved = + Duration.between(instant, now()).toMillis()); + + try { + log.debug("Accepting the incremental build digest after a successful execution"); + File digestFile = getDigestFile(); + if (digestFile.exists()) { + if (!digestFile.delete()) { + log.warn("Failed to delete the previous incremental build digest. You'll have to delete it manually @ {}", digestFile.getAbsolutePath()); + } + } + + digestFile.getParentFile().mkdirs(); + saveDigest(digest); + } catch (Exception exception) { + log.warn("Failed to save the incremental build digest." + SEE_DEBUG_LOGS_MSG); + log.debug("Failed to save the incremental build digest, because: ", exception); + } + } + + static class IncrementalVisitor extends SimpleFileVisitor { + private final Set files; + private final Set excludedFilenames; + + public IncrementalVisitor(Set files, Set excludedFilenames) { + this.files = files; + this.excludedFilenames = excludedFilenames; + } + + private static final Set DIGEST_EXTENSIONS = new HashSet<>(asList( + // JS + "js", + "jsx", + "cjs", + "mjs", + "ts", + "tsx", + // patches + "patch", + // CSS + "css", + "scss", + "sass", + "less", + "styl", + "stylus", + // templates + "ejs", + "hbs", + "handlebars", + "pug", + "soy", + "html", + "vm", + "vmd", + "vtl", + "ftl", + // config + "graphql", + "json", + "xml", + "yaml", + "yml", + "csv", + "lock", + // Images + "apng", + "png", + "jpg", + "jpeg", + "gif", + "webp", + "svg", + "ico", + "bmp", + "tiff", + "tif", + "avif", + "eps", + // Fonts + "ttf", + "otf", + "woff", + "woff2", + "eot", + "sfnt", + // Audio and Video + "mp3", + "mp4", + "webm", + "wav", + "flac", + "aac", + "ogg", + "oga", + "opus", + "m4a", + "m4v", + "mov", + "avi", + "wmv", + "flv", + "mkv", + "flac" + )); + + // Files that are to be included in the digest but are not of the above extensions + private static final Set DIGEST_FILES = new HashSet<>(asList( + ".parcelrc", + ".babelrc", + ".eslintrc", + ".eslintignore", + ".prettierrc", + ".prettierrc.js", // this would otherwise get skpped over + ".prettierignore", + ".stylelintrc", + ".stylelintignore", + ".browserslistrc", + ".npmrc", + ".swcrc" + )); + + @Override + public FileVisitResult preVisitDirectory(Path file, BasicFileAttributes attrs) { + String filename = file.getFileName().toString(); + if (excludedFilenames.contains(filename)) { + return FileVisitResult.SKIP_SUBTREE; + } + + + return FileVisitResult.CONTINUE; + } + + /** + * PERF NOTES: + *
    + *
  • Yes, we're mixing the walking with the work, but due to the + * underlying library calls, we're 300ms better off in JSM DC
  • + *
  • Yes, this removes parallelism, but even in JSM DC (worst case), + * it's negligible, while for Stash and Jira DC it's faster to be single + * threaded. Combined with these all usually being run in Maven + * reactors, this is more likely to pay off by allowing for better + * overall parallelism
  • + *
+ */ + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { + String fileName = file.getFileName().toString(); + + if (excludedFilenames.contains(fileName)) { + return FileVisitResult.CONTINUE; + } + + if (DIGEST_FILES.contains(fileName) || + DIGEST_EXTENSIONS.contains(getFileExtension(fileName))) { + addTrackedFile(files, file); + } + + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFileFailed(Path path, IOException exception) throws IOException { + log.debug("Failed to visit {}", path, exception); + return super.visitFileFailed(path, exception); + } + + private static String getFileExtension(String fileName) { + int dotIndex = fileName.lastIndexOf('.'); + if (dotIndex > 0 // skip over dot-files like .babelrc + // check if the '.' is the last character == no extension + && dotIndex < fileName.length() - 1) { + return fileName.substring(dotIndex + 1); + } else { + return null; + } + } + } + + private Set createFilesDigest() throws IOException { + final Set files = new HashSet<>(); + + IncrementalVisitor visitor = new IncrementalVisitor(files, excludedFilenames); + Files.walkFileTree(workingDirectory.toPath(), visitor); + triggerFiles.forEach(file -> addTrackedFile(files, file.toPath())); + + return files; + } + + private static void addTrackedFile(Collection files, Path file) { + try { + byte[] fileBytes = Files.readAllBytes(file); + // Requirements for hash function: 1 - single byte change is + // highly likely to result in a different hash, 2 - fast, baby fast! + long[] hash = MurmurHash3.hash128x64(fileBytes); + String hashString = Arrays.toString(hash); + files.add(new Execution.File(file.toString(), fileBytes.length, hashString)); + } catch (IOException exception) { + throw new RuntimeException(format("Failed to read file: %s", file), exception); + } + } + + private static Map getAllEnvVars(Map userDefinedEnvVars) { + final Map effectiveEnvVars = new HashMap<>(); + + List defaultEnvVars = asList( + "NODE_ENV", + "BABEL_ENV", + "OS", + "OS_VERSION", + "OS_ARCH", + "OS_NAME", + "OS_FAMILY" + ); + defaultEnvVars.forEach(envVarKey -> { + String envVarValue = System.getenv(envVarKey); + effectiveEnvVars.put(envVarKey, nullStringIsEmpty(envVarValue)); + }); + + if (userDefinedEnvVars != null) { + // These would override our defaults + effectiveEnvVars.putAll(userDefinedEnvVars); + } + + return effectiveEnvVars; + } + + /** + * Most stuff treats empty and unset as the same + */ + private static String nullStringIsEmpty(String string) { + if (isNull(string)) { + return ""; + } + return string; + } + + /** + * This is expensive to init (200ms), should only do it once and as needed + */ + static ObjectMapper getObjectMapper() { + if (isNull(objectMapper)) { + objectMapper = new ObjectMapper() + // Allow for reading without blowing up + .configure(FAIL_ON_UNKNOWN_PROPERTIES, false) + // for serialisation performance + .configure(INDENT_OUTPUT, false); + } + return objectMapper; + } + + private void saveDigest(IncrementalBuildExecutionDigest digest) throws IOException { + getObjectMapper().writeValue(getDigestFile(), digest); + } + + private IncrementalBuildExecutionDigest readDigest(File digest) throws IOException { + return getObjectMapper().readValue(digest, IncrementalBuildExecutionDigest.class); + } + + private File getDigestFile() { + return new File(targetDirectory, "frontend-maven-plugin-incremental-build-digest.json"); + } +} diff --git a/frontend-plugin-core/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/NodeTaskExecutor.java b/frontend-plugin-core/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/NodeTaskExecutor.java index 03378a737..83823dbeb 100644 --- a/frontend-plugin-core/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/NodeTaskExecutor.java +++ b/frontend-plugin-core/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/NodeTaskExecutor.java @@ -3,6 +3,9 @@ import static com.github.eirslett.maven.plugins.frontend.lib.Utils.implode; import static com.github.eirslett.maven.plugins.frontend.lib.Utils.normalize; import static com.github.eirslett.maven.plugins.frontend.lib.Utils.prepend; +import static java.util.Collections.emptyMap; +import static java.util.Collections.singletonList; +import static java.util.Optional.empty; import java.io.File; import java.util.ArrayList; @@ -10,11 +13,13 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; +import com.github.eirslett.maven.plugins.frontend.lib.IncrementalBuildExecutionDigest.Execution.Runtime; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -abstract class NodeTaskExecutor { +abstract class NodeTaskExecutor implements NodeTaskRunner { private static final String DS = "//"; private static final String AT = "@"; @@ -137,4 +142,15 @@ private static String maskPassword(String proxyString) { public void setTaskLocation(String taskLocation) { this.taskLocation = taskLocation; } + + public Optional getRuntime() { + try { + String version = new NodeExecutor(config, singletonList("--version"), emptyMap()) + .executeAndGetResult(logger); + return Optional.of(new Runtime("node", version)); + } catch (Exception exception) { + logger.debug("Failed to get the Node version, because: ", exception); + return empty(); + } + } } diff --git a/frontend-plugin-core/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/NodeTaskRunner.java b/frontend-plugin-core/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/NodeTaskRunner.java index 98d224acc..64a272528 100644 --- a/frontend-plugin-core/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/NodeTaskRunner.java +++ b/frontend-plugin-core/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/NodeTaskRunner.java @@ -1,7 +1,12 @@ package com.github.eirslett.maven.plugins.frontend.lib; +import com.github.eirslett.maven.plugins.frontend.lib.IncrementalBuildExecutionDigest.Execution.Runtime; + import java.util.Map; +import java.util.Optional; interface NodeTaskRunner { void execute(String args, Map environment) throws TaskRunnerException; + + Optional getRuntime(); } diff --git a/frontend-plugin-core/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/YarnRunner.java b/frontend-plugin-core/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/YarnRunner.java index 665fe8be4..43f9bf2b7 100644 --- a/frontend-plugin-core/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/YarnRunner.java +++ b/frontend-plugin-core/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/YarnRunner.java @@ -1,9 +1,16 @@ package com.github.eirslett.maven.plugins.frontend.lib; +import com.github.eirslett.maven.plugins.frontend.lib.IncrementalBuildExecutionDigest.Execution.Runtime; +import com.github.eirslett.maven.plugins.frontend.lib.ProxyConfig.Proxy; + import java.util.ArrayList; import java.util.List; +import java.util.Optional; -import com.github.eirslett.maven.plugins.frontend.lib.ProxyConfig.Proxy; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyMap; +import static java.util.Collections.singletonList; +import static java.util.Optional.empty; public interface YarnRunner extends NodeTaskRunner { } @@ -51,4 +58,81 @@ private static List buildArguments(final YarnExecutorConfig config, Prox return arguments; } + + /** + *

Why not use? / Why not do this instead?

+ *
    + *
  • Execute node/yarn from the path?It's often different to the + * Node version used by this plugin because people often have to mix + * versions AND because this plugin executes all tasks from the version + * it downloaded
  • + *
  • Use the same command between yarn versions?There is + * no command compatible with all version of yarn we use and support
  • + *
  • Use yarn to call node?In Yarn classic it's not a reserved + * command which can lead to crashes if a project has a "node" + * script (reasonably common)
  • + *
+ *

Output of the commands

+ *
In Yarn Berry
+ *

+ * Running {@code yarn node --version} in yarn classic gives us an output + * like this of all the C++ libraries and the package name in the + * {@code package.json}: + *

+     * yarn node v1.22.22
+     * v20.10.0
+     * Done in 0.02s.
+     * 
+ * while yarn berry will give us just the output + *

+ *
In Yarn Classic
+ *

+ * Running {@code yarn versions} gives an output like this: + *

+     * yarn versions v1.22.22
+     * {
+     *   yarn: '1.22.22',
+     *   '@atlassian/aui-workspace': '9.13.0-SNAPSHOT',
+     *   node: '18.17.0',
+     *   acorn: '8.8.2',
+     *   ada: '2.5.0',
+     *   ....
+     * }
+     * Done in 0.01s.
+     * 
+ * The first line is repeated and the last one will cause an unnecessary diff. Running + * with {@code --silent} culls the first and last lines, but this isn't available on all yarn + * classic versions, e.g. 1.22.17 + *

+ */ + @Override + public Optional getRuntime() { + // Why not call config.isYarnBerry() ?? Well it can lie since it's not using what's + // on the path..... it's just looking at files + try { + String output = new YarnExecutor(config, asList("node", "--version"), emptyMap()) + .executeAndGetResult(logger); + if (output.startsWith("yarn node")) { + output = output.substring(output.indexOf("\n"), output.lastIndexOf("\n")).trim(); + } + return Optional.of(new Runtime("node", output)); + } catch (Exception exception) { + logger.debug("Failed to get the Node version from yarn, will fallback and hope it's yarn classic. Failed because: ", exception); + } + + try { + String rawVersions = new YarnExecutor(config, singletonList("versions"), emptyMap()) + .executeAndGetResult(logger); + int startIndex = rawVersions.indexOf("{"); + int endIndex = rawVersions.indexOf("}") + 1; + String desiredVersions = rawVersions.substring(startIndex, endIndex) + .replaceAll("\\s+", ""); + // Yes, yarn is not a runtime, but here we can glean a little more + // information that's more ideal to track + return Optional.of(new Runtime("yarn", desiredVersions)); + } catch (Exception exception) { + logger.debug("Failed to get the Node version from yarn, even after assuming it's yarn classic. Failed because: ", exception); + return empty(); + } + } } diff --git a/frontend-plugin-core/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/YarnTaskExecutor.java b/frontend-plugin-core/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/YarnTaskExecutor.java index 87d826a05..e0d5314ec 100644 --- a/frontend-plugin-core/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/YarnTaskExecutor.java +++ b/frontend-plugin-core/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/YarnTaskExecutor.java @@ -16,13 +16,13 @@ abstract class YarnTaskExecutor { private static final String AT = "@"; - private final Logger logger; + final Logger logger; private final String taskName; private final ArgumentsParser argumentsParser; - private final YarnExecutorConfig config; + final YarnExecutorConfig config; public YarnTaskExecutor(YarnExecutorConfig config, String taskLocation) { this(config, taskLocation, Collections. emptyList()); diff --git a/frontend-plugin-core/src/test/java/com/github/eirslett/maven/plugins/frontend/lib/IncrementalBuildExecutionDigestTest.java b/frontend-plugin-core/src/test/java/com/github/eirslett/maven/plugins/frontend/lib/IncrementalBuildExecutionDigestTest.java new file mode 100644 index 000000000..da98fbef7 --- /dev/null +++ b/frontend-plugin-core/src/test/java/com/github/eirslett/maven/plugins/frontend/lib/IncrementalBuildExecutionDigestTest.java @@ -0,0 +1,72 @@ +package com.github.eirslett.maven.plugins.frontend.lib; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.eirslett.maven.plugins.frontend.lib.IncrementalBuildExecutionDigest.Execution; +import com.github.eirslett.maven.plugins.frontend.lib.IncrementalBuildExecutionDigest.Execution.File; +import com.github.eirslett.maven.plugins.frontend.lib.IncrementalBuildExecutionDigest.Execution.Runtime; +import com.github.eirslett.maven.plugins.frontend.lib.IncrementalBuildExecutionDigest.ExecutionCoordinates; +import org.junit.jupiter.api.Test; + +import java.util.Collections; +import java.util.HashSet; + +import static com.github.eirslett.maven.plugins.frontend.lib.IncrementalBuildExecutionDigest.SERIALIZATION_SEPARATOR; +import static java.util.Collections.singletonList; +import static java.util.Collections.singletonMap; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class IncrementalBuildExecutionDigestTest { + + /** + * De/serialization in Jackson is not trivial and a bunch of stuff can blow up at runtime. + */ + @Test + public void serializationSmokeTest() throws Exception { + IncrementalBuildExecutionDigest digestToSerialize = new IncrementalBuildExecutionDigest( + 2L, + singletonMap( + new ExecutionCoordinates( + "test-goal", + "test-id" + // make sure we got a good character + + SERIALIZATION_SEPARATOR, + "test-phase"), + new Execution( + "test-arguments", + Collections.singletonMap("NODE_ENV", "test"), + new HashSet<>(singletonList(new File("test-file.js", 12345, "abc123"))), + new Runtime("node", "{\n" + + " '@atlassian/solicitorio': '3.4.0',\n" + + " npm: '8.15.0',\n" + + " node: '18.17.0',\n" + + " acorn: '8.8.2',\n" + + " ada: '2.5.0',\n" + + " ares: '1.19.1',\n" + + " brotli: '1.0.9',\n" + + " cldr: '43.0',\n" + + " icu: '73.1',\n" + + " llhttp: '6.0.11',\n" + + " modules: '108',\n" + + " napi: '9',\n" + + " nghttp2: '1.52.0',\n" + + " nghttp3: '0.7.0',\n" + + " ngtcp2: '0.8.1',\n" + + " openssl: '3.0.9+quic',\n" + + " simdutf: '3.2.12',\n" + + " tz: '2023c',\n" + + " undici: '5.22.1',\n" + + " unicode: '15.0',\n" + + " uv: '1.44.2',\n" + + " uvwasi: '0.0.18',\n" + + " v8: '10.2.154.26-node.26',\n" + + " zlib: '1.2.13.1-motley'\n" + + "}")))); + + ObjectMapper objectMapper = IncrementalMojoHelper.getObjectMapper(); + + String jsonString = objectMapper.writeValueAsString(digestToSerialize); + IncrementalBuildExecutionDigest deserializedDigest = objectMapper.readValue(jsonString, IncrementalBuildExecutionDigest.class); + + assertEquals(digestToSerialize, deserializedDigest); + } +} diff --git a/pom.xml b/pom.xml index 54d397994..3d253ba2c 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ com.github.eirslett frontend-plugins - 1.15.1-atlassian-1-SNAPSHOT + 1.15.1-atlassian-4-SNAPSHOT pom @@ -33,7 +33,8 @@ https://github.com/atlassian-forks/frontend-maven-plugin scm:git:https://github.com/atlassian-forks/frontend-maven-plugin.git scm:git:git@github.com:atlassian-forks/frontend-maven-plugin.git - + HEAD +