feat(core): auto detect proxy server on load (#3195)
* feat: auto detect proxy server on load * fix: opt-in for auto proxy server detection
This commit is contained in:
parent
2043c0b782
commit
614f1aea63
@ -1,11 +1,15 @@
|
|||||||
backend:
|
backend:
|
||||||
name: proxy
|
name: github
|
||||||
branch: master
|
branch: master
|
||||||
proxy_url: http://localhost:8082/api/v1
|
repo: owner/repo
|
||||||
|
|
||||||
publish_mode: editorial_workflow
|
publish_mode: editorial_workflow
|
||||||
media_folder: static/media
|
media_folder: static/media
|
||||||
public_folder: /media
|
public_folder: /media
|
||||||
|
|
||||||
|
local_backend:
|
||||||
|
url: http://localhost:8082/api/v1
|
||||||
|
|
||||||
collections:
|
collections:
|
||||||
- name: posts
|
- name: posts
|
||||||
label: Posts
|
label: Posts
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import { fromJS } from 'immutable';
|
import { fromJS } from 'immutable';
|
||||||
import { applyDefaults } from '../config';
|
import { applyDefaults, detectProxyServer } from '../config';
|
||||||
|
|
||||||
|
jest.spyOn(console, 'log').mockImplementation(() => {});
|
||||||
|
|
||||||
describe('config', () => {
|
describe('config', () => {
|
||||||
describe('applyDefaults', () => {
|
describe('applyDefaults', () => {
|
||||||
@ -168,4 +170,66 @@ describe('config', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('detectProxyServer', () => {
|
||||||
|
const assetFetchCalled = (url = 'http://localhost:8081/api/v1') => {
|
||||||
|
expect(global.fetch).toHaveBeenCalledTimes(1);
|
||||||
|
expect(global.fetch).toHaveBeenCalledWith(url, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ action: 'info' }),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
delete window.location;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return undefined when not on localhost', async () => {
|
||||||
|
window.location = { hostname: 'www.netlify.com' };
|
||||||
|
global.fetch = jest.fn();
|
||||||
|
await expect(detectProxyServer()).resolves.toBeUndefined();
|
||||||
|
|
||||||
|
expect(global.fetch).toHaveBeenCalledTimes(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return undefined when fetch returns an error', async () => {
|
||||||
|
window.location = { hostname: 'localhost' };
|
||||||
|
global.fetch = jest.fn().mockRejectedValue(new Error());
|
||||||
|
await expect(detectProxyServer(true)).resolves.toBeUndefined();
|
||||||
|
|
||||||
|
assetFetchCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return undefined when fetch returns an invalid response', async () => {
|
||||||
|
window.location = { hostname: 'localhost' };
|
||||||
|
global.fetch = jest
|
||||||
|
.fn()
|
||||||
|
.mockResolvedValue({ json: jest.fn().mockResolvedValue({ repo: [] }) });
|
||||||
|
await expect(detectProxyServer(true)).resolves.toBeUndefined();
|
||||||
|
|
||||||
|
assetFetchCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return proxyUrl when fetch returns a valid response', async () => {
|
||||||
|
window.location = { hostname: 'localhost' };
|
||||||
|
global.fetch = jest
|
||||||
|
.fn()
|
||||||
|
.mockResolvedValue({ json: jest.fn().mockResolvedValue({ repo: 'test-repo' }) });
|
||||||
|
await expect(detectProxyServer(true)).resolves.toBe('http://localhost:8081/api/v1');
|
||||||
|
|
||||||
|
assetFetchCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should use local_backend url', async () => {
|
||||||
|
const url = 'http://localhost:8082/api/v1';
|
||||||
|
window.location = { hostname: 'localhost' };
|
||||||
|
global.fetch = jest
|
||||||
|
.fn()
|
||||||
|
.mockResolvedValue({ json: jest.fn().mockResolvedValue({ repo: 'test-repo' }) });
|
||||||
|
await expect(detectProxyServer({ url })).resolves.toBe(url);
|
||||||
|
|
||||||
|
assetFetchCalled(url);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import yaml from 'js-yaml';
|
import yaml from 'js-yaml';
|
||||||
import { Map, fromJS } from 'immutable';
|
import { Map, fromJS } from 'immutable';
|
||||||
import { trimStart, get } from 'lodash';
|
import { trimStart, get, isPlainObject } from 'lodash';
|
||||||
import { authenticateUser } from 'Actions/auth';
|
import { authenticateUser } from 'Actions/auth';
|
||||||
import * as publishModes from 'Constants/publishModes';
|
import * as publishModes from 'Constants/publishModes';
|
||||||
import { validateConfig } from 'Constants/configSchema';
|
import { validateConfig } from 'Constants/configSchema';
|
||||||
@ -145,6 +145,31 @@ export function mergeConfig(config) {
|
|||||||
return { type: CONFIG_MERGE, payload: config };
|
return { type: CONFIG_MERGE, payload: config };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function detectProxyServer(localBackend) {
|
||||||
|
if (location.hostname === 'localhost' || location.hostname === '127.0.0.1') {
|
||||||
|
let proxyUrl;
|
||||||
|
if (localBackend === true) {
|
||||||
|
proxyUrl = 'http://localhost:8081/api/v1';
|
||||||
|
} else if (isPlainObject(localBackend)) {
|
||||||
|
proxyUrl = localBackend.url;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
console.log(`Looking for Netlify CMS Proxy Server at '${proxyUrl}'`);
|
||||||
|
const { repo } = await fetch(`${proxyUrl}`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ action: 'info' }),
|
||||||
|
}).then(res => res.json());
|
||||||
|
if (typeof repo === 'string') {
|
||||||
|
console.log(`Detected Netlify CMS Proxy Server at '${proxyUrl}' with repo: '${repo}'`);
|
||||||
|
return proxyUrl;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
console.log(`Netlify CMS Proxy Server not detected at '${proxyUrl}'`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function loadConfig() {
|
export function loadConfig() {
|
||||||
if (window.CMS_CONFIG) {
|
if (window.CMS_CONFIG) {
|
||||||
return configDidLoad(fromJS(window.CMS_CONFIG));
|
return configDidLoad(fromJS(window.CMS_CONFIG));
|
||||||
@ -155,17 +180,29 @@ export function loadConfig() {
|
|||||||
try {
|
try {
|
||||||
const preloadedConfig = getState().config;
|
const preloadedConfig = getState().config;
|
||||||
const configUrl = getConfigUrl();
|
const configUrl = getConfigUrl();
|
||||||
|
const isPreloaded = preloadedConfig && preloadedConfig.size > 1;
|
||||||
const loadedConfig =
|
const loadedConfig =
|
||||||
preloadedConfig && preloadedConfig.get('load_config_file') === false
|
preloadedConfig && preloadedConfig.get('load_config_file') === false
|
||||||
? {}
|
? {}
|
||||||
: await getConfig(configUrl, preloadedConfig && preloadedConfig.size > 1);
|
: await getConfig(configUrl, isPreloaded);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Merge any existing configuration so the result can be validated.
|
* Merge any existing configuration so the result can be validated.
|
||||||
*/
|
*/
|
||||||
const mergedConfig = mergePreloadedConfig(preloadedConfig, loadedConfig);
|
let mergedConfig = mergePreloadedConfig(preloadedConfig, loadedConfig);
|
||||||
|
|
||||||
validateConfig(mergedConfig.toJS());
|
validateConfig(mergedConfig.toJS());
|
||||||
|
|
||||||
|
// detect running Netlify CMS proxy
|
||||||
|
if (mergedConfig.has('local_backend')) {
|
||||||
|
const proxyUrl = await detectProxyServer(mergedConfig.toJS().local_backend);
|
||||||
|
if (proxyUrl) {
|
||||||
|
mergedConfig = mergePreloadedConfig(mergedConfig, {
|
||||||
|
backend: { name: 'proxy', proxy_url: proxyUrl },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const config = applyDefaults(mergedConfig);
|
const config = applyDefaults(mergedConfig);
|
||||||
|
|
||||||
dispatch(configDidLoad(config));
|
dispatch(configDidLoad(config));
|
||||||
|
@ -126,5 +126,31 @@ describe('config', () => {
|
|||||||
});
|
});
|
||||||
}).toThrowError("'collections[0]' should be object");
|
}).toThrowError("'collections[0]' should be object");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should throw if local_backend is not a boolean or plain object', () => {
|
||||||
|
expect(() => {
|
||||||
|
validateConfig(merge(validConfig, { local_backend: [] }));
|
||||||
|
}).toThrowError("'local_backend' should be boolean");
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw if local_backend is a plain object but missing url property', () => {
|
||||||
|
expect(() => {
|
||||||
|
validateConfig(merge(validConfig, { local_backend: {} }));
|
||||||
|
}).toThrowError("'local_backend' should be object");
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not throw if local_backend is a boolean', () => {
|
||||||
|
expect(() => {
|
||||||
|
validateConfig(merge(validConfig, { local_backend: true }));
|
||||||
|
}).not.toThrowError();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not throw if local_backend is a plain object with url property', () => {
|
||||||
|
expect(() => {
|
||||||
|
validateConfig(
|
||||||
|
merge(validConfig, { local_backend: { url: 'http://localhost:8081/api/v1' } }),
|
||||||
|
);
|
||||||
|
}).not.toThrowError();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -42,6 +42,18 @@ const getConfigSchema = () => ({
|
|||||||
},
|
},
|
||||||
required: ['name'],
|
required: ['name'],
|
||||||
},
|
},
|
||||||
|
local_backend: {
|
||||||
|
oneOf: [
|
||||||
|
{ type: 'boolean' },
|
||||||
|
{
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
url: { type: 'string', examples: ['http://localhost:8081/api/v1'] },
|
||||||
|
},
|
||||||
|
required: ['url'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
locale: { type: 'string', examples: ['en', 'fr', 'de'] },
|
locale: { type: 'string', examples: ['en', 'fr', 'de'] },
|
||||||
site_url: { type: 'string', examples: ['https://example.com'] },
|
site_url: { type: 'string', examples: ['https://example.com'] },
|
||||||
display_url: { type: 'string', examples: ['https://example.com'] },
|
display_url: { type: 'string', examples: ['https://example.com'] },
|
||||||
|
@ -14,18 +14,20 @@ You can connect Netlify CMS to a local Git repository, instead of working with a
|
|||||||
|
|
||||||
1. Navigate to a local Git repository configured with the CMS.
|
1. Navigate to a local Git repository configured with the CMS.
|
||||||
2. Run `npx netlify-cms-proxy-server` from the root directory of the above repository.
|
2. Run `npx netlify-cms-proxy-server` from the root directory of the above repository.
|
||||||
3. Update your `config.yml` to connect to the server:
|
3. Add the `local_backend` configuration to your `config.yml`:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
backend:
|
# when using the default proxy server port
|
||||||
name: proxy
|
local_backend: true
|
||||||
proxy_url: http://localhost:8081/api/v1
|
|
||||||
branch: master # optional, defaults to master
|
# when using a custom proxy server port
|
||||||
|
local_backend:
|
||||||
|
url: http://localhost:8082/api/v1
|
||||||
```
|
```
|
||||||
|
|
||||||
4. Start you local development server (e.g. run `gatsby develop`).
|
4. Start your local development server (e.g. run `gatsby develop`).
|
||||||
|
|
||||||
> `netlify-cms-proxy-server` runs an unauthenticated express server. As any client can send requests to the server, it should only be used for local development.
|
**Note:** `netlify-cms-proxy-server` runs an unauthenticated express server. As any client can send requests to the server, it should only be used for local development.
|
||||||
|
|
||||||
## GitLab and BitBucket Editorial Workflow Support
|
## GitLab and BitBucket Editorial Workflow Support
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user