-
Notifications
You must be signed in to change notification settings - Fork 27
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
refactor: migrate to new application config #1633
Changes from 8 commits
c076e03
4c52a1f
939d38c
8066fba
ea48e04
e917f40
a62eba8
90c55fa
5e0c37f
2654cb9
16be1ab
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
--- | ||
'merchant-center-application-template-starter': minor | ||
'@commercetools-frontend/application-config': minor | ||
'@commercetools-frontend/application-shell': minor | ||
'@commercetools-frontend/mc-html-template': minor | ||
'@commercetools-frontend/mc-http-server': minor | ||
'@commercetools-frontend/mc-scripts': minor | ||
'playground': minor | ||
'@commercetools-local/visual-testing-app': minor | ||
--- | ||
|
||
This release introduces the usage of a new configuration file format and marks the deprecation of the `env.json` and `headers.json` files. | ||
|
||
> The `env.json` and `headers.json` files will still keep working but they will be removed in the next major release. | ||
|
||
The new configuration format aims to drastically simplify how to configure the Custom Application, as well as to make the configuration process less error prone. | ||
In fact, the new configuration file is backed by a JSON schema that is shipped together with the new package. The configuration file is then validated against the JSON schema. | ||
|
||
Furthermore, the new configuration file tries to infer as many required information as possible. | ||
|
||
**Migrate to the new configuration file** | ||
|
||
The new configuration file is a JSON file with one of the following names: | ||
|
||
- `.custom-application-configrc` | ||
- `.custom-application-config.json` | ||
- `custom-application-config.json` | ||
|
||
The file is automatically loaded by the other packages, so you don't need to explicitly specify it in, for example, the `mc-scripts` commands. | ||
|
||
> The file needs to be located in the project root folder. | ||
|
||
For example, given the following `env.json` and `headers.json` files: | ||
|
||
```json | ||
{ | ||
"applicationName": "Avengers app", | ||
"frontendHost": "localhost:3001", | ||
"mcApiUrl": "https://mc-api.europe-west1.gcp.commercetools.com", | ||
"location": "gcp-eu", | ||
"env": "development", | ||
"cdnUrl": "http://localhost:3001", | ||
"servedByProxy": false | ||
} | ||
``` | ||
|
||
```json | ||
{ | ||
"csp": { | ||
"script-src": [], | ||
"connect-src": ["mc-api.europe-west1.gcp.commercetools.com"], | ||
"style-src": [] | ||
} | ||
} | ||
``` | ||
|
||
and for production mode `env.prod.json` and `headers.prod.json`: | ||
|
||
```json | ||
{ | ||
"applicationName": "Avengers app", | ||
"frontendHost": "avengers.app", | ||
"mcApiUrl": "https://mc-api.europe-west1.gcp.commercetools.com", | ||
"location": "gcp-eu", | ||
"env": "production", | ||
"cdnUrl": "https://cdn.avengers.app", | ||
"servedByProxy": true | ||
} | ||
``` | ||
|
||
```json | ||
{ | ||
"csp": { | ||
"script-src": ["avengers.app", "cdn.avengers.app"], | ||
"connect-src": [ | ||
"mc-api.europe-west1.gcp.commercetools.com", | ||
"avengers.app", | ||
"cdn.avengers.app" | ||
], | ||
"style-src": ["avengers.app", "cdn.avengers.app"] | ||
} | ||
} | ||
``` | ||
|
||
To migrate them to the new format, add a `custom-application-config.json` (or one of the other file names) with the following content: | ||
|
||
```json | ||
{ | ||
"name": "Avengers app", | ||
"entryPointUriPath": "avengers", | ||
"cloudIdentifier": "gcp-eu", | ||
"env": { | ||
"production": { | ||
"url": "https://avengers.app", | ||
"cdnUrl": "https://cdn.avengers.app" | ||
} | ||
} | ||
} | ||
``` | ||
|
||
That's it! All other values are inferred from the config, like CSP headers, etc. | ||
|
||
> Note that the environment placeholder values `${env:VALUE}` are still working. | ||
|
||
**JSON Schema support for VSCode** | ||
|
||
In the VSCode settings (either user settings or workspace settings), reference the schema JSON as described below: | ||
|
||
```json | ||
"json.schemas": [ | ||
{ | ||
"fileMatch": [ | ||
"/.custom-application-configrc", | ||
"/.custom-application-config.json", | ||
"/custom-application-config.json" | ||
], | ||
"url": "https://unpkg.com/@commercetools-frontend/application-config/schema.json" | ||
} | ||
] | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
declare module '@commercetools-uikit/notifications' { | ||
import * as React from 'react'; | ||
import { MessageDescriptor } from 'react-intl'; | ||
|
||
export const version: string; | ||
|
||
// <ContentNotification> | ||
export type ContentNotificationProps = { | ||
type: 'success' | 'info' | 'warning' | 'error'; | ||
children?: React.ReactNode; | ||
intlMessage?: MessageDescriptor; | ||
}; | ||
export const ContentNotification: { | ||
(props: ContentNotificationProps): JSX.Element; | ||
displayName: string; | ||
}; | ||
} |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
{ | ||
"name": "Custom Application Template Starter", | ||
"entryPointUriPath": "examples-starter", | ||
"cloudIdentifier": "gcp-eu", | ||
"env": { | ||
"production": { | ||
"url": "https://<your_app_hostname>" | ||
} | ||
} | ||
} |
This file was deleted.
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -48,5 +48,3 @@ In the VSCode settings (either user settings or workspace settings), reference t | |
} | ||
] | ||
``` | ||
|
||
## API |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1 @@ | ||
export { default as processConfig } from './process-config'; | ||
export { default as validateConfig } from './validate-config'; | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
import type { CspDirective } from './schema'; | ||
import type { ApplicationConfig } from './types'; | ||
|
||
import fs from 'fs'; | ||
import substituteEnvVariablePlaceholders from './substitute-env-variable-placeholders'; | ||
import { parseJsonFile } from './utils'; | ||
|
||
export type DeprecatedOptions = { | ||
envPath: string; | ||
headersPath: string; | ||
cspPath?: string; | ||
}; | ||
|
||
// List the required fields of `env.json` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not 100% sure what you mean here but in general we want to start using the new config file (as we do now). I also need to update the documentation and write a release note (separate PR). Does this answer your question? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes. That answers the question |
||
const requiredEnvJsonFields = [ | ||
'applicationName', | ||
'frontendHost', | ||
'mcApiUrl', | ||
'location', | ||
'env', | ||
'cdnUrl', | ||
]; | ||
|
||
// For backwards compatibility, we still support the `env.json` and `headers.json` files. | ||
// TODO: remove in `v17`. | ||
const loadDeprecatedConfig = ( | ||
Comment on lines
+24
to
+26
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the part where we load the |
||
options: DeprecatedOptions | ||
): ApplicationConfig | undefined => { | ||
const hasEnvJson = fs.existsSync(options.envPath); | ||
const hasHeadersJson = fs.existsSync(options.headersPath); | ||
const hasCspJson = Boolean(options.cspPath && fs.existsSync(options.cspPath)); | ||
const shouldUseDeprecatedCspJson = hasCspJson && !hasHeadersJson; | ||
|
||
if (!hasEnvJson) return; | ||
emmenko marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
const loadedEnvJson = parseJsonFile<ApplicationConfig['env']>( | ||
options.envPath | ||
); | ||
// Validate required fields | ||
requiredEnvJsonFields.forEach((key) => { | ||
const hasKey = Object.prototype.hasOwnProperty.call(loadedEnvJson, key); | ||
if (!hasKey) { | ||
throw new Error( | ||
`Missing '${key}' required configuration field. ${loadedEnvJson}` | ||
); | ||
} | ||
}); | ||
const env = substituteEnvVariablePlaceholders(loadedEnvJson); | ||
|
||
// Parse headers from `headers.json` (or the already deprecated `csp.json`). | ||
if (shouldUseDeprecatedCspJson) { | ||
const loadedCspJson = parseJsonFile<{ | ||
'connect-src': CspDirective; | ||
'font-src'?: CspDirective; | ||
'img-src'?: CspDirective; | ||
'script-src'?: CspDirective; | ||
'style-src'?: CspDirective; | ||
}>(options.cspPath as string); | ||
const cspHeaders = substituteEnvVariablePlaceholders(loadedCspJson); | ||
return { env, headers: { csp: cspHeaders } }; | ||
} | ||
|
||
const loadedHeadersJson = parseJsonFile<ApplicationConfig['headers']>( | ||
options.headersPath | ||
); | ||
const headers = substituteEnvVariablePlaceholders(loadedHeadersJson); | ||
return { env, headers }; | ||
}; | ||
|
||
export default loadDeprecatedConfig; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,11 @@ | ||
import type { JSONSchemaForCustomApplicationConfigurationFiles } from './schema'; | ||
import type { ApplicationConfig, CloudIdentifier } from './types'; | ||
import type { DeprecatedOptions } from './load-deprecated-config'; | ||
|
||
// Loads the configuration file and parse the environment and header values. | ||
// Most of the resulting values are inferred from the config. | ||
import loadConfig from './load-config'; | ||
import loadDeprecatedConfig from './load-deprecated-config'; | ||
import validateConfig from './validate-config'; | ||
import { | ||
mapCloudIdentifierToApiUrl, | ||
|
@@ -14,6 +16,9 @@ import { | |
import substituteEnvVariablePlaceholders from './substitute-env-variable-placeholders'; | ||
|
||
type ProcessConfigOptions = { | ||
// Options for backwards compatibility | ||
deprecatedOptions?: DeprecatedOptions; | ||
// Options useful for testing | ||
disableCache?: boolean; | ||
processEnv?: NodeJS.ProcessEnv; | ||
}; | ||
|
@@ -27,7 +32,7 @@ const developmentConfig: JSONSchemaForCustomApplicationConfigurationFiles['env'] | |
let cachedConfig: ApplicationConfig; | ||
|
||
const processConfig = ({ | ||
// Options useful for testing | ||
deprecatedOptions, | ||
disableCache = false, | ||
processEnv = process.env, | ||
}: ProcessConfigOptions = {}): ApplicationConfig => { | ||
|
@@ -37,9 +42,21 @@ const processConfig = ({ | |
processEnv.MC_APP_ENV ?? processEnv.NODE_ENV ?? 'development'; | ||
const isProd = getIsProd(processEnv); | ||
|
||
// TODO: handle legacy configs for backwards compatibility | ||
|
||
// Read first the new config file. If none is found, attempt to load | ||
// the config from the `env.json` and `headers.json` files. | ||
const loadedAppConfig = loadConfig(); | ||
if (!loadedAppConfig && deprecatedOptions) { | ||
console.warn( | ||
`No custom application config found, attempting to load the config from env.json and headers.json.` | ||
); | ||
const loadedDeprecatedAppConfig = loadDeprecatedConfig(deprecatedOptions); | ||
if (!loadedDeprecatedAppConfig) { | ||
throw new Error(`No configuration for the Custom Application found.`); | ||
} | ||
// Return the legacy config as-is, no need to transform it. | ||
return loadedDeprecatedAppConfig; | ||
} | ||
Comment on lines
+48
to
+58
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the logic where we try to load the deprecated files, in case the new config file was not found. If you have other suggestions for implementing this logic, please let me know. |
||
|
||
validateConfig(loadedAppConfig); | ||
const validatedLoadedAppConfig = loadedAppConfig as JSONSchemaForCustomApplicationConfigurationFiles; | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not necessary to expose it