Skip to content

Commit

Permalink
refactor: migrate to new application config (#1633)
Browse files Browse the repository at this point in the history
* refactor: use new application config package to process env and headers

* chore: include application config package in changeset config

* refactor(config): fall back to load deprecated config

* refactor: migrate codebase to use new application config

* refactor: migrate leftovers

* docs: document migration to the new config

* test: fix import

* test(config): for deprecated config files

* fix(vrt): build app for dev mode

* fix(ci): remove unnecessary step

* refactor(config): address feedback
  • Loading branch information
emmenko authored Jul 16, 2020
1 parent 77c2aed commit 6e41e67
Show file tree
Hide file tree
Showing 59 changed files with 779 additions and 571 deletions.
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"
}
]
```
4 changes: 0 additions & 4 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -100,10 +100,6 @@ jobs:
CYPRESS_LOGIN_PASSWORD: ${{ secrets.CYPRESS_LOGIN_PASSWORD }}
CYPRESS_PROJECT_KEY: ${{ secrets. CYPRESS_PROJECT_KEY }}

- name: Preparing Starter template application environment
run: cp .env.template .env
working-directory: application-templates/starter

- name: Building Starter template application
run: yarn template-starter:build

Expand Down
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ packages/i18n/*.json
packages/application-shell/test-utils/index.js
packages/application-shell-connectors/test-utils/index.js
packages/application-sdk/test-utils/index.js
packages/application-config/src/schema.ts
**/types/generated/
.percy.yml
.cache
Expand Down
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';
71 changes: 71 additions & 0 deletions packages/application-config/src/load-deprecated-config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
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`
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 = (
options: DeprecatedOptions
): ApplicationConfig | undefined => {
const hasEnvJson = fs.existsSync(options.envPath);

if (!hasEnvJson) return;

const hasHeadersJson = fs.existsSync(options.headersPath);
const hasCspJson = Boolean(options.cspPath && fs.existsSync(options.cspPath));
const shouldUseDeprecatedCspJson = hasCspJson && !hasHeadersJson;

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

0 comments on commit 6e41e67

Please sign in to comment.