diff --git a/.github/workflows/cli-release-process.yml b/.github/workflows/cli-release-process.yml
index cc4930befaa9..428ca47018d1 100644
--- a/.github/workflows/cli-release-process.yml
+++ b/.github/workflows/cli-release-process.yml
@@ -19,7 +19,10 @@ env:
JAVA_DISTRO: temurin
NEXT_VERSION: '1.0.0-SNAPSHOT'
GRAALVM_VERSION: '22.1.0'
- PACKAGE_TYPE: 'uber-jar'
+ MVN_PACKAGE_TYPE: 'uber-jar'
+ NPM_PACKAGE_NAME: 'dotcli'
+ MVN_PACKAGE_NAME: 'dotcms-cli'
+ NODE_VERSION: 19
jobs:
precheck:
@@ -75,7 +78,7 @@ jobs:
./mvnw -B -ntp versions:set versions:commit -DnewVersion=$RELEASE_VERSION
- git commit --allow-empty -a -m "🏁 Releasing version $RELEASE_VERSION"
+ git commit --allow-empty -a -m "🏁 Releasing CLI version $RELEASE_VERSION"
git push https://${{ secrets.CI_MACHINE_USER }}:${{ secrets.CI_MACHINE_TOKEN }}@github.com/${GITHUB_REPOSITORY}
echo "RELEASE_VERSION=$RELEASE_VERSION" >> "$GITHUB_OUTPUT"
@@ -89,7 +92,7 @@ jobs:
strategy:
fail-fast: true
matrix:
- os: [ macos-13-xlarge, macOS-latest, ubuntu-latest, windows-latest ] #
+ os: [ macos-13-xlarge, macOS-latest, ubuntu-latest ]
runs-on: ${{ matrix.os }}
steps:
@@ -104,12 +107,12 @@ jobs:
run: |
if [ "${{ matrix.os }}" == "ubuntu-latest" ]; then
echo "GraalVM on Linux (AMD64)"
-
+
ARCH=amd64
PLATFORM=linux
INSTALLATION_PATH=/usr/lib/jvm
-
- else
+
+ else
if [ "${{ matrix.os }}" == "macos-13-xlarge" ]; then
echo "GraalVM on Mac (AARCH64)"
ARCH=aarch64
@@ -117,27 +120,27 @@ jobs:
echo "GraalVM on Mac (AMD64)"
ARCH=amd64
fi
-
+
PLATFORM=darwin
- INSTALLATION_PATH=/Library/Java/JavaVirtualMachines
- fi
-
+ INSTALLATION_PATH=/Library/Java/JavaVirtualMachines
+ fi
+
echo "PLATFORM=$PLATFORM"
echo "ARCH=$ARCH"
echo "INSTALLATION_PATH=$INSTALLATION_PATH"
-
+
wget https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-${{ env.GRAALVM_VERSION }}/graalvm-ce-java11-${PLATFORM}-${ARCH}-${{ env.GRAALVM_VERSION }}.tar.gz
sudo mkdir -p $INSTALLATION_PATH
- tar -xzf graalvm-ce-java11-${PLATFORM}-${ARCH}-${{ env.GRAALVM_VERSION }}.tar.gz
+ tar -xzf graalvm-ce-java11-${PLATFORM}-${ARCH}-${{ env.GRAALVM_VERSION }}.tar.gz
sudo mv graalvm-ce-java11-${{ env.GRAALVM_VERSION }} $INSTALLATION_PATH
-
+
if [ "${{ matrix.os }}" != "ubuntu-latest" ]; then
- sudo xattr -r -d com.apple.quarantine /Library/Java/JavaVirtualMachines/graalvm-ce-java11-${{ env.GRAALVM_VERSION }}/Contents/Home
+ sudo xattr -r -d com.apple.quarantine /Library/Java/JavaVirtualMachines/graalvm-ce-java11-${{ env.GRAALVM_VERSION }}/Contents/Home
GRAALVM_HOME="${INSTALLATION_PATH}/graalvm-ce-java11-${{ env.GRAALVM_VERSION }}/Contents/Home"
else
GRAALVM_HOME="${INSTALLATION_PATH}/graalvm-ce-java11-${{ env.GRAALVM_VERSION }}"
fi
-
+
echo "GRAALVM_HOME=$GRAALVM_HOME" >> $GITHUB_ENV
echo "JAVA_HOME=$GRAALVM_HOME" >> $GITHUB_ENV
PATH="$GRAALVM_HOME/bin:$PATH"
@@ -170,7 +173,7 @@ jobs:
echo "JAVA_HOME=${env:JAVA_HOME}" >> "$env:GITHUB_ENV"
echo "Path=${env:Path}" >> "$env:GITHUB_ENV"
- gu.cmd install native-image
+ gu.cmd install native-image
- name: 'Cache Maven packages'
uses: actions/cache@v4
@@ -196,7 +199,7 @@ jobs:
- name: 'Build uber-jar'
working-directory: ${{ github.workspace }}
run: |
- ./mvnw package -Dquarkus.package.type=${{ env.PACKAGE_TYPE }} -DskipTests=${{ github.event.inputs.skipTests }} -pl :dotcms-cli
+ ./mvnw package -Dquarkus.package.type=${{ env.MVN_PACKAGE_TYPE }} -DskipTests=${{ github.event.inputs.skipTests }} -pl :dotcms-cli
- name: 'Build Native Image (Linux/MacOS)'
if: ${{ matrix.os != 'windows-latest' }}
@@ -228,7 +231,7 @@ jobs:
path: |
${{ github.workspace }}/tools/dotcms-cli/cli/target/*-runner.jar
${{ github.workspace }}/tools/dotcms-cli/cli/target/distributions/*.zip
- ${{ github.workspace }}/tools/dotcms-cli/cli/target/distributions/*.tar.gz
+ ${{ github.workspace }}/tools/dotcms-cli/cli/target/distributions/*.tar.gz
release:
needs: [ precheck, build ]
@@ -249,10 +252,10 @@ jobs:
- name: 'Download all build artifacts'
uses: actions/download-artifact@v4
with:
- pattern: artifacts-*
path: ${{ github.workspace }}/artifacts
+ pattern: artifacts-*
merge-multiple: true
-
+
- name: 'List artifacts'
run: |
ls -R
@@ -296,5 +299,95 @@ jobs:
./mvnw -B -ntp versions:set versions:commit -DnewVersion=$NEXT_VERSION
- git commit --allow-empty -a -m "⬆️ Next version $NEXT_VERSION"
+ git commit --allow-empty -a -m "⬆️ Next CLI version $NEXT_VERSION"
git push https://${{ secrets.CI_MACHINE_USER }}:${{ secrets.CI_MACHINE_TOKEN }}@github.com/${GITHUB_REPOSITORY}
+
+ publish-npm-package:
+ name: "Publish NPM Package"
+ if: success()
+ needs: [ build, release ]
+ runs-on: ubuntu-latest
+ steps:
+ - name: 'Checkout code'
+ uses: actions/checkout@v4
+ with:
+ ref: ${{ github.ref }}
+ fetch-depth: 0
+
+ - name: 'Set up Node.js'
+ uses: actions/setup-node@v4
+ with:
+ node-version: ${{ env.NODE_VERSION }}
+
+ - name: 'Install Jinja2'
+ run: pip install jinja2-cli
+
+ - name: 'Download all build artifacts'
+ uses: actions/download-artifact@v4
+ with:
+ path: ${{ github.workspace }}/artifacts
+ pattern: artifacts-*
+ merge-multiple: true
+
+ - name: 'Generate package version suffix'
+ id: generate-version-suffix
+ env:
+ MVN_PACKAGE_VERSION: ${{ github.event.inputs.version }}
+ IS_SNAPSHOT_VERSION: ${{ contains(github.event.inputs.version, 'SNAPSHOT') }}
+ run: |
+ if [ "$IS_SNAPSHOT_VERSION" = "true" ]; then
+ echo "Snapshot version found.";
+ TAG="rc"
+ if npm view $NPM_PACKAGE_NAME &> /dev/null; then
+ echo "${NPM_PACKAGE_NAME} found.";
+ LAST_RC_VERSION=$(npm view $NPM_PACKAGE_NAME versions --json | jq -r 'map(select(test("-rc\\d+$"))) | max')
+ echo $LAST_RC_VERSION
+ NEXT_RC_VERSION=$(echo "$LAST_RC_VERSION" | awk -F '-rc' '{print $1 "-rc" $2 + 1}')
+ echo $NEXT_RC_VERSION
+ RC_SUFFIX=$(echo "$NEXT_RC_VERSION" | sed -n 's/.*-rc\([0-9]*\)/-rc\1/p')
+ echo $RC_SUFFIX
+ else
+ echo "${NPM_PACKAGE_NAME} not found.";
+ RC_SUFFIX="-rc1"
+ fi;
+ else
+ echo "Release version found.";
+ TAG="latest"
+ RC_SUFFIX=""
+ fi;
+
+ VERSION=$(echo "$MVN_PACKAGE_VERSION" | sed 's/-SNAPSHOT//I')
+ echo "NPM_PACKAGE_VERSION=${VERSION}${RC_SUFFIX}" >> $GITHUB_ENV
+ echo "NPM_PACKAGE_VERSION_TAG=$TAG" >> $GITHUB_ENV
+
+ - name: 'NPM Package setup'
+ working-directory: ${{ github.workspace }}/tools/dotcms-cli/npm/
+ env:
+ MVN_PACKAGE_VERSION: ${{ github.event.inputs.version }}
+ run: |
+ echo "Adding bin folder with all the binaries"
+ mkdir -p bin
+ find ${{ github.workspace }}/artifacts/distributions/ -name "*.zip" -exec unzip -d bin {} \;
+
+ echo "Adding wrapper script"
+ mv src/postinstall.js.seed src/postinstall.js
+
+ echo "Adding README.md file"
+ cp ${{ github.workspace }}/tools/dotcms-cli/README.md .
+
+ echo "Adding package.json file"
+ jinja2 package.j2 -D packageName=${MVN_PACKAGE_NAME} -D npmPackageName=${NPM_PACKAGE_NAME} -D npmPackageVersion=${NPM_PACKAGE_VERSION} -D packageVersion=${MVN_PACKAGE_VERSION} --format json -o package.json
+ rm -f package.j2
+
+ cat package.json
+ cat src/postinstall.js
+
+ - name: 'NPM Package tree'
+ run: ls -R ${{ github.workspace }}/tools/dotcms-cli/npm/
+
+ - name: 'Publish to NPM registry'
+ env:
+ NPM_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
+ run: |
+ echo "//registry.npmjs.org/:_authToken=${NPM_AUTH_TOKEN}" > ~/.npmrc
+ npm publish --access public --tag ${NPM_PACKAGE_VERSION_TAG}
diff --git a/tools/dotcms-cli/cli/src/assembly/assembly.xml b/tools/dotcms-cli/cli/src/assembly/assembly.xml
index b00cd3c07e6f..2a29c787dfdc 100644
--- a/tools/dotcms-cli/cli/src/assembly/assembly.xml
+++ b/tools/dotcms-cli/cli/src/assembly/assembly.xml
@@ -25,19 +25,15 @@
xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.2.0 http://maven.apache.org/xsd/assembly-2.2.0.xsd">
dist
- tar.gz
+
zip
- dir
+ false
-
-
-
-
${project.build.directory}/${project.artifactId}-${project.version}-runner${executable-suffix}
- ./bin
- ${project.artifactId}${executable-suffix}
+ ./
+ ${project.artifactId}-${project.version}-${os.detected.classifier}${executable-suffix}
0755
diff --git a/tools/dotcms-cli/jreleaser.yml b/tools/dotcms-cli/jreleaser.yml
index 82124d919849..f1fa50337164 100644
--- a/tools/dotcms-cli/jreleaser.yml
+++ b/tools/dotcms-cli/jreleaser.yml
@@ -56,10 +56,10 @@ distributions:
platform: 'osx-aarch_64'
- path: '{{artifactsDir}}/distributions/dotcms-cli-{{projectVersion}}-osx-x86_64.zip'
platform: 'osx-x86_64'
- - path: '{{artifactsDir}}/distributions/dotcms-cli-{{projectVersion}}-linux-x86_64.tar.gz'
+ - path: '{{artifactsDir}}/distributions/dotcms-cli-{{projectVersion}}-linux-x86_64.zip'
platform: 'linux-x86_64'
- - path: '{{artifactsDir}}/distributions/dotcms-cli-{{projectVersion}}-windows-x86_64.zip'
- platform: 'windows-x86_64'
+# - path: '{{artifactsDir}}/distributions/dotcms-cli-{{projectVersion}}-windows-x86_64.zip'
+# platform: 'windows-x86_64'
upload:
artifactory:
diff --git a/tools/dotcms-cli/npm/package.j2 b/tools/dotcms-cli/npm/package.j2
new file mode 100644
index 000000000000..ed03f5a1b797
--- /dev/null
+++ b/tools/dotcms-cli/npm/package.j2
@@ -0,0 +1,37 @@
+{
+ "name": "@dotcms/{{ npmPackageName }}",
+ "version": "{{ npmPackageVersion }}",
+ "scripts": {
+ "postinstall": "node src/postinstall.js install",
+ "postuninstall": "node src/postinstall.js uninstall && npm prune"
+ },
+ "binaries": {
+ "{{ packageName }}-darwin-arm64": "bin/{{ packageName }}-{{ packageVersion }}-osx-aarch_64",
+ "{{ packageName }}-darwin-x64": "bin/{{ packageName }}-{{ packageVersion }}-osx-x86_64",
+ "{{ packageName }}-linux-x64": "bin/{{ packageName }}-{{ packageVersion }}-linux-x86_64"
+ },
+ "alias": "{{ npmPackageName }}",
+ "packageName": "{{ packageName }}",
+ "files": [
+ "bin",
+ "src"
+ ],
+ "description": "Official command-line tool to manage dotCMS content.",
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/dotCMS/core.git#master"
+ },
+ "keywords": [
+ "dotCMS",
+ "CMS",
+ "Content Management",
+ "CLI",
+ "dotCMS CLI",
+ "dotCMS command-line tool"
+ ],
+ "author": "dotcms ",
+ "license": "MIT",
+ "bugs": {
+ "url": "https://github.com/dotCMS/core/issues"
+ }
+}
diff --git a/tools/dotcms-cli/npm/src/postinstall.js.seed b/tools/dotcms-cli/npm/src/postinstall.js.seed
new file mode 100644
index 000000000000..041077aa7b62
--- /dev/null
+++ b/tools/dotcms-cli/npm/src/postinstall.js.seed
@@ -0,0 +1,154 @@
+"use strict";
+
+const path = require('path');
+const fs = require('fs').promises;
+const os = require('os');
+
+const ARCHITECTURE_MAPPING = {
+ "x64": "x86_64",
+ "arm64": "aarch_64"
+};
+
+const PLATFORM_MAPPING = {
+ "darwin": "osx",
+ "linux": "linux"
+};
+
+function getGlobalBinPath() {
+ const npmGlobalPrefix = process.env.PREFIX || process.env.npm_config_prefix || process.env.HOME;
+ return path.join(npmGlobalPrefix, 'bin');
+}
+
+function validatePackageConfig(packageJson) {
+ if (!packageJson.version || !packageJson.packageName || !packageJson.alias || !packageJson.binaries || typeof packageJson.binaries !== "object") {
+ throw new Error("Invalid package.json. 'version', 'packageName', 'alias' and 'binaries' must be specified.");
+ }
+}
+
+async function parsePackageJson() {
+
+ console.log("Installing CLI");
+ const platform = os.platform();
+ const architecture = os.arch();
+
+ console.log("Platform: " + platform);
+ console.log("Architecture: " + architecture);
+
+ if (!(os.arch() in ARCHITECTURE_MAPPING) || !(os.platform() in PLATFORM_MAPPING)) {
+ throw new Error(`Installation is not supported for this ${platform}/${architecture} combination.`);
+ }
+
+ const packageJsonPath = path.join(".", "package.json");
+
+ try {
+ const packageJsonContent = await fs.readFile(packageJsonPath, "utf-8");
+ const packageJson = JSON.parse(packageJsonContent.toString());
+ validatePackageConfig(packageJson);
+
+ const packageName = packageJson.packageName;
+ const alias = packageJson.alias;
+ const binaries = packageJson.binaries;
+ const extension = platform === "win32" ? ".exe" : "";
+ const binaryKey = `${packageName}-${platform}-${architecture}`;
+ const binaryPath = binaries[binaryKey];
+
+ if (binaryPath) {
+ console.log(`Binary found for your platform ${platform}-${architecture}: ${binaryPath}`);
+ } else {
+ throw new Error(`No binary found for your platform ${platform}-${architecture}.`);
+ }
+
+ return {
+ alias,
+ binaryKey,
+ binaryPath,
+ extension
+ };
+ } catch (error) {
+ throw new Error("Unable to read or parse package.json. Please run this script at the root of the package you want to be installed.");
+ }
+}
+
+
+async function createSymlink(binarySource, binaryDestination) {
+ const globalBinPath = getGlobalBinPath();
+ const symlinkPath = path.join(globalBinPath, binaryDestination);
+
+ try {
+ try {
+ await fs.access(symlinkPath, fs.constants.F_OK);
+ // If the symlink exists, remove it.
+ await fs.unlink(symlinkPath);
+ console.log(`Existing symlink ${symlinkPath} found and removed.`);
+ } catch (error) {
+ // The symlink does not exist, continue.
+ }
+
+ if (os.platform() === "win32") {
+ // Create a junction for the binary for Windows.
+ // await fs.symlink(binarySource, symlinkPath, "junction");
+ } else {
+ // Create a symlink for the binary for macOS and Linux.
+ await fs.symlink(binarySource, symlinkPath);
+ }
+ console.info(`Created symlink ${symlinkPath} pointing to ${binarySource}`);
+ } catch (error) {
+ console.error("Error while creating symlink:", error);
+ throw new Error("Failed to create symlink.");
+ }
+}
+
+async function installCli() {
+ const config = await parsePackageJson();
+
+ console.log({
+ config
+ });
+
+ console.info(`Creating symlink for the relevant binary for your platform ${os.platform()}-${os.arch()}`);
+
+ const currentDir = __dirname;
+ const targetDir = path.join(currentDir, '..');
+ const binarySource = path.join(targetDir, config.binaryPath);
+ const binaryDestination = config.alias;
+
+ console.info("Installing cli:", binarySource, binaryDestination);
+
+ await createSymlink(binarySource, binaryDestination + config.extension);
+}
+
+async function uninstallCli() {
+ const config = await parsePackageJson();
+
+ try {
+ const globalBinPath = getGlobalBinPath();
+ const symlinkPath = path.join(globalBinPath, config.alias + config.extension);
+
+ console.info("Removing symlink:", symlinkPath);
+
+ await fs.unlink(symlinkPath);
+ } catch (ex) {
+ console.error("Error while uninstalling:", ex);
+ }
+
+ console.info("Uninstalled cli successfully");
+}
+
+const actions = {
+ "install": installCli,
+ "uninstall": uninstallCli
+};
+
+const [cmd] = process.argv.slice(2);
+if (cmd && actions[cmd]) {
+ actions[cmd]().then(
+ () => process.exit(0),
+ (err) => {
+ console.error(err);
+ process.exit(1);
+ }
+ );
+} else {
+ console.log("Invalid command. `install` and `uninstall` are the only supported commands");
+ process.exit(1);
+}
\ No newline at end of file