From 383bababeb2c202d3269be57f239d012cb92a2ae Mon Sep 17 00:00:00 2001 From: Krystian Nowak Date: Fri, 1 May 2026 18:32:09 +0200 Subject: [PATCH 1/3] Add --debug flag for tail-logs and purge-cache, fix API endpoint for non-production environments --- CHANGELOG.md | 14 +++++ README.md | 9 +++ package-lock.json | 4 +- package.json | 2 +- src/commands/aem/edge-functions/info.js | 12 ++-- .../aem/edge-functions/purge-cache.js | 33 +++++------ src/commands/aem/edge-functions/tail-logs.js | 11 +++- src/libs/base-command.js | 55 +++++++++++++++---- src/libs/fastly-cli.js | 17 ++++-- 9 files changed, 115 insertions(+), 42 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ea0bce8..7db680f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # Changelog +## [0.8.1] - 2026-05-18 + +### Added + +- `--debug` / `-d` flag for `tail-logs` and `purge-cache` commands to show API endpoint + +### Changed + +- API endpoint is now only shown when `--debug` flag is explicitly used + +### Fixed + +- Correct API endpoint resolution for non-production Cloud Manager environments + ## [0.8.0] - 2026-05-15 ### Added diff --git a/README.md b/README.md index b01247a..20ade2b 100644 --- a/README.md +++ b/README.md @@ -205,6 +205,14 @@ The following command will tail your edge function logs to help you debug your a aio aem edge-functions tail-logs first-function ``` +### Debug Mode + +To see the API endpoint used for log tailing (useful for troubleshooting connectivity issues), use the `--debug` / `-d` flag: + +``` +aio aem edge-functions tail-logs first-function --debug +``` + ## Purge cache Edge Function responses can be cached at the CDN layer. When cached content becomes stale due to inter-resource dependencies, you can explicitly purge it: @@ -228,6 +236,7 @@ aio aem edge-functions purge-cache first-function -k my-key --soft | `--surrogateKey` / `-k` | Surrogate key to purge (can be specified multiple times) | | `--all` / `-a` | Purge all cached content for the edge function | | `--soft` / `-s` | Perform a soft purge (retain stale entries, reduce origin load) | +| `--debug` / `-d` | Show debug information including API endpoint | Surrogate keys are set on Edge Function responses via the `Surrogate-Key` header. For more information on caching and purging, see the [AEM Edge Functions documentation](https://experienceleague.adobe.com/en/docs/experience-manager-cloud-service/content/implementing/developing/edge-functions). diff --git a/package-lock.json b/package-lock.json index 7aeec77..ff5b6c4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@adobe/aio-cli-plugin-aem-edge-functions", - "version": "0.8.0", + "version": "0.8.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@adobe/aio-cli-plugin-aem-edge-functions", - "version": "0.8.0", + "version": "0.8.1", "license": "Apache-2.0", "dependencies": { "@adobe/aio-lib-console": "5.5.0", diff --git a/package.json b/package.json index d402941..f0153b3 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@adobe/aio-cli-plugin-aem-edge-functions", "description": "Adobe I/O CLI plugin for interacting with AEM Edge Functions", - "version": "0.8.0", + "version": "0.8.1", "author": "Adobe Inc.", "engines": { "npm": ">= 8.0.0", diff --git a/src/commands/aem/edge-functions/info.js b/src/commands/aem/edge-functions/info.js index 7a037cf..153bb08 100644 --- a/src/commands/aem/edge-functions/info.js +++ b/src/commands/aem/edge-functions/info.js @@ -56,12 +56,15 @@ class InfoCommand extends BaseCommand { let adcFetchFailed = false; let adcError = null; + let isStage = this.isStageEnv(); + // Fetch Cloud Manager data if we have orgId and programId if (orgId && programId && !this.flags.batch) { this.spinnerStart('Loading Cloud Manager program and environment names...'); try { const { accessToken, apiKey, data } = await this.getTokenAndKey(); - const cloudManagerUrl = this.getBaseUrl(data?.env === 'stage'); + isStage = data?.env === 'stage'; + const cloudManagerUrl = this.getBaseUrl(isStage); const cloudmanager = new Cloudmanager( `${cloudManagerUrl}/api`, apiKey, @@ -191,11 +194,12 @@ class InfoCommand extends BaseCommand { // Display Cloud Manager URL if (orgId && programId) { + const experienceHost = isStage ? 'experience-stage.adobe.com' : 'experience.adobe.com'; let cloudManagerUrl; if (edgeDelivery) { - cloudManagerUrl = `https://experience.adobe.com/#/@${orgId}/cloud-manager/edge-delivery.html/program/${programId}`; + cloudManagerUrl = `https://${experienceHost}/#/@${orgId}/cloud-manager/edge-delivery.html/program/${programId}`; } else if (environmentId) { - cloudManagerUrl = `https://experience.adobe.com/#/@${orgId}/cloud-manager/environments.html/program/${programId}/environment/${environmentId}`; + cloudManagerUrl = `https://${experienceHost}/#/@${orgId}/cloud-manager/environments.html/program/${programId}/environment/${environmentId}`; } if (cloudManagerUrl) { @@ -223,7 +227,7 @@ class InfoCommand extends BaseCommand { if (this.flags.debug) { console.log(`\nTimestamp (UTC): ${chalk.cyan(new Date().toISOString())}`); - const apiEndpoint = this.getApiBasePath() ? this.getApiBasePath() : null; + const apiEndpoint = this.getApiBasePath(isStage) ? this.getApiBasePath(isStage) : null; const adcClientId = this.getConfig(this.CONFIG_ADC_CLIENT_ID); const adcClientSecret = this.getConfig(this.CONFIG_ADC_CLIENT_SECRET); const adcScopes = this.getConfig(this.CONFIG_ADC_SCOPES); diff --git a/src/commands/aem/edge-functions/purge-cache.js b/src/commands/aem/edge-functions/purge-cache.js index ccd2930..10f5eab 100644 --- a/src/commands/aem/edge-functions/purge-cache.js +++ b/src/commands/aem/edge-functions/purge-cache.js @@ -46,6 +46,11 @@ By default performs a hard purge (immediate removal). Use --soft for soft purge char: 's', description: 'Perform a soft purge (retain stale entries for revalidation)', default: false + }), + debug: Flags.boolean({ + char: 'd', + description: 'Show debug information including API endpoint', + default: false }) }; @@ -83,28 +88,20 @@ By default performs a hard purge (immediate removal). Use --soft for soft purge body.surrogateKeys = surrogateKey; } - // Get access token - const basePath = this.getApiBasePath(); - if (!basePath) { - this.error('API endpoint not configured. Run "aio aem edge-functions setup" first.'); - } + // Get access token and detect stage + const { accessToken, isStage } = await this.getAccessTokenAndStage(); - let accessToken = process.env.AEM_EDGE_FUNCTIONS_TOKEN; if (!accessToken) { - const adcConfigured = this.getConfig(this.CONFIG_ADC_CONFIGURED); - if (adcConfigured) { - const adcToken = await this.getAdcToken(); - if (adcToken) { - accessToken = adcToken.accessToken; - } - } - if (!accessToken) { - accessToken = (await this.getTokenAndKey())?.accessToken; - } + this.error('No access token available. Please authenticate first.'); } - if (!accessToken) { - this.error('No access token available. Please authenticate first.'); + const basePath = this.getApiBasePath(isStage); + if (!basePath) { + this.error('API endpoint not configured. Run "aio aem edge-functions setup" first.'); + } + + if (this.flags.debug) { + console.log(`Using API endpoint: ${basePath}`); } const request = new Request(basePath, { diff --git a/src/commands/aem/edge-functions/tail-logs.js b/src/commands/aem/edge-functions/tail-logs.js index 2033a84..bdf6120 100644 --- a/src/commands/aem/edge-functions/tail-logs.js +++ b/src/commands/aem/edge-functions/tail-logs.js @@ -13,7 +13,7 @@ 'use strict'; const BaseCommand = require('../../../libs/base-command'); -const { Args } = require('@oclif/core'); +const { Args, Flags } = require('@oclif/core'); class TailLogsCommand extends BaseCommand { static description = 'Tail logs from your AEM edge function.'; @@ -23,10 +23,17 @@ class TailLogsCommand extends BaseCommand { required: true }) }; + static flags = { + debug: Flags.boolean({ + char: 'd', + description: 'Show debug information including API endpoint', + default: false + }) + }; async run() { const fastly = await this.getFastlyCli(); - await fastly.logTail(this.args.serviceId); + await fastly.logTail(this.args.serviceId, { debug: this.flags.debug }); } } diff --git a/src/libs/base-command.js b/src/libs/base-command.js index 4dd7db9..974d4b8 100644 --- a/src/libs/base-command.js +++ b/src/libs/base-command.js @@ -318,11 +318,26 @@ class BaseCommand extends Command { return !stage ? 'https://cloudmanager.adobe.io' : 'https://cloudmanager-stage.adobe.io'; } + /** + * Detect whether the CLI is configured for the Cloud Manager stage environment. + * Checks AIO_CLI_ENV env var and aio config 'cli.env' / 'ims.contexts.cli.env'. + * This does not require a token and works in batch mode. + */ + isStageEnv() { + if (process.env.AIO_CLI_ENV === 'stage') return true; + const cliEnv = Config.get('cli.env'); + if (cliEnv === 'stage') return true; + const imsEnv = Config.get('ims.contexts.cli.env'); + if (imsEnv === 'stage') return true; + return false; + } + /** * Get the API base path for AEM Edge Functions (without the edge functions path suffix) + * @param {boolean} [stage=false] Whether to use the Cloud Manager stage domain suffix (-cmstg) * @returns {string|null} The computed base URL or null if configuration is incomplete */ - getApiBasePath() { + getApiBasePath(stage = false) { if (process.env.AEM_EDGE_FUNCTIONS_API_ENDPOINT) { return process.env.AEM_EDGE_FUNCTIONS_API_ENDPOINT; } @@ -338,27 +353,33 @@ class BaseCommand extends Command { return null; } + const stageSuffix = stage ? '-cmstg' : ''; return isEdgeDelivery ? `https://${siteDomain}/adobe/experimental/compute-expires-20251231/cdn` - : `https://author-p${programId}-e${environmentId}.adobeaemcloud.com/adobe/experimental/compute-expires-20251231/cdn`; + : `https://author-p${programId}-e${environmentId}${stageSuffix}.adobeaemcloud.com/adobe/experimental/compute-expires-20251231/cdn`; } /** * Get the API endpoint for AEM Edge Functions + * @param {boolean} [stage=false] Whether to use the Cloud Manager stage domain suffix (-cmstg) * @returns {string|null} The computed API endpoint or null if configuration is incomplete */ - getApiEndpoint() { - const basePath = this.getApiBasePath(); + getApiEndpoint(stage = false) { + const basePath = this.getApiBasePath(stage); if (!basePath) return null; return basePath + (process.env.AEM_EDGE_FUNCTIONS_API_ENDPOINT_URL ?? '/edgeFunctions/fastly'); } - async getFastlyCli() { - const apiEndpoint = this.getApiEndpoint(); - - // For edge function API requests, try to use ADC token if configured + /** + * Get an access token and detect whether the environment is stage. + * Tries (in order): AEM_EDGE_FUNCTIONS_TOKEN env var, ADC OAuth, IMS token. + * Stage is detected from IMS context data when available, falling back to isStageEnv(). + * @returns {Promise<{accessToken: string|null, isStage: boolean}>} + */ + async getAccessTokenAndStage() { let accessToken = process.env.AEM_EDGE_FUNCTIONS_TOKEN; + let isStage = this.isStageEnv(); if (!accessToken) { const adcConfigured = this.getConfig(this.CONFIG_ADC_CONFIGURED); @@ -370,18 +391,30 @@ class BaseCommand extends Command { accessToken = adcToken.accessToken; } else { ux.warn('Failed to get ADC token, falling back to IMS token'); - accessToken = (await this.getTokenAndKey())?.accessToken; + const tokenResult = await this.getTokenAndKey(); + accessToken = tokenResult?.accessToken; + isStage = tokenResult?.data?.env === 'stage' || isStage; } } catch (error) { ux.warn(`Failed to get ADC token: ${error.message}, falling back to IMS token`); - accessToken = (await this.getTokenAndKey())?.accessToken; + const tokenResult = await this.getTokenAndKey(); + accessToken = tokenResult?.accessToken; + isStage = tokenResult?.data?.env === 'stage' || isStage; } } else { // No ADC configured, use IMS token - accessToken = (await this.getTokenAndKey())?.accessToken; + const tokenResult = await this.getTokenAndKey(); + accessToken = tokenResult?.accessToken; + isStage = tokenResult?.data?.env === 'stage' || isStage; } } + return { accessToken, isStage }; + } + + async getFastlyCli() { + const { accessToken, isStage } = await this.getAccessTokenAndStage(); + const apiEndpoint = this.getApiEndpoint(isStage); return new FastlyCli(accessToken, apiEndpoint); } } diff --git a/src/libs/fastly-cli.js b/src/libs/fastly-cli.js index 4a75187..0560fb1 100644 --- a/src/libs/fastly-cli.js +++ b/src/libs/fastly-cli.js @@ -125,10 +125,16 @@ class FastlyCli { } } - async run(args, { filterOutput: shouldFilter = false } = {}) { + async run(args, { filterOutput: shouldFilter = false, debug = false } = {}) { if (!this.fastlyCliPath) { await this.init(); } + + // print API endpoint only in explicit debug mode + if (debug) { + console.log(`Using API endpoint: ${this.apiEndpoint}`); + } + const env = { ...process.env, FASTLY_API_TOKEN: this.apiToken, @@ -197,7 +203,10 @@ class FastlyCli { async deploy(serviceId, { debug = false } = {}) { this.ensureTokenIsSet(); this.ensureServiceIdIsSafe(serviceId); - await this.run(['compute', 'deploy', '--service-id', serviceId], { filterOutput: !debug }); + await this.run(['compute', 'deploy', '--service-id', serviceId], { + filterOutput: !debug, + debug + }); } async serve({ watch = false } = {}) { @@ -208,10 +217,10 @@ class FastlyCli { await this.run(args); } - async logTail(serviceId) { + async logTail(serviceId, { debug = false } = {}) { this.ensureTokenIsSet(); this.ensureServiceIdIsSafe(serviceId); - await this.run(['log-tail', '--service-id', serviceId]); + await this.run(['log-tail', '--service-id', serviceId], { debug }); } } From 29b661ee32608a581ae2c8e414294ef0000f3399 Mon Sep 17 00:00:00 2001 From: Krystian Nowak Date: Fri, 15 May 2026 12:04:10 +0200 Subject: [PATCH 2/3] Fix purge-cache test to mock getAccessTokenAndStage for CI environment --- test/commands/aem/edge-functions/purge-cache.test.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/commands/aem/edge-functions/purge-cache.test.js b/test/commands/aem/edge-functions/purge-cache.test.js index ebbeab3..d5640d7 100644 --- a/test/commands/aem/edge-functions/purge-cache.test.js +++ b/test/commands/aem/edge-functions/purge-cache.test.js @@ -70,6 +70,9 @@ describe('PurgeCacheCommand', () => { it('should error when API endpoint is not configured', async () => { command.args = { serviceId: 'my-func' }; command.flags = { all: true, soft: false }; + command.getAccessTokenAndStage = sandbox + .stub() + .resolves({ accessToken: 'token', isStage: false }); command.getApiBasePath = sandbox.stub().returns(null); await assert.rejects(() => command.run(), /command error/); From 6767a7ea72cc3320136fc34668fe65b521800b33 Mon Sep 17 00:00:00 2001 From: Krystian Nowak Date: Fri, 15 May 2026 12:06:15 +0200 Subject: [PATCH 3/3] setting 2026-05-15 date for version 0.8.1 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7db680f..53c1c8a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## [0.8.1] - 2026-05-18 +## [0.8.1] - 2026-05-15 ### Added