Skip to content
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

Merged
merged 11 commits into from
Jul 16, 2020
1 change: 1 addition & 0 deletions .changeset/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"@commercetools-backend/loggers",
"@commercetools-frontend/actions-global",
"@commercetools-frontend/application-components",
"@commercetools-frontend/application-config",
"@commercetools-frontend/application-shell",
"@commercetools-frontend/application-shell-connectors",
"@commercetools-frontend/assets",
Expand Down
120 changes: 120 additions & 0 deletions .changeset/wet-insects-push.md
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"
}
]
```
17 changes: 17 additions & 0 deletions @types/commercetools-uikit__notifications/index.d.ts
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;
};
}
3 changes: 0 additions & 3 deletions application-templates/starter/.env.template

This file was deleted.

12 changes: 7 additions & 5 deletions application-templates/starter/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,9 @@ $ create-mc-app my-new-custom-application-project --template starter
$ npx @commercetools-frontend/create-mc-app my-new-custom-application-project --template starter
```

## Adjust the configuration
## Adjust the cloud identifier

Rename the file `.env.template` to `.env`. It contains some environment variables used in the `env.json` and `headers.json` file.

By default the GCP EU environment is configured. If you plan to run the Custom Application in another environment, change the hostname accordingly.
The `custom-application-config.json` is configured to use the HTTP APIs in the GCP-EU region. If you plan to run the Custom Application in another environment, change the cloud identifier accordingly.

## Start the development server

Expand All @@ -49,8 +47,12 @@ Run the following command to build the production bundles with webpack:
$ yarn build
```

## Adjust the configuration for production

The `custom-application-config.json` has a `env.production` configuration object. When you're ready to deploy the application to production, make sure to provide the URL where the Custom Application is hosted.

## Linting, formatting, and so on

We only provide the minimal scripts and tooling to _start_, _test_ and _build_ the application. If you want to add more development tooling such as **linters**, **prettier**, etc. you need to provide those on your own.
We only provide the minimal scripts and tooling to _start_, _test_, and _build_ the application. If you want to add more development tooling such as **linters**, **prettier**, etc. you need to provide those on your own.

You can have a look at our setup in the [`merchant-center-application-kit`](https://github.com/commercetools/merchant-center-application-kit) repository to help you getting set up.
10 changes: 10 additions & 0 deletions application-templates/starter/custom-application-config.json
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>"
}
}
}
9 changes: 0 additions & 9 deletions application-templates/starter/env.json

This file was deleted.

7 changes: 0 additions & 7 deletions application-templates/starter/headers.json

This file was deleted.

2 changes: 1 addition & 1 deletion application-templates/starter/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"scripts": {
"build": "mc-scripts build",
"start": "dotenv -- mc-scripts start",
"start:prod:local": "NODE_ENV=production dotenv -- mc-http-server --config=$(pwd)/env.json --headers=$(pwd)/headers.json --use-local-assets",
"start:prod:local": "NODE_ENV=production MC_APP_ENV=development dotenv -- mc-http-server --use-local-assets",
"i18n:build": "mc-scripts extract-intl --output-path=$(pwd)/src/i18n/data 'src/**/!(*.spec).js' --build-translations",
"test": "jest --config jest.test.config.js",
"test:watch": "jest --config jest.test.config.js --watch",
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"private": true,
"scripts": {
"auth": "npm_config_registry=https://registry.npmjs.org npm whoami",
"clean": "lerna exec 'rm -rf dist'",
"clean": "lerna exec 'rm -rf build dist'",
"i18n:build": "./packages/mc-scripts/bin/mc-scripts.js extract-intl --output-path=$(pwd)/packages/i18n/data 'packages/**/!(*.spec|*.d).{js,ts,tsx}'",
"l10n:build": "pushd packages/l10n; yarn generate-data",
"lint": "jest --projects jest.eslint.config.js jest.stylelint.config.js jest.text.config.js",
Expand Down
2 changes: 0 additions & 2 deletions packages/application-config/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,5 +48,3 @@ In the VSCode settings (either user settings or workspace settings), reference t
}
]
```

## API
10 changes: 6 additions & 4 deletions packages/application-config/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"config"
],
"license": "MIT",
"private": true,
"private": false,
"publishConfig": {
"access": "public"
},
Expand All @@ -35,9 +35,10 @@
"scripts": {
"prepare": "./../../scripts/version.js replace",
"prebuild": "rimraf build/**",
"build": "yarn build:schema && yarn build:src && yarn build:typings",
"build": "yarn build:schema && yarn build:bundles && yarn build:typings",
"build:schema": "json2ts schema.json src/schema.ts --style.singleQuote --bannerComment '/* eslint-disable prettier/prettier */\n// This file was automatically generated by json-schema-to-typescript.\n// DO NOT MODIFY IT BY HAND. Instead, modify the source schema.json file.'",
"build:src": "babel src --out-dir build --extensions .ts --ignore src/types.ts,src/schema.ts",
"build:bundles": "babel src --out-dir build --extensions .ts --ignore src/types.ts,src/schema.ts",
"build:bundles:watch": "yarn build:bundles -w",
"build:typings": "cross-env tsc -p tsconfig.declarations.json --emitDeclarationOnly --declarationDir build/typings"
},
"dependencies": {
Expand All @@ -52,7 +53,8 @@
"@babel/plugin-transform-runtime": "7.10.4",
"@babel/preset-env": "7.10.4",
"@babel/preset-typescript": "7.10.4",
"json-schema-to-typescript": "9.1.1"
"json-schema-to-typescript": "9.1.1",
"shelljs": "0.8.4"
},
"engines": {
"node": ">=10",
Expand Down
1 change: 0 additions & 1 deletion packages/application-config/src/index.ts
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';
Copy link
Member Author

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

70 changes: 70 additions & 0 deletions packages/application-config/src/load-deprecated-config.ts
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`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

env.json should be created by the users by themselves and no longer part of the repo. Right?

Copy link
Member Author

Choose a reason for hiding this comment

The 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).
People can still use the env.json file, as long as it's supported. For example, in the next major version v17 we can drop support for that.

Does this answer your question?

Copy link
Contributor

Choose a reason for hiding this comment

The 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
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the part where we load the env.json and headers.json for backwards compatibility

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;
23 changes: 20 additions & 3 deletions packages/application-config/src/process-config.ts
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,
Expand All @@ -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;
};
Expand All @@ -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 => {
Expand All @@ -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
Copy link
Member Author

Choose a reason for hiding this comment

The 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;

Expand Down
Loading