From 9dcc30b660e530826de8c32636f794c6c4737e89 Mon Sep 17 00:00:00 2001 From: Michael Sverdlov Date: Sun, 6 Sep 2026 14:14:55 +0300 Subject: [PATCH 1/7] Document that latest plus download-repository can return a stale CLI. Log an info line for that combination so CI can pin a version instead of relying on generic [RELEASE]. --- README.md | 13 ++++++++++++- action.yml | 4 ++-- lib/utils.js | 18 +++++++++++++++--- src/utils.ts | 14 ++++++++++++++ test/main.spec.ts | 21 +++++++++++++++++++++ 5 files changed, 64 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index cfbe42b42..424cc017a 100644 --- a/README.md +++ b/README.md @@ -289,6 +289,8 @@ It is also possible to set the latest JFrog CLI version by adding the _version_ version: latest ``` +Do not combine `version: latest` with `download-repository`. See [Downloading JFrog CLI from Artifactory](#downloading-jfrog-cli-from-artifactory). + | Important: Only JFrog CLI versions 1.46.4 or above are supported. | |-------------------------------------------------------------------| @@ -417,10 +419,18 @@ In this example, each job builds and publishes a different service from the mono If your agent has no Internet access, you can configure the workflow to download JFrog CLI from a [remote repository](https://www.jfrog.com/confluence/display/JFROG/Remote+Repositories) in your JFrog Artifactory, which is configured to proxy the official download URL. +> [!NOTE] +> When `download-repository` is set, pin `version` to a concrete `X.Y.Z`. +> `latest` is requested as Artifactory `[RELEASE]`. That token is for Maven, not a generic remote of `https://releases.jfrog.io/artifactory/jfrog-cli`. +> You can get an old cached binary for this OS/arch, not the newest CLI. +> +> Air-gapped jobs: pin the version (or pre-cache that exact path). +> Jobs that can reach the internet and want newest: omit `download-repository`. + Here's how you do this: 1. Create a remote repository in Artifactory. Name the repository jfrog-cli-remote and set its URL to https://releases.jfrog.io/artifactory/jfrog-cli/ -2. Set _download-repository_ input to jfrog-cli-remote: +2. Set _download-repository_ input to jfrog-cli-remote and pin `version`: ```yml - uses: jfrog/setup-jfrog-cli@v4 @@ -430,6 +440,7 @@ Here's how you do this: JF_ACCESS_TOKEN: ${{ secrets.JF_ACCESS_TOKEN }} with: + version: X.Y.Z download-repository: jfrog-cli-remote ``` diff --git a/action.yml b/action.yml index d1a779ea2..1bfb4321b 100644 --- a/action.yml +++ b/action.yml @@ -3,11 +3,11 @@ description: "Install and configure JFrog CLI." author: "JFrog" inputs: version: - description: "JFrog CLI Version" + description: "JFrog CLI Version. When using download-repository, pin to X.Y.Z; do not use latest." default: "2.91.0" required: false download-repository: - description: "Remote repository in Artifactory pointing to 'https://releases.jfrog.io/artifactory/jfrog-cli'. Use this parameter in case you don't have an Internet access." + description: "Remote repository in Artifactory pointing to 'https://releases.jfrog.io/artifactory/jfrog-cli'. Use this parameter in case you don't have an Internet access. Pin version to X.Y.Z; latest maps to [RELEASE] and can return a stale cached binary on a generic remote." required: false oidc-provider-name: description: "Provider Name's value that was set in OpenId Connect integration in the JFrog platform." diff --git a/lib/utils.js b/lib/utils.js index 3e3f302ca..253935cd2 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -82,6 +82,7 @@ class Utils { let version = core.getInput(Utils.CLI_VERSION_ARG); let cliRemote = core.getInput(Utils.CLI_REMOTE_ARG); const isLatestVer = version === Utils.LATEST_CLI_VERSION; + Utils.logIfLatestDownloadedFromRemote(version, cliRemote); if (!isLatestVer && (0, semver_1.lt)(version, this.MIN_CLI_VERSION)) { throw new Error('Requested to download JFrog CLI version ' + version + ' but must be at least ' + this.MIN_CLI_VERSION); } @@ -168,6 +169,15 @@ class Utils { } return `${artifactoryUrl}/${downloadDetails.repository}/v${major}/${version}/${architecture}/${fileName}`; } + /** + * Log when latest is resolved through an Artifactory remote. + * Generic remotes do not treat [RELEASE] as newest-on-origin; pin a concrete version instead. + */ + static logIfLatestDownloadedFromRemote(version, cliRemote) { + if (cliRemote && version === Utils.LATEST_CLI_VERSION) { + core.info(Utils.LATEST_FROM_REMOTE_INFO); + } + } // Get Config Tokens created on your local machine using JFrog CLI. // The Tokens configured with JF_ENV_ environment variables. static getConfigTokens() { @@ -470,7 +480,7 @@ class Utils { } /** * If enable-package-alias is true and GITHUB_PATH is set, runs `jf package-alias install` - * and appends the alias bin directory to GITHUB_PATH so subsequent steps intercept mvn, npm, go, etc. + * and adds the alias bin directory to PATH via core.addPath so subsequent steps intercept mvn, npm, go, etc. * On failure (e.g. older CLI without package-alias), logs a warning and does not fail the job. */ static setupPackageAliasIfRequested() { @@ -513,8 +523,8 @@ class Utils { return; } const aliasBinDir = Utils.getPackageAliasBinDir(); - (0, fs_1.appendFileSync)(githubPath, aliasBinDir + '\n'); - core.info('Package aliases installed and "' + aliasBinDir + '" appended to GITHUB_PATH.'); + core.addPath(aliasBinDir); + core.info('Package aliases installed and "' + aliasBinDir + '" added to PATH.'); }); } } @@ -534,6 +544,8 @@ Utils.MIN_CLI_VERSION = '1.46.4'; Utils.LATEST_CLI_VERSION = 'latest'; // The value in the download URL to set to get the latest version Utils.LATEST_RELEASE_VERSION = '[RELEASE]'; +// Logged when version=latest is downloaded through an Artifactory remote (generic [RELEASE] is not newest-on-origin) +Utils.LATEST_FROM_REMOTE_INFO = 'download-repository is set with version=latest. The CLI is fetched as Artifactory [RELEASE] on a generic remote, which can return an old cached binary for this OS/arch instead of the newest CLI. Pin version to X.Y.Z, or omit download-repository if the runner can reach releases.jfrog.io.'; // Placeholder CLI version to use to keep 'latest' in cache. Utils.LATEST_SEMVER = '100.100.100'; // The default server id name for separate env config diff --git a/src/utils.ts b/src/utils.ts index 8ff668407..1f01e232a 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -30,6 +30,9 @@ export class Utils { public static readonly LATEST_CLI_VERSION: string = 'latest'; // The value in the download URL to set to get the latest version private static readonly LATEST_RELEASE_VERSION: string = '[RELEASE]'; + // Logged when version=latest is downloaded through an Artifactory remote (generic [RELEASE] is not newest-on-origin) + public static readonly LATEST_FROM_REMOTE_INFO: string = + 'download-repository is set with version=latest. The CLI is fetched as Artifactory [RELEASE] on a generic remote, which can return an old cached binary for this OS/arch instead of the newest CLI. Pin version to X.Y.Z, or omit download-repository if the runner can reach releases.jfrog.io.'; // Placeholder CLI version to use to keep 'latest' in cache. public static readonly LATEST_SEMVER: string = '100.100.100'; // The default server id name for separate env config @@ -108,6 +111,7 @@ export class Utils { let version: string = core.getInput(Utils.CLI_VERSION_ARG); let cliRemote: string = core.getInput(Utils.CLI_REMOTE_ARG); const isLatestVer: boolean = version === Utils.LATEST_CLI_VERSION; + Utils.logIfLatestDownloadedFromRemote(version, cliRemote); if (!isLatestVer && lt(version, this.MIN_CLI_VERSION)) { throw new Error('Requested to download JFrog CLI version ' + version + ' but must be at least ' + this.MIN_CLI_VERSION); @@ -199,6 +203,16 @@ export class Utils { return `${artifactoryUrl}/${downloadDetails.repository}/v${major}/${version}/${architecture}/${fileName}`; } + /** + * Log when latest is resolved through an Artifactory remote. + * Generic remotes do not treat [RELEASE] as newest-on-origin; pin a concrete version instead. + */ + public static logIfLatestDownloadedFromRemote(version: string, cliRemote: string): void { + if (cliRemote && version === Utils.LATEST_CLI_VERSION) { + core.info(Utils.LATEST_FROM_REMOTE_INFO); + } + } + // Get Config Tokens created on your local machine using JFrog CLI. // The Tokens configured with JF_ENV_ environment variables. public static getConfigTokens(): Set { diff --git a/test/main.spec.ts b/test/main.spec.ts index 19258b5a4..3ad46d998 100644 --- a/test/main.spec.ts +++ b/test/main.spec.ts @@ -259,6 +259,27 @@ describe('JFrog CLI V2 URL Tests', () => { }); }); +describe('logIfLatestDownloadedFromRemote', () => { + beforeEach(() => { + (core.info as jest.Mock).mockClear(); + }); + + test('Logs info when latest is downloaded from a remote repository', () => { + Utils.logIfLatestDownloadedFromRemote(Utils.LATEST_CLI_VERSION, 'jfrog-cli-remote'); + expect(core.info).toHaveBeenCalledWith(Utils.LATEST_FROM_REMOTE_INFO); + }); + + test('Does not log when version is pinned', () => { + Utils.logIfLatestDownloadedFromRemote('2.91.0', 'jfrog-cli-remote'); + expect(core.info).not.toHaveBeenCalled(); + }); + + test('Does not log when download-repository is unset', () => { + Utils.logIfLatestDownloadedFromRemote(Utils.LATEST_CLI_VERSION, ''); + expect(core.info).not.toHaveBeenCalled(); + }); +}); + test('Extract download details Tests', () => { for (let config of [V1_CONFIG, V2_CONFIG]) { process.env.JF_ENV_LOCAL = config; From cb0b2b1c49131dd69844d660bacb8cdd60341718 Mon Sep 17 00:00:00 2001 From: Michael Sverdlov Date: Sun, 6 Sep 2026 14:25:03 +0300 Subject: [PATCH 2/7] Clarify non-failing latest repository guidance --- README.md | 6 +++--- action.yml | 2 +- lib/utils.js | 14 +++++++------- src/utils.ts | 8 ++++---- test/main.spec.ts | 2 +- 5 files changed, 16 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 424cc017a..3eec176de 100644 --- a/README.md +++ b/README.md @@ -421,10 +421,10 @@ If your agent has no Internet access, you can configure the workflow to download > [!NOTE] > When `download-repository` is set, pin `version` to a concrete `X.Y.Z`. -> `latest` is requested as Artifactory `[RELEASE]`. That token is for Maven, not a generic remote of `https://releases.jfrog.io/artifactory/jfrog-cli`. -> You can get an old cached binary for this OS/arch, not the newest CLI. +> `latest` is requested as Artifactory `[RELEASE]`. That token is for Maven, not a generic repository. +> A remote proxy can return an old cached binary for this OS/arch. A fully air-gapped local repository can only return a CLI version that its administrators imported. > -> Air-gapped jobs: pin the version (or pre-cache that exact path). +> Air-gapped jobs: import the required CLI and pin its version. > Jobs that can reach the internet and want newest: omit `download-repository`. Here's how you do this: diff --git a/action.yml b/action.yml index 1bfb4321b..f68c18e01 100644 --- a/action.yml +++ b/action.yml @@ -7,7 +7,7 @@ inputs: default: "2.91.0" required: false download-repository: - description: "Remote repository in Artifactory pointing to 'https://releases.jfrog.io/artifactory/jfrog-cli'. Use this parameter in case you don't have an Internet access. Pin version to X.Y.Z; latest maps to [RELEASE] and can return a stale cached binary on a generic remote." + description: "Artifactory repository containing JFrog CLI, such as a remote pointing to 'https://releases.jfrog.io/artifactory/jfrog-cli' or a manually populated air-gap repository. Pin version to X.Y.Z; latest maps to [RELEASE] and can return an old available or cached binary from a generic repository." required: false oidc-provider-name: description: "Provider Name's value that was set in OpenId Connect integration in the JFrog platform." diff --git a/lib/utils.js b/lib/utils.js index 253935cd2..8b4c74044 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -170,8 +170,8 @@ class Utils { return `${artifactoryUrl}/${downloadDetails.repository}/v${major}/${version}/${architecture}/${fileName}`; } /** - * Log when latest is resolved through an Artifactory remote. - * Generic remotes do not treat [RELEASE] as newest-on-origin; pin a concrete version instead. + * Log when latest is resolved through an Artifactory repository. + * Generic repositories do not treat [RELEASE] as newest-on-origin; pin a concrete version instead. */ static logIfLatestDownloadedFromRemote(version, cliRemote) { if (cliRemote && version === Utils.LATEST_CLI_VERSION) { @@ -480,7 +480,7 @@ class Utils { } /** * If enable-package-alias is true and GITHUB_PATH is set, runs `jf package-alias install` - * and adds the alias bin directory to PATH via core.addPath so subsequent steps intercept mvn, npm, go, etc. + * and appends the alias bin directory to GITHUB_PATH so subsequent steps intercept mvn, npm, go, etc. * On failure (e.g. older CLI without package-alias), logs a warning and does not fail the job. */ static setupPackageAliasIfRequested() { @@ -523,8 +523,8 @@ class Utils { return; } const aliasBinDir = Utils.getPackageAliasBinDir(); - core.addPath(aliasBinDir); - core.info('Package aliases installed and "' + aliasBinDir + '" added to PATH.'); + (0, fs_1.appendFileSync)(githubPath, aliasBinDir + '\n'); + core.info('Package aliases installed and "' + aliasBinDir + '" appended to GITHUB_PATH.'); }); } } @@ -544,8 +544,8 @@ Utils.MIN_CLI_VERSION = '1.46.4'; Utils.LATEST_CLI_VERSION = 'latest'; // The value in the download URL to set to get the latest version Utils.LATEST_RELEASE_VERSION = '[RELEASE]'; -// Logged when version=latest is downloaded through an Artifactory remote (generic [RELEASE] is not newest-on-origin) -Utils.LATEST_FROM_REMOTE_INFO = 'download-repository is set with version=latest. The CLI is fetched as Artifactory [RELEASE] on a generic remote, which can return an old cached binary for this OS/arch instead of the newest CLI. Pin version to X.Y.Z, or omit download-repository if the runner can reach releases.jfrog.io.'; +// Logged when version=latest is downloaded through an Artifactory repository (generic [RELEASE] is not newest-on-origin) +Utils.LATEST_FROM_REMOTE_INFO = 'download-repository is set with version=latest. The CLI is fetched as Artifactory [RELEASE] from a generic repository, which can return an old available or cached binary for this OS/arch instead of the newest CLI. Pin version to X.Y.Z, or omit download-repository if the runner can reach releases.jfrog.io.'; // Placeholder CLI version to use to keep 'latest' in cache. Utils.LATEST_SEMVER = '100.100.100'; // The default server id name for separate env config diff --git a/src/utils.ts b/src/utils.ts index 1f01e232a..313c70d00 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -30,9 +30,9 @@ export class Utils { public static readonly LATEST_CLI_VERSION: string = 'latest'; // The value in the download URL to set to get the latest version private static readonly LATEST_RELEASE_VERSION: string = '[RELEASE]'; - // Logged when version=latest is downloaded through an Artifactory remote (generic [RELEASE] is not newest-on-origin) + // Logged when version=latest is downloaded through an Artifactory repository (generic [RELEASE] is not newest-on-origin) public static readonly LATEST_FROM_REMOTE_INFO: string = - 'download-repository is set with version=latest. The CLI is fetched as Artifactory [RELEASE] on a generic remote, which can return an old cached binary for this OS/arch instead of the newest CLI. Pin version to X.Y.Z, or omit download-repository if the runner can reach releases.jfrog.io.'; + 'download-repository is set with version=latest. The CLI is fetched as Artifactory [RELEASE] from a generic repository, which can return an old available or cached binary for this OS/arch instead of the newest CLI. Pin version to X.Y.Z, or omit download-repository if the runner can reach releases.jfrog.io.'; // Placeholder CLI version to use to keep 'latest' in cache. public static readonly LATEST_SEMVER: string = '100.100.100'; // The default server id name for separate env config @@ -204,8 +204,8 @@ export class Utils { } /** - * Log when latest is resolved through an Artifactory remote. - * Generic remotes do not treat [RELEASE] as newest-on-origin; pin a concrete version instead. + * Log when latest is resolved through an Artifactory repository. + * Generic repositories do not treat [RELEASE] as newest-on-origin; pin a concrete version instead. */ public static logIfLatestDownloadedFromRemote(version: string, cliRemote: string): void { if (cliRemote && version === Utils.LATEST_CLI_VERSION) { diff --git a/test/main.spec.ts b/test/main.spec.ts index 3ad46d998..58c511ea4 100644 --- a/test/main.spec.ts +++ b/test/main.spec.ts @@ -264,7 +264,7 @@ describe('logIfLatestDownloadedFromRemote', () => { (core.info as jest.Mock).mockClear(); }); - test('Logs info when latest is downloaded from a remote repository', () => { + test('Logs info when latest is downloaded from an Artifactory repository', () => { Utils.logIfLatestDownloadedFromRemote(Utils.LATEST_CLI_VERSION, 'jfrog-cli-remote'); expect(core.info).toHaveBeenCalledWith(Utils.LATEST_FROM_REMOTE_INFO); }); From 714050be4de825ebcedc4df696144dd2d4b487d9 Mon Sep 17 00:00:00 2001 From: Michael Sverdlov Date: Sun, 6 Sep 2026 14:43:58 +0300 Subject: [PATCH 3/7] Explain the cached [RELEASE] path behavior --- README.md | 7 +++---- action.yml | 2 +- lib/utils.js | 7 ++++--- src/utils.ts | 7 ++++--- 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 3eec176de..364929585 100644 --- a/README.md +++ b/README.md @@ -421,11 +421,10 @@ If your agent has no Internet access, you can configure the workflow to download > [!NOTE] > When `download-repository` is set, pin `version` to a concrete `X.Y.Z`. -> `latest` is requested as Artifactory `[RELEASE]`. That token is for Maven, not a generic repository. -> A remote proxy can return an old cached binary for this OS/arch. A fully air-gapped local repository can only return a CLI version that its administrators imported. > -> Air-gapped jobs: import the required CLI and pin its version. -> Jobs that can reach the internet and want newest: omit `download-repository`. +> `latest` is requested as `[RELEASE]`, which is part of the artifact path (`v2/[RELEASE]/...`). A remote repository caches whatever it resolved under that exact path, so the first version downloaded through it keeps being served as `latest` until the cache is revalidated — which may never happen. Caching works well for a concrete version such as `2.123.0`; it does not work for a moving tag. +> +> Jobs that can reach the internet and want the newest CLI: omit `download-repository`. Here's how you do this: diff --git a/action.yml b/action.yml index f68c18e01..937bc8d54 100644 --- a/action.yml +++ b/action.yml @@ -7,7 +7,7 @@ inputs: default: "2.91.0" required: false download-repository: - description: "Artifactory repository containing JFrog CLI, such as a remote pointing to 'https://releases.jfrog.io/artifactory/jfrog-cli' or a manually populated air-gap repository. Pin version to X.Y.Z; latest maps to [RELEASE] and can return an old available or cached binary from a generic repository." + description: "Remote repository in Artifactory pointing to 'https://releases.jfrog.io/artifactory/jfrog-cli'. Use this parameter in case you don't have an Internet access. Pin version to X.Y.Z; latest is requested as the path v2/[RELEASE], and the repository caches that path, so it keeps serving the first version it resolved." required: false oidc-provider-name: description: "Provider Name's value that was set in OpenId Connect integration in the JFrog platform." diff --git a/lib/utils.js b/lib/utils.js index 8b4c74044..189622e41 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -171,7 +171,8 @@ class Utils { } /** * Log when latest is resolved through an Artifactory repository. - * Generic repositories do not treat [RELEASE] as newest-on-origin; pin a concrete version instead. + * [RELEASE] is part of the artifact path, so a remote repository caches it like any other file + * and keeps serving the first version it resolved. Pin a concrete version instead. */ static logIfLatestDownloadedFromRemote(version, cliRemote) { if (cliRemote && version === Utils.LATEST_CLI_VERSION) { @@ -544,8 +545,8 @@ Utils.MIN_CLI_VERSION = '1.46.4'; Utils.LATEST_CLI_VERSION = 'latest'; // The value in the download URL to set to get the latest version Utils.LATEST_RELEASE_VERSION = '[RELEASE]'; -// Logged when version=latest is downloaded through an Artifactory repository (generic [RELEASE] is not newest-on-origin) -Utils.LATEST_FROM_REMOTE_INFO = 'download-repository is set with version=latest. The CLI is fetched as Artifactory [RELEASE] from a generic repository, which can return an old available or cached binary for this OS/arch instead of the newest CLI. Pin version to X.Y.Z, or omit download-repository if the runner can reach releases.jfrog.io.'; +// Logged when version=latest is downloaded through a remote repository, which caches the literal [RELEASE] path +Utils.LATEST_FROM_REMOTE_INFO = 'download-repository is set with version=latest, so the CLI is requested from the path v2/[RELEASE]. A remote repository caches whatever it resolved under that exact path, so the first version downloaded through it stays in place until the cache is revalidated, which may never happen. Pin version to X.Y.Z, or omit download-repository if the runner can reach releases.jfrog.io.'; // Placeholder CLI version to use to keep 'latest' in cache. Utils.LATEST_SEMVER = '100.100.100'; // The default server id name for separate env config diff --git a/src/utils.ts b/src/utils.ts index 313c70d00..6dbe48131 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -30,9 +30,9 @@ export class Utils { public static readonly LATEST_CLI_VERSION: string = 'latest'; // The value in the download URL to set to get the latest version private static readonly LATEST_RELEASE_VERSION: string = '[RELEASE]'; - // Logged when version=latest is downloaded through an Artifactory repository (generic [RELEASE] is not newest-on-origin) + // Logged when version=latest is downloaded through a remote repository, which caches the literal [RELEASE] path public static readonly LATEST_FROM_REMOTE_INFO: string = - 'download-repository is set with version=latest. The CLI is fetched as Artifactory [RELEASE] from a generic repository, which can return an old available or cached binary for this OS/arch instead of the newest CLI. Pin version to X.Y.Z, or omit download-repository if the runner can reach releases.jfrog.io.'; + 'download-repository is set with version=latest, so the CLI is requested from the path v2/[RELEASE]. A remote repository caches whatever it resolved under that exact path, so the first version downloaded through it stays in place until the cache is revalidated, which may never happen. Pin version to X.Y.Z, or omit download-repository if the runner can reach releases.jfrog.io.'; // Placeholder CLI version to use to keep 'latest' in cache. public static readonly LATEST_SEMVER: string = '100.100.100'; // The default server id name for separate env config @@ -205,7 +205,8 @@ export class Utils { /** * Log when latest is resolved through an Artifactory repository. - * Generic repositories do not treat [RELEASE] as newest-on-origin; pin a concrete version instead. + * [RELEASE] is part of the artifact path, so a remote repository caches it like any other file + * and keeps serving the first version it resolved. Pin a concrete version instead. */ public static logIfLatestDownloadedFromRemote(version: string, cliRemote: string): void { if (cliRemote && version === Utils.LATEST_CLI_VERSION) { From bf477dc71350930fb66a4745737b44ffa92e5cee Mon Sep 17 00:00:00 2001 From: Michael Sverdlov Date: Sun, 6 Sep 2026 14:51:35 +0300 Subject: [PATCH 4/7] Soften version guidance and cite Artifactory cache behavior --- README.md | 4 ++-- action.yml | 4 ++-- lib/utils.js | 2 +- src/utils.ts | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 364929585..32b410744 100644 --- a/README.md +++ b/README.md @@ -420,9 +420,9 @@ In this example, each job builds and publishes a different service from the mono If your agent has no Internet access, you can configure the workflow to download JFrog CLI from a [remote repository](https://www.jfrog.com/confluence/display/JFROG/Remote+Repositories) in your JFrog Artifactory, which is configured to proxy the official download URL. > [!NOTE] -> When `download-repository` is set, pin `version` to a concrete `X.Y.Z`. +> With `download-repository`, prefer a concrete `version: X.Y.Z` over `latest`. > -> `latest` is requested as `[RELEASE]`, which is part of the artifact path (`v2/[RELEASE]/...`). A remote repository caches whatever it resolved under that exact path, so the first version downloaded through it keeps being served as `latest` until the cache is revalidated — which may never happen. Caching works well for a concrete version such as `2.123.0`; it does not work for a moving tag. +> `latest` is requested as `[RELEASE]`, which is part of the artifact path (`v2/[RELEASE]/jfrog-cli-.../jfrog`). If the remote repository has **Store Artifacts Locally** enabled (the default), the binary it resolved is cached under that literal path. Artifactory expires cached *metadata* files such as `maven-metadata.xml`, and a CLI binary is not one of them, so the first version resolved through the repository can keep being served as `latest`. Zapping the cache does not help either, because it invalidates metadata and not binaries. A concrete version avoids this: each version is cached under its own immutable path. > > Jobs that can reach the internet and want the newest CLI: omit `download-repository`. diff --git a/action.yml b/action.yml index 937bc8d54..274a07a93 100644 --- a/action.yml +++ b/action.yml @@ -3,11 +3,11 @@ description: "Install and configure JFrog CLI." author: "JFrog" inputs: version: - description: "JFrog CLI Version. When using download-repository, pin to X.Y.Z; do not use latest." + description: "JFrog CLI Version. A concrete X.Y.Z is recommended when download-repository points to a remote repository that stores artifacts locally, since latest is requested as the v2/[RELEASE] path and can be served from cache." default: "2.91.0" required: false download-repository: - description: "Remote repository in Artifactory pointing to 'https://releases.jfrog.io/artifactory/jfrog-cli'. Use this parameter in case you don't have an Internet access. Pin version to X.Y.Z; latest is requested as the path v2/[RELEASE], and the repository caches that path, so it keeps serving the first version it resolved." + description: "Remote repository in Artifactory pointing to 'https://releases.jfrog.io/artifactory/jfrog-cli'. Use this parameter in case you don't have an Internet access. When the repository stores artifacts locally, prefer a concrete version over latest: latest is requested as the v2/[RELEASE] path, and the cached binary under that path is not treated as expirable metadata." required: false oidc-provider-name: description: "Provider Name's value that was set in OpenId Connect integration in the JFrog platform." diff --git a/lib/utils.js b/lib/utils.js index 189622e41..701737de8 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -546,7 +546,7 @@ Utils.LATEST_CLI_VERSION = 'latest'; // The value in the download URL to set to get the latest version Utils.LATEST_RELEASE_VERSION = '[RELEASE]'; // Logged when version=latest is downloaded through a remote repository, which caches the literal [RELEASE] path -Utils.LATEST_FROM_REMOTE_INFO = 'download-repository is set with version=latest, so the CLI is requested from the path v2/[RELEASE]. A remote repository caches whatever it resolved under that exact path, so the first version downloaded through it stays in place until the cache is revalidated, which may never happen. Pin version to X.Y.Z, or omit download-repository if the runner can reach releases.jfrog.io.'; +Utils.LATEST_FROM_REMOTE_INFO = 'download-repository is set with version=latest, so the CLI is requested from the path v2/[RELEASE]. If that repository stores artifacts locally, the binary cached under this path is not expirable metadata, so the first version resolved through it can keep being served as latest. Use a concrete version to get a per-version cache path, or omit download-repository if the runner can reach releases.jfrog.io.'; // Placeholder CLI version to use to keep 'latest' in cache. Utils.LATEST_SEMVER = '100.100.100'; // The default server id name for separate env config diff --git a/src/utils.ts b/src/utils.ts index 6dbe48131..ea3c2ee88 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -32,7 +32,7 @@ export class Utils { private static readonly LATEST_RELEASE_VERSION: string = '[RELEASE]'; // Logged when version=latest is downloaded through a remote repository, which caches the literal [RELEASE] path public static readonly LATEST_FROM_REMOTE_INFO: string = - 'download-repository is set with version=latest, so the CLI is requested from the path v2/[RELEASE]. A remote repository caches whatever it resolved under that exact path, so the first version downloaded through it stays in place until the cache is revalidated, which may never happen. Pin version to X.Y.Z, or omit download-repository if the runner can reach releases.jfrog.io.'; + 'download-repository is set with version=latest, so the CLI is requested from the path v2/[RELEASE]. If that repository stores artifacts locally, the binary cached under this path is not expirable metadata, so the first version resolved through it can keep being served as latest. Use a concrete version to get a per-version cache path, or omit download-repository if the runner can reach releases.jfrog.io.'; // Placeholder CLI version to use to keep 'latest' in cache. public static readonly LATEST_SEMVER: string = '100.100.100'; // The default server id name for separate env config From d4a5ce2320eb9f397d0f1e39189d10963347b185 Mon Sep 17 00:00:00 2001 From: Michael Sverdlov Date: Sun, 6 Sep 2026 15:05:44 +0300 Subject: [PATCH 5/7] Drop metadata wording from latest+download-repository docs --- README.md | 2 +- action.yml | 2 +- lib/utils.js | 8 ++++---- src/utils.ts | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 32b410744..64be2c7c6 100644 --- a/README.md +++ b/README.md @@ -422,7 +422,7 @@ If your agent has no Internet access, you can configure the workflow to download > [!NOTE] > With `download-repository`, prefer a concrete `version: X.Y.Z` over `latest`. > -> `latest` is requested as `[RELEASE]`, which is part of the artifact path (`v2/[RELEASE]/jfrog-cli-.../jfrog`). If the remote repository has **Store Artifacts Locally** enabled (the default), the binary it resolved is cached under that literal path. Artifactory expires cached *metadata* files such as `maven-metadata.xml`, and a CLI binary is not one of them, so the first version resolved through the repository can keep being served as `latest`. Zapping the cache does not help either, because it invalidates metadata and not binaries. A concrete version avoids this: each version is cached under its own immutable path. +> `latest` is not resolved to a version number. It becomes the literal path segment `[RELEASE]` in the download URL (`v2/[RELEASE]/jfrog-cli-.../jfrog`), and a generic repository serves that path like any other. If the repository has **Store Artifacts Locally** enabled (the default), the binary returned for that path is cached under it, so later runs can keep receiving that same binary instead of a newer CLI. A concrete version avoids this, because every version has its own immutable path. > > Jobs that can reach the internet and want the newest CLI: omit `download-repository`. diff --git a/action.yml b/action.yml index 274a07a93..5e17f6127 100644 --- a/action.yml +++ b/action.yml @@ -7,7 +7,7 @@ inputs: default: "2.91.0" required: false download-repository: - description: "Remote repository in Artifactory pointing to 'https://releases.jfrog.io/artifactory/jfrog-cli'. Use this parameter in case you don't have an Internet access. When the repository stores artifacts locally, prefer a concrete version over latest: latest is requested as the v2/[RELEASE] path, and the cached binary under that path is not treated as expirable metadata." + description: "Remote repository in Artifactory pointing to 'https://releases.jfrog.io/artifactory/jfrog-cli'. Use this parameter in case you don't have an Internet access. When the repository stores artifacts locally, prefer a concrete version over latest: latest is requested as the literal v2/[RELEASE] path, and the binary cached under that path can keep being served on later runs." required: false oidc-provider-name: description: "Provider Name's value that was set in OpenId Connect integration in the JFrog platform." diff --git a/lib/utils.js b/lib/utils.js index 701737de8..0cb235467 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -481,7 +481,7 @@ class Utils { } /** * If enable-package-alias is true and GITHUB_PATH is set, runs `jf package-alias install` - * and appends the alias bin directory to GITHUB_PATH so subsequent steps intercept mvn, npm, go, etc. + * and adds the alias bin directory to PATH via core.addPath so subsequent steps intercept mvn, npm, go, etc. * On failure (e.g. older CLI without package-alias), logs a warning and does not fail the job. */ static setupPackageAliasIfRequested() { @@ -524,8 +524,8 @@ class Utils { return; } const aliasBinDir = Utils.getPackageAliasBinDir(); - (0, fs_1.appendFileSync)(githubPath, aliasBinDir + '\n'); - core.info('Package aliases installed and "' + aliasBinDir + '" appended to GITHUB_PATH.'); + core.addPath(aliasBinDir); + core.info('Package aliases installed and "' + aliasBinDir + '" added to PATH.'); }); } } @@ -546,7 +546,7 @@ Utils.LATEST_CLI_VERSION = 'latest'; // The value in the download URL to set to get the latest version Utils.LATEST_RELEASE_VERSION = '[RELEASE]'; // Logged when version=latest is downloaded through a remote repository, which caches the literal [RELEASE] path -Utils.LATEST_FROM_REMOTE_INFO = 'download-repository is set with version=latest, so the CLI is requested from the path v2/[RELEASE]. If that repository stores artifacts locally, the binary cached under this path is not expirable metadata, so the first version resolved through it can keep being served as latest. Use a concrete version to get a per-version cache path, or omit download-repository if the runner can reach releases.jfrog.io.'; +Utils.LATEST_FROM_REMOTE_INFO = 'download-repository is set with version=latest, so the CLI is requested from the literal path v2/[RELEASE] instead of a version number. If that repository stores artifacts locally, the binary cached under this path can keep being served on later runs. Use a concrete version to download from a per-version path, or omit download-repository if the runner can reach releases.jfrog.io.'; // Placeholder CLI version to use to keep 'latest' in cache. Utils.LATEST_SEMVER = '100.100.100'; // The default server id name for separate env config diff --git a/src/utils.ts b/src/utils.ts index ea3c2ee88..edbdb9e8a 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -32,7 +32,7 @@ export class Utils { private static readonly LATEST_RELEASE_VERSION: string = '[RELEASE]'; // Logged when version=latest is downloaded through a remote repository, which caches the literal [RELEASE] path public static readonly LATEST_FROM_REMOTE_INFO: string = - 'download-repository is set with version=latest, so the CLI is requested from the path v2/[RELEASE]. If that repository stores artifacts locally, the binary cached under this path is not expirable metadata, so the first version resolved through it can keep being served as latest. Use a concrete version to get a per-version cache path, or omit download-repository if the runner can reach releases.jfrog.io.'; + 'download-repository is set with version=latest, so the CLI is requested from the literal path v2/[RELEASE] instead of a version number. If that repository stores artifacts locally, the binary cached under this path can keep being served on later runs. Use a concrete version to download from a per-version path, or omit download-repository if the runner can reach releases.jfrog.io.'; // Placeholder CLI version to use to keep 'latest' in cache. public static readonly LATEST_SEMVER: string = '100.100.100'; // The default server id name for separate env config From e86c3d9aa725e34a4539204cc75e2cabc0d4e03f Mon Sep 17 00:00:00 2001 From: Michael Sverdlov Date: Sun, 6 Sep 2026 16:03:58 +0300 Subject: [PATCH 6/7] Pin Go 1.24 for Auto-Build and skip macOS Artifactory bootstrap --- .github/workflows/auto-build-publish.yml | 10 +++++++++- .github/workflows/frogbot-scan-repository.yml | 2 ++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.github/workflows/auto-build-publish.yml b/.github/workflows/auto-build-publish.yml index 8a19842b4..463d3aeef 100644 --- a/.github/workflows/auto-build-publish.yml +++ b/.github/workflows/auto-build-publish.yml @@ -22,7 +22,10 @@ jobs: strategy: fail-fast: false matrix: - os: [ ubuntu, windows, macos ] + # macos omitted: local Artifactory bootstrap on GitHub-hosted macOS has been + # broken since macos-latest moved to macOS 15 (first red 2025-09-02; jobs hit + # the 1200s local-rt-setup wait then exit 1). Same skip as jfrog-cli (JGC-413). + os: [ ubuntu, windows ] cli-version: [ "latest", "2.66.0" ] runs-on: ${{ matrix.os }}-latest steps: @@ -31,8 +34,13 @@ jobs: with: ref: ${{ github.event.pull_request.head.sha }} + # This repo is a Node action and has no go.mod. install-go-with-cache@main + # reads go.mod when go-version is unset (#28), which fails Setup Go immediately. + # local-rt-setup (used by install-local-artifactory) requires Go 1.24+. - name: Setup Go with cache uses: jfrog/.github/actions/install-go-with-cache@main + with: + go-version: "1.24" - name: Install local Artifactory uses: jfrog/.github/actions/install-local-artifactory@main diff --git a/.github/workflows/frogbot-scan-repository.yml b/.github/workflows/frogbot-scan-repository.yml index 19936936a..5294a1176 100644 --- a/.github/workflows/frogbot-scan-repository.yml +++ b/.github/workflows/frogbot-scan-repository.yml @@ -19,6 +19,8 @@ jobs: steps: - name: Setup Go with cache uses: jfrog/.github/actions/install-go-with-cache@main + with: + go-version: "1.24" - uses: jfrog/frogbot@v2 env: From ebfa8fa9c2f50d4fa515ca1f96f4768803cbbbbc Mon Sep 17 00:00:00 2001 From: Michael Sverdlov Date: Sun, 6 Sep 2026 17:20:03 +0300 Subject: [PATCH 7/7] Skip macOS Auto-Build cells the way jfrog-cli does (JGC-413) --- .github/workflows/auto-build-publish.yml | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/.github/workflows/auto-build-publish.yml b/.github/workflows/auto-build-publish.yml index 463d3aeef..3484456f0 100644 --- a/.github/workflows/auto-build-publish.yml +++ b/.github/workflows/auto-build-publish.yml @@ -22,14 +22,18 @@ jobs: strategy: fail-fast: false matrix: - # macos omitted: local Artifactory bootstrap on GitHub-hosted macOS has been - # broken since macos-latest moved to macOS 15 (first red 2025-09-02; jobs hit - # the 1200s local-rt-setup wait then exit 1). Same skip as jfrog-cli (JGC-413). - os: [ ubuntu, windows ] + os: [ ubuntu, windows, macos ] cli-version: [ "latest", "2.66.0" ] runs-on: ${{ matrix.os }}-latest steps: + - name: Skip macOS - JGC-413 + if: matrix.os == 'macos' + run: | + echo "::warning::JGC-413 - Skip until artifactory bootstrap in osx is fixed" + exit 0 + - name: Checkout Repository + if: matrix.os != 'macos' uses: actions/checkout@v6 with: ref: ${{ github.event.pull_request.head.sha }} @@ -38,16 +42,19 @@ jobs: # reads go.mod when go-version is unset (#28), which fails Setup Go immediately. # local-rt-setup (used by install-local-artifactory) requires Go 1.24+. - name: Setup Go with cache + if: matrix.os != 'macos' uses: jfrog/.github/actions/install-go-with-cache@main with: go-version: "1.24" - name: Install local Artifactory + if: matrix.os != 'macos' uses: jfrog/.github/actions/install-local-artifactory@main with: RTLIC: ${{ secrets.RTLIC }} - name: Post Step to Test the Auto Build-Publish post step + if: matrix.os != 'macos' uses: gacts/run-and-post-run@v1 with: post: | @@ -65,6 +72,7 @@ jobs: fi - name: Setup JFrog CLI + if: matrix.os != 'macos' id: setup-jfrog-cli uses: ./ with: @@ -75,6 +83,7 @@ jobs: JF_PASSWORD: password - name: Create NPM Remote Repository JSON + if: matrix.os != 'macos' uses: jsdaniell/create-json@v1.2.3 with: name: "npm-remote-template.json" @@ -86,10 +95,12 @@ jobs: }' - name: Configure Artifactory NPM Remote Repository + if: matrix.os != 'macos' run: jf rt repo-create npm-remote-template.json shell: bash - name: Add npm modules to local build-info + if: matrix.os != 'macos' run: | jf npm-config --repo-resolve npm-remote jf npm install \ No newline at end of file