From 2e4a35a6e5e86f06a2ac8ea1598948f259a93a8d Mon Sep 17 00:00:00 2001 From: Narendra Vyas Date: Tue, 7 Apr 2026 18:22:28 +0530 Subject: [PATCH 1/3] chore: allign local secrets with worker by JSON-parsing top-level string values --- src/serverUtils.js | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/src/serverUtils.js b/src/serverUtils.js index 32a388f9..f8f9b1ef 100644 --- a/src/serverUtils.js +++ b/src/serverUtils.js @@ -316,6 +316,31 @@ function ccDirectivesToString(directives) { return chStr.toString(); } +/** + * Match production tenant-worker behavior: each bound secret value is JSON.parsed. + * YAML often leaves JSON blobs as strings; without this step, values like TOKEN: '{"COMMERCE": "dummy-value"}' stay broken locally. + * + * @param {Record} secrets Parsed secrets object + * @returns {Record} + */ +function normalizeSecretsEnvValues(secrets) { + if (!secrets || typeof secrets !== 'object' || Array.isArray(secrets)) { + return secrets; + } + return Object.fromEntries( + Object.entries(secrets).map(([key, value]) => { + if (typeof value === 'string') { + try { + return [key, JSON.parse(value)]; + } catch { + return [key, value]; + } + } + return [key, value]; + }), + ); +} + /** * Returns secrets content from artifacts * @param meshPath @@ -326,7 +351,8 @@ function readSecretsFile(meshPath) { try { const filePath = path.resolve(process.cwd(), `${meshPath}`, 'secrets.yaml'); if (fs.existsSync(filePath)) { - secrets = YAML.parse(fs.readFileSync(filePath, 'utf8')); + const parsed = YAML.parse(fs.readFileSync(filePath, 'utf8')); + secrets = normalizeSecretsEnvValues(parsed || {}); } } catch (error) { logger.error('Unexpected error: unable to locate secrets file in mesh artifacts.'); From 9406f84a4e65aaecf443a4eeb8aab542acbe841f Mon Sep 17 00:00:00 2001 From: Narendra Vyas Date: Tue, 7 Apr 2026 18:33:47 +0530 Subject: [PATCH 2/3] chore: bumped version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3c34f397..8d6e7d8e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@adobe/aio-cli-plugin-api-mesh", - "version": "5.6.3", + "version": "5.6.4-beta.1", "description": "Adobe I/O CLI plugin to develop and manage API mesh sources", "keywords": [ "oclif-plugin" From 5e61f2b75f46ea490bdc454d9c28bfd37010ce03 Mon Sep 17 00:00:00 2001 From: Narendra Vyas Date: Mon, 13 Apr 2026 16:26:49 +0530 Subject: [PATCH 3/3] chore: added unit tests --- .../__tests__/readSecretsFile.test.js | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 src/commands/api-mesh/__tests__/readSecretsFile.test.js diff --git a/src/commands/api-mesh/__tests__/readSecretsFile.test.js b/src/commands/api-mesh/__tests__/readSecretsFile.test.js new file mode 100644 index 00000000..a22468bc --- /dev/null +++ b/src/commands/api-mesh/__tests__/readSecretsFile.test.js @@ -0,0 +1,57 @@ +/* +Copyright 2021 Adobe. All rights reserved. +This file is licensed to you under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. You may obtain a copy +of the License at http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under +the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS +OF ANY KIND, either express or implied. See the License for the specific language +governing permissions and limitations under the License. +*/ + +const fs = require('fs'); +const os = require('os'); +const path = require('path'); +const { readSecretsFile } = require('../../../serverUtils'); +const { loadMeshSecrets } = require('../../../secrets'); + +describe('readSecretsFile', () => { + let tmp; + let prevCwd; + + afterEach(() => { + if (prevCwd !== undefined) { + process.chdir(prevCwd); + prevCwd = undefined; + } + if (tmp) { + fs.rmSync(tmp, { recursive: true, force: true }); + tmp = undefined; + } + }); + + test('parses JSON object strings in secrets.yaml like the tenant worker', () => { + tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'readSecretsFile-test-')); + const meshDir = path.join(tmp, '.mesh'); + fs.mkdirSync(meshDir, { recursive: true }); + fs.writeFileSync( + path.join(meshDir, 'secrets.yaml'), + `TOKEN: '{"COMMERCE": "dummy-value"}'\nPLAIN: not-json\n`, + 'utf8', + ); + prevCwd = process.cwd(); + process.chdir(tmp); + + const secrets = readSecretsFile('.mesh'); + + expect(secrets.TOKEN).toEqual({ COMMERCE: 'dummy-value' }); + expect(secrets.PLAIN).toBe('not-json'); + + const mockLogger = { error: jest.fn() }; + const asWorkerSees = loadMeshSecrets(mockLogger, JSON.stringify(secrets)); + expect(asWorkerSees.TOKEN.COMMERCE).toBe('dummy-value'); + expect(asWorkerSees.PLAIN).toBe('not-json'); + expect(mockLogger.error).not.toHaveBeenCalled(); + }); +});