From b1b27b4662cedba17231bd25c631ede79a08584a Mon Sep 17 00:00:00 2001 From: moritzraho Date: Thu, 22 Jan 2026 20:55:02 +0100 Subject: [PATCH 1/4] feat: support include-ims-credentials annotation --- src/lib/constants.js | 3 ++- src/lib/run-dev.js | 17 ++++++++++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/lib/constants.js b/src/lib/constants.js index d6df93d..d32b383 100644 --- a/src/lib/constants.js +++ b/src/lib/constants.js @@ -40,5 +40,6 @@ module.exports = { STAGE_LAUNCH_PREFIX: 'https://experience-stage.adobe.com/?devMode=true#/custom-apps/?localDevUrl=', PRIVATE_KEY_PATH: `${DEV_KEYS_DIR}/private.key`, PUB_CERT_PATH: `${DEV_KEYS_DIR}/cert-pub.crt`, - BUNDLE_OPTIONS + BUNDLE_OPTIONS, + IMS_OAUTH_S2S_ENV_KEY: 'IMS_OAUTH_S2S' } diff --git a/src/lib/run-dev.js b/src/lib/run-dev.js index d90e39f..e59e016 100644 --- a/src/lib/run-dev.js +++ b/src/lib/run-dev.js @@ -25,9 +25,12 @@ const coreLogger = require('@adobe/aio-lib-core-logging') const { getReasonPhrase } = require('http-status-codes') const utils = require('./app-helper') -const { SERVER_HOST, SERVER_DEFAULT_PORT, BUNDLER_DEFAULT_PORT, DEV_API_PREFIX, DEV_API_WEB_PREFIX, BUNDLE_OPTIONS, CHANGED_ASSETS_PRINT_LIMIT } = require('./constants') +const { SERVER_HOST, SERVER_DEFAULT_PORT, BUNDLER_DEFAULT_PORT, DEV_API_PREFIX, DEV_API_WEB_PREFIX, BUNDLE_OPTIONS, CHANGED_ASSETS_PRINT_LIMIT, IMS_OAUTH_S2S_ENV_KEY } = require('./constants') const RAW_CONTENT_TYPES = ['application/octet-stream', 'multipart/form-data'] +// for the include-ims-credentials annotation +let imsAuthObject = null + /* global Request, Response */ /** @@ -86,6 +89,11 @@ async function runDev (runOptions, config, _inprocHookRunner) { // ex. console.log('AIO_DEV ', process.env.AIO_DEV ? 'dev' : 'prod') process.env.AIO_DEV = 'true' + // for the include-ims-credentials annotation + try { + imsAuthObject = JSON.parse(process.env[IMS_OAUTH_S2S_ENV_KEY]) + } catch (e) {} + const serverPortToUse = parseInt(process.env.PORT) || SERVER_DEFAULT_PORT const serverPort = await getPort({ port: serverPortToUse }) @@ -346,6 +354,13 @@ async function invokeAction ({ actionRequestContext, logger }) { } } + // process the include-ims-credentials annotation + const newInputs = rtLib.utils.getIncludeIMSCredentialsAnnotationInputs(action, imsAuthObject) + if (newInputs) { + Object.entries(newInputs).forEach(([k, v]) => { params[k] = v }) + logger.debug(`Added IMS credentials to action params for action '${actionName}'.`) + } + // if we run an action, we will restore the process.env after the call // we must do this before we load the action because code can execute on require/import const preCallEnv = { ...process.env } From 96eed6ad3708346ad31452684dc40da14b7cd8f6 Mon Sep 17 00:00:00 2001 From: moritzraho Date: Thu, 22 Jan 2026 21:38:40 +0100 Subject: [PATCH 2/4] chore: tests --- test/__mocks__/@adobe/aio-lib-runtime.js | 3 +- test/lib/constants.test.js | 4 +- test/lib/run-dev.test.js | 148 +++++++++++++++++++++++ 3 files changed, 153 insertions(+), 2 deletions(-) diff --git a/test/__mocks__/@adobe/aio-lib-runtime.js b/test/__mocks__/@adobe/aio-lib-runtime.js index 2027a4c..af8c82e 100644 --- a/test/__mocks__/@adobe/aio-lib-runtime.js +++ b/test/__mocks__/@adobe/aio-lib-runtime.js @@ -86,7 +86,8 @@ const mockRtLibInstance = { const mockRtUtils = { getActionUrls: jest.fn(), - checkOpenWhiskCredentials: jest.fn() + checkOpenWhiskCredentials: jest.fn(), + getIncludeIMSCredentialsAnnotationInputs: jest.fn() } const init = jest.fn().mockReturnValue(mockRtLibInstance) diff --git a/test/lib/constants.test.js b/test/lib/constants.test.js index 7618b69..fa17102 100644 --- a/test/lib/constants.test.js +++ b/test/lib/constants.test.js @@ -23,7 +23,8 @@ const { STAGE_LAUNCH_PREFIX, PRIVATE_KEY_PATH, PUB_CERT_PATH, - BUNDLE_OPTIONS + BUNDLE_OPTIONS, + IMS_OAUTH_S2S_ENV_KEY } = require(CONSTANTS_PATH) test('exports', () => { @@ -39,6 +40,7 @@ test('exports', () => { expect(PRIVATE_KEY_PATH).toBeDefined() expect(PUB_CERT_PATH).toBeDefined() expect(BUNDLE_OPTIONS).toBeDefined() + expect(IMS_OAUTH_S2S_ENV_KEY).toBeDefined() }) describe('override via env vars', () => { diff --git a/test/lib/run-dev.test.js b/test/lib/run-dev.test.js index aeefc9e..28094e5 100644 --- a/test/lib/run-dev.test.js +++ b/test/lib/run-dev.test.js @@ -1231,6 +1231,12 @@ describe('invokeSequence', () => { }) describe('runDev', () => { + const originalEnv = process.env + + afterEach(() => { + process.env = { ...originalEnv } + }) + test('no front end, no back end', async () => { const actionPath = fixturePath('actions/successNoReturnAction.js') const config = createConfig({ @@ -1253,6 +1259,53 @@ describe('runDev', () => { expect(Object.keys(actionUrls).length).toEqual(0) }) + test('parses IMS_OAUTH_S2S environment variable', async () => { + const imsAuthData = { access_token: 'test-token', org_id: 'test-org' } + process.env.IMS_OAUTH_S2S = JSON.stringify(imsAuthData) + + const actionPath = fixturePath('actions/successNoReturnAction.js') + const config = createConfig({ + hasFrontend: false, + hasBackend: true, + packageName: 'mypackage', + actions: { + myaction: { + function: actionPath + } + } + }) + const runOptions = createRunOptions({ cert: 'my-cert', key: 'my-key' }) + const hookRunner = () => {} + const { actionUrls, serverCleanup } = await runDev(runOptions, config, hookRunner) + + await serverCleanup() + // Verify runDev completes successfully when IMS_OAUTH_S2S env var is set with valid JSON + expect(Object.keys(actionUrls).length).toBeGreaterThan(0) + }) + + test('handles invalid IMS_OAUTH_S2S JSON gracefully', async () => { + process.env.IMS_OAUTH_S2S = 'not-valid-json' + + const actionPath = fixturePath('actions/successNoReturnAction.js') + const config = createConfig({ + hasFrontend: false, + hasBackend: true, + packageName: 'mypackage', + actions: { + myaction: { + function: actionPath + } + } + }) + const runOptions = createRunOptions({ cert: 'my-cert', key: 'my-key' }) + const hookRunner = () => {} + const { actionUrls, serverCleanup } = await runDev(runOptions, config, hookRunner) + + await serverCleanup() + // Verify runDev completes successfully even when IMS_OAUTH_S2S contains invalid JSON + expect(Object.keys(actionUrls).length).toBeGreaterThan(0) + }) + test('no front end, has back end', async () => { const actionPath = fixturePath('actions/successNoReturnAction.js') const config = createConfig({ @@ -1706,6 +1759,101 @@ describe('invokeAction', () => { statusCode: 400 }) }) + + describe('include-ims-credentials annotation', () => { + const rtLib = jest.requireActual('@adobe/aio-lib-runtime') + let getIncludeIMSCredentialsAnnotationInputsSpy + + beforeEach(() => { + getIncludeIMSCredentialsAnnotationInputsSpy = jest.spyOn(rtLib.utils, 'getIncludeIMSCredentialsAnnotationInputs') + }) + + afterEach(() => { + getIncludeIMSCredentialsAnnotationInputsSpy.mockRestore() + }) + + test('adds IMS credentials to params when getIncludeIMSCredentialsAnnotationInputs returns inputs', async () => { + const packageName = 'foo' + const actionPath = fixturePath('actions/successReturnAction.js') + const actionLoader = createActionLoader(actionPath) + + const action = { + function: actionPath, + annotations: { + 'include-ims-credentials': true + } + } + const actionParams = { existingParam: 'value' } + const actionName = 'a' + const actionConfig = { + [packageName]: { + actions: { + [actionName]: action + } + } + } + + // Mock the function to return IMS credentials + const mockImsInputs = { + __ims_oauth_s2s: { client_id: 'mock-access-token', org_id: 'mock-org-id' }, + __ims_env: 'stage' + } + getIncludeIMSCredentialsAnnotationInputsSpy.mockReturnValue(mockImsInputs) + + const actionRequestContext = { + contextActionLoader: actionLoader, + contextItem: action, + contextItemParams: actionParams, + contextItemName: actionName, + packageName, + actionConfig + } + const response = await invokeAction({ actionRequestContext, logger: mockLogger }) + + expect(getIncludeIMSCredentialsAnnotationInputsSpy).toHaveBeenCalledWith(action, expect.anything()) + expect(actionParams.__ims_oauth_s2s).toEqual({ client_id: 'mock-access-token', org_id: 'mock-org-id' }) + expect(actionParams.__ims_env).toEqual('stage') + expect(actionParams.existingParam).toEqual('value') + expect(mockLogger.debug).toHaveBeenCalledWith(`Added IMS credentials to action params for action '${actionName}'.`) + expect(response.statusCode).toEqual(200) + }) + + test('does not add IMS credentials when getIncludeIMSCredentialsAnnotationInputs returns null', async () => { + const packageName = 'foo' + const actionPath = fixturePath('actions/successReturnAction.js') + const actionLoader = createActionLoader(actionPath) + + const action = { function: actionPath } + const actionParams = { existingParam: 'value' } + const actionName = 'a' + const actionConfig = { + [packageName]: { + actions: { + [actionName]: action + } + } + } + + // Mock the function to return null (no annotation or no IMS auth object) + getIncludeIMSCredentialsAnnotationInputsSpy.mockReturnValue(null) + + const actionRequestContext = { + contextActionLoader: actionLoader, + contextItem: action, + contextItemParams: actionParams, + contextItemName: actionName, + packageName, + actionConfig + } + const response = await invokeAction({ actionRequestContext, logger: mockLogger }) + + expect(getIncludeIMSCredentialsAnnotationInputsSpy).toHaveBeenCalledWith(action, expect.anything()) + expect(actionParams.__ow_ims_access_token).toBeUndefined() + expect(actionParams.__ow_ims_org_id).toBeUndefined() + expect(actionParams.existingParam).toEqual('value') + expect(response.statusCode).toEqual(200) + }) + }) }) describe('defaultActionLoader', () => { From f4af7ed11e1e2197aa038aee1bdacfbac39dca0b Mon Sep 17 00:00:00 2001 From: moritzraho Date: Fri, 13 Feb 2026 15:06:51 +0100 Subject: [PATCH 3/4] update to new changes --- src/lib/constants.js | 3 +-- src/lib/run-dev.js | 8 +++----- test/lib/constants.test.js | 4 +--- test/lib/run-dev.test.js | 35 +++++++---------------------------- 4 files changed, 12 insertions(+), 38 deletions(-) diff --git a/src/lib/constants.js b/src/lib/constants.js index d32b383..d6df93d 100644 --- a/src/lib/constants.js +++ b/src/lib/constants.js @@ -40,6 +40,5 @@ module.exports = { STAGE_LAUNCH_PREFIX: 'https://experience-stage.adobe.com/?devMode=true#/custom-apps/?localDevUrl=', PRIVATE_KEY_PATH: `${DEV_KEYS_DIR}/private.key`, PUB_CERT_PATH: `${DEV_KEYS_DIR}/cert-pub.crt`, - BUNDLE_OPTIONS, - IMS_OAUTH_S2S_ENV_KEY: 'IMS_OAUTH_S2S' + BUNDLE_OPTIONS } diff --git a/src/lib/run-dev.js b/src/lib/run-dev.js index e59e016..190173b 100644 --- a/src/lib/run-dev.js +++ b/src/lib/run-dev.js @@ -25,7 +25,7 @@ const coreLogger = require('@adobe/aio-lib-core-logging') const { getReasonPhrase } = require('http-status-codes') const utils = require('./app-helper') -const { SERVER_HOST, SERVER_DEFAULT_PORT, BUNDLER_DEFAULT_PORT, DEV_API_PREFIX, DEV_API_WEB_PREFIX, BUNDLE_OPTIONS, CHANGED_ASSETS_PRINT_LIMIT, IMS_OAUTH_S2S_ENV_KEY } = require('./constants') +const { SERVER_HOST, SERVER_DEFAULT_PORT, BUNDLER_DEFAULT_PORT, DEV_API_PREFIX, DEV_API_WEB_PREFIX, BUNDLE_OPTIONS, CHANGED_ASSETS_PRINT_LIMIT } = require('./constants') const RAW_CONTENT_TYPES = ['application/octet-stream', 'multipart/form-data'] // for the include-ims-credentials annotation @@ -89,10 +89,8 @@ async function runDev (runOptions, config, _inprocHookRunner) { // ex. console.log('AIO_DEV ', process.env.AIO_DEV ? 'dev' : 'prod') process.env.AIO_DEV = 'true' - // for the include-ims-credentials annotation - try { - imsAuthObject = JSON.parse(process.env[IMS_OAUTH_S2S_ENV_KEY]) - } catch (e) {} + // include ims credentials inputs __ims_oauth_s2s and __ims_env + imsAuthObject = rtLib.utils.loadIMSCredentialsFromEnv() const serverPortToUse = parseInt(process.env.PORT) || SERVER_DEFAULT_PORT const serverPort = await getPort({ port: serverPortToUse }) diff --git a/test/lib/constants.test.js b/test/lib/constants.test.js index fa17102..7618b69 100644 --- a/test/lib/constants.test.js +++ b/test/lib/constants.test.js @@ -23,8 +23,7 @@ const { STAGE_LAUNCH_PREFIX, PRIVATE_KEY_PATH, PUB_CERT_PATH, - BUNDLE_OPTIONS, - IMS_OAUTH_S2S_ENV_KEY + BUNDLE_OPTIONS } = require(CONSTANTS_PATH) test('exports', () => { @@ -40,7 +39,6 @@ test('exports', () => { expect(PRIVATE_KEY_PATH).toBeDefined() expect(PUB_CERT_PATH).toBeDefined() expect(BUNDLE_OPTIONS).toBeDefined() - expect(IMS_OAUTH_S2S_ENV_KEY).toBeDefined() }) describe('override via env vars', () => { diff --git a/test/lib/run-dev.test.js b/test/lib/run-dev.test.js index 28094e5..5eb0ce7 100644 --- a/test/lib/run-dev.test.js +++ b/test/lib/run-dev.test.js @@ -1259,9 +1259,9 @@ describe('runDev', () => { expect(Object.keys(actionUrls).length).toEqual(0) }) - test('parses IMS_OAUTH_S2S environment variable', async () => { - const imsAuthData = { access_token: 'test-token', org_id: 'test-org' } - process.env.IMS_OAUTH_S2S = JSON.stringify(imsAuthData) + test('calls loadIMSCredentialsFromEnv for include-ims-credentials support', async () => { + const rtLib = jest.requireActual('@adobe/aio-lib-runtime') + const loadIMSCredentialsFromEnvSpy = jest.spyOn(rtLib.utils, 'loadIMSCredentialsFromEnv') const actionPath = fixturePath('actions/successNoReturnAction.js') const config = createConfig({ @@ -1279,31 +1279,10 @@ describe('runDev', () => { const { actionUrls, serverCleanup } = await runDev(runOptions, config, hookRunner) await serverCleanup() - // Verify runDev completes successfully when IMS_OAUTH_S2S env var is set with valid JSON - expect(Object.keys(actionUrls).length).toBeGreaterThan(0) - }) - - test('handles invalid IMS_OAUTH_S2S JSON gracefully', async () => { - process.env.IMS_OAUTH_S2S = 'not-valid-json' - - const actionPath = fixturePath('actions/successNoReturnAction.js') - const config = createConfig({ - hasFrontend: false, - hasBackend: true, - packageName: 'mypackage', - actions: { - myaction: { - function: actionPath - } - } - }) - const runOptions = createRunOptions({ cert: 'my-cert', key: 'my-key' }) - const hookRunner = () => {} - const { actionUrls, serverCleanup } = await runDev(runOptions, config, hookRunner) - await serverCleanup() - // Verify runDev completes successfully even when IMS_OAUTH_S2S contains invalid JSON + expect(loadIMSCredentialsFromEnvSpy).toHaveBeenCalled() expect(Object.keys(actionUrls).length).toBeGreaterThan(0) + loadIMSCredentialsFromEnvSpy.mockRestore() }) test('no front end, has back end', async () => { @@ -1848,8 +1827,8 @@ describe('invokeAction', () => { const response = await invokeAction({ actionRequestContext, logger: mockLogger }) expect(getIncludeIMSCredentialsAnnotationInputsSpy).toHaveBeenCalledWith(action, expect.anything()) - expect(actionParams.__ow_ims_access_token).toBeUndefined() - expect(actionParams.__ow_ims_org_id).toBeUndefined() + expect(actionParams.__ims_oauth_s2s).toBeUndefined() + expect(actionParams.__ims_env).toBeUndefined() expect(actionParams.existingParam).toEqual('value') expect(response.statusCode).toEqual(200) }) From 353d3c32cb2978e33004f660e41d6734548da14a Mon Sep 17 00:00:00 2001 From: moritzraho Date: Fri, 20 Feb 2026 18:32:17 +0100 Subject: [PATCH 4/4] tmp: next tag for runtime - to be reverted --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7f0f44f..3ff7d14 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "@adobe/aio-lib-core-config": "^5", "@adobe/aio-lib-core-logging": "^3", "@adobe/aio-lib-env": "^3", - "@adobe/aio-lib-runtime": "^7.0.0", + "@adobe/aio-lib-runtime": "next", "@adobe/aio-lib-web": "^7", "@oclif/core": "^3", "chalk": "^4",