This is an automated email from the ASF dual-hosted git repository. benweidig pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/tapestry-5.git
commit cd3518fea9fe6a2ef02e410af9a78ab11ca68384 Author: Ben Weidig <[email protected]> AuthorDate: Sat Apr 18 11:23:33 2026 +0200 TAP5-2809: NpmTask with Docker support, don't emit TypeScript types into es-modules As it's not guaranteed that npm is available, and to make builds more stable/reproducible, an NpmTask was added that checks for Docker first to run npm with a fallback to a local installation. The existing npm/typescript related code in tapestr-core build.gradle were adapted. Furthermore, the TypeScript project package.json/tsconfig.json was slightly improved, no longer emitting the typings into the es-modules, and hardening script calling in general. --- buildSrc/src/main/groovy/t5build/NpmTask.groovy | 109 ++++++++++++++++++++++++ tapestry-core/build.gradle | 39 +++------ tapestry-core/src/main/typescript/package.json | 22 ++--- tapestry-core/src/main/typescript/tsconfig.json | 2 - 4 files changed, 134 insertions(+), 38 deletions(-) diff --git a/buildSrc/src/main/groovy/t5build/NpmTask.groovy b/buildSrc/src/main/groovy/t5build/NpmTask.groovy new file mode 100644 index 000000000..8786d9745 --- /dev/null +++ b/buildSrc/src/main/groovy/t5build/NpmTask.groovy @@ -0,0 +1,109 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package t5build + +import org.gradle.api.tasks.Exec +import org.gradle.api.tasks.Internal + +/** + * Gradle task that runs an npm command, either via Docker or the local npm executable. + * <p> + * When Docker is available, the task mounts the project's {@code src/main} directory into + * a container and runs {@cpde npm} there, ensuring a consistent Node.js environment. + * <p> + * On Linux, the container process runs as the current host user to avoid file-ownership + * issues on mounted volumes. + * <p> + * When Docker is not available, the task falls back to invoking the local {@code npm} (or + * {@code npm.cmd} on Windows) directly. + * <p> + * The Docker image used can be overridden via the {@code nodeDockerImage} Gradle project + * property (defaults to {@code node:lts-alpine}). + * <p> + * Usage in a build script: + * <pre> + * tasks.register('npmInstall', NpmTask) { + * npmArgs = ['install'] + * } + * </pre> + */ +class NpmTask extends Exec { + + private static Boolean dockerAvailableCache = null + + /** + * Arguments passed to the npm command (e.g. {@code ['install']}). + */ + @Internal + List<String> npmArgs = [] + + /** + * Docker image used to run npm when Docker is available. + * <p> + * Default: {@code node:lts-alpine} + */ + @Internal + String nodeDockerImage = (project.findProperty('nodeDockerImage') ?: 'node:lts-alpine') as String + + NpmTask() { + workingDir = 'src/main/typescript' + } + + @Override + protected void exec() { + commandLine(buildCommand()) + super.exec() + } + + private List<String> buildCommand() { + return isDockerAvailable() ? buildDockerCommand() : [npmExecutable()] + npmArgs + } + + private List<String> buildDockerCommand() { + def cmd = [ + 'docker', 'run', '--rm', + '-v', "${project.projectDir}/src/main:/work", + '-w', '/work/typescript'] + if (isLinux()) { + cmd += ['--user', "${['id', '-u'].execute().text.trim()}:${['id', '-g'].execute().text.trim()}"] + } + cmd += [nodeDockerImage, 'npm'] + npmArgs + return cmd + } + + private static boolean isDockerAvailable() { + if (dockerAvailableCache == null) { + try { + def proc = ['docker', 'info'].execute() + proc.waitFor() + dockerAvailableCache = proc.exitValue() == 0 + } catch (Exception ignored) { + dockerAvailableCache = false + } + } + return dockerAvailableCache + } + + private static String npmExecutable() { + return isWindows() ? 'npm.cmd' : 'npm' + } + + private static boolean isWindows() { + return System.properties['os.name'].toLowerCase().contains('windows') + } + + private static boolean isLinux() { + def os = System.properties['os.name'].toLowerCase() + return !os.contains('windows') && !os.contains('mac') + } +} diff --git a/tapestry-core/build.gradle b/tapestry-core/build.gradle index 0b0afd063..d4d837b3e 100644 --- a/tapestry-core/build.gradle +++ b/tapestry-core/build.gradle @@ -1,3 +1,5 @@ +import t5build.NpmTask + plugins { id 'tapestry.junit5-convention' id 'tapestry.testng-convention' @@ -5,12 +7,6 @@ plugins { description = 'Central module for Tapestry, containing all core services and components' -def npmWorkingDir = 'src/main/typescript/' -def npmExec = 'npm' -if (System.properties['os.name'].toLowerCase().contains('windows')) { - npmExec += '.cmd' -} - dependencies { api project(':tapestry-ioc') api project(':tapestry-json') @@ -29,37 +25,32 @@ dependencies { provided libs.jakarta.servlet.api testImplementation libs.commons.httpclient - testImplementation project(":tapestry-spock") + testImplementation project(':tapestry-spock') testRuntimeOnly libs.hsqldb } -tasks.register('npmInstall', Exec) { +tasks.register('npmInstall', NpmTask) { group = 'typescript' - description = 'Runs npm install' - - workingDir = npmWorkingDir - commandLine npmExec, 'install' + description = 'Runs npm ci' + + npmArgs = ['ci'] } -tasks.register('compileTypeScriptToAmd', Exec) { +tasks.register('compileTypeScriptToAmd', NpmTask) { group = 'typescript' description = 'Compiles TypeScript to AMD modules' - dependsOn npmInstall - workingDir = npmWorkingDir - commandLine npmExec, 'run', 'build-amd' + npmArgs = ['run', 'build-amd'] } -tasks.register('compileTypeScriptToEsModule', Exec) { +tasks.register('compileTypeScriptToEsModule', NpmTask) { group = 'typescript' description = 'Compiles TypeScript to ES modules' - dependsOn npmInstall - - workingDir = npmWorkingDir - commandLine npmExec, 'run', 'build-es-module' + + npmArgs = ['run', 'build-es-module'] } // Lifecycle task that triggers both TypeScript compilations. @@ -76,14 +67,12 @@ tasks.named('sourcesJar') { dependsOn compileTypeScript } -tasks.register('generateTypeScriptDocs', Exec) { +tasks.register('generateTypeScriptDocs', NpmTask) { group = 'typescript' description = 'Generates TypeScript documentation' - dependsOn npmInstall - workingDir = npmWorkingDir - commandLine npmExec, 'run', 'docs' + npmArgs = ['run', 'docs'] } tasks.register('cleanTypeScriptFiles', Delete) { diff --git a/tapestry-core/src/main/typescript/package.json b/tapestry-core/src/main/typescript/package.json index 51d7af186..d13fdc044 100644 --- a/tapestry-core/src/main/typescript/package.json +++ b/tapestry-core/src/main/typescript/package.json @@ -10,21 +10,21 @@ }, "dependencies": {}, "scripts": { - "build": "npm run build-es-module; npm run build-amd; rm -f .", - "build-es-module": "npx tsc --outDir ../resources/META-INF/assets/es-modules/; npm run adjust-es-module;", - "build-amd": "npx tsc --module amd --outDir ../resources/META-INF/modules/; npm run adjust-amd;", - "clean-amd": "rm -rf ../resources/META-INF/modules/t5/core/*.js; rm -rf ../resources/META-INF/modules/t5/beanvalidation/*.js;", - "clean-es-module": "rm -rf ../resources/META-INF/assets/es-modules/t5/core/*.js; ../resources/META-INF/assets/es-modules/t5/beanvalidation/*.js;", - "clean": "npm run clean-amd; npm run clean-es-module; npm run clean-docs; npm run clean-tsc;", - "adjust-amd": "mv ../resources/META-INF/modules/t5/core/messages-amd.js ../resources/META-INF/modules/t5/core/messages.js; rm ../resources/META-INF/modules/t5/core/messages-es-module.js;", - "adjust-es-module": "mv ../resources/META-INF/assets/es-modules/t5/core/messages-es-module.js ../resources/META-INF/assets/es-modules/t5/core/messages.js; rm ../resources/META-INF/assets/es-modules/t5/core/messages-amd.js;", + "build": "npm run build-es-module && npm run build-amd", + "build-es-module": "tsc --outDir ../resources/META-INF/assets/es-modules/ && npm run adjust-es-module", + "build-amd": "tsc --module amd --outDir ../resources/META-INF/modules/ && npm run adjust-amd", + "clean-amd": "rm -rf ../resources/META-INF/modules/t5/core/*.js && rm -rf ../resources/META-INF/modules/t5/beanvalidation/*.js", + "clean-es-module": "rm -rf ../resources/META-INF/assets/es-modules/t5/core/*.js && rm -rf ../resources/META-INF/assets/es-modules/t5/beanvalidation/*.js", + "clean": "npm run clean-amd && npm run clean-es-module && npm run clean-docs && npm run clean-tsc", + "adjust-amd": "mv ../resources/META-INF/modules/t5/core/messages-amd.js ../resources/META-INF/modules/t5/core/messages.js && rm ../resources/META-INF/modules/t5/core/messages-es-module.js", + "adjust-es-module": "mv ../resources/META-INF/assets/es-modules/t5/core/messages-es-module.js ../resources/META-INF/assets/es-modules/t5/core/messages.js && rm ../resources/META-INF/assets/es-modules/t5/core/messages-amd.js", "docs": "typedoc", "clean-docs": "rm -rf docs", "tsc": "tsc", "clean-tsc": "rm -rf ./types/*", - "generate-type-definitions": "tsc --emitDeclarationOnly --outDir ./types", - "test-publish-to-npm": "npm run clean-tsc; npm run generate-type-definitions; npm pack;", - "publish-to-npm": "npm run clean-tsc; npm run generate-type-definitions; npm publish --access public;" + "generate-type-definitions": "tsc --declaration --emitDeclarationOnly --outDir ./types", + "test-publish-to-npm": "npm run clean-tsc && npm run generate-type-definitions && npm pack", + "publish-to-npm": "npm run clean-tsc && npm run generate-type-definitions && npm publish --access public" }, "license": "Apache-2.0", "name": "@thiagohp/tapestry-5", diff --git a/tapestry-core/src/main/typescript/tsconfig.json b/tapestry-core/src/main/typescript/tsconfig.json index 33aa9ddde..bd501e030 100644 --- a/tapestry-core/src/main/typescript/tsconfig.json +++ b/tapestry-core/src/main/typescript/tsconfig.json @@ -34,8 +34,6 @@ // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ "skipLibCheck": true /* Skip type checking all .d.ts files. */, - "declaration": true, - "outDir": "output", "rootDir": "src",
