@commercetools-frontend/application-cli 0.0.2 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -57,6 +57,16 @@ Depending on the environment you are deploying to, you need to:
57
57
 
58
58
  Additionally, when specifying the `--dotenv-folder` option, you can specify a dotenv file for each environment (for example `.env.gcp-production-eu`) and a single `.env.production` dotenv file. These files are then loaded when compiling the application for the respective environment.
59
59
 
60
+ ### Command: `compile-menu`
61
+
62
+ This command compiles the menu configuration [defined in the application config](https://docs.commercetools.com/custom-applications/api-reference/application-config#mainmenulink) into a `menu.json` file.
63
+
64
+ > This is mostly useful for internal Merchant Center applications.
65
+
66
+ ```bash
67
+ yarn application-cli compile-menu
68
+ ```
69
+
60
70
  ### Command: `create-version`
61
71
 
62
72
  This command outputs a JSON string containing a list of deployed versions.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@commercetools-frontend/application-cli",
3
- "version": "0.0.2",
3
+ "version": "1.0.1",
4
4
  "description": "Internal CLI to manage Merchant Center application deployments across various environments.",
5
5
  "keywords": [
6
6
  "commercetools",
@@ -12,9 +12,7 @@
12
12
  },
13
13
  "license": "MIT",
14
14
  "type": "module",
15
- "bin": {
16
- "application-cli": "./src/bin/cli.js"
17
- },
15
+ "bin": "./src/bin/cli.js",
18
16
  "files": [
19
17
  "src",
20
18
  "package.json",
@@ -22,18 +20,19 @@
22
20
  "README.md"
23
21
  ],
24
22
  "dependencies": {
23
+ "@commercetools-frontend/application-config": "21.3.4",
25
24
  "@manypkg/find-root": "1.1.0",
26
- "@manypkg/get-packages": "1.1.1",
27
- "cosmiconfig": "7.0.0",
28
- "dotenv": "9.0.1",
29
- "execa": "5.0.0",
25
+ "@manypkg/get-packages": "1.1.3",
26
+ "cosmiconfig": "7.0.1",
27
+ "dotenv": "16.0.0",
28
+ "execa": "6.1.0",
30
29
  "listr": "0.14.3",
31
30
  "listr-verbose-renderer": "0.6.0",
32
31
  "lodash.lowerfirst": "4.3.1",
33
- "mri": "1.1.6",
34
- "node-fetch": "2.6.1",
35
- "prettier": "2.2.1",
36
- "prompts": "2.4.1",
32
+ "mri": "1.2.0",
33
+ "node-fetch": "2.6.7",
34
+ "prettier": "2.6.2",
35
+ "prompts": "2.4.2",
37
36
  "rcfile": "1.0.3"
38
37
  },
39
38
  "engines": {
package/src/bin/cli.js CHANGED
@@ -1,8 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import mri from 'mri';
4
- import createVersion from '../commands/create-version.js';
5
4
  import compileDeployments from '../commands/compile-deployments.js';
5
+ import compileMenu from '../commands/compile-menu.js';
6
+ import createVersion from '../commands/create-version.js';
6
7
 
7
8
  const cwd = process.cwd();
8
9
 
@@ -46,6 +47,12 @@ Options:
46
47
  --application-assets-upload-script-out-file <path> (optional) The name of the the assets upload script file. Defaults to upload-assets.sh.
47
48
  --ci-assets-root-path <path> (optional) A replacement value for the scripts root path only used on CI (e.g. '--ci-assets-root-path=/root/') used in generated scripts.
48
49
 
50
+ Command:
51
+ compile-menu Compile the menu links of an application into a 'menu.json'. This is only required for internal applications
52
+
53
+ Options:
54
+ --dotenv-folder <string> (optional) The path to a folder containing a dotenv file '.env.production' and a cloud-environment specific dotenv file (for example '.env.gcp-production-eu'). Those values are parsed and merged together to be used by the application config.
55
+
49
56
  Command:
50
57
  create-version Output a JSON string about the information in the 'version.json' for a deployment, including the updated list of rollbacks.
51
58
 
@@ -66,6 +73,10 @@ Options:
66
73
  await compileDeployments(cliFlags, cwd);
67
74
  process.exit(0);
68
75
  break;
76
+ case 'compile-menu':
77
+ await compileMenu(cliFlags, cwd);
78
+ process.exit(0);
79
+ break;
69
80
  case 'create-version':
70
81
  await createVersion(cliFlags, cwd);
71
82
  process.exit(0);
@@ -1,8 +1,7 @@
1
- import path from 'path';
2
1
  import fs from 'fs';
2
+ import path from 'path';
3
3
  import Listr from 'listr';
4
- import execa from 'execa';
5
- import dotenv from 'dotenv';
4
+ import { execa } from 'execa';
6
5
  import { cosmiconfig } from 'cosmiconfig';
7
6
  import { findRootSync } from '@manypkg/find-root';
8
7
  import ListrVerboseRenderer from 'listr-verbose-renderer';
@@ -11,45 +10,10 @@ import getApplicationDirectory from '../utils/get-application-directory.js';
11
10
  import isCI from '../utils/is-ci.js';
12
11
  import createApplicationIndexUploadScript from '../utils/create-application-index-upload-script.js';
13
12
  import createApplicationAssetsUploadScript from '../utils/create-application-assets-upload-script.js';
13
+ import loadDotenvFiles from '../utils/load-dotenv-files.js';
14
14
 
15
15
  const buckedConfigExplorer = cosmiconfig('google-storage-buckets');
16
16
 
17
- function loadDotenvFiles({ cliFlags, dotenvPath, cloudEnvironment }) {
18
- // No dotenv folder specified
19
- if (!cliFlags['dotenv-folder']) return {};
20
-
21
- // Load the environment values
22
- const sharedDotenvFile = '.env.production';
23
- const cloudDotenvFile = `.env.${cloudEnvironment}`;
24
-
25
- // The shared dotenv file across environments is optional
26
- const sharedProductionEnvironment = dotenv.config({
27
- encoding: 'utf8',
28
- path: path.join(dotenvPath, sharedDotenvFile),
29
- });
30
-
31
- const cloudSpecificProductionEnvironment = dotenv.config({
32
- encoding: 'utf8',
33
- path: path.join(dotenvPath, cloudDotenvFile),
34
- });
35
-
36
- if (cloudSpecificProductionEnvironment.error) {
37
- throw new Error(
38
- `Failed loading '${cloudDotenvFile}' in '${dotenvPath}'. Make sure it exists.`
39
- );
40
- }
41
- if (sharedProductionEnvironment.error) {
42
- throw new Error(
43
- `Failed loading '${sharedDotenvFile}' in '${dotenvPath}'. Make sure it exists.`
44
- );
45
- }
46
-
47
- return {
48
- ...sharedProductionEnvironment.parsed,
49
- ...cloudSpecificProductionEnvironment.parsed,
50
- };
51
- }
52
-
53
17
  function writeUploadScriptFile({ fileName, fileContent, filePath }) {
54
18
  fs.writeFileSync(path.join(filePath, fileName), fileContent, {
55
19
  // Make the script executable
@@ -58,6 +22,12 @@ function writeUploadScriptFile({ fileName, fileContent, filePath }) {
58
22
  });
59
23
  }
60
24
 
25
+ function getBucketNamespace(prNumber) {
26
+ if (!prNumber) return;
27
+ if (prNumber === 'merchant-center-preview') return prNumber;
28
+ return `mc-${prNumber}`;
29
+ }
30
+
61
31
  /**
62
32
  * Construct the storage bucket URL for the specific application and cloud environment.
63
33
  *
@@ -74,7 +44,7 @@ function getApplicationAssetsBucketUrl({
74
44
  }) {
75
45
  const applicationAssetsBucketUrl = [
76
46
  `gs://${bucketRegion}`,
77
- prNumber && `mc-${prNumber}`,
47
+ getBucketNamespace(prNumber),
78
48
  applicationName,
79
49
  ].filter(Boolean);
80
50
 
@@ -98,7 +68,7 @@ function getApplicationIndexBucketUrl({
98
68
  function getCdnUrl({ bucketRegion, prNumber, applicationName }) {
99
69
  return [
100
70
  `https://storage.googleapis.com/${bucketRegion}`,
101
- prNumber && `mc-${prNumber}`,
71
+ getBucketNamespace(prNumber),
102
72
  applicationName,
103
73
  ]
104
74
  .filter(Boolean)
@@ -106,16 +76,15 @@ function getCdnUrl({ bucketRegion, prNumber, applicationName }) {
106
76
  }
107
77
 
108
78
  async function compileApplicationAssets({ cliFlags, bucketRegion, paths }) {
109
- const applicationAssetsUploadScriptContent = createApplicationAssetsUploadScript(
110
- {
79
+ const applicationAssetsUploadScriptContent =
80
+ createApplicationAssetsUploadScript({
111
81
  bucketUrl: getApplicationAssetsBucketUrl({
112
82
  bucketRegion,
113
83
  prNumber: cliFlags['pr-number'],
114
84
  applicationName: cliFlags['application-name'],
115
85
  }),
116
86
  assetsPath: paths.assetsPath,
117
- }
118
- );
87
+ });
119
88
  const parsedApplicationAssetsUploadScriptFile = path.parse(
120
89
  cliFlags['application-assets-upload-script-out-file']
121
90
  );
@@ -162,7 +131,6 @@ async function compileEnvironmentApplicationIndexes({
162
131
  extendEnv: true,
163
132
  env: {
164
133
  ...loadDotenvFiles({
165
- cliFlags,
166
134
  dotenvPath: paths.dotenvPath,
167
135
  cloudEnvironment,
168
136
  }),
@@ -188,24 +156,21 @@ async function compileEnvironmentApplicationIndexes({
188
156
  throw new Error(compileResult.stderr);
189
157
  }
190
158
 
191
- const compiledHeadersJson = JSON.parse(compileResult.stdout);
192
- const applicationIndexUploadScriptContent = createApplicationIndexUploadScript(
193
- {
159
+ const applicationIndexUploadScriptContent =
160
+ createApplicationIndexUploadScript({
194
161
  bucketUrl: getApplicationIndexBucketUrl({
195
162
  bucketRegion,
196
163
  prNumber: cliFlags['pr-number'],
197
164
  applicationName: cliFlags['application-name'],
198
165
  cloudEnvironment,
199
166
  }),
200
- compiledHeadersJson,
201
167
  cdnUrl,
202
168
  cloudEnvironment,
203
169
  buildRevision: cliFlags['build-revision'],
204
170
  buildNumber: cliFlags['build-number'],
205
171
  applicationIndexOutFile: cliFlags['application-index-out-file'],
206
172
  versionJsonOutFile: cliFlags['version-json-out-file'],
207
- }
208
- );
173
+ });
209
174
  // Generate bash scripts to run the `gsutil` upload command.
210
175
  writeUploadScriptFile({
211
176
  fileName: cliFlags['application-index-upload-script-out-file'],
@@ -231,7 +196,8 @@ async function command(cliFlags, cwd) {
231
196
  let cloudEnvironmentsGroupedByBucketRegions;
232
197
  try {
233
198
  // This is the list of the supported cloud environments and their related bucket location.
234
- cloudEnvironmentsGroupedByBucketRegions = await buckedConfigExplorer.search();
199
+ cloudEnvironmentsGroupedByBucketRegions =
200
+ await buckedConfigExplorer.search();
235
201
  } catch (e) {
236
202
  throw new Error(
237
203
  'Failed loading a Google Bucket configuration. Create a cosmiconfig for `google-storage-buckets` for example `google-storage-buckets.config.cjs`.'
@@ -0,0 +1,126 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { findRootSync } from '@manypkg/find-root';
4
+ import { processConfig } from '@commercetools-frontend/application-config';
5
+ import getApplicationDirectory from '../utils/get-application-directory.js';
6
+ import loadDotenvFiles from '../utils/load-dotenv-files.js';
7
+
8
+ // The menu links are only parsed from the config in development mode.
9
+ process.env.NODE_ENV = 'development';
10
+
11
+ const supportedLocales = ['en', 'de', 'es', 'fr-FR', 'zh-CN', 'ja'];
12
+
13
+ const mapLabelAllLocalesWithDefaults = (labelAllLocales, defaultLabel) => {
14
+ let mappedLabelAllLocales = labelAllLocales;
15
+ if (defaultLabel) {
16
+ // Map all supported locales with the given localized labels.
17
+ // If a locale is not defined in the config, we use the `default` label as the value.
18
+ // This is only needed for development as we're trying to map two different schemas.
19
+ mappedLabelAllLocales = supportedLocales.map((supportedLocale) => {
20
+ const existingField = labelAllLocales.find(
21
+ (field) => field.locale === supportedLocale
22
+ );
23
+ if (existingField) return existingField;
24
+ return {
25
+ locale: supportedLocale,
26
+ value: defaultLabel,
27
+ };
28
+ });
29
+ }
30
+ return mappedLabelAllLocales;
31
+ };
32
+
33
+ /**
34
+ * Transform menu links defined in the `custom-application-config.json` to the format
35
+ * used by the HTTP Proxy GraphQL API.
36
+ */
37
+ const mapApplicationMenuConfigToGraqhQLMenuJson = (config) => {
38
+ const entryPointUriPath = config.entryPointUriPath;
39
+ const accountLinks = config.__DEVELOPMENT__.accountLinks;
40
+
41
+ if (accountLinks) {
42
+ return accountLinks.map((menuLink) => ({
43
+ key: menuLink.uriPath,
44
+ uriPath: menuLink.uriPath,
45
+ labelAllLocales: mapLabelAllLocalesWithDefaults(
46
+ menuLink.labelAllLocales,
47
+ menuLink.defaultLabel
48
+ ),
49
+ permissions: menuLink.permissions ?? [],
50
+ featureToggle: menuLink.featureToggle ?? null,
51
+ }));
52
+ }
53
+
54
+ const menuLinks = config.__DEVELOPMENT__.menuLinks;
55
+ return {
56
+ key: entryPointUriPath,
57
+ uriPath: entryPointUriPath,
58
+ icon: menuLinks.icon,
59
+ labelAllLocales: mapLabelAllLocalesWithDefaults(
60
+ menuLinks.labelAllLocales,
61
+ menuLinks.defaultLabel
62
+ ),
63
+ permissions: menuLinks.permissions,
64
+ featureToggle: menuLinks.featureToggle ?? null,
65
+ menuVisibility: menuLinks.menuVisibility ?? null,
66
+ actionRights: menuLinks.actionRights ?? null,
67
+ dataFences: menuLinks.dataFences ?? null,
68
+ submenu: menuLinks.submenuLinks.map((submenuLink) => ({
69
+ key: submenuLink.uriPath.replace('/', '-'),
70
+ uriPath: submenuLink.uriPath,
71
+ labelAllLocales: mapLabelAllLocalesWithDefaults(
72
+ submenuLink.labelAllLocales,
73
+ submenuLink.defaultLabel
74
+ ),
75
+ permissions: submenuLink.permissions,
76
+ featureToggle: submenuLink.featureToggle ?? null,
77
+ menuVisibility: submenuLink.menuVisibility ?? null,
78
+ actionRights: submenuLink.actionRights ?? null,
79
+ dataFences: submenuLink.dataFences ?? null,
80
+ })),
81
+ shouldRenderDivider: menuLinks.shouldRenderDivider ?? false,
82
+ };
83
+ };
84
+
85
+ async function command(cliFlags, cwd) {
86
+ const applicationDirectory = getApplicationDirectory(cwd);
87
+
88
+ const dotenvPath =
89
+ cliFlags['dotenv-folder'] &&
90
+ path.join(findRootSync(cwd), cliFlags['dotenv-folder']);
91
+
92
+ const processEnv = {
93
+ ...loadDotenvFiles({
94
+ dotenvPath,
95
+ // The env itself is not important for the menu. However, the application config
96
+ // uses environment placeholders and therefore we need to provide the variables for it.
97
+ cloudEnvironment: 'ctp-gcp-staging',
98
+ }),
99
+ // Again, make sure that the environment is "development", otherwise
100
+ // the menu config won't be available.
101
+ NODE_ENV: 'development',
102
+ MC_APP_ENV: 'development',
103
+ // Something random, just to have environment variable defined.
104
+ REVISION: '123',
105
+ };
106
+
107
+ const applicationRuntimeConfig = processConfig({
108
+ disableCache: true,
109
+ applicationPath: applicationDirectory,
110
+ processEnv,
111
+ });
112
+
113
+ const applicationMenu = mapApplicationMenuConfigToGraqhQLMenuJson(
114
+ applicationRuntimeConfig.env
115
+ );
116
+
117
+ const formattedJson = JSON.stringify(applicationMenu, null, 2);
118
+
119
+ fs.writeFileSync(
120
+ path.join(applicationDirectory, 'menu.json'),
121
+ formattedJson,
122
+ { encoding: 'utf8' }
123
+ );
124
+ }
125
+
126
+ export default command;
@@ -1,6 +1,5 @@
1
1
  function createApplicationIndexUploadScript({
2
2
  bucketUrl,
3
- compiledHeadersJson,
4
3
  cdnUrl,
5
4
  cloudEnvironment,
6
5
  buildRevision,
@@ -15,12 +14,6 @@ echo "Uploading compiled ${applicationIndexOutFile} to bucket ${bucketUrl}"
15
14
  gsutil \\
16
15
  -h "Content-Type: text/html" \\
17
16
  -h "Cache-Control: public, max-age=0, no-transform" \\
18
- ${Object.entries(compiledHeadersJson)
19
- .map(
20
- ([headerKey, headerValue]) =>
21
- ` -h "x-goog-meta-mc-${headerKey}: ${headerValue}" \\`
22
- )
23
- .join('\n')}
24
17
  cp -z html \\
25
18
  "$(dirname "$0")/${applicationIndexOutFile}" \\
26
19
  "${bucketUrl}/"
@@ -0,0 +1,48 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import dotenv from 'dotenv';
4
+
5
+ function loadDotenvFiles({ dotenvPath, cloudEnvironment }) {
6
+ // No path requested, skip.
7
+ if (!dotenvPath) {
8
+ return {};
9
+ }
10
+
11
+ // Check if the given path exists.
12
+ if (!fs.existsSync(dotenvPath)) {
13
+ throw new Error(`The dotenv folder path does not exist: "${dotenvPath}".`);
14
+ }
15
+
16
+ // Load the environment values
17
+ const sharedDotenvFile = '.env.production';
18
+ const cloudDotenvFile = `.env.${cloudEnvironment}`;
19
+
20
+ // The shared dotenv file across environments is optional
21
+ const sharedProductionEnvironment = dotenv.config({
22
+ encoding: 'utf8',
23
+ path: path.join(dotenvPath, sharedDotenvFile),
24
+ });
25
+
26
+ const cloudSpecificProductionEnvironment = dotenv.config({
27
+ encoding: 'utf8',
28
+ path: path.join(dotenvPath, cloudDotenvFile),
29
+ });
30
+
31
+ if (cloudSpecificProductionEnvironment.error) {
32
+ throw new Error(
33
+ `Failed loading '${cloudDotenvFile}' in '${dotenvPath}'. Make sure it exists.`
34
+ );
35
+ }
36
+ if (sharedProductionEnvironment.error) {
37
+ throw new Error(
38
+ `Failed loading '${sharedDotenvFile}' in '${dotenvPath}'. Make sure it exists.`
39
+ );
40
+ }
41
+
42
+ return {
43
+ ...sharedProductionEnvironment.parsed,
44
+ ...cloudSpecificProductionEnvironment.parsed,
45
+ };
46
+ }
47
+
48
+ export default loadDotenvFiles;
package/CHANGELOG.md DELETED
@@ -1,11 +0,0 @@
1
- # @commercetools-frontend/application-cli
2
-
3
- ## 0.0.2
4
-
5
- ### Patch Changes
6
-
7
- - [#10810](https://github.com/commercetools/merchant-center-frontend/pull/10810) [`736eaca1a0`](https://github.com/commercetools/merchant-center-frontend/commit/736eaca1a03a9504890b961a32a3d0552157b231) Thanks [@tdeekens](https://github.com/tdeekens)! - Adds an `--assets-root-path` option to the application-cli.
8
-
9
- - [#10808](https://github.com/commercetools/merchant-center-frontend/pull/10808) [`d755a6ea25`](https://github.com/commercetools/merchant-center-frontend/commit/d755a6ea257f150bc82eb3c8c7a78b94b1489284) Thanks [@tdeekens](https://github.com/tdeekens)! - Refactors CLIs to only use execa and drop shelljs
10
-
11
- - [#10813](https://github.com/commercetools/merchant-center-frontend/pull/10813) [`5a3b8993bf`](https://github.com/commercetools/merchant-center-frontend/commit/5a3b8993bf1ecee0f107fab039fc06ae19846773) Thanks [@tdeekens](https://github.com/tdeekens)! - Defaults the revision to CircleCI env variable `CIRCLE_SHA1` for the `--revision` option.