From d9ff0eeacd5fa6e823e16277d88534cf3c30a295 Mon Sep 17 00:00:00 2001 From: Roman Kolesnikov Date: Thu, 16 Jul 2026 14:31:14 +0200 Subject: [PATCH 01/14] test: fix session storage mock initialization Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3aafafb2-5e0b-49a1-b15d-ca56d5dd535e --- src/clients/mapiClient.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/clients/mapiClient.spec.ts b/src/clients/mapiClient.spec.ts index cbd98f24f..fa687b8af 100644 --- a/src/clients/mapiClient.spec.ts +++ b/src/clients/mapiClient.spec.ts @@ -21,7 +21,7 @@ describe("Mapi Client", async () => { global.sessionStorage = { _values: new Map(), - length: global.sessionStorage._values.size, + get length() { return this._values.size; }, key: (index: number) => { return null; }, getItem: (key: string) => { return global.sessionStorage._values.get(key); }, setItem: (key: string, value: string) => { global.sessionStorage._values.set(key, value); }, From 9347c515c5fc9a1deb9d2ea674dee0e6e48b377b Mon Sep 17 00:00:00 2001 From: Roman Kolesnikov Date: Thu, 16 Jul 2026 15:00:34 +0200 Subject: [PATCH 02/14] build: update lodash to 4.18.1 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3aafafb2-5e0b-49a1-b15d-ca56d5dd535e --- package-lock.json | 7 ++++--- package.json | 1 + 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index bfddb08da..719ea56ab 100644 --- a/package-lock.json +++ b/package-lock.json @@ -45,6 +45,7 @@ "knockout-mapping": "^2.6.0", "knockout.validation": "^2.0.4", "liquidjs": "^10.9.2", + "lodash": "4.18.1", "lunr": "^2.3.9", "mime": "^3.0.0", "moment": "^2.29.4", @@ -10294,9 +10295,9 @@ } }, "node_modules/lodash": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", - "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", "license": "MIT" }, "node_modules/lodash.get": { diff --git a/package.json b/package.json index 6aebf0a78..e403d1ebe 100644 --- a/package.json +++ b/package.json @@ -116,6 +116,7 @@ "knockout-mapping": "^2.6.0", "knockout.validation": "^2.0.4", "liquidjs": "^10.9.2", + "lodash": "4.18.1", "lunr": "^2.3.9", "mime": "^3.0.0", "moment": "^2.29.4", From 1e217a2320865f117fb60c62034f3bf399bb4169 Mon Sep 17 00:00:00 2001 From: Roman Kolesnikov Date: Thu, 16 Jul 2026 18:13:38 +0200 Subject: [PATCH 03/14] ci: pin validation to Node 20 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3aafafb2-5e0b-49a1-b15d-ca56d5dd535e --- .github/workflows/mainCI.yml | 7 +++++-- .github/workflows/test.yml | 5 +++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/.github/workflows/mainCI.yml b/.github/workflows/mainCI.yml index df7d88e0e..e8a51624e 100644 --- a/.github/workflows/mainCI.yml +++ b/.github/workflows/mainCI.yml @@ -18,10 +18,13 @@ jobs: security-events: write steps: - - name: Use Node.js 20.x + - name: Checkout code uses: actions/checkout@v2 + + - name: Setup Node.js 20 + uses: actions/setup-node@v4 with: - node-version: 20.x + node-version: 20 - name: Install run: npm install diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7793ba33f..8f912a981 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -13,6 +13,11 @@ jobs: - name: Checkout code uses: actions/checkout@v3 + - name: Setup Node.js 20 + uses: actions/setup-node@v4 + with: + node-version: 20 + - name: Install run: npm install From 8ae8a31477e0390f9d531783a26b8d0d9f2fc269 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 16 Jul 2026 16:25:20 +0000 Subject: [PATCH 04/14] fix: load static data webpack configs asynchronously --- webpack.staticData.js | 56 +++++++++++++++++++++---------------------- 1 file changed, 27 insertions(+), 29 deletions(-) diff --git a/webpack.staticData.js b/webpack.staticData.js index a78466a57..876809039 100644 --- a/webpack.staticData.js +++ b/webpack.staticData.js @@ -1,35 +1,33 @@ -const {staticDataEnvironment} = require("./environmentConstants") +const { staticDataEnvironment } = require("./environmentConstants"); const webpack = require("webpack"); -const publisherConfig = require("./webpack.publisher"); +const getPublisherConfigs = require("./webpack.publisher"); const CopyWebpackPlugin = require("copy-webpack-plugin"); +module.exports = async () => { + const [publisherConfig, publisherRuntimeConfig] = await getPublisherConfigs(); -let staticData = publisherConfig; + publisherConfig.mode = "none"; + publisherConfig.plugins.push(new webpack.DefinePlugin({ + "process.env.NODE_ENV": JSON.stringify(staticDataEnvironment) + })); + publisherConfig.plugins.push(new CopyWebpackPlugin({ + patterns: [ + { from: `./templates/default-old.json`, to: "editors/templates/default.json" }, + { from: `./tests/mocks/defaultStaticData.json`, to: "tests/mocks/defaultStaticData.json" } + ] + })); -staticData.publisherConfig.mode = "none"; -staticData.publisherConfig.plugins.push(new webpack.DefinePlugin({ - "process.env.NODE_ENV": JSON.stringify(staticDataEnvironment) -})); -staticData.publisherConfig.plugins.push(new CopyWebpackPlugin({ - patterns: [ - { from: `./templates/default-old.json`, to: "editors/templates/default.json" }, - { from: `./tests/mocks/defaultStaticData.json`, to: "tests/mocks/defaultStaticData.json" } - ] -})); + publisherRuntimeConfig.mode = "none"; + publisherRuntimeConfig.plugins.push(new webpack.DefinePlugin({ + "process.env.NODE_ENV": JSON.stringify(staticDataEnvironment), + "process.env.ACCESS_TOKEN": "\"\"", + })); + publisherRuntimeConfig.plugins.push(new CopyWebpackPlugin({ + patterns: [ + { from: `./templates/default-old.json`, to: "editors/templates/default.json" }, + { from: `./tests/mocks/defaultStaticData.json`, to: "tests/mocks/defaultStaticData.json" } + ] + })); -staticData.publisherRuntimeConfig.mode = "none"; -staticData.publisherRuntimeConfig.plugins.push(new webpack.DefinePlugin({ - "process.env.NODE_ENV": JSON.stringify(staticDataEnvironment), - "process.env.ACCESS_TOKEN": "\"\"", -})); - -staticData.publisherRuntimeConfig.plugins.push(new CopyWebpackPlugin({ - patterns: [ - { from: `./templates/default-old.json`, to: "editors/templates/default.json" }, - { from: `./tests/mocks/defaultStaticData.json`, to: "tests/mocks/defaultStaticData.json" } - ] -})); - -module.exports = { - default: [staticData.publisherConfig, staticData.publisherRuntimeConfig] -} \ No newline at end of file + return [publisherConfig, publisherRuntimeConfig]; +}; \ No newline at end of file From 57e1ad0be02b7ebebbdc00687923ea26c48310f1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 16 Jul 2026 16:26:47 +0000 Subject: [PATCH 05/14] fix: handle undefined ARM_TOKEN in authenticators for tests --- src/authentication/authenticatorResolver.ts | 2 ++ src/authentication/staticAuthenticator.ts | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/authentication/authenticatorResolver.ts b/src/authentication/authenticatorResolver.ts index d0da4566d..9c9d68a8f 100644 --- a/src/authentication/authenticatorResolver.ts +++ b/src/authentication/authenticatorResolver.ts @@ -6,6 +6,8 @@ import { ArmAuthenticator } from "./armAuthenticator"; import { IAuthenticator } from "./IAuthenticator"; import { IEditorSettings } from "./IEditorSettings"; +declare const ARM_TOKEN: string | undefined; + export class AuthenticatorResolver { private loadPromise: Promise; diff --git a/src/authentication/staticAuthenticator.ts b/src/authentication/staticAuthenticator.ts index 3d6a0a5c6..d349f091b 100644 --- a/src/authentication/staticAuthenticator.ts +++ b/src/authentication/staticAuthenticator.ts @@ -1,5 +1,7 @@ import { IAuthenticator, AccessToken } from "."; +declare const ARM_TOKEN: string | undefined; + /** * Static implementation of the IAuthenticator interface to mimic actual authentication in publish time. */ @@ -11,7 +13,7 @@ export class StaticAuthenticator implements IAuthenticator { * The ARM token injected acquired in build-time. It's used in on local development only. * TODO: Static authenticator is used in production publishing, therefore it's safer to introduce dedicated implementation. */ - if (!ARM_TOKEN) { + if (typeof ARM_TOKEN === "undefined" || !ARM_TOKEN) { return; } From 02be57e5df73ea05dc848b3b57c7bff36c82a733 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 16 Jul 2026 16:28:10 +0000 Subject: [PATCH 06/14] refactor: deduplicate static data copy patterns --- webpack.staticData.js | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/webpack.staticData.js b/webpack.staticData.js index 876809039..ab6090347 100644 --- a/webpack.staticData.js +++ b/webpack.staticData.js @@ -3,6 +3,11 @@ const webpack = require("webpack"); const getPublisherConfigs = require("./webpack.publisher"); const CopyWebpackPlugin = require("copy-webpack-plugin"); +const staticDataCopyPatterns = [ + { from: `./templates/default-old.json`, to: "editors/templates/default.json" }, + { from: `./tests/mocks/defaultStaticData.json`, to: "tests/mocks/defaultStaticData.json" } +]; + module.exports = async () => { const [publisherConfig, publisherRuntimeConfig] = await getPublisherConfigs(); @@ -11,10 +16,7 @@ module.exports = async () => { "process.env.NODE_ENV": JSON.stringify(staticDataEnvironment) })); publisherConfig.plugins.push(new CopyWebpackPlugin({ - patterns: [ - { from: `./templates/default-old.json`, to: "editors/templates/default.json" }, - { from: `./tests/mocks/defaultStaticData.json`, to: "tests/mocks/defaultStaticData.json" } - ] + patterns: staticDataCopyPatterns })); publisherRuntimeConfig.mode = "none"; @@ -23,10 +25,7 @@ module.exports = async () => { "process.env.ACCESS_TOKEN": "\"\"", })); publisherRuntimeConfig.plugins.push(new CopyWebpackPlugin({ - patterns: [ - { from: `./templates/default-old.json`, to: "editors/templates/default.json" }, - { from: `./tests/mocks/defaultStaticData.json`, to: "tests/mocks/defaultStaticData.json" } - ] + patterns: staticDataCopyPatterns })); return [publisherConfig, publisherRuntimeConfig]; From 55936bf0245de7f461827246e7bb374dfe5abef3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 16 Jul 2026 16:44:53 +0000 Subject: [PATCH 07/14] fix: skip ARM token fetch in static data build to fix CI failure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit webpack.staticData.js calls webpack.publisher.js which previously always fetched an ARM token via getArmToken(). In CI there is no AZURE_CLIENT_SECRET set, so it fell back to interactive browser auth which fails with AuthenticationRequiredError. The static data build does not need a real ARM token — startup.publish.ts uses a hardcoded StaticSettingsProvider when NODE_ENV === staticDataEnvironment, and StaticAuthenticator already handles undefined/empty ARM_TOKEN gracefully. - Add optional skipArmToken parameter to webpack.publisher.js - Pass { skipArmToken: true } from webpack.staticData.js --- webpack.publisher.js | 30 +++++++++++++++++------------- webpack.staticData.js | 2 +- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/webpack.publisher.js b/webpack.publisher.js index 3ad82ef31..32fc0723e 100644 --- a/webpack.publisher.js +++ b/webpack.publisher.js @@ -12,18 +12,22 @@ const publisherRuntimeConfig = merge(runtimeConfig, { output: { "path": path.resolve(__dirname, "dist/publisher/assets") } }); -async function generateWebpackConfig() { - const tokenOptions = {}; - - if (config.tenantId) { - console.log(`Using tenantId: ${config.tenantId}`); - tokenOptions.tenantId = config.tenantId; - } - if (config.clientId) { - console.log(`Using clientId: ${config.clientId}`); - tokenOptions.clientId = config.clientId; +async function generateWebpackConfig(skipArmToken = false) { + let armToken; + + if (!skipArmToken) { + const tokenOptions = {}; + + if (config.tenantId) { + console.log(`Using tenantId: ${config.tenantId}`); + tokenOptions.tenantId = config.tenantId; + } + if (config.clientId) { + console.log(`Using clientId: ${config.clientId}`); + tokenOptions.clientId = config.clientId; + } + armToken = await getArmToken(tokenOptions); } - const armToken = await getArmToken(tokenOptions); const publisherConfig = { mode: "development", @@ -110,7 +114,7 @@ async function generateWebpackConfig() { return publisherConfig; } -module.exports = async () => { - const publisherConfig = await generateWebpackConfig(); +module.exports = async (options = {}) => { + const publisherConfig = await generateWebpackConfig(options.skipArmToken); return [publisherConfig, publisherRuntimeConfig]; }; \ No newline at end of file diff --git a/webpack.staticData.js b/webpack.staticData.js index ab6090347..dd8b3fb9e 100644 --- a/webpack.staticData.js +++ b/webpack.staticData.js @@ -9,7 +9,7 @@ const staticDataCopyPatterns = [ ]; module.exports = async () => { - const [publisherConfig, publisherRuntimeConfig] = await getPublisherConfigs(); + const [publisherConfig, publisherRuntimeConfig] = await getPublisherConfigs({ skipArmToken: true }); publisherConfig.mode = "none"; publisherConfig.plugins.push(new webpack.DefinePlugin({ From 9fc8268ad5a50a3aae444d257cf8de5cc2a1cbda Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 16 Jul 2026 17:04:26 +0000 Subject: [PATCH 08/14] fix: await async publisher webpack configs --- webpack.build.js | 18 ++++++++------ webpack.mockStaticData.js | 51 ++++++++++++++++++--------------------- 2 files changed, 34 insertions(+), 35 deletions(-) diff --git a/webpack.build.js b/webpack.build.js index 79384463d..9da6b4c2d 100644 --- a/webpack.build.js +++ b/webpack.build.js @@ -1,7 +1,7 @@ const { merge } = require("webpack-merge"); const TerserPlugin = require("terser-webpack-plugin"); const { designerConfig, designerRuntimeConfig } = require("./webpack.designer.js"); -const { publisherConfig, publisherRuntimeConfig } = require("./webpack.publisher.js"); +const getPublisherConfigs = require("./webpack.publisher.js"); const productionConfig = { @@ -20,9 +20,13 @@ const productionConfig = { } } -module.exports = [ - designerConfig, - designerRuntimeConfig, - publisherConfig, - publisherRuntimeConfig -].map(x => merge(x, productionConfig)); \ No newline at end of file +module.exports = async () => { + const [publisherConfig, publisherRuntimeConfig] = await getPublisherConfigs(); + + return [ + designerConfig, + designerRuntimeConfig, + publisherConfig, + publisherRuntimeConfig + ].map(x => merge(x, productionConfig)); +}; \ No newline at end of file diff --git a/webpack.mockStaticData.js b/webpack.mockStaticData.js index c2c336b74..6df94062d 100644 --- a/webpack.mockStaticData.js +++ b/webpack.mockStaticData.js @@ -1,31 +1,26 @@ -const {mockStaticDataEnvironment, staticDataEnvironment} = require("./environmentConstants") +const { mockStaticDataEnvironment } = require("./environmentConstants"); const webpack = require("webpack"); -const publisherConfig = require("./webpack.publisher"); +const getPublisherConfigs = require("./webpack.publisher"); const CopyWebpackPlugin = require("copy-webpack-plugin"); - -let staticData = publisherConfig; - -staticData.publisherConfig.mode = "none"; -staticData.publisherConfig.plugins.push(new webpack.DefinePlugin({ - "process.env.NODE_ENV": JSON.stringify(mockStaticDataEnvironment) -})); - -staticData.publisherRuntimeConfig.mode = "none"; - -staticData.publisherConfig.mode = "none"; -staticData.publisherConfig.plugins.push(new CopyWebpackPlugin({ - patterns: [ - { from: `./src/config.validate.publish.json`, to: `config.json`, force: true }, - { from: `./src/config.validate.runtime.json`, to: `assets/config.json` , force: true}, - ] -})); - - -staticData.publisherRuntimeConfig.plugins.push(new webpack.DefinePlugin({ - "process.env.NODE_ENV": JSON.stringify(mockStaticDataEnvironment), -})); - -module.exports = { - default: [staticData.publisherConfig, staticData.publisherRuntimeConfig] -} \ No newline at end of file +module.exports = async () => { + const [publisherConfig, publisherRuntimeConfig] = await getPublisherConfigs({ skipArmToken: true }); + + publisherConfig.mode = "none"; + publisherConfig.plugins.push(new webpack.DefinePlugin({ + "process.env.NODE_ENV": JSON.stringify(mockStaticDataEnvironment) + })); + publisherConfig.plugins.push(new CopyWebpackPlugin({ + patterns: [ + { from: `./src/config.validate.publish.json`, to: `config.json`, force: true }, + { from: `./src/config.validate.runtime.json`, to: `assets/config.json`, force: true }, + ] + })); + + publisherRuntimeConfig.mode = "none"; + publisherRuntimeConfig.plugins.push(new webpack.DefinePlugin({ + "process.env.NODE_ENV": JSON.stringify(mockStaticDataEnvironment), + })); + + return [publisherConfig, publisherRuntimeConfig]; +}; \ No newline at end of file From 57a23bdec16c882dd0a2e567f0a2be89a8af9498 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 17 Jul 2026 10:15:46 +0000 Subject: [PATCH 09/14] fix: set isLocalRun=true and root=http://localhost:8080 in config.validate.json to fix end2end-tests CI --- src/config.validate.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/config.validate.json b/src/config.validate.json index b9a167789..e2ce2c081 100644 --- a/src/config.validate.json +++ b/src/config.validate.json @@ -1,7 +1,7 @@ { "environment": "validation", - "isLocalRun": false, - "root": "https://.developer.azure-api.net", + "isLocalRun": true, + "root": "http://localhost:8080", "managementUrl": "https://.management.azure-api.net", "accessToken": "SharedAccessSignature ...", "useArmAuth": false, From d75ae27739604bb1f5865ddb9cfa135bdd26bd7d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 17 Jul 2026 10:16:52 +0000 Subject: [PATCH 10/14] fix: use nullish coalescing for skipArmToken option in webpack.publisher.js --- webpack.publisher.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webpack.publisher.js b/webpack.publisher.js index 32fc0723e..4a16aea1b 100644 --- a/webpack.publisher.js +++ b/webpack.publisher.js @@ -115,6 +115,6 @@ async function generateWebpackConfig(skipArmToken = false) { } module.exports = async (options = {}) => { - const publisherConfig = await generateWebpackConfig(options.skipArmToken); + const publisherConfig = await generateWebpackConfig(options.skipArmToken ?? false); return [publisherConfig, publisherRuntimeConfig]; }; \ No newline at end of file From 70f19a44601484e782433a3c4a1c833f602cab3e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 17 Jul 2026 11:14:12 +0000 Subject: [PATCH 11/14] fix: restore managementApiUrl fallback in MapiClient.setBaseUrl and static settings for mock publishing mode --- src/clients/mapiClient.ts | 27 ++++++++++++++++----------- src/startup.publish.ts | 3 ++- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/src/clients/mapiClient.ts b/src/clients/mapiClient.ts index e0925f607..3cbd1facc 100644 --- a/src/clients/mapiClient.ts +++ b/src/clients/mapiClient.ts @@ -7,6 +7,7 @@ import * as Constants from "../constants"; import ApiClient from "./apiClient"; import { IRetryStrategy } from "./retryStrategy/retryStrategy"; import { SettingNames } from "../constants"; +import { Utils } from "../utils"; export class MapiClient extends ApiClient { constructor( @@ -22,25 +23,29 @@ export class MapiClient extends ApiClient { protected override async setBaseUrl() { const settings = await this.settingsProvider.getSettings(); - + + const managementApiUrl = settings[Constants.SettingNames.managementApiUrl]; + if (managementApiUrl) { + this.baseUrl = managementApiUrl; + return; + } + const serviceName = settings[Constants.SettingNames.serviceName]; const subscriptionId = settings[Constants.SettingNames.subscriptionId]; const resourceGroupName = settings[Constants.SettingNames.resourceGroupName]; - if (!serviceName) { - throw new Error("Service name setting is missing."); + if (serviceName && subscriptionId && resourceGroupName) { + this.baseUrl = `https://management.azure.com/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.ApiManagement/service/${serviceName}`; + return; } - if (!subscriptionId) { - throw new Error("Subscription ID setting is missing."); + const backendUrl = settings[Constants.SettingNames.backendUrl]; + if (backendUrl) { + this.baseUrl = Utils.getBaseUrlWithMapiSuffix(backendUrl); + return; } - if (!resourceGroupName) { - throw new Error("Resource Group name setting is missing."); - } - - this.baseUrl = `https://management.azure.com/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.ApiManagement/service/${serviceName}`; - + throw new Error("Service name setting is missing."); } public async getTenantArmUriAsync(): Promise { diff --git a/src/startup.publish.ts b/src/startup.publish.ts index 0bb86c980..cf0a2d15c 100644 --- a/src/startup.publish.ts +++ b/src/startup.publish.ts @@ -20,7 +20,8 @@ if (process.env.NODE_ENV === staticDataEnvironment || process.env.NODE_ENV === m "environment": "publishing", "backendUrl": "https://contoso.developer.azure-api.net", "managementApiAccessToken": "SharedAccessSignature&1&", - "useHipCaptcha": false + "useHipCaptcha": false, + "managementApiUrl": "https://contoso.developer.azure-api.net/mapi" }); } else { From 8aca732ec98700a3ea04b17de733726db1c432c3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 17 Jul 2026 12:44:39 +0000 Subject: [PATCH 12/14] fix: remove redundant 'products/' prefix in getProduct calls in product-subscribe components --- .../products/product-subscribe/ko/runtime/product-subscribe.ts | 2 +- .../product-subscribe/react/runtime/ProductSubscribeRuntime.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/products/product-subscribe/ko/runtime/product-subscribe.ts b/src/components/products/product-subscribe/ko/runtime/product-subscribe.ts index 1554a2caf..fd77abc34 100644 --- a/src/components/products/product-subscribe/ko/runtime/product-subscribe.ts +++ b/src/components/products/product-subscribe/ko/runtime/product-subscribe.ts @@ -81,7 +81,7 @@ export class ProductSubscribe { return; } - const product = await this.productService.getProduct(`products/${productName}`); + const product = await this.productService.getProduct(productName); if (!product) { return; diff --git a/src/components/products/product-subscribe/react/runtime/ProductSubscribeRuntime.tsx b/src/components/products/product-subscribe/react/runtime/ProductSubscribeRuntime.tsx index 3ada1b2ed..09b374456 100644 --- a/src/components/products/product-subscribe/react/runtime/ProductSubscribeRuntime.tsx +++ b/src/components/products/product-subscribe/react/runtime/ProductSubscribeRuntime.tsx @@ -27,7 +27,7 @@ type ProductSubscribeRuntimeFCProps = ProductSubscribeRuntimeProps & { const loadProduct = async (usersService: UsersService, delegationService: IDelegationService, productService: ProductService, productName: string) => { const promises = [ await delegationService.isSubscriptionDelegationEnabled(), - await productService.getProduct(`products/${productName}`), + await productService.getProduct(productName), await usersService.getCurrentUserId(), ] as const; const [isDelegationEnabled, product, userId] = await Promise.all(promises); From 29269ae91d7862b1dfbff1fdb8739d575c1b63b1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 17 Jul 2026 12:46:10 +0000 Subject: [PATCH 13/14] fix: improve error message in MapiClient.setBaseUrl to reflect all config sources --- src/clients/mapiClient.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/clients/mapiClient.ts b/src/clients/mapiClient.ts index 3cbd1facc..454961f37 100644 --- a/src/clients/mapiClient.ts +++ b/src/clients/mapiClient.ts @@ -45,7 +45,7 @@ export class MapiClient extends ApiClient { return; } - throw new Error("Service name setting is missing."); + throw new Error("Unable to determine management API URL: no valid configuration found (managementApiUrl, serviceName/subscriptionId/resourceGroupName, or backendUrl must be set)."); } public async getTenantArmUriAsync(): Promise { From 8f2b8d6023c8829c2c71960ee89b040e2ba3f852 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 17 Jul 2026 14:16:30 +0000 Subject: [PATCH 14/14] fix: pre-seed sessionStorage mock and add --exit to mocha to fix test failures and stuck process --- package.json | 2 +- src/clients/mapiClient.spec.ts | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index e403d1ebe..4a10dea85 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "build-publisher": "webpack --config webpack.publisher.js", "build-runtime": "webpack --config webpack.runtime.js", "build-function": "webpack --config webpack.function.js", - "test": "node node_modules/mocha/bin/_mocha -r mocha.js src/**/*.spec.ts", + "test": "node node_modules/mocha/bin/_mocha -r mocha.js --exit src/**/*.spec.ts", "deploy-function": "npm run build-function && cd dist/function && func azure functionapp publish < function app name >", "publish": "webpack --config webpack.publisher.js && node dist/publisher/index.js && npm run serve-website", "serve-website": "webpack serve --open --static ./dist/website --no-stats", diff --git a/src/clients/mapiClient.spec.ts b/src/clients/mapiClient.spec.ts index fa687b8af..07062a43c 100644 --- a/src/clients/mapiClient.spec.ts +++ b/src/clients/mapiClient.spec.ts @@ -29,6 +29,10 @@ describe("Mapi Client", async () => { clear: () => { global.sessionStorage._values.clear(); } } + // Pre-seed a valid non-expired SAS token so ArmAuthenticator.getAccessToken() returns from + // sessionStorage cache and never calls authenticate(), which relies on browser-only `location`. + global.sessionStorage.setItem("armAccessToken", createMockToken()); + const settingsProvider = new StaticSettingsProvider({ managementApiUrl: "https://contoso.management.azure-api.net", backendUrl: "https://contoso.developer.azure-api.net",