Skip to content

Commit

Permalink
refactor(cli): options of build and compile-html commands
Browse files Browse the repository at this point in the history
  • Loading branch information
emmenko committed Jan 21, 2022
1 parent b8fb4cb commit 07f5b00
Show file tree
Hide file tree
Showing 15 changed files with 107 additions and 115 deletions.
20 changes: 20 additions & 0 deletions .changeset/many-tables-appear.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
'merchant-center-application-template-starter': major
'@commercetools-frontend/application-config': major
'@commercetools-frontend/application-shell': major
'@commercetools-frontend/mc-dev-authentication': major
'@commercetools-frontend/mc-html-template': major
'@commercetools-frontend/mc-scripts': major
'playground': major
'@commercetools-local/visual-testing-app': major
---

Following breaking changes were introduced:

- In `mc-scripts`, the `build` command additionally compiles the `index.html` by default.
- Running the `compile-html` command by default should not be necessary anymore. However, you can pass `--build-only` to the `build` command to opt-out of the compilation step, in case you want to run it separately, for example to use the `--transformer`.
- Running the `compile-html` command by default does not print to `stdout` the JSON string with the security headers. You can opt into the old behavior by passing the `--print-security-headers` option.
- The `--inline-csp` of `compile-html` has been dropped, as it's now the built-in behavior.
- The `dist` folder created by the `build` command has been removed. Instead, the `build` command writes the production bundles directly into the `public` folder.

For more information see [Release notes v21](https://docs.commercetools.com/custom-applications/releases/2022-01-15-custom-applications-v21).
10 changes: 5 additions & 5 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,11 @@ jobs:

- name: Building Playground application
run: yarn playground:build
env:
APP_ID: ${{ secrets.APP_ID_PLAYGROUND }}
MC_API_URL: ${{ secrets.MC_API_URL }}
CTP_INITIAL_PROJECT_KEY: ${{ secrets. CYPRESS_PROJECT_KEY }}
HOST_GCP_STAGING: ${{ secrets.HOST_GCP_STAGING }}

- name: Running End-to-End tests for Playground application
run: yarn start-server-and-test 'yarn playground:start:prod:local' 3001 'yarn percy exec -- yarn test:e2e:playground'
Expand All @@ -108,13 +113,8 @@ jobs:
- name: Deploying Playground application to Vercel (production)
if: github.ref == 'refs/heads/main'
run: |
yarn workspace playground run compile-html
yarn workspace playground run deploy --prod --token="${{ secrets.VERCEL_TOKEN_PLAYGROUND }}"
env:
APP_ID: ${{ secrets.APP_ID_PLAYGROUND }}
MC_API_URL: ${{ secrets.MC_API_URL }}
CTP_INITIAL_PROJECT_KEY: ${{ secrets. CYPRESS_PROJECT_KEY }}
HOST_GCP_STAGING: ${{ secrets.HOST_GCP_STAGING }}
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID_PLAYGROUND }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID_PLAYGROUND }}

Expand Down
4 changes: 2 additions & 2 deletions application-templates/starter/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
"postinstall": "manypkg check",
"build": "mc-scripts build",
"start": "mc-scripts start",
"compile-html": "NODE_ENV=production mc-scripts compile-html",
"compile-html:local": "NODE_ENV=production MC_APP_ENV=development mc-scripts compile-html --transformer @commercetools-frontend/mc-dev-authentication/transformer-local.js",
"compile-html": "mc-scripts compile-html",
"compile-html:local": "MC_APP_ENV=development mc-scripts compile-html --transformer @commercetools-frontend/mc-dev-authentication/transformer-local.js",
"start:prod:local": "yarn compile-html:local && mc-scripts serve",
"extract-intl": "formatjs extract --format=./intl-formatter.js --out-file=./src/i18n/data/core.json 'src/**/!(*.spec).js'",
"test": "jest --config jest.test.config.js",
Expand Down
12 changes: 6 additions & 6 deletions packages/mc-dev-authentication/transformer-local.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const compileLogoutView = pug.compileFile(
const appDirectory = fs.realpathSync(process.cwd());
const resolveApp = (relativePath) => path.resolve(appDirectory, relativePath);
const paths = {
appPublic: resolveApp('public'),
appBuild: resolveApp('public'),
};

// This transformer will generate a development `login` and `logout` HTML files
Expand All @@ -26,23 +26,23 @@ module.exports = ({ env }) => {

fs.copyFileSync(
path.join(__dirname, 'views', 'login.css'),
path.join(paths.appPublic, 'login.css')
path.join(paths.appBuild, 'login.css')
);
fs.copyFileSync(
path.join(__dirname, 'views', 'login.js'),
path.join(paths.appPublic, 'login.js')
path.join(paths.appBuild, 'login.js')
);
fs.copyFileSync(
path.join(__dirname, 'views', 'logout.js'),
path.join(paths.appPublic, 'logout.js')
path.join(paths.appBuild, 'logout.js')
);
fs.writeFileSync(
path.join(paths.appPublic, 'login.html'),
path.join(paths.appBuild, 'login.html'),
loginViewHtml,
'utf8'
);
fs.writeFileSync(
path.join(paths.appPublic, 'logout.html'),
path.join(paths.appBuild, 'logout.html'),
logoutViewHtml,
'utf8'
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,7 @@ const replaceHtmlPlaceholders = (indexHtmlContent, options) =>
indexHtmlContent
.replace(
new RegExp('__CSP__', 'g'),
options.headers && options.cliFlags && options.cliFlags.inlineCsp
? options.headers['Content-Security-Policy']
: ''
options.headers ? options.headers['Content-Security-Policy'] : ''
)
.replace(
new RegExp('__CDN_URL__', 'g'),
Expand Down
59 changes: 40 additions & 19 deletions packages/mc-scripts/src/bin/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,29 +12,35 @@ const dotenv = require('dotenv');
const dotenvExpand = require('dotenv-expand');
const spawn = require('react-dev-utils/crossSpawn');

const flags = mri(process.argv.slice(2), { alias: { help: ['h'] } });
const flags = mri(process.argv.slice(2), {
alias: { help: ['h'] },
boolean: ['build-only'],
});
const commands = flags._;

if (commands.length === 0 || (flags.help && commands.length === 0)) {
console.log(`
Usage: mc-scripts [global-options] [command] [options]
https://docs.commercetools.com/custom-applications/api-reference/cli.
Global options:
--env <path> (optional) Parses the file path as a dotenv file and adds the variables to the environment. Multiple flags are allowed.
--env <path> (optional) Parses the file path as a dotenv file and adds the variables to the environment. Multiple flags are allowed.
Commands:
build Bundles the application in production mode. Outputs a "dist" folder.
build Bundles the application in production mode. Outputs a "public" folder.
--build-only (optional) If defined, the command only creates the production bundles without compiling the "index.html".
compile-html Compiles "index.html.template" file into a "index.html" with all the required runtime configuration. Outputs a "public" folder. Additionally, the security headers are also compiled and printed to stdout, unless you use a "transformer".
More info at https://docs.commercetools.com/custom-applications/deployment/compiling-a-custom-application.
--transformer <path> (optional) The path to a JS module that can be used to generate a configuration for a specific cloud provider (e.g. Netlify, Vercel, Firebase).
--inline-csp (optional) If defined, the CSP config is inlined in the HTML head as a meta tag. This might be useful to keep using a static config for a hosting provider without the dynamically generated Content-Security-Policy header.
compile-html Compiles "index.html.template" file into a "index.html" with all the required runtime configuration. Additionally, the security headers are also compiled and printed to stdout, unless you use a "transformer".
--transformer <path> (optional) The path to a JS module that can be used to generate a configuration for a specific cloud provider (e.g. Vercel, Netlify).
--print-security-headers (optional) The path to a JS module that can be used to generate a configuration for a specific cloud provider (e.g. Vercel, Netlify).
start Starts the application in development mode using Webpack Dev Server.
start Starts the application in development mode using Webpack Dev Server.
serve Serves previously built and compiled application from the "public" folder.
serve Serves previously built and compiled application from the "public" folder.
`);
process.exit(0);
}
Expand All @@ -52,31 +58,40 @@ const applicationDirectory = fs.realpathSync(process.cwd());
process.env.BABEL_ENV = 'production';
process.env.NODE_ENV = 'production';

proxyCommand();
const shouldAlsoCompile = !flags['build-only'];

proxyCommand(command, {
noExit: shouldAlsoCompile,
});

if (shouldAlsoCompile) {
proxyCommand('compile-html');
}

break;
}
case 'serve': {
// Do this as the first thing so that any code reading it knows the right env.
process.env.NODE_ENV = 'production';

proxyCommand();
proxyCommand(command);
break;
}
case 'compile-html': {
// Do this as the first thing so that any code reading it knows the right env.
process.env.NODE_ENV = 'production';

// Get specific flag for this command.
const commandArgs = getArgsForCommand(['transformer', 'inline-csp']);
proxyCommand({ commandArgs });
const commandArgs = getArgsForCommand(['transformer']);
proxyCommand(command, { commandArgs });
break;
}
case 'start': {
// Do this as the first thing so that any code reading it knows the right env.
process.env.BABEL_ENV = 'development';
process.env.NODE_ENV = 'development';

proxyCommand();
proxyCommand(command);
break;
}
default:
Expand All @@ -102,15 +117,15 @@ function getArgsForCommand(allowedFlags = []) {
}, []);
}

function proxyCommand({ commandArgs } = {}) {
function proxyCommand(fileName, { commandArgs, noExit } = {}) {
// Load dotenv files into the process environment.
// This is essentially what `dotenv-cli` does, but it's now built into this CLI.
loadDotEnvFiles(flags);

// Spawn the actual command.
const result = spawn.sync(
'node',
[require.resolve(`../commands/${command}`)].concat(commandArgs),
[require.resolve(`../commands/${fileName}`)].concat(commandArgs),
{ stdio: 'inherit' }
);

Expand All @@ -119,13 +134,13 @@ function proxyCommand({ commandArgs } = {}) {
switch (result.signal) {
case 'SIGKILL': {
console.log(
`The command ${command} failed because the process exited too early. This probably means the system ran out of memory or someone called "kill -9" on the process.`
`The command ${fileName} failed because the process exited too early. This probably means the system ran out of memory or someone called "kill -9" on the process.`
);
break;
}
case 'SIGTERM': {
console.log(
`The command ${command} failed because the process exited too early. Someone might have called "kill" or "killall", or the system could be shutting down.`
`The command ${fileName} failed because the process exited too early. Someone might have called "kill" or "killall", or the system could be shutting down.`
);
break;
}
Expand All @@ -134,7 +149,13 @@ function proxyCommand({ commandArgs } = {}) {
}
process.exit(1);
}
process.exit(result.status);
if (result.status > 0) {
process.exit(result.status);
} else {
if (!noExit) {
process.exit(result.status);
}
}
}

// Load dotenv files into the process environment.
Expand Down
14 changes: 14 additions & 0 deletions packages/mc-scripts/src/commands/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,17 @@
// NOTE: `react-dev-utils` does not currently fully support Webpack v5.
// Most of the imports work, however we might bump into some edge cases.
// In any case, once they release a compatible version, we should't have problems.
const path = require('path');
const fs = require('fs-extra');
const webpack = require('webpack');
const chalk = require('react-dev-utils/chalk');
const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles');
const formatWebpackMessages = require('react-dev-utils/formatWebpackMessages');
const FileSizeReporter = require('react-dev-utils/FileSizeReporter');
const printBuildError = require('react-dev-utils/printBuildError');
const {
packageLocation: applicationStaticAssetsPath,
} = require('@commercetools-frontend/assets');
const paths = require('../config/paths');
const createWebpackConfigForProduction = require('../config/create-webpack-config-for-production');

Expand All @@ -34,6 +38,8 @@ measureFileSizesBeforeBuild(paths.appBuild)
// Remove all content but keep the directory so that
// if you're in it, you don't end up in Trash
fs.emptyDirSync(paths.appBuild);
// Copy default files
copyDefaultFiles();
// Start the webpack build
return build(previousFileSizes);
})
Expand Down Expand Up @@ -149,3 +155,11 @@ function build(previousFileSizes) {
});
});
}

function copyDefaultFiles() {
fs.copySync(
path.join(applicationStaticAssetsPath, 'html-page'),
paths.appBuild,
{ dereference: true }
);
}
62 changes: 9 additions & 53 deletions packages/mc-scripts/src/commands/compile-html.js
Original file line number Diff line number Diff line change
@@ -1,66 +1,20 @@
/* eslint-disable no-console,global-require,import/no-dynamic-require */
const fs = require('fs');
const path = require('path');
const shelljs = require('shelljs');
const mri = require('mri');
const {
packageLocation: applicationStaticAssetsPath,
} = require('@commercetools-frontend/assets');
const chalk = require('react-dev-utils/chalk');
const { compileHtml } = require('@commercetools-frontend/mc-html-template');
const paths = require('../config/paths');

const flags = mri(process.argv.slice(2), {
boolean: ['inline-csp'],
boolean: ['print-security-headers'],
});
const appDirectory = fs.realpathSync(process.cwd());
const resolveApp = (relativePath) => path.resolve(appDirectory, relativePath);

const publicAssetsPath = resolveApp('public');

const paths = {
publicAssetsPath,
// NOTE: previously, for running the prod server locally, we were copying
// assets into public/assets and compiling the index.html into public folder.
// To run the app then we would define the cdnUrl as http://localhost:3001/assets,
// because of the assets subfolder.
// However, on the webpack-dev-server, the index.html and the bundles are
// served from the same folder. It's really complicated to get the config right
// and eventually it's not worth the effort as it just feels like a bloody workaround.
// Therefore, we do it a bit different now: keep the webpack-dev-server
// config as is and adjust the mc-script to serve the files the same way.
// Remember that for normal production usage, assets are served from a CDN.
// So now we have a flat public folder instead of an assets subfolder.
// Which means that the 3 static files (favicon, google**.html, robots.txt)
// need to be put somewhere else (public-assets) and copy into the public
// folder when the server starts.
publicAssetsFolderPath: path.join(applicationStaticAssetsPath, 'html-page/*'),
indexHtmlTemplatePath: path.join(publicAssetsPath, 'index.html.template'),
indexHtmlPath: path.join(publicAssetsPath, 'index.html'),
};

shelljs.rm('-rf', publicAssetsPath);
shelljs.mkdir('-p', publicAssetsPath);
shelljs.cp('-R', paths.publicAssetsFolderPath, publicAssetsPath);

// Resolve the absolute path of the caller location. This is necessary
// to point to files within that folder.
const assetsFrom = resolveApp('dist/assets');
// Make sure that the `dist/assets` folder is available.
try {
fs.accessSync(assetsFrom, fs.F_OK);
} catch (error) {
throw new Error(
'Could not find "dist/assets" folder. Did you run `mc-scripts build` first?'
);
}
// Copy the `dist/assets` folder into the `public` folder.
shelljs.cp('-R', path.join(assetsFrom, '/*'), publicAssetsPath);

const generateStatic = async () => {
const compiled = await compileHtml(paths.indexHtmlTemplatePath, {
inlineCsp: flags['inline-csp'],
});
console.log('Compiling index.html...');
const compiled = await compileHtml(paths.appIndexHtmlTemplate);

fs.writeFileSync(paths.indexHtmlPath, compiled.indexHtmlContent, {
fs.writeFileSync(paths.appIndexHtml, compiled.indexHtmlContent, {
encoding: 'utf8',
});

Expand All @@ -76,9 +30,11 @@ const generateStatic = async () => {
`Could not load transformer module "${flags.transformer}"\n${error.stack}`
);
}
} else {
} else if (flags['print-security-headers']) {
console.log(JSON.stringify(compiled.headers));
}

console.log(chalk.green('Compiled successfully.\n'));
};

generateStatic().catch((error) => {
Expand Down
10 changes: 2 additions & 8 deletions packages/mc-scripts/src/commands/serve.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,15 @@
/* eslint-disable no-console,global-require,import/no-dynamic-require */
const fs = require('fs');
const path = require('path');
const http = require('http');
const serveHandler = require('serve-handler');
const paths = require('../config/paths');

const port = 3001;

const appDirectory = fs.realpathSync(process.cwd());
const resolveApp = (relativePath) => path.resolve(appDirectory, relativePath);

const publicAssetsPath = resolveApp('public');

const server = http.createServer((request, response) => {
// You pass two more arguments for config and middleware
// More details here: https://github.com/vercel/serve-handler#options
return serveHandler(request, response, {
public: publicAssetsPath,
public: paths.appBuild,
rewrites: [
{
source: '/favicon*',
Expand Down
Loading

0 comments on commit 07f5b00

Please sign in to comment.