Merge pull request #31381 from enyineer/fix/create-app-with-proxy

Allow configuration of Proxy when using create-app CLI
This commit is contained in:
Ben Lambert
2025-11-25 10:25:23 +01:00
committed by GitHub
9 changed files with 179 additions and 22 deletions
+7
View File
@@ -0,0 +1,7 @@
---
'@backstage/cli-common': patch
'@backstage/create-app': patch
'@backstage/cli': patch
---
Add proxy configuration from env-vars to create-app tasks
+4
View File
@@ -34,6 +34,10 @@
"start": "backstage-cli package start",
"test": "backstage-cli package test"
},
"dependencies": {
"global-agent": "^3.0.0",
"undici": "^7.2.3"
},
"devDependencies": {
"@backstage/cli": "workspace:^",
"@types/node": "^20.16.0"
+3
View File
@@ -6,6 +6,9 @@
// @public
export const BACKSTAGE_JSON = 'backstage.json';
// @public
export function bootstrapEnvProxyAgents(): void;
// @public
export function findPaths(searchDir: string): Paths;
+1
View File
@@ -23,3 +23,4 @@
export { findPaths, BACKSTAGE_JSON } from './paths';
export { isChildPath } from './isChildPath';
export type { Paths, ResolveFunc } from './paths';
export { bootstrapEnvProxyAgents } from './proxyBootstrap';
@@ -0,0 +1,108 @@
/*
* Copyright 2025 The Backstage Authors
*
* Licensed 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 CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { bootstrapEnvProxyAgents } from './proxyBootstrap';
// Avoid mutating the global agents used in other tests
jest.mock('global-agent', () => ({
bootstrap: jest.fn(),
}));
jest.mock('undici', () => ({
setGlobalDispatcher: jest.fn(),
EnvHttpProxyAgent: jest.fn(),
}));
describe('bootstrapEnvProxyAgents', () => {
const originalEnv = process.env;
beforeEach(() => {
jest.resetModules();
process.env = { ...originalEnv };
});
afterEach(() => {
process.env = originalEnv;
jest.clearAllMocks();
});
it('should bootstrap global-agent if GLOBAL_AGENT_HTTP_PROXY is set', () => {
process.env.GLOBAL_AGENT_HTTP_PROXY = 'http://proxy.example.com';
const { bootstrap } =
require('global-agent') as typeof import('global-agent');
bootstrapEnvProxyAgents();
expect(bootstrap).toHaveBeenCalledTimes(1);
});
it('should bootstrap global-agent if GLOBAL_AGENT_HTTPS_PROXY is set', () => {
process.env.GLOBAL_AGENT_HTTPS_PROXY = 'https://proxy.example.com';
const { bootstrap } =
require('global-agent') as typeof import('global-agent');
bootstrapEnvProxyAgents();
expect(bootstrap).toHaveBeenCalledTimes(1);
});
it('should use undici EnvHttpProxyAgent if HTTP_PROXY is set', () => {
process.env.HTTP_PROXY = 'http://proxy.example.com';
const { setGlobalDispatcher, EnvHttpProxyAgent } =
require('undici') as typeof import('undici');
bootstrapEnvProxyAgents();
expect(EnvHttpProxyAgent).toHaveBeenCalledTimes(1);
expect(setGlobalDispatcher).toHaveBeenCalledWith(
expect.any(EnvHttpProxyAgent),
);
});
it('should use undici EnvHttpProxyAgent if HTTPS_PROXY is set', () => {
process.env.HTTPS_PROXY = 'https://proxy.example.com';
const { setGlobalDispatcher, EnvHttpProxyAgent } =
require('undici') as typeof import('undici');
bootstrapEnvProxyAgents();
expect(EnvHttpProxyAgent).toHaveBeenCalledTimes(1);
expect(setGlobalDispatcher).toHaveBeenCalledWith(
expect.any(EnvHttpProxyAgent),
);
});
it('should not bootstrap global-agent or set undici dispatcher if no proxy is set', () => {
const { bootstrap } =
require('global-agent') as typeof import('global-agent');
const { setGlobalDispatcher } =
require('undici') as typeof import('undici');
bootstrapEnvProxyAgents();
expect(bootstrap).not.toHaveBeenCalled();
expect(setGlobalDispatcher).not.toHaveBeenCalled();
});
it('should respect GLOBAL_AGENT_ENVIRONMENT_VARIABLE_NAMESPACE', () => {
process.env.GLOBAL_AGENT_ENVIRONMENT_VARIABLE_NAMESPACE = 'CUSTOM_AGENT_';
process.env.CUSTOM_AGENT_HTTP_PROXY = 'http://proxy.example.com';
const { bootstrap } =
require('global-agent') as typeof import('global-agent');
bootstrapEnvProxyAgents();
expect(bootstrap).toHaveBeenCalledTimes(1);
});
});
+49
View File
@@ -0,0 +1,49 @@
/*
* Copyright 2025 The Backstage Authors
*
* Licensed 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 CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* This function can be called to setup undici and node-fetch Proxy agents.
*
* You can set GLOBAL_AGENT_HTTP(S)_PROXY to configure a proxy to be used in the
* CLIs.
*
* You can also configure a custom namespace by setting
* GLOBAL_AGENT_ENVIRONMENT_VARIABLE_NAMESPACE which will replace the default
* "GLOBAL_AGENT_" env-var prefix.
*
* Make sure to call this function before any other imports.
*
* @public
*/
export function bootstrapEnvProxyAgents() {
// see https://www.npmjs.com/package/global-agent
const globalAgentNamespace =
process.env.GLOBAL_AGENT_ENVIRONMENT_VARIABLE_NAMESPACE ?? 'GLOBAL_AGENT_';
if (
process.env[`${globalAgentNamespace}HTTP_PROXY`] ||
process.env[`${globalAgentNamespace}HTTPS_PROXY`]
) {
const globalAgent =
require('global-agent') as typeof import('global-agent');
globalAgent.bootstrap();
}
if (process.env.HTTP_PROXY || process.env.HTTPS_PROXY) {
const { setGlobalDispatcher, EnvHttpProxyAgent } =
require('undici') as typeof import('undici');
setGlobalDispatcher(new EnvHttpProxyAgent());
}
}
@@ -13,8 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { BACKSTAGE_JSON, bootstrapEnvProxyAgents } from '@backstage/cli-common';
maybeBootstrapProxy();
bootstrapEnvProxyAgents();
import { env } from 'process';
import fs from 'fs-extra';
@@ -32,7 +33,6 @@ import {
mapDependencies,
YarnInfoInspectData,
} from '../../../../lib/versioning';
import { BACKSTAGE_JSON } from '@backstage/cli-common';
import { runParallelWorkers } from '../../../../lib/parallel';
import {
getManifestByReleaseLine,
@@ -43,26 +43,6 @@ import { migrateMovedPackages } from './migrate';
import { runYarnInstall } from '../../lib/utils';
import { run } from '../../../../lib/run';
function maybeBootstrapProxy() {
// see https://www.npmjs.com/package/global-agent
const globalAgentNamespace =
process.env.GLOBAL_AGENT_ENVIRONMENT_VARIABLE_NAMESPACE ?? 'GLOBAL_AGENT_';
if (
process.env[`${globalAgentNamespace}HTTP_PROXY`] ||
process.env[`${globalAgentNamespace}HTTPS_PROXY`]
) {
const globalAgent =
require('global-agent') as typeof import('global-agent');
globalAgent.bootstrap();
}
if (process.env.HTTP_PROXY || process.env.HTTPS_PROXY) {
const { setGlobalDispatcher, EnvHttpProxyAgent } =
require('undici') as typeof import('undici');
setGlobalDispatcher(new EnvHttpProxyAgent());
}
}
const DEP_TYPES = [
'dependencies',
'devDependencies',
+3
View File
@@ -13,6 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { bootstrapEnvProxyAgents } from '@backstage/cli-common';
bootstrapEnvProxyAgents();
import chalk from 'chalk';
import fs from 'fs-extra';
+2
View File
@@ -3141,6 +3141,8 @@ __metadata:
dependencies:
"@backstage/cli": "workspace:^"
"@types/node": "npm:^20.16.0"
global-agent: "npm:^3.0.0"
undici: "npm:^7.2.3"
languageName: unknown
linkType: soft