Compare commits

..

7 Commits

Author SHA1 Message Date
Bruno Borges b62e56b399 Merge branch 'main' into copilot/support-caching-maven-plugin-dependencies 2026-07-07 15:02:10 -04:00
Bruno Borges 3356b3f394 Merge branch 'main' into copilot/support-caching-maven-plugin-dependencies 2026-06-23 13:40:08 -04:00
Bruno Borges a24460f88d Merge branch 'main' into copilot/support-caching-maven-plugin-dependencies 2026-06-22 22:58:26 -04:00
copilot-swe-agent[bot] 47340fb2e8 test: update maven cache error test name for extensions.xml 2026-06-22 20:59:43 +00:00
Bruno Borges 9525efcdb4 Merge branch 'main' into copilot/support-caching-maven-plugin-dependencies 2026-06-22 16:44:12 -04:00
copilot-swe-agent[bot] 7cec43d3da feat: add .mvn/extensions.xml to Maven cache key pattern
Closes #990

Maven build extensions declared in `.mvn/extensions.xml` can introduce
additional plugin dependencies (e.g. lifecycle participants, custom
packaging types). Including this file in the cache key hash ensures that
changes to extensions — which affect what plugin JARs Maven downloads —
properly invalidate the cache, preventing stale caches from missing
newly-required plugin dependencies.

Changes:
- src/cache.ts: add `**/.mvn/extensions.xml` to Maven pattern array
- __tests__/cache.test.ts: update pattern expectations; add new test
- README.md: document the new file in the Maven cache key hash list
2026-06-22 19:23:39 +00:00
copilot-swe-agent[bot] c3b0b6b300 Initial plan 2026-06-22 19:16:54 +00:00
11 changed files with 55 additions and 314 deletions
-30
View File
@@ -670,36 +670,6 @@ jobs:
run: bash __tests__/verify-java.sh "17.0.10" "$JAVA_PATH" run: bash __tests__/verify-java.sh "17.0.10" "$JAVA_PATH"
shell: bash shell: bash
setup-java-version-from-file-with-inferred-dist:
name: distribution inferred from file '${{ matrix.java-version-file }}' - ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [macos-latest, windows-latest, ubuntu-latest]
java-version-file: ['.java-version', '.tool-versions']
steps:
- name: Checkout
uses: actions/checkout@v7
with:
persist-credentials: false
- name: Create .java-version file
shell: bash
run: echo "temurin-17.0.10" > .java-version
- name: Create .tool-versions file
shell: bash
run: echo "java temurin-17.0.10" > .tool-versions
- name: setup-java
uses: ./
id: setup-java
with:
java-version-file: ${{matrix.java-version-file }}
- name: Verify Java
env:
JAVA_PATH: ${{ steps.setup-java.outputs.path }}
run: bash __tests__/verify-java.sh "17.0.10" "$JAVA_PATH"
shell: bash
setup-java-set-default: setup-java-set-default:
name: set-default option - ${{ matrix.os }} name: set-default option - ${{ matrix.os }}
needs: setup-java-major-versions needs: setup-java-major-versions
+2 -2
View File
@@ -31,7 +31,7 @@ For more details, see the full release notes on the [releases page](https://git
- `java-version-file`: The path to a file containing java version. Supported file types are `.java-version`, `.tool-versions`, and `.sdkmanrc`. See more details in [about .java-version-file](docs/advanced-usage.md#Java-version-file). - `java-version-file`: The path to a file containing java version. Supported file types are `.java-version`, `.tool-versions`, and `.sdkmanrc`. See more details in [about .java-version-file](docs/advanced-usage.md#Java-version-file).
- `distribution`: Java [distribution](#supported-distributions). Required unless `java-version-file` specifies a recognized distribution, for example a `.sdkmanrc` distribution suffix (`java=21.0.5-tem`) or a `.java-version`/`.tool-versions` distribution prefix (`temurin-21.0.5`). - `distribution`: Java [distribution](#supported-distributions). Required unless `java-version-file` points to `.sdkmanrc` with a recognized distribution suffix (for example `java=21.0.5-tem`).
- `java-package`: The packaging variant of the chosen distribution. Possible values: `jdk`, `jre`, `jdk+fx`, `jre+fx`. Default value: `jdk`. - `java-package`: The packaging variant of the chosen distribution. Possible values: `jdk`, `jre`, `jdk+fx`, `jre+fx`. Default value: `jdk`.
@@ -138,7 +138,7 @@ Currently, the following distributions are supported:
The action has a built-in functionality for caching and restoring dependencies. It uses [toolkit/cache](https://github.com/actions/toolkit/tree/main/packages/cache) under hood for caching dependencies but requires less configuration settings. Supported package managers are gradle, maven and sbt. The format of the used cache key is `setup-java-${{ platform }}-${{ packageManager }}-${{ fileHash }}`, where the hash is based on the following files: The action has a built-in functionality for caching and restoring dependencies. It uses [toolkit/cache](https://github.com/actions/toolkit/tree/main/packages/cache) under hood for caching dependencies but requires less configuration settings. Supported package managers are gradle, maven and sbt. The format of the used cache key is `setup-java-${{ platform }}-${{ packageManager }}-${{ fileHash }}`, where the hash is based on the following files:
- gradle: `**/*.gradle*`, `**/gradle-wrapper.properties`, `buildSrc/**/Versions.kt`, `buildSrc/**/Dependencies.kt`, `gradle/*.versions.toml`, and `**/versions.properties` - gradle: `**/*.gradle*`, `**/gradle-wrapper.properties`, `buildSrc/**/Versions.kt`, `buildSrc/**/Dependencies.kt`, `gradle/*.versions.toml`, and `**/versions.properties`
- maven: `**/pom.xml` and `**/.mvn/wrapper/maven-wrapper.properties` - maven: `**/pom.xml`, `**/.mvn/wrapper/maven-wrapper.properties`, and `**/.mvn/extensions.xml`
- sbt: all sbt build definition files `**/*.sbt`, `**/project/build.properties`, `**/project/**.scala`, `**/project/**.sbt` - sbt: all sbt build definition files `**/*.sbt`, `**/project/build.properties`, `**/project/**.scala`, `**/project/**.sbt`
When the option `cache-dependency-path` is specified, the hash is based on the matching file. This option supports wildcards and a list of file names, and is especially useful for monorepos. When the option `cache-dependency-path` is specified, the hash is based on the matching file. This option supports wildcards and a list of file names, and is especially useful for monorepos.
+22 -4
View File
@@ -96,11 +96,11 @@ describe('dependency cache', () => {
}); });
describe('for maven', () => { describe('for maven', () => {
it('throws error if no pom.xml or maven-wrapper.properties found', async () => { it('throws error if no pom.xml, maven-wrapper.properties, or extensions.xml found', async () => {
await expect(restore('maven', '')).rejects.toThrow( await expect(restore('maven', '')).rejects.toThrow(
`No file in ${projectRoot( `No file in ${projectRoot(
workspace workspace
)} matched to [**/pom.xml,**/.mvn/wrapper/maven-wrapper.properties], make sure you have checked out the target repository` )} matched to [**/pom.xml,**/.mvn/wrapper/maven-wrapper.properties,**/.mvn/extensions.xml], make sure you have checked out the target repository`
); );
}); });
it('downloads cache based on pom.xml', async () => { it('downloads cache based on pom.xml', async () => {
@@ -115,7 +115,7 @@ describe('dependency cache', () => {
expect.any(String) expect.any(String)
); );
expect(spyGlobHashFiles).toHaveBeenCalledWith( expect(spyGlobHashFiles).toHaveBeenCalledWith(
'**/pom.xml\n**/.mvn/wrapper/maven-wrapper.properties' '**/pom.xml\n**/.mvn/wrapper/maven-wrapper.properties\n**/.mvn/extensions.xml'
); );
expect(spyWarning).not.toHaveBeenCalled(); expect(spyWarning).not.toHaveBeenCalled();
expect(spyInfo).toHaveBeenCalledWith('maven cache is not found'); expect(spyInfo).toHaveBeenCalledWith('maven cache is not found');
@@ -136,7 +136,25 @@ describe('dependency cache', () => {
expect.any(String) expect.any(String)
); );
expect(spyGlobHashFiles).toHaveBeenCalledWith( expect(spyGlobHashFiles).toHaveBeenCalledWith(
'**/pom.xml\n**/.mvn/wrapper/maven-wrapper.properties' '**/pom.xml\n**/.mvn/wrapper/maven-wrapper.properties\n**/.mvn/extensions.xml'
);
expect(spyWarning).not.toHaveBeenCalled();
expect(spyInfo).toHaveBeenCalledWith('maven cache is not found');
});
it('downloads cache based on extensions.xml', async () => {
createDirectory(join(workspace, '.mvn'));
createFile(join(workspace, '.mvn', 'extensions.xml'));
await restore('maven', '');
expect(spyCacheRestore).toHaveBeenCalledWith(
[
join(os.homedir(), '.m2', 'repository'),
join(os.homedir(), '.m2', 'wrapper', 'dists')
],
expect.any(String)
);
expect(spyGlobHashFiles).toHaveBeenCalledWith(
'**/pom.xml\n**/.mvn/wrapper/maven-wrapper.properties\n**/.mvn/extensions.xml'
); );
expect(spyWarning).not.toHaveBeenCalled(); expect(spyWarning).not.toHaveBeenCalled();
expect(spyInfo).toHaveBeenCalledWith('maven cache is not found'); expect(spyInfo).toHaveBeenCalledWith('maven cache is not found');
-71
View File
@@ -243,77 +243,6 @@ describe('getVersionFromFileContent', () => {
); );
}); });
}); });
describe('.java-version', () => {
it.each([
['temurin-21.0.5', '21.0.5', 'temurin'],
['temurin-17', '17', 'temurin'], // hyphen separator is not captured into the distribution
['temurin-21.0.5+11.0.LTS', '21.0.5+11.0.LTS', 'temurin'],
['zulu64-8.0.202', '8.0.202', 'zulu'],
['temurin64-17', '17', 'temurin'],
['corretto-17', '17', 'corretto'], // corretto reduced to major version
['graalvm-community-17.0.9', '17.0.9', 'graalvm-community']
])(
'parsing %s should return version %s and distribution %s',
(content: string, expectedVersion: string, expectedDist: string) => {
const actual = getVersionFromFileContent(content, '', '.java-version');
expect(actual?.version).toBe(expectedVersion);
expect(actual?.distribution).toBe(expectedDist);
}
);
it.each([
['17.0.7', '17.0.7'],
['17', '17'],
['15-ea', '15-ea'],
['15.0.0-ea', '15.0.0-ea'],
['8.0.282+8', '8.0.282+8'],
['1.8.0.202', '8.0.202'],
['openjdk64-11.0.2', '11.0.2'] // generic openjdk prefix is not a supported distribution
])(
'parsing %s should return version %s and undefined distribution',
(content: string, expectedVersion: string) => {
const actual = getVersionFromFileContent(
content,
'temurin',
'.java-version'
);
expect(actual?.version).toBe(expectedVersion);
expect(actual?.distribution).toBeUndefined();
}
);
});
describe('.tool-versions', () => {
it.each([
['java temurin-21.0.5+11.0.LTS', '21.0.5+11.0.LTS', 'temurin'],
['java corretto-11', '11', 'corretto']
])(
'parsing %s should return version %s and distribution %s',
(content: string, expectedVersion: string, expectedDist: string) => {
const actual = getVersionFromFileContent(content, '', '.tool-versions');
expect(actual?.version).toBe(expectedVersion);
expect(actual?.distribution).toBe(expectedDist);
}
);
it.each([
['java 17', '17'],
['java 17.0.10', '17.0.10'],
['java openjdk64-17.0.10', '17.0.10'] // generic openjdk prefix is ignored
])(
'parsing %s should return version %s and undefined distribution',
(content: string, expectedVersion: string) => {
const actual = getVersionFromFileContent(
content,
'temurin',
'.tool-versions'
);
expect(actual?.version).toBe(expectedVersion);
expect(actual?.distribution).toBeUndefined();
}
);
});
}); });
describe('isGhes', () => { describe('isGhes', () => {
+1 -1
View File
@@ -10,7 +10,7 @@ inputs:
description: 'The path to a file containing the Java version to set up (.java-version, .tool-versions, .sdkmanrc). Used when java-version is not set. See examples of supported syntax in README file' description: 'The path to a file containing the Java version to set up (.java-version, .tool-versions, .sdkmanrc). Used when java-version is not set. See examples of supported syntax in README file'
required: false required: false
distribution: distribution:
description: 'Java distribution. See the list of supported distributions in README file. This input is required except when java-version-file specifies a recognized distribution (e.g., .sdkmanrc suffix java=21.0.5-tem, or .java-version/.tool-versions prefix temurin-21.0.5).' description: 'Java distribution. See the list of supported distributions in README file. This input is required except when java-version-file points to .sdkmanrc with a recognized distribution suffix (e.g., java=21.0.5-tem).'
required: false required: false
java-package: java-package:
description: 'The package type (jdk, jre, jdk+fx, jre+fx)' description: 'The package type (jdk, jre, jdk+fx, jre+fx)'
+10 -58
View File
@@ -51973,7 +51973,11 @@ const supportedPackageManager = [
(0, path_1.join)(os_1.default.homedir(), '.m2', 'wrapper', 'dists') (0, path_1.join)(os_1.default.homedir(), '.m2', 'wrapper', 'dists')
], ],
// https://github.com/actions/cache/blob/0638051e9af2c23d10bb70fa9beffcad6cff9ce3/examples.md#java---maven // https://github.com/actions/cache/blob/0638051e9af2c23d10bb70fa9beffcad6cff9ce3/examples.md#java---maven
pattern: ['**/pom.xml', '**/.mvn/wrapper/maven-wrapper.properties'] pattern: [
'**/pom.xml',
'**/.mvn/wrapper/maven-wrapper.properties',
'**/.mvn/extensions.xml'
]
}, },
{ {
id: 'gradle', id: 'gradle',
@@ -52241,7 +52245,7 @@ else {
"use strict"; "use strict";
Object.defineProperty(exports, "__esModule", ({ value: true })); Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.SUPPORTED_DISTRIBUTIONS = exports.DISTRIBUTIONS_ONLY_MAJOR_VERSION = exports.MAVEN_NO_TRANSFER_PROGRESS_LONG_FLAG = exports.MAVEN_NO_TRANSFER_PROGRESS_FLAG = exports.MAVEN_ARGS_ENV = exports.INPUT_SHOW_DOWNLOAD_PROGRESS = exports.INPUT_MVN_TOOLCHAIN_VENDOR = exports.INPUT_MVN_TOOLCHAIN_ID = exports.MVN_TOOLCHAINS_FILE = exports.MVN_SETTINGS_FILE = exports.M2_DIR = exports.STATE_GPG_PRIVATE_KEY_FINGERPRINT = exports.INPUT_JOB_STATUS = exports.INPUT_CACHE_DEPENDENCY_PATH = exports.INPUT_CACHE = exports.INPUT_DEFAULT_GPG_PASSPHRASE = exports.INPUT_DEFAULT_GPG_PRIVATE_KEY = exports.INPUT_GPG_PASSPHRASE = exports.INPUT_GPG_PRIVATE_KEY = exports.INPUT_OVERWRITE_SETTINGS = exports.INPUT_SETTINGS_PATH = exports.INPUT_SERVER_PASSWORD = exports.INPUT_SERVER_USERNAME = exports.INPUT_SERVER_ID = exports.INPUT_VERIFY_SIGNATURE_PUBLIC_KEY = exports.INPUT_VERIFY_SIGNATURE = exports.INPUT_SET_DEFAULT = exports.INPUT_CHECK_LATEST = exports.INPUT_JDK_FILE = exports.INPUT_DISTRIBUTION = exports.INPUT_JAVA_PACKAGE = exports.INPUT_ARCHITECTURE = exports.INPUT_JAVA_VERSION_FILE = exports.INPUT_JAVA_VERSION = exports.MACOS_JAVA_CONTENT_POSTFIX = void 0; exports.DISTRIBUTIONS_ONLY_MAJOR_VERSION = exports.MAVEN_NO_TRANSFER_PROGRESS_LONG_FLAG = exports.MAVEN_NO_TRANSFER_PROGRESS_FLAG = exports.MAVEN_ARGS_ENV = exports.INPUT_SHOW_DOWNLOAD_PROGRESS = exports.INPUT_MVN_TOOLCHAIN_VENDOR = exports.INPUT_MVN_TOOLCHAIN_ID = exports.MVN_TOOLCHAINS_FILE = exports.MVN_SETTINGS_FILE = exports.M2_DIR = exports.STATE_GPG_PRIVATE_KEY_FINGERPRINT = exports.INPUT_JOB_STATUS = exports.INPUT_CACHE_DEPENDENCY_PATH = exports.INPUT_CACHE = exports.INPUT_DEFAULT_GPG_PASSPHRASE = exports.INPUT_DEFAULT_GPG_PRIVATE_KEY = exports.INPUT_GPG_PASSPHRASE = exports.INPUT_GPG_PRIVATE_KEY = exports.INPUT_OVERWRITE_SETTINGS = exports.INPUT_SETTINGS_PATH = exports.INPUT_SERVER_PASSWORD = exports.INPUT_SERVER_USERNAME = exports.INPUT_SERVER_ID = exports.INPUT_VERIFY_SIGNATURE_PUBLIC_KEY = exports.INPUT_VERIFY_SIGNATURE = exports.INPUT_SET_DEFAULT = exports.INPUT_CHECK_LATEST = exports.INPUT_JDK_FILE = exports.INPUT_DISTRIBUTION = exports.INPUT_JAVA_PACKAGE = exports.INPUT_ARCHITECTURE = exports.INPUT_JAVA_VERSION_FILE = exports.INPUT_JAVA_VERSION = exports.MACOS_JAVA_CONTENT_POSTFIX = void 0;
exports.MACOS_JAVA_CONTENT_POSTFIX = 'Contents/Home'; exports.MACOS_JAVA_CONTENT_POSTFIX = 'Contents/Home';
exports.INPUT_JAVA_VERSION = 'java-version'; exports.INPUT_JAVA_VERSION = 'java-version';
exports.INPUT_JAVA_VERSION_FILE = 'java-version-file'; exports.INPUT_JAVA_VERSION_FILE = 'java-version-file';
@@ -52276,26 +52280,6 @@ exports.MAVEN_ARGS_ENV = 'MAVEN_ARGS';
exports.MAVEN_NO_TRANSFER_PROGRESS_FLAG = '-ntp'; exports.MAVEN_NO_TRANSFER_PROGRESS_FLAG = '-ntp';
exports.MAVEN_NO_TRANSFER_PROGRESS_LONG_FLAG = '--no-transfer-progress'; exports.MAVEN_NO_TRANSFER_PROGRESS_LONG_FLAG = '--no-transfer-progress';
exports.DISTRIBUTIONS_ONLY_MAJOR_VERSION = ['corretto']; exports.DISTRIBUTIONS_ONLY_MAJOR_VERSION = ['corretto'];
// Distribution names supported by the `distribution` input. Used to validate
// distribution identifiers inferred from a `.java-version`/`.tool-versions` file.
exports.SUPPORTED_DISTRIBUTIONS = [
'adopt',
'adopt-hotspot',
'adopt-openj9',
'temurin',
'zulu',
'liberica',
'microsoft',
'semeru',
'corretto',
'oracle',
'dragonwell',
'sapmachine',
'graalvm',
'graalvm-community',
'jetbrains',
'kona'
];
/***/ }), /***/ }),
@@ -52592,7 +52576,7 @@ function isCacheFeatureAvailable() {
} }
exports.isCacheFeatureAvailable = isCacheFeatureAvailable; exports.isCacheFeatureAvailable = isCacheFeatureAvailable;
function getVersionFromFileContent(content, distributionName, versionFile) { function getVersionFromFileContent(content, distributionName, versionFile) {
var _a, _b, _c, _d; var _a, _b, _c;
let javaVersionRegExp; let javaVersionRegExp;
let extractedDistribution; let extractedDistribution;
function getFileName(versionFile) { function getFileName(versionFile) {
@@ -52601,7 +52585,7 @@ function getVersionFromFileContent(content, distributionName, versionFile) {
const versionFileName = getFileName(versionFile); const versionFileName = getFileName(versionFile);
if (versionFileName == '.tool-versions') { if (versionFileName == '.tool-versions') {
javaVersionRegExp = javaVersionRegExp =
/^java\s+(?:(?<distribution>\S*?)-)?(?<version>\d+(?:\.\d+)*([+_.-](?:openj9[-._]?\d[\w.-]*|java\d+|jre[-_\w]*|OpenJDK\d+[\w_.-]*|[a-z0-9]+))*)/im; /^java\s+(?:\S*-)?(?<version>\d+(?:\.\d+)*([+_.-](?:openj9[-._]?\d[\w.-]*|java\d+|jre[-_\w]*|OpenJDK\d+[\w_.-]*|[a-z0-9]+))*)/im;
} }
else if (versionFileName == '.sdkmanrc') { else if (versionFileName == '.sdkmanrc') {
// Match both version and optional distribution identifier // Match both version and optional distribution identifier
@@ -52609,10 +52593,7 @@ function getVersionFromFileContent(content, distributionName, versionFile) {
/^java\s*=\s*(?<version>[^-\s]+)(?:-(?<distribution>[a-z0-9]+))?/m; /^java\s*=\s*(?<version>[^-\s]+)(?:-(?<distribution>[a-z0-9]+))?/m;
} }
else { else {
// .java-version (jenv), version optionally prefixed with a distribution javaVersionRegExp = /(?<version>(?<=(^|\s|-))(\d+\S*))(\s|$)/;
// identifier, e.g. `temurin-21.0.5` or `openjdk64-11.0.2`.
javaVersionRegExp =
/(?:(?<distribution>[a-zA-Z][a-zA-Z0-9-]*?)-)?(?<version>(?<=(^|\s|-))(\d+\S*))(\s|$)/;
} }
const match = content.match(javaVersionRegExp); const match = content.match(javaVersionRegExp);
const capturedVersion = ((_a = match === null || match === void 0 ? void 0 : match.groups) === null || _a === void 0 ? void 0 : _a.version) const capturedVersion = ((_a = match === null || match === void 0 ? void 0 : match.groups) === null || _a === void 0 ? void 0 : _a.version)
@@ -52624,16 +52605,6 @@ function getVersionFromFileContent(content, distributionName, versionFile) {
extractedDistribution = mapSdkmanDistribution(sdkmanDist); extractedDistribution = mapSdkmanDistribution(sdkmanDist);
core.debug(`Parsed distribution '${extractedDistribution}' from SDKMAN identifier '${sdkmanDist}'`); core.debug(`Parsed distribution '${extractedDistribution}' from SDKMAN identifier '${sdkmanDist}'`);
} }
// Extract distribution from .java-version (jenv) or .tool-versions (asdf) file
if (versionFileName != '.sdkmanrc' &&
((_c = match === null || match === void 0 ? void 0 : match.groups) === null || _c === void 0 ? void 0 : _c.distribution) &&
capturedVersion) {
const fileDistribution = match.groups.distribution;
extractedDistribution = mapJavaVersionFileDistribution(fileDistribution);
if (extractedDistribution) {
core.debug(`Parsed distribution '${extractedDistribution}' from identifier '${fileDistribution}' in '${versionFileName}'`);
}
}
core.debug(`Parsed version '${capturedVersion}' from file '${versionFileName}'`); core.debug(`Parsed version '${capturedVersion}' from file '${versionFileName}'`);
if (!capturedVersion) { if (!capturedVersion) {
return null; return null;
@@ -52650,7 +52621,7 @@ function getVersionFromFileContent(content, distributionName, versionFile) {
// Apply DISTRIBUTIONS_ONLY_MAJOR_VERSION logic whenever the effective distribution // Apply DISTRIBUTIONS_ONLY_MAJOR_VERSION logic whenever the effective distribution
// (either explicitly provided or extracted from the version file) is in the list. // (either explicitly provided or extracted from the version file) is in the list.
if (constants_1.DISTRIBUTIONS_ONLY_MAJOR_VERSION.includes(extractedDistribution || distributionName)) { if (constants_1.DISTRIBUTIONS_ONLY_MAJOR_VERSION.includes(extractedDistribution || distributionName)) {
const coerceVersion = (_d = semver.coerce(version)) !== null && _d !== void 0 ? _d : version; const coerceVersion = (_c = semver.coerce(version)) !== null && _c !== void 0 ? _c : version;
version = semver.major(coerceVersion).toString(); version = semver.major(coerceVersion).toString();
} }
return { return {
@@ -52683,25 +52654,6 @@ function mapSdkmanDistribution(sdkmanDist) {
} }
return mapped; return mapped;
} }
// Map a distribution identifier found in a `.java-version` (jenv) or
// `.tool-versions` (asdf) file to a setup-java distribution name.
// jenv-style identifiers may carry an architecture suffix (e.g. `openjdk64`,
// `temurin64`), which is stripped before matching. Identifiers that do not map
// to a supported distribution (e.g. the generic `openjdk`) are ignored so the
// `distribution` input is used instead.
function mapJavaVersionFileDistribution(identifier) {
const normalized = identifier.toLowerCase();
if (constants_1.SUPPORTED_DISTRIBUTIONS.includes(normalized)) {
return normalized;
}
// Strip a trailing architecture suffix (e.g. `temurin64` -> `temurin`).
const withoutArch = normalized.replace(/(32|64)$/, '');
if (constants_1.SUPPORTED_DISTRIBUTIONS.includes(withoutArch)) {
return withoutArch;
}
core.debug(`Distribution identifier '${identifier}' from version file is not a supported distribution; falling back to the 'distribution' input.`);
return undefined;
}
// By convention, action expects version 8 in the format `8.*` instead of `1.8` // By convention, action expects version 8 in the format `8.*` instead of `1.8`
function avoidOldNotation(content) { function avoidOldNotation(content) {
return content.startsWith('1.') ? content.substring(2) : content; return content.startsWith('1.') ? content.substring(2) : content;
+10 -58
View File
@@ -77838,7 +77838,11 @@ const supportedPackageManager = [
(0, path_1.join)(os_1.default.homedir(), '.m2', 'wrapper', 'dists') (0, path_1.join)(os_1.default.homedir(), '.m2', 'wrapper', 'dists')
], ],
// https://github.com/actions/cache/blob/0638051e9af2c23d10bb70fa9beffcad6cff9ce3/examples.md#java---maven // https://github.com/actions/cache/blob/0638051e9af2c23d10bb70fa9beffcad6cff9ce3/examples.md#java---maven
pattern: ['**/pom.xml', '**/.mvn/wrapper/maven-wrapper.properties'] pattern: [
'**/pom.xml',
'**/.mvn/wrapper/maven-wrapper.properties',
'**/.mvn/extensions.xml'
]
}, },
{ {
id: 'gradle', id: 'gradle',
@@ -78001,7 +78005,7 @@ function isProbablyGradleDaemonProblem(packageManager, error) {
"use strict"; "use strict";
Object.defineProperty(exports, "__esModule", ({ value: true })); Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.SUPPORTED_DISTRIBUTIONS = exports.DISTRIBUTIONS_ONLY_MAJOR_VERSION = exports.MAVEN_NO_TRANSFER_PROGRESS_LONG_FLAG = exports.MAVEN_NO_TRANSFER_PROGRESS_FLAG = exports.MAVEN_ARGS_ENV = exports.INPUT_SHOW_DOWNLOAD_PROGRESS = exports.INPUT_MVN_TOOLCHAIN_VENDOR = exports.INPUT_MVN_TOOLCHAIN_ID = exports.MVN_TOOLCHAINS_FILE = exports.MVN_SETTINGS_FILE = exports.M2_DIR = exports.STATE_GPG_PRIVATE_KEY_FINGERPRINT = exports.INPUT_JOB_STATUS = exports.INPUT_CACHE_DEPENDENCY_PATH = exports.INPUT_CACHE = exports.INPUT_DEFAULT_GPG_PASSPHRASE = exports.INPUT_DEFAULT_GPG_PRIVATE_KEY = exports.INPUT_GPG_PASSPHRASE = exports.INPUT_GPG_PRIVATE_KEY = exports.INPUT_OVERWRITE_SETTINGS = exports.INPUT_SETTINGS_PATH = exports.INPUT_SERVER_PASSWORD = exports.INPUT_SERVER_USERNAME = exports.INPUT_SERVER_ID = exports.INPUT_VERIFY_SIGNATURE_PUBLIC_KEY = exports.INPUT_VERIFY_SIGNATURE = exports.INPUT_SET_DEFAULT = exports.INPUT_CHECK_LATEST = exports.INPUT_JDK_FILE = exports.INPUT_DISTRIBUTION = exports.INPUT_JAVA_PACKAGE = exports.INPUT_ARCHITECTURE = exports.INPUT_JAVA_VERSION_FILE = exports.INPUT_JAVA_VERSION = exports.MACOS_JAVA_CONTENT_POSTFIX = void 0; exports.DISTRIBUTIONS_ONLY_MAJOR_VERSION = exports.MAVEN_NO_TRANSFER_PROGRESS_LONG_FLAG = exports.MAVEN_NO_TRANSFER_PROGRESS_FLAG = exports.MAVEN_ARGS_ENV = exports.INPUT_SHOW_DOWNLOAD_PROGRESS = exports.INPUT_MVN_TOOLCHAIN_VENDOR = exports.INPUT_MVN_TOOLCHAIN_ID = exports.MVN_TOOLCHAINS_FILE = exports.MVN_SETTINGS_FILE = exports.M2_DIR = exports.STATE_GPG_PRIVATE_KEY_FINGERPRINT = exports.INPUT_JOB_STATUS = exports.INPUT_CACHE_DEPENDENCY_PATH = exports.INPUT_CACHE = exports.INPUT_DEFAULT_GPG_PASSPHRASE = exports.INPUT_DEFAULT_GPG_PRIVATE_KEY = exports.INPUT_GPG_PASSPHRASE = exports.INPUT_GPG_PRIVATE_KEY = exports.INPUT_OVERWRITE_SETTINGS = exports.INPUT_SETTINGS_PATH = exports.INPUT_SERVER_PASSWORD = exports.INPUT_SERVER_USERNAME = exports.INPUT_SERVER_ID = exports.INPUT_VERIFY_SIGNATURE_PUBLIC_KEY = exports.INPUT_VERIFY_SIGNATURE = exports.INPUT_SET_DEFAULT = exports.INPUT_CHECK_LATEST = exports.INPUT_JDK_FILE = exports.INPUT_DISTRIBUTION = exports.INPUT_JAVA_PACKAGE = exports.INPUT_ARCHITECTURE = exports.INPUT_JAVA_VERSION_FILE = exports.INPUT_JAVA_VERSION = exports.MACOS_JAVA_CONTENT_POSTFIX = void 0;
exports.MACOS_JAVA_CONTENT_POSTFIX = 'Contents/Home'; exports.MACOS_JAVA_CONTENT_POSTFIX = 'Contents/Home';
exports.INPUT_JAVA_VERSION = 'java-version'; exports.INPUT_JAVA_VERSION = 'java-version';
exports.INPUT_JAVA_VERSION_FILE = 'java-version-file'; exports.INPUT_JAVA_VERSION_FILE = 'java-version-file';
@@ -78036,26 +78040,6 @@ exports.MAVEN_ARGS_ENV = 'MAVEN_ARGS';
exports.MAVEN_NO_TRANSFER_PROGRESS_FLAG = '-ntp'; exports.MAVEN_NO_TRANSFER_PROGRESS_FLAG = '-ntp';
exports.MAVEN_NO_TRANSFER_PROGRESS_LONG_FLAG = '--no-transfer-progress'; exports.MAVEN_NO_TRANSFER_PROGRESS_LONG_FLAG = '--no-transfer-progress';
exports.DISTRIBUTIONS_ONLY_MAJOR_VERSION = ['corretto']; exports.DISTRIBUTIONS_ONLY_MAJOR_VERSION = ['corretto'];
// Distribution names supported by the `distribution` input. Used to validate
// distribution identifiers inferred from a `.java-version`/`.tool-versions` file.
exports.SUPPORTED_DISTRIBUTIONS = [
'adopt',
'adopt-hotspot',
'adopt-openj9',
'temurin',
'zulu',
'liberica',
'microsoft',
'semeru',
'corretto',
'oracle',
'dragonwell',
'sapmachine',
'graalvm',
'graalvm-community',
'jetbrains',
'kona'
];
/***/ }), /***/ }),
@@ -82010,7 +81994,7 @@ function isCacheFeatureAvailable() {
} }
exports.isCacheFeatureAvailable = isCacheFeatureAvailable; exports.isCacheFeatureAvailable = isCacheFeatureAvailable;
function getVersionFromFileContent(content, distributionName, versionFile) { function getVersionFromFileContent(content, distributionName, versionFile) {
var _a, _b, _c, _d; var _a, _b, _c;
let javaVersionRegExp; let javaVersionRegExp;
let extractedDistribution; let extractedDistribution;
function getFileName(versionFile) { function getFileName(versionFile) {
@@ -82019,7 +82003,7 @@ function getVersionFromFileContent(content, distributionName, versionFile) {
const versionFileName = getFileName(versionFile); const versionFileName = getFileName(versionFile);
if (versionFileName == '.tool-versions') { if (versionFileName == '.tool-versions') {
javaVersionRegExp = javaVersionRegExp =
/^java\s+(?:(?<distribution>\S*?)-)?(?<version>\d+(?:\.\d+)*([+_.-](?:openj9[-._]?\d[\w.-]*|java\d+|jre[-_\w]*|OpenJDK\d+[\w_.-]*|[a-z0-9]+))*)/im; /^java\s+(?:\S*-)?(?<version>\d+(?:\.\d+)*([+_.-](?:openj9[-._]?\d[\w.-]*|java\d+|jre[-_\w]*|OpenJDK\d+[\w_.-]*|[a-z0-9]+))*)/im;
} }
else if (versionFileName == '.sdkmanrc') { else if (versionFileName == '.sdkmanrc') {
// Match both version and optional distribution identifier // Match both version and optional distribution identifier
@@ -82027,10 +82011,7 @@ function getVersionFromFileContent(content, distributionName, versionFile) {
/^java\s*=\s*(?<version>[^-\s]+)(?:-(?<distribution>[a-z0-9]+))?/m; /^java\s*=\s*(?<version>[^-\s]+)(?:-(?<distribution>[a-z0-9]+))?/m;
} }
else { else {
// .java-version (jenv), version optionally prefixed with a distribution javaVersionRegExp = /(?<version>(?<=(^|\s|-))(\d+\S*))(\s|$)/;
// identifier, e.g. `temurin-21.0.5` or `openjdk64-11.0.2`.
javaVersionRegExp =
/(?:(?<distribution>[a-zA-Z][a-zA-Z0-9-]*?)-)?(?<version>(?<=(^|\s|-))(\d+\S*))(\s|$)/;
} }
const match = content.match(javaVersionRegExp); const match = content.match(javaVersionRegExp);
const capturedVersion = ((_a = match === null || match === void 0 ? void 0 : match.groups) === null || _a === void 0 ? void 0 : _a.version) const capturedVersion = ((_a = match === null || match === void 0 ? void 0 : match.groups) === null || _a === void 0 ? void 0 : _a.version)
@@ -82042,16 +82023,6 @@ function getVersionFromFileContent(content, distributionName, versionFile) {
extractedDistribution = mapSdkmanDistribution(sdkmanDist); extractedDistribution = mapSdkmanDistribution(sdkmanDist);
core.debug(`Parsed distribution '${extractedDistribution}' from SDKMAN identifier '${sdkmanDist}'`); core.debug(`Parsed distribution '${extractedDistribution}' from SDKMAN identifier '${sdkmanDist}'`);
} }
// Extract distribution from .java-version (jenv) or .tool-versions (asdf) file
if (versionFileName != '.sdkmanrc' &&
((_c = match === null || match === void 0 ? void 0 : match.groups) === null || _c === void 0 ? void 0 : _c.distribution) &&
capturedVersion) {
const fileDistribution = match.groups.distribution;
extractedDistribution = mapJavaVersionFileDistribution(fileDistribution);
if (extractedDistribution) {
core.debug(`Parsed distribution '${extractedDistribution}' from identifier '${fileDistribution}' in '${versionFileName}'`);
}
}
core.debug(`Parsed version '${capturedVersion}' from file '${versionFileName}'`); core.debug(`Parsed version '${capturedVersion}' from file '${versionFileName}'`);
if (!capturedVersion) { if (!capturedVersion) {
return null; return null;
@@ -82068,7 +82039,7 @@ function getVersionFromFileContent(content, distributionName, versionFile) {
// Apply DISTRIBUTIONS_ONLY_MAJOR_VERSION logic whenever the effective distribution // Apply DISTRIBUTIONS_ONLY_MAJOR_VERSION logic whenever the effective distribution
// (either explicitly provided or extracted from the version file) is in the list. // (either explicitly provided or extracted from the version file) is in the list.
if (constants_1.DISTRIBUTIONS_ONLY_MAJOR_VERSION.includes(extractedDistribution || distributionName)) { if (constants_1.DISTRIBUTIONS_ONLY_MAJOR_VERSION.includes(extractedDistribution || distributionName)) {
const coerceVersion = (_d = semver.coerce(version)) !== null && _d !== void 0 ? _d : version; const coerceVersion = (_c = semver.coerce(version)) !== null && _c !== void 0 ? _c : version;
version = semver.major(coerceVersion).toString(); version = semver.major(coerceVersion).toString();
} }
return { return {
@@ -82101,25 +82072,6 @@ function mapSdkmanDistribution(sdkmanDist) {
} }
return mapped; return mapped;
} }
// Map a distribution identifier found in a `.java-version` (jenv) or
// `.tool-versions` (asdf) file to a setup-java distribution name.
// jenv-style identifiers may carry an architecture suffix (e.g. `openjdk64`,
// `temurin64`), which is stripped before matching. Identifiers that do not map
// to a supported distribution (e.g. the generic `openjdk`) are ignored so the
// `distribution` input is used instead.
function mapJavaVersionFileDistribution(identifier) {
const normalized = identifier.toLowerCase();
if (constants_1.SUPPORTED_DISTRIBUTIONS.includes(normalized)) {
return normalized;
}
// Strip a trailing architecture suffix (e.g. `temurin64` -> `temurin`).
const withoutArch = normalized.replace(/(32|64)$/, '');
if (constants_1.SUPPORTED_DISTRIBUTIONS.includes(withoutArch)) {
return withoutArch;
}
core.debug(`Distribution identifier '${identifier}' from version file is not a supported distribution; falling back to the 'distribution' input.`);
return undefined;
}
// By convention, action expects version 8 in the format `8.*` instead of `1.8` // By convention, action expects version 8 in the format `8.*` instead of `1.8`
function avoidOldNotation(content) { function avoidOldNotation(content) {
return content.startsWith('1.') ? content.substring(2) : content; return content.startsWith('1.') ? content.substring(2) : content;
+2 -16
View File
@@ -779,8 +779,8 @@ steps:
If the `java-version-file` input is specified, the action will extract the version from the file and install it. If the `java-version-file` input is specified, the action will extract the version from the file and install it.
Supported files are `.java-version`, `.tool-versions` and `.sdkmanrc`. Supported files are `.java-version`, `.tool-versions` and `.sdkmanrc`.
* In `.java-version` file, only the version should be specified (e.g., 17.0.7). The `.java-version` file recognizes all variants of the version description according to [jenv](https://github.com/jenv/jenv). A [supported distribution](#supported-distributions) may optionally be specified as a prefix (e.g., `temurin-17.0.7`), in which case setup-java infers the `distribution` input automatically. Generic prefixes that are not a supported distribution (e.g., `openjdk64-17.0.7`) are ignored and require setting `distribution` explicitly. * In `.java-version` file, only the version should be specified (e.g., 17.0.7). The `.java-version` file recognizes all variants of the version description according to [jenv](https://github.com/jenv/jenv).
* In `.tool-versions` file, java version should be preceded by the java keyword (e.g., java 17.0.7). The `.tool-versions` file supports version specifications in accordance with [asdf](https://github.com/asdf-vm/asdf) standards, adhering to Semantic Versioning ([semver](https://semver.org/)). As with `.java-version`, a supported distribution may be specified as a prefix (e.g., `java temurin-17.0.7`) to infer the `distribution` input automatically. * In `.tool-versions` file, java version should be preceded by the java keyword (e.g., java 17.0.7). The `.tool-versions` file supports version specifications in accordance with [asdf](https://github.com/asdf-vm/asdf) standards, adhering to Semantic Versioning ([semver](https://semver.org/)).
* In `.sdkmanrc` file, java version should be preceded by the `java=` prefix (e.g., `java=17.0.7-tem`). When a recognized SDKMAN distribution suffix is present, setup-java can infer the `distribution` input automatically. Unrecognized suffixes require setting `distribution` explicitly. The `.sdkmanrc` file supports version specifications in accordance with [file format](https://sdkman.io/usage#env-command), see [Sdkman! documentation](https://sdkman.io/jdks) for more information. * In `.sdkmanrc` file, java version should be preceded by the `java=` prefix (e.g., `java=17.0.7-tem`). When a recognized SDKMAN distribution suffix is present, setup-java can infer the `distribution` input automatically. Unrecognized suffixes require setting `distribution` explicitly. The `.sdkmanrc` file supports version specifications in accordance with [file format](https://sdkman.io/usage#env-command), see [Sdkman! documentation](https://sdkman.io/jdks) for more information.
Supported SDKMAN suffix mappings: Supported SDKMAN suffix mappings:
@@ -816,26 +816,12 @@ steps:
java=17.0.7-tem java=17.0.7-tem
``` ```
**Example step using `.java-version`** (distribution inferred from the file):
```yml
- name: Setup java
uses: actions/setup-java@v5
with:
java-version-file: '.java-version'
```
**Example `.java-version`** (distribution inferred from the `temurin-` prefix):
```
temurin-17.0.7
```
Valid entry options (does not apply to `.sdkmanrc`): Valid entry options (does not apply to `.sdkmanrc`):
``` ```
major versions: 8, 11, 16, 17, 21 major versions: 8, 11, 16, 17, 21
more specific versions: 8.0.282+8, 8.0.232, 11.0, 11.0.4, 17.0 more specific versions: 8.0.282+8, 8.0.232, 11.0, 11.0.4, 17.0
early access (EA) versions: 15-ea, 15.0.0-ea early access (EA) versions: 15-ea, 15.0.0-ea
versions with specified distribution: openjdk64-11.0.2 versions with specified distribution: openjdk64-11.0.2
versions with inferred distribution: temurin-17.0.7, corretto-21
LTS versions : temurin-21.0.5+11.0.LTS LTS versions : temurin-21.0.5+11.0.LTS
``` ```
If the file contains multiple versions, only the first one will be recognized. If the file contains multiple versions, only the first one will be recognized.
+5 -1
View File
@@ -28,7 +28,11 @@ const supportedPackageManager: PackageManager[] = [
join(os.homedir(), '.m2', 'wrapper', 'dists') join(os.homedir(), '.m2', 'wrapper', 'dists')
], ],
// https://github.com/actions/cache/blob/0638051e9af2c23d10bb70fa9beffcad6cff9ce3/examples.md#java---maven // https://github.com/actions/cache/blob/0638051e9af2c23d10bb70fa9beffcad6cff9ce3/examples.md#java---maven
pattern: ['**/pom.xml', '**/.mvn/wrapper/maven-wrapper.properties'] pattern: [
'**/pom.xml',
'**/.mvn/wrapper/maven-wrapper.properties',
'**/.mvn/extensions.xml'
]
}, },
{ {
id: 'gradle', id: 'gradle',
-21
View File
@@ -38,24 +38,3 @@ export const MAVEN_NO_TRANSFER_PROGRESS_FLAG = '-ntp';
export const MAVEN_NO_TRANSFER_PROGRESS_LONG_FLAG = '--no-transfer-progress'; export const MAVEN_NO_TRANSFER_PROGRESS_LONG_FLAG = '--no-transfer-progress';
export const DISTRIBUTIONS_ONLY_MAJOR_VERSION = ['corretto']; export const DISTRIBUTIONS_ONLY_MAJOR_VERSION = ['corretto'];
// Distribution names supported by the `distribution` input. Used to validate
// distribution identifiers inferred from a `.java-version`/`.tool-versions` file.
export const SUPPORTED_DISTRIBUTIONS = [
'adopt',
'adopt-hotspot',
'adopt-openj9',
'temurin',
'zulu',
'liberica',
'microsoft',
'semeru',
'corretto',
'oracle',
'dragonwell',
'sapmachine',
'graalvm',
'graalvm-community',
'jetbrains',
'kona'
];
+3 -52
View File
@@ -6,11 +6,7 @@ import * as cache from '@actions/cache';
import * as core from '@actions/core'; import * as core from '@actions/core';
import * as tc from '@actions/tool-cache'; import * as tc from '@actions/tool-cache';
import { import {INPUT_JOB_STATUS, DISTRIBUTIONS_ONLY_MAJOR_VERSION} from './constants';
INPUT_JOB_STATUS,
DISTRIBUTIONS_ONLY_MAJOR_VERSION,
SUPPORTED_DISTRIBUTIONS
} from './constants';
import {OutgoingHttpHeaders} from 'http'; import {OutgoingHttpHeaders} from 'http';
export function getTempDir() { export function getTempDir() {
@@ -151,16 +147,13 @@ export function getVersionFromFileContent(
const versionFileName = getFileName(versionFile); const versionFileName = getFileName(versionFile);
if (versionFileName == '.tool-versions') { if (versionFileName == '.tool-versions') {
javaVersionRegExp = javaVersionRegExp =
/^java\s+(?:(?<distribution>\S*?)-)?(?<version>\d+(?:\.\d+)*([+_.-](?:openj9[-._]?\d[\w.-]*|java\d+|jre[-_\w]*|OpenJDK\d+[\w_.-]*|[a-z0-9]+))*)/im; /^java\s+(?:\S*-)?(?<version>\d+(?:\.\d+)*([+_.-](?:openj9[-._]?\d[\w.-]*|java\d+|jre[-_\w]*|OpenJDK\d+[\w_.-]*|[a-z0-9]+))*)/im;
} else if (versionFileName == '.sdkmanrc') { } else if (versionFileName == '.sdkmanrc') {
// Match both version and optional distribution identifier // Match both version and optional distribution identifier
javaVersionRegExp = javaVersionRegExp =
/^java\s*=\s*(?<version>[^-\s]+)(?:-(?<distribution>[a-z0-9]+))?/m; /^java\s*=\s*(?<version>[^-\s]+)(?:-(?<distribution>[a-z0-9]+))?/m;
} else { } else {
// .java-version (jenv), version optionally prefixed with a distribution javaVersionRegExp = /(?<version>(?<=(^|\s|-))(\d+\S*))(\s|$)/;
// identifier, e.g. `temurin-21.0.5` or `openjdk64-11.0.2`.
javaVersionRegExp =
/(?:(?<distribution>[a-zA-Z][a-zA-Z0-9-]*?)-)?(?<version>(?<=(^|\s|-))(\d+\S*))(\s|$)/;
} }
const match = content.match(javaVersionRegExp); const match = content.match(javaVersionRegExp);
@@ -177,21 +170,6 @@ export function getVersionFromFileContent(
); );
} }
// Extract distribution from .java-version (jenv) or .tool-versions (asdf) file
if (
versionFileName != '.sdkmanrc' &&
match?.groups?.distribution &&
capturedVersion
) {
const fileDistribution = match.groups.distribution;
extractedDistribution = mapJavaVersionFileDistribution(fileDistribution);
if (extractedDistribution) {
core.debug(
`Parsed distribution '${extractedDistribution}' from identifier '${fileDistribution}' in '${versionFileName}'`
);
}
}
core.debug( core.debug(
`Parsed version '${capturedVersion}' from file '${versionFileName}'` `Parsed version '${capturedVersion}' from file '${versionFileName}'`
); );
@@ -257,33 +235,6 @@ function mapSdkmanDistribution(sdkmanDist: string): string | undefined {
return mapped; return mapped;
} }
// Map a distribution identifier found in a `.java-version` (jenv) or
// `.tool-versions` (asdf) file to a setup-java distribution name.
// jenv-style identifiers may carry an architecture suffix (e.g. `openjdk64`,
// `temurin64`), which is stripped before matching. Identifiers that do not map
// to a supported distribution (e.g. the generic `openjdk`) are ignored so the
// `distribution` input is used instead.
function mapJavaVersionFileDistribution(
identifier: string
): string | undefined {
const normalized = identifier.toLowerCase();
if (SUPPORTED_DISTRIBUTIONS.includes(normalized)) {
return normalized;
}
// Strip a trailing architecture suffix (e.g. `temurin64` -> `temurin`).
const withoutArch = normalized.replace(/(32|64)$/, '');
if (SUPPORTED_DISTRIBUTIONS.includes(withoutArch)) {
return withoutArch;
}
core.debug(
`Distribution identifier '${identifier}' from version file is not a supported distribution; falling back to the 'distribution' input.`
);
return undefined;
}
// By convention, action expects version 8 in the format `8.*` instead of `1.8` // By convention, action expects version 8 in the format `8.*` instead of `1.8`
function avoidOldNotation(content: string): string { function avoidOldNotation(content: string): string {
return content.startsWith('1.') ? content.substring(2) : content; return content.startsWith('1.') ? content.substring(2) : content;