@commercetools-frontend/application-cli 1.4.0 → 1.5.0

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.
Files changed (35) hide show
  1. package/bin/cli.js +9 -0
  2. package/cli/dist/commercetools-frontend-application-cli-cli.cjs.d.ts +2 -0
  3. package/cli/dist/commercetools-frontend-application-cli-cli.cjs.d.ts.map +1 -0
  4. package/cli/dist/commercetools-frontend-application-cli-cli.cjs.dev.js +725 -0
  5. package/cli/dist/commercetools-frontend-application-cli-cli.cjs.js +7 -0
  6. package/cli/dist/commercetools-frontend-application-cli-cli.cjs.prod.js +725 -0
  7. package/cli/dist/commercetools-frontend-application-cli-cli.esm.js +699 -0
  8. package/cli/package.json +4 -0
  9. package/dist/declarations/src/cli.d.ts +2 -0
  10. package/dist/declarations/src/commands/compile-deployments.d.ts +3 -0
  11. package/dist/declarations/src/commands/compile-menu.d.ts +3 -0
  12. package/dist/declarations/src/commands/create-version.d.ts +3 -0
  13. package/dist/declarations/src/commands/validate-menu.d.ts +5 -0
  14. package/dist/declarations/src/schema.d.ts +228 -0
  15. package/dist/declarations/src/types.d.ts +92 -0
  16. package/dist/declarations/src/utils/create-application-assets-upload-script.d.ts +3 -0
  17. package/dist/declarations/src/utils/create-application-index-upload-script.d.ts +3 -0
  18. package/dist/declarations/src/utils/get-application-directory.d.ts +2 -0
  19. package/dist/declarations/src/utils/is-ci.d.ts +2 -0
  20. package/dist/declarations/src/utils/load-dotenv-files.d.ts +5 -0
  21. package/dist/declarations/src/utils/resolve-in-application.d.ts +2 -0
  22. package/package.json +27 -9
  23. package/src/bin/cli.js +0 -100
  24. package/src/commands/compile-deployments.js +0 -283
  25. package/src/commands/compile-menu.js +0 -126
  26. package/src/commands/create-version.js +0 -43
  27. package/src/commands/validate-menu.js +0 -33
  28. package/src/commands/validate-menu.spec.js +0 -43
  29. package/src/schema.js +0 -112
  30. package/src/utils/create-application-assets-upload-script.js +0 -62
  31. package/src/utils/create-application-index-upload-script.js +0 -41
  32. package/src/utils/get-application-directory.js +0 -7
  33. package/src/utils/is-ci.js +0 -5
  34. package/src/utils/load-dotenv-files.js +0 -48
  35. package/src/utils/resolve-in-application.js +0 -8
@@ -0,0 +1,699 @@
1
+ import { cac } from 'cac';
2
+ import _Object$keys from '@babel/runtime-corejs3/core-js-stable/object/keys';
3
+ import _Object$getOwnPropertySymbols from '@babel/runtime-corejs3/core-js-stable/object/get-own-property-symbols';
4
+ import _Object$getOwnPropertyDescriptor from '@babel/runtime-corejs3/core-js-stable/object/get-own-property-descriptor';
5
+ import _forEachInstanceProperty from '@babel/runtime-corejs3/core-js-stable/instance/for-each';
6
+ import _Object$getOwnPropertyDescriptors from '@babel/runtime-corejs3/core-js-stable/object/get-own-property-descriptors';
7
+ import _Object$defineProperties from '@babel/runtime-corejs3/core-js-stable/object/define-properties';
8
+ import _Object$defineProperty from '@babel/runtime-corejs3/core-js-stable/object/define-property';
9
+ import _slicedToArray from '@babel/runtime-corejs3/helpers/esm/slicedToArray';
10
+ import _defineProperty from '@babel/runtime-corejs3/helpers/esm/defineProperty';
11
+ import _filterInstanceProperty from '@babel/runtime-corejs3/core-js-stable/instance/filter';
12
+ import _concatInstanceProperty from '@babel/runtime-corejs3/core-js-stable/instance/concat';
13
+ import _mapInstanceProperty from '@babel/runtime-corejs3/core-js-stable/instance/map';
14
+ import _Object$entries from '@babel/runtime-corejs3/core-js-stable/object/entries';
15
+ import fs from 'fs';
16
+ import path from 'path';
17
+ import { Listr } from 'listr2';
18
+ import execa from 'execa';
19
+ import { cosmiconfig } from 'cosmiconfig';
20
+ import { findRootSync } from '@manypkg/find-root';
21
+ import dotenv from 'dotenv';
22
+ import _findInstanceProperty from '@babel/runtime-corejs3/core-js-stable/instance/find';
23
+ import _JSON$stringify from '@babel/runtime-corejs3/core-js-stable/json/stringify';
24
+ import { processConfig } from '@commercetools-frontend/application-config';
25
+ import _sliceInstanceProperty from '@babel/runtime-corejs3/core-js-stable/instance/slice';
26
+ import fetch from 'node-fetch';
27
+ import { Validator } from 'jsonschema';
28
+
29
+ function getApplicationDirectory(cwd) {
30
+ return fs.realpathSync(cwd);
31
+ }
32
+
33
+ function resolveInApplication(relativePath, cwd) {
34
+ return path.resolve(getApplicationDirectory(cwd), relativePath);
35
+ }
36
+
37
+ function isCI() {
38
+ // @ts-expect-error The env is sometimes overwritten by code to a boolean
39
+ return process.env.CI === true || process.env.CI === 'true';
40
+ }
41
+
42
+ function createApplicationIndexUploadScript(_ref) {
43
+ var _context, _context2, _context3, _context4, _context5, _context6, _context7, _context8, _context9, _context10;
44
+ let packageManagerName = _ref.packageManagerName,
45
+ bucketUrl = _ref.bucketUrl,
46
+ cdnUrl = _ref.cdnUrl,
47
+ cloudEnvironment = _ref.cloudEnvironment,
48
+ buildRevision = _ref.buildRevision,
49
+ buildNumber = _ref.buildNumber,
50
+ applicationIndexOutFile = _ref.applicationIndexOutFile;
51
+ const uploadScriptContent = _concatInstanceProperty(_context = _concatInstanceProperty(_context2 = _concatInstanceProperty(_context3 = _concatInstanceProperty(_context4 = _concatInstanceProperty(_context5 = _concatInstanceProperty(_context6 = _concatInstanceProperty(_context7 = _concatInstanceProperty(_context8 = _concatInstanceProperty(_context9 = _concatInstanceProperty(_context10 = "#!/usr/bin/env bash\n\nset -e\n\necho \"Uploading compiled ".concat(applicationIndexOutFile, " to bucket ")).call(_context10, bucketUrl, "\"\ngsutil \\\n-h \"Content-Type: text/html\" \\\n-h \"Cache-Control: public, max-age=0, no-transform\" \\\ncp -z html \\\n\"$(dirname \"$0\")/")).call(_context9, applicationIndexOutFile, "\" \\\n\"")).call(_context8, bucketUrl, "/\"\n\necho \"Creating version.json and uploading it to bucket ")).call(_context7, bucketUrl, "\"\n\nNODE_ENV=production ")).call(_context6, packageManagerName, " application-cli create-version \\\n --version-url=")).call(_context5, cdnUrl, "/")).call(_context4, cloudEnvironment, "/version.json \\\n --build-revision=")).call(_context3, buildRevision, " \\\n --build-number=")).call(_context2, buildNumber, " \\\n --out-file=$(dirname \"$0\")/version.json\n\ngsutil \\\n-h \"Content-Type: application/json\" \\\n-h \"Cache-Control: private, max-age=0, no-transform\" \\\ncp -z json \\\n\"$(dirname \"$0\")/version.json\" \\\n\"")).call(_context, bucketUrl, "/\"\n");
52
+ return uploadScriptContent;
53
+ }
54
+
55
+ function createApplicationAssetsUploadScript(_ref) {
56
+ var _context, _context2, _context3, _context4, _context5, _context6;
57
+ let bucketUrl = _ref.bucketUrl,
58
+ assetsPath = _ref.assetsPath,
59
+ skipMenu = _ref.skipMenu;
60
+ const uploadScriptContent = _concatInstanceProperty(_context = _concatInstanceProperty(_context2 = _concatInstanceProperty(_context3 = _concatInstanceProperty(_context4 = _concatInstanceProperty(_context5 = _concatInstanceProperty(_context6 = "#!/usr/bin/env bash\n\nset -e\n\n# NOTES:\n# https://cloud.google.com/storage/docs/gsutil/commands/cp#options\n# 1. The '-z' option triggers compressing the assets before\n# uploading them and sets the 'Content-Encoding' to 'gzip'.\n# 2. The 'Accept-encoding: gzip' is set automatically by the 'gsutils'.\n# 3. The 'max-age' is set to 1 year which is considered the maximum\n# \"valid\" lifetime of an asset to be cached.\n# 5. The '-n' will skip uploading existing files and prevents them to\n# be overwritten\necho \"Uploading static assets to bucket ".concat(bucketUrl, "\"\n\ngsutil -m \\\n -h \"Cache-Control: public, max-age=31536000, no-transform\" \\\n cp -z js,css \\\n -n \\\n ")).call(_context6, assetsPath, "/public/{*.css,*.js,*.js.map,*.png,*.html,robots.txt} \\\n \"")).call(_context5, bucketUrl, "\"\n\nif ")).call(_context4, skipMenu, "; then\n echo \"Skipping menu.json upload\"\nelse\n echo \"Uploading menu.json to bucket ")).call(_context3, bucketUrl, "\"\n # NOTE: somehow the 'cache-control:private' doesn't work.\n # I mean, the file is uploaded with the correct metadata but when I fetch\n # the file the response contains the header\n # 'cache-control: public, max-age=31536000, no-transform', even though the\n # documentation clearly states that by marking the header as 'private' will\n # disable the cache (for publicly readable objects).\n # https://cloud.google.com/storage/docs/gsutil/addlhelp/WorkingWithObjectMetadata#cache-control\n # However, I found out that, by requesting the file with any RANDOM\n # query parameter, will instruct the storage to return a 'fresh' object\n # (without any cache control).\n # Unofficial source: https://stackoverflow.com/a/49052895\n # This seems to be the 'easiest' option to 'disable' the cache for public\n # objects. Other alternative approaces are:\n # * make the object private with some simple ACL (private objects are not cached)\n # * suffix the file name with e.g. the git SHA, so we have different files\n # for each upload ('index.html.template-${CIRCLE_SHA1}'). The server knows\n # the git SHA on runtime and can get the correct file when it starts.\n # * find out why the 'private' cache control does not work\n gsutil \\\n -h \"Content-Type: application/json\" \\\n -h \"Cache-Control: private, max-age=0, no-transform\" \\\n cp -z json \\\n ")).call(_context2, assetsPath, "/menu.json \\\n ")).call(_context, bucketUrl, "\nfi\n");
61
+ return uploadScriptContent;
62
+ }
63
+
64
+ function ownKeys$3(object, enumerableOnly) { var keys = _Object$keys(object); if (_Object$getOwnPropertySymbols) { var symbols = _Object$getOwnPropertySymbols(object); enumerableOnly && (symbols = _filterInstanceProperty(symbols).call(symbols, function (sym) { return _Object$getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
65
+ function _objectSpread$3(target) { for (var i = 1; i < arguments.length; i++) { var _context2, _context3; var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? _forEachInstanceProperty(_context2 = ownKeys$3(Object(source), !0)).call(_context2, function (key) { _defineProperty(target, key, source[key]); }) : _Object$getOwnPropertyDescriptors ? _Object$defineProperties(target, _Object$getOwnPropertyDescriptors(source)) : _forEachInstanceProperty(_context3 = ownKeys$3(Object(source))).call(_context3, function (key) { _Object$defineProperty(target, key, _Object$getOwnPropertyDescriptor(source, key)); }); } return target; }
66
+ function loadDotenvFiles(_ref) {
67
+ let dotenvPath = _ref.dotenvPath,
68
+ cloudEnvironment = _ref.cloudEnvironment;
69
+ // No path requested, skip.
70
+ if (!dotenvPath) {
71
+ return {};
72
+ }
73
+
74
+ // Check if the given path exists.
75
+ if (!fs.existsSync(dotenvPath)) {
76
+ throw new Error("The dotenv folder path does not exist: \"".concat(dotenvPath, "\"."));
77
+ }
78
+
79
+ // Load the environment values
80
+ const sharedDotenvFile = '.env.production';
81
+ const cloudDotenvFile = ".env.".concat(cloudEnvironment);
82
+
83
+ // The shared dotenv file across environments is optional
84
+ const sharedProductionEnvironment = dotenv.config({
85
+ encoding: 'utf8',
86
+ path: path.join(dotenvPath, sharedDotenvFile)
87
+ });
88
+ const cloudSpecificProductionEnvironment = dotenv.config({
89
+ encoding: 'utf8',
90
+ path: path.join(dotenvPath, cloudDotenvFile)
91
+ });
92
+ if (cloudSpecificProductionEnvironment.error) {
93
+ var _context;
94
+ throw new Error(_concatInstanceProperty(_context = "Failed loading '".concat(cloudDotenvFile, "' in '")).call(_context, dotenvPath, "'. Make sure it exists."));
95
+ }
96
+ if (sharedProductionEnvironment.error) {
97
+ throw new Error("Failed loading '".concat(sharedDotenvFile, "' in '").concat(dotenvPath, "'. Make sure it exists."));
98
+ }
99
+ return _objectSpread$3(_objectSpread$3({}, sharedProductionEnvironment.parsed), cloudSpecificProductionEnvironment.parsed);
100
+ }
101
+
102
+ function ownKeys$2(object, enumerableOnly) { var keys = _Object$keys(object); if (_Object$getOwnPropertySymbols) { var symbols = _Object$getOwnPropertySymbols(object); enumerableOnly && (symbols = _filterInstanceProperty(symbols).call(symbols, function (sym) { return _Object$getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
103
+ function _objectSpread$2(target) { for (var i = 1; i < arguments.length; i++) { var _context8, _context9; var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? _forEachInstanceProperty(_context8 = ownKeys$2(Object(source), !0)).call(_context8, function (key) { _defineProperty(target, key, source[key]); }) : _Object$getOwnPropertyDescriptors ? _Object$defineProperties(target, _Object$getOwnPropertyDescriptors(source)) : _forEachInstanceProperty(_context9 = ownKeys$2(Object(source))).call(_context9, function (key) { _Object$defineProperty(target, key, _Object$getOwnPropertyDescriptor(source, key)); }); } return target; }
104
+ const buckedConfigExplorer = cosmiconfig('google-storage-buckets');
105
+ function writeUploadScriptFile(_ref) {
106
+ let fileName = _ref.fileName,
107
+ fileContent = _ref.fileContent,
108
+ filePath = _ref.filePath;
109
+ fs.writeFileSync(path.join(filePath, fileName), fileContent, {
110
+ // Make the script executable
111
+ mode: 0o755,
112
+ encoding: 'utf8'
113
+ });
114
+ }
115
+ function getBucketNamespace(prNumber) {
116
+ if (!prNumber) return;
117
+ if (prNumber === 'merchant-center-preview') return prNumber;
118
+ return "mc-".concat(prNumber);
119
+ }
120
+
121
+ /**
122
+ * Construct the storage bucket URL for the specific application and cloud environment.
123
+ *
124
+ * 1. Static assets are uploaded to `:bucketRegion/:prNumber?/:applicationName`
125
+ * 2. The application index is uploaded to `:bucketRegion/:prNumber?/:applicationName/:cloudEnvironment`
126
+ *
127
+ * This allows all cloud environments sharing the same static assets while each application's index
128
+ * is uploaded with different headers (e.g. CSP rules).
129
+ */
130
+
131
+ function getApplicationAssetsBucketUrl(_ref2) {
132
+ var _context;
133
+ let bucketRegion = _ref2.bucketRegion,
134
+ prNumber = _ref2.prNumber,
135
+ applicationName = _ref2.applicationName;
136
+ const applicationAssetsBucketUrl = _filterInstanceProperty(_context = ["gs://".concat(bucketRegion), getBucketNamespace(prNumber), applicationName]).call(_context, Boolean);
137
+ return applicationAssetsBucketUrl.join('/');
138
+ }
139
+ function getApplicationIndexBucketUrl(_ref3) {
140
+ var _context2;
141
+ let bucketRegion = _ref3.bucketRegion,
142
+ prNumber = _ref3.prNumber,
143
+ applicationName = _ref3.applicationName,
144
+ cloudEnvironment = _ref3.cloudEnvironment;
145
+ const applicationAssetsBucketUrl = getApplicationAssetsBucketUrl({
146
+ bucketRegion,
147
+ prNumber,
148
+ applicationName
149
+ });
150
+ const applicationIndexBucketUrl = _concatInstanceProperty(_context2 = "".concat(applicationAssetsBucketUrl, "/")).call(_context2, cloudEnvironment);
151
+ return applicationIndexBucketUrl;
152
+ }
153
+ function getCdnUrl(_ref4) {
154
+ var _context3;
155
+ let bucketRegion = _ref4.bucketRegion,
156
+ prNumber = _ref4.prNumber,
157
+ applicationName = _ref4.applicationName;
158
+ return _filterInstanceProperty(_context3 = ["https://storage.googleapis.com/".concat(bucketRegion), getBucketNamespace(prNumber), applicationName]).call(_context3, Boolean).join('/');
159
+ }
160
+ async function compileApplicationAssets(_ref5) {
161
+ var _context4, _context5;
162
+ let cliFlags = _ref5.cliFlags,
163
+ bucketRegion = _ref5.bucketRegion,
164
+ paths = _ref5.paths;
165
+ const applicationAssetsUploadScriptContent = createApplicationAssetsUploadScript({
166
+ bucketUrl: getApplicationAssetsBucketUrl({
167
+ bucketRegion,
168
+ prNumber: cliFlags.prNumber,
169
+ applicationName: cliFlags.applicationName
170
+ }),
171
+ assetsPath: paths.assetsPath,
172
+ skipMenu: cliFlags.skipMenu
173
+ });
174
+ const parsedApplicationAssetsUploadScriptFile = path.parse(cliFlags.applicationAssetsUploadScriptOutFile);
175
+ const applicationAssetsUploadScriptFileName = _concatInstanceProperty(_context4 = _concatInstanceProperty(_context5 = "".concat(parsedApplicationAssetsUploadScriptFile.name, "-")).call(_context5, bucketRegion)).call(_context4, parsedApplicationAssetsUploadScriptFile.ext);
176
+ writeUploadScriptFile({
177
+ fileName: applicationAssetsUploadScriptFileName,
178
+ fileContent: applicationAssetsUploadScriptContent,
179
+ filePath: paths.deploymentsPath
180
+ });
181
+ }
182
+ async function compileEnvironmentApplicationIndexes(_ref6) {
183
+ let cliFlags = _ref6.cliFlags,
184
+ paths = _ref6.paths,
185
+ bucketRegion = _ref6.bucketRegion,
186
+ cloudEnvironment = _ref6.cloudEnvironment;
187
+ const cloudEnvironmentDeploymentPath = path.join(paths.deploymentsPath, cloudEnvironment);
188
+ // Ensure the folder exists
189
+ const createDeploymentsFolderResult = await execa('mkdir', ['-p', cloudEnvironmentDeploymentPath], {
190
+ encoding: 'utf8'
191
+ });
192
+ if (createDeploymentsFolderResult.failed) {
193
+ throw new Error(createDeploymentsFolderResult.stderr);
194
+ }
195
+
196
+ // Construct the proper CDN URL for the specific application
197
+ const cdnUrl = getCdnUrl({
198
+ bucketRegion,
199
+ prNumber: cliFlags.prNumber,
200
+ applicationName: cliFlags.applicationName
201
+ });
202
+ const environmentVariablesForCompilation = _objectSpread$2(_objectSpread$2(_objectSpread$2(_objectSpread$2({}, loadDotenvFiles({
203
+ dotenvPath: paths.dotenvPath,
204
+ cloudEnvironment
205
+ })), {}, {
206
+ // The trailing slash is important to indicate to the CSP directive that all the resources
207
+ // under that path should be allowed.
208
+ MC_CDN_URL: "".concat(cdnUrl, "/")
209
+ }, cliFlags.mcUrl ? {
210
+ MC_URL: cliFlags.mcUrl
211
+ } : {}), cliFlags.mcApiUrl ? {
212
+ MC_API_URL: cliFlags.mcApiUrl
213
+ } : {}), {}, {
214
+ // Will be used by the Application Kit for Sentry and exposed on `window.app.revision`.
215
+ REVISION: cliFlags.buildRevision
216
+ });
217
+
218
+ /// Sentry and GTM is disabled on branch deployments
219
+ if (cliFlags.prNumber) {
220
+ // @ts-expect-error The env is sometimes overwritten by code to a boolean
221
+ process.env.TRACKING_SENTRY = null;
222
+ // @ts-expect-error The env is sometimes overwritten by code to a boolean
223
+ process.env.TRACKING_GTM = null;
224
+ // @ts-expect-error
225
+ environmentVariablesForCompilation.TRACKING_SENTRY = null;
226
+ // @ts-expect-error
227
+ environmentVariablesForCompilation.TRACKING_GTM = null;
228
+ }
229
+
230
+ // Compile the application using the loaded environment values
231
+ const compileResult = await execa('mc-scripts', ['compile-html'], {
232
+ encoding: 'utf8',
233
+ preferLocal: true,
234
+ extendEnv: true,
235
+ env: environmentVariablesForCompilation
236
+ });
237
+ if (compileResult.failed) {
238
+ throw new Error(compileResult.stderr);
239
+ }
240
+ const applicationIndexUploadScriptContent = createApplicationIndexUploadScript({
241
+ packageManagerName: cliFlags.packageManagerName,
242
+ bucketUrl: getApplicationIndexBucketUrl({
243
+ bucketRegion,
244
+ prNumber: cliFlags.prNumber,
245
+ applicationName: cliFlags.applicationName,
246
+ cloudEnvironment
247
+ }),
248
+ cdnUrl,
249
+ cloudEnvironment,
250
+ buildRevision: cliFlags.buildRevision,
251
+ buildNumber: cliFlags.buildNumber,
252
+ applicationIndexOutFile: cliFlags.applicationIndexOutFile
253
+ });
254
+ // Generate bash scripts to run the `gsutil` upload command.
255
+
256
+ writeUploadScriptFile({
257
+ fileName: cliFlags.applicationIndexUploadScriptOutFile,
258
+ fileContent: applicationIndexUploadScriptContent,
259
+ filePath: cloudEnvironmentDeploymentPath
260
+ });
261
+
262
+ // Move the compiled `index.html` to the deployments folder of the related cloud environment.
263
+
264
+ const moveResult = await execa('mv', [path.join(paths.publicAssetsPath, 'index.html'), path.join(cloudEnvironmentDeploymentPath, cliFlags.applicationIndexOutFile)]);
265
+ if (moveResult.failed) {
266
+ throw new Error(moveResult.stderr);
267
+ }
268
+ }
269
+ async function command$3(cliFlags, cwd) {
270
+ var _context6;
271
+ let cloudEnvironmentsGroupedByBucketRegions;
272
+ try {
273
+ // This is the list of the supported cloud environments and their related bucket location.
274
+ cloudEnvironmentsGroupedByBucketRegions = await buckedConfigExplorer.search();
275
+ } catch (e) {
276
+ throw new Error('Failed loading a Google Bucket configuration. Create a cosmiconfig for `google-storage-buckets` for example `google-storage-buckets.config.cjs`.');
277
+ }
278
+ if (!cloudEnvironmentsGroupedByBucketRegions) {
279
+ throw new Error('Failed loading a Google Bucket configuration');
280
+ }
281
+ const applicationDirectory = getApplicationDirectory(cwd);
282
+ let assetsPath;
283
+ if (cliFlags.ciAssetsRootPath && isCI()) {
284
+ assetsPath = applicationDirectory.replace('/home/circleci/', cliFlags.ciAssetsRootPath);
285
+ } else {
286
+ assetsPath = applicationDirectory;
287
+ }
288
+ const monorepoRoot = findRootSync(cwd);
289
+ const paths = {
290
+ publicAssetsPath: resolveInApplication('public', cwd),
291
+ deploymentsPath: resolveInApplication('deployments', cwd),
292
+ dotenvPath: cliFlags.dotenvFolder && path.join(monorepoRoot.rootDir, cliFlags.dotenvFolder),
293
+ assetsPath
294
+ };
295
+ const taskList = new Listr(_mapInstanceProperty(_context6 = _Object$entries(cloudEnvironmentsGroupedByBucketRegions.config)).call(_context6, _ref7 => {
296
+ let _ref8 = _slicedToArray(_ref7, 2),
297
+ bucketRegion = _ref8[0],
298
+ cloudEnvironments = _ref8[1];
299
+ return {
300
+ title: "Compiling application for bucket ".concat(bucketRegion),
301
+ task: () => {
302
+ var _context7;
303
+ return new Listr(_concatInstanceProperty(_context7 = _mapInstanceProperty(cloudEnvironments).call(cloudEnvironments, cloudEnvironment => ({
304
+ title: "Compiling ".concat(cloudEnvironment, " application index"),
305
+ task: () => compileEnvironmentApplicationIndexes({
306
+ cliFlags,
307
+ paths,
308
+ bucketRegion,
309
+ cloudEnvironment
310
+ })
311
+ }))).call(_context7, {
312
+ title: "Compiling application assets",
313
+ task: () => compileApplicationAssets({
314
+ cliFlags,
315
+ bucketRegion,
316
+ paths
317
+ })
318
+ }));
319
+ }
320
+ };
321
+ }), {
322
+ renderer: isCI() ? 'verbose' : 'default'
323
+ });
324
+ await taskList.run();
325
+ }
326
+
327
+ function ownKeys$1(object, enumerableOnly) { var keys = _Object$keys(object); if (_Object$getOwnPropertySymbols) { var symbols = _Object$getOwnPropertySymbols(object); enumerableOnly && (symbols = _filterInstanceProperty(symbols).call(symbols, function (sym) { return _Object$getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
328
+ function _objectSpread$1(target) { for (var i = 1; i < arguments.length; i++) { var _context2, _context3; var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? _forEachInstanceProperty(_context2 = ownKeys$1(Object(source), !0)).call(_context2, function (key) { _defineProperty(target, key, source[key]); }) : _Object$getOwnPropertyDescriptors ? _Object$defineProperties(target, _Object$getOwnPropertyDescriptors(source)) : _forEachInstanceProperty(_context3 = ownKeys$1(Object(source))).call(_context3, function (key) { _Object$defineProperty(target, key, _Object$getOwnPropertyDescriptor(source, key)); }); } return target; }
329
+
330
+ // The menu links are only parsed from the config in development mode.
331
+ process.env.NODE_ENV = 'development';
332
+ const supportedLocales = ['en', 'de', 'es', 'fr-FR', 'zh-CN', 'ja'];
333
+ const mapLabelAllLocalesWithDefaults = (labelAllLocales, defaultLabel) => {
334
+ let mappedLabelAllLocales = labelAllLocales;
335
+ if (defaultLabel) {
336
+ // Map all supported locales with the given localized labels.
337
+ // If a locale is not defined in the config, we use the `default` label as the value.
338
+ // This is only needed for development as we're trying to map two different schemas.
339
+ mappedLabelAllLocales = _mapInstanceProperty(supportedLocales).call(supportedLocales, supportedLocale => {
340
+ const existingField = _findInstanceProperty(labelAllLocales).call(labelAllLocales, field => field.locale === supportedLocale);
341
+ if (existingField) return existingField;
342
+ return {
343
+ locale: supportedLocale,
344
+ value: defaultLabel
345
+ };
346
+ });
347
+ }
348
+ return mappedLabelAllLocales;
349
+ };
350
+
351
+ /**
352
+ * Transform menu links defined in the `custom-application-config.json` to the format
353
+ * used by the HTTP Proxy GraphQL API.
354
+ */
355
+
356
+ const mapApplicationMenuConfigToGraqhQLMenuJson = config => {
357
+ var _ref, _config$env$__DEVELOP, _config$env$__DEVELOP2, _menuLinks$featureTog, _menuLinks$menuVisibi, _menuLinks$actionRigh, _menuLinks$dataFences, _context, _menuLinks$shouldRend;
358
+ const entryPointUriPath = config.env.entryPointUriPath;
359
+
360
+ // @ts-expect-error: the `accountLinks` is not explicitly typed as it's only used by the account app.
361
+ const accountLinks = (_ref = (_config$env$__DEVELOP = config.env.__DEVELOPMENT__) === null || _config$env$__DEVELOP === void 0 ? void 0 : _config$env$__DEVELOP.accountLinks) !== null && _ref !== void 0 ? _ref : [];
362
+ if (accountLinks.length > 0) {
363
+ return _mapInstanceProperty(accountLinks).call(accountLinks, menuLink => {
364
+ var _menuLink$permissions, _menuLink$featureTogg;
365
+ return {
366
+ key: menuLink.uriPath,
367
+ uriPath: menuLink.uriPath,
368
+ labelAllLocales: mapLabelAllLocalesWithDefaults(menuLink.labelAllLocales, menuLink.defaultLabel),
369
+ permissions: (_menuLink$permissions = menuLink.permissions) !== null && _menuLink$permissions !== void 0 ? _menuLink$permissions : [],
370
+ // @ts-ignore: not defined in schema, as it's only used internally.
371
+ featureToggle: (_menuLink$featureTogg = menuLink.featureToggle) !== null && _menuLink$featureTogg !== void 0 ? _menuLink$featureTogg : null
372
+ };
373
+ });
374
+ }
375
+ const menuLinks = (_config$env$__DEVELOP2 = config.env.__DEVELOPMENT__) === null || _config$env$__DEVELOP2 === void 0 ? void 0 : _config$env$__DEVELOP2.menuLinks;
376
+ return {
377
+ key: entryPointUriPath,
378
+ uriPath: entryPointUriPath,
379
+ icon: menuLinks.icon,
380
+ labelAllLocales: mapLabelAllLocalesWithDefaults(menuLinks === null || menuLinks === void 0 ? void 0 : menuLinks.labelAllLocales, menuLinks === null || menuLinks === void 0 ? void 0 : menuLinks.defaultLabel),
381
+ permissions: menuLinks.permissions,
382
+ // @ts-ignore: not defined in schema, as it's only used internally.
383
+ featureToggle: (_menuLinks$featureTog = menuLinks.featureToggle) !== null && _menuLinks$featureTog !== void 0 ? _menuLinks$featureTog : null,
384
+ // @ts-ignore: not defined in schema, as it's only used internally.
385
+ menuVisibility: (_menuLinks$menuVisibi = menuLinks.menuVisibility) !== null && _menuLinks$menuVisibi !== void 0 ? _menuLinks$menuVisibi : null,
386
+ // @ts-ignore: not defined in schema, as it's only used internally.
387
+ actionRights: (_menuLinks$actionRigh = menuLinks.actionRights) !== null && _menuLinks$actionRigh !== void 0 ? _menuLinks$actionRigh : null,
388
+ // @ts-ignore: not defined in schema, as it's only used internally.
389
+ dataFences: (_menuLinks$dataFences = menuLinks.dataFences) !== null && _menuLinks$dataFences !== void 0 ? _menuLinks$dataFences : null,
390
+ submenu: _mapInstanceProperty(_context = menuLinks.submenuLinks).call(_context, submenuLink => {
391
+ var _submenuLink$featureT, _submenuLink$menuVisi, _submenuLink$actionRi, _submenuLink$dataFenc;
392
+ return {
393
+ key: submenuLink.uriPath.replace('/', '-'),
394
+ uriPath: submenuLink.uriPath,
395
+ labelAllLocales: mapLabelAllLocalesWithDefaults(submenuLink.labelAllLocales, submenuLink.defaultLabel),
396
+ permissions: submenuLink.permissions,
397
+ // @ts-ignore: not defined in schema, as it's only used internally.
398
+ featureToggle: (_submenuLink$featureT = submenuLink.featureToggle) !== null && _submenuLink$featureT !== void 0 ? _submenuLink$featureT : null,
399
+ // @ts-ignore: not defined in schema, as it's only used internally.
400
+ menuVisibility: (_submenuLink$menuVisi = submenuLink.menuVisibility) !== null && _submenuLink$menuVisi !== void 0 ? _submenuLink$menuVisi : null,
401
+ // @ts-ignore: not defined in schema, as it's only used internally.
402
+ actionRights: (_submenuLink$actionRi = submenuLink.actionRights) !== null && _submenuLink$actionRi !== void 0 ? _submenuLink$actionRi : null,
403
+ // @ts-ignore: not defined in schema, as it's only used internally.
404
+ dataFences: (_submenuLink$dataFenc = submenuLink.dataFences) !== null && _submenuLink$dataFenc !== void 0 ? _submenuLink$dataFenc : null
405
+ };
406
+ }),
407
+ // @ts-ignore: not defined in schema, as it's only used internally.
408
+ shouldRenderDivider: (_menuLinks$shouldRend = menuLinks.shouldRenderDivider) !== null && _menuLinks$shouldRend !== void 0 ? _menuLinks$shouldRend : false
409
+ };
410
+ };
411
+ async function command$2(cliFlags, cwd) {
412
+ const applicationDirectory = getApplicationDirectory(cwd);
413
+ const monorepoRoot = findRootSync(cwd);
414
+ const dotenvPath = cliFlags.dotenvFolder && path.join(monorepoRoot.rootDir, cliFlags.dotenvFolder);
415
+ const processEnv = _objectSpread$1(_objectSpread$1({}, loadDotenvFiles({
416
+ dotenvPath,
417
+ // The env itself is not important for the menu. However, the application config
418
+ // uses environment placeholders and therefore we need to provide the variables for it.
419
+ cloudEnvironment: 'ctp-gcp-staging'
420
+ })), {}, {
421
+ // Again, make sure that the environment is "development", otherwise
422
+ // the menu config won't be available.
423
+ NODE_ENV: 'development',
424
+ MC_APP_ENV: 'development',
425
+ // Something random, just to have environment variable defined.
426
+ REVISION: '123'
427
+ });
428
+ const applicationRuntimeConfig = processConfig({
429
+ disableCache: true,
430
+ applicationPath: applicationDirectory,
431
+ processEnv
432
+ });
433
+ const applicationMenu = mapApplicationMenuConfigToGraqhQLMenuJson(applicationRuntimeConfig);
434
+ const formattedJson = _JSON$stringify(applicationMenu, null, 2);
435
+ fs.writeFileSync(path.join(applicationDirectory, 'menu.json'), formattedJson, {
436
+ encoding: 'utf8'
437
+ });
438
+ }
439
+
440
+ async function command$1(cliFlags) {
441
+ const numberOfRollbacks = cliFlags.rollbacks - 1;
442
+ let nextRollbacks;
443
+ try {
444
+ var _context, _context2;
445
+ // The last build's JSON becomes the first rollback
446
+ // while all previous rollbacks remain but are sliced.
447
+ const lastVersionResponse = await fetch(cliFlags.versionUrl);
448
+ const lastVersionJson = await lastVersionResponse.json();
449
+ const previousBuild = lastVersionJson && {
450
+ buildNumber: lastVersionJson.buildNumber,
451
+ revision: lastVersionJson.revision,
452
+ deployedAt: lastVersionJson.deployedAt
453
+ };
454
+ nextRollbacks = _sliceInstanceProperty(_context = _filterInstanceProperty(_context2 = [previousBuild, ...lastVersionJson.rollbacks]).call(_context2, Boolean)).call(_context, 0, numberOfRollbacks);
455
+ } catch (error) {
456
+ nextRollbacks = [];
457
+ }
458
+ const nextBuild = {
459
+ buildNumber: cliFlags.buildNumber,
460
+ revision: cliFlags.buildRevision,
461
+ deployedAt: new Date().toISOString(),
462
+ rollbacks: nextRollbacks
463
+ };
464
+ const formattedJson = _JSON$stringify(nextBuild, null, 2);
465
+ // Logging to stdout which is from where it will be picked
466
+ // up by the caller (a bash script).
467
+ if (cliFlags.outFile) {
468
+ fs.writeFileSync(cliFlags.outFile, formattedJson, {
469
+ encoding: 'utf8'
470
+ });
471
+ } else {
472
+ console.log(formattedJson);
473
+ }
474
+ }
475
+
476
+ function ownKeys(object, enumerableOnly) { var keys = _Object$keys(object); if (_Object$getOwnPropertySymbols) { var symbols = _Object$getOwnPropertySymbols(object); enumerableOnly && (symbols = _filterInstanceProperty(symbols).call(symbols, function (sym) { return _Object$getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
477
+ function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var _context, _context2; var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? _forEachInstanceProperty(_context = ownKeys(Object(source), !0)).call(_context, function (key) { _defineProperty(target, key, source[key]); }) : _Object$getOwnPropertyDescriptors ? _Object$defineProperties(target, _Object$getOwnPropertyDescriptors(source)) : _forEachInstanceProperty(_context2 = ownKeys(Object(source))).call(_context2, function (key) { _Object$defineProperty(target, key, _Object$getOwnPropertyDescriptor(source, key)); }); } return target; }
478
+ const baseMenuProperties = {
479
+ key: {
480
+ type: 'string'
481
+ },
482
+ uriPath: {
483
+ type: 'string'
484
+ },
485
+ icon: {
486
+ type: 'string'
487
+ },
488
+ featureToggle: {
489
+ type: ['string', 'null']
490
+ },
491
+ labelAllLocales: {
492
+ type: 'array',
493
+ items: [{
494
+ type: 'object',
495
+ properties: {
496
+ locale: {
497
+ type: 'string'
498
+ },
499
+ value: {
500
+ type: 'string'
501
+ }
502
+ },
503
+ required: ['locale', 'value']
504
+ }]
505
+ },
506
+ menuVisibility: {
507
+ type: ['string', 'null']
508
+ },
509
+ permissions: {
510
+ type: 'array',
511
+ items: {
512
+ type: 'string'
513
+ }
514
+ },
515
+ dataFences: {
516
+ type: ['array', 'null'],
517
+ items: [{
518
+ type: ['object'],
519
+ properties: {
520
+ group: {
521
+ type: 'string'
522
+ },
523
+ name: {
524
+ type: 'string'
525
+ },
526
+ type: {
527
+ type: 'string'
528
+ }
529
+ }
530
+ }]
531
+ },
532
+ actionRights: {
533
+ type: ['array', 'null'],
534
+ items: [{
535
+ type: ['object'],
536
+ properties: {
537
+ group: {
538
+ type: 'string'
539
+ },
540
+ name: {
541
+ type: 'string'
542
+ }
543
+ }
544
+ }]
545
+ }
546
+ };
547
+ const navbarMenuSchema = {
548
+ $schema: 'https://json-schema.org/draft/2020-12/schema',
549
+ // "$id":""
550
+ title: 'NavbarMenu',
551
+ type: 'object',
552
+ properties: _objectSpread(_objectSpread({}, baseMenuProperties), {}, {
553
+ submenu: {
554
+ type: 'array',
555
+ items: [{
556
+ type: 'object',
557
+ properties: baseMenuProperties
558
+ }]
559
+ }
560
+ }),
561
+ required: ['icon', 'key', 'labelAllLocales', 'permissions', 'submenu', 'uriPath']
562
+ };
563
+ const appbarMenuSchema = {
564
+ $schema: 'https://json-schema.org/draft/2020-12/schema',
565
+ // "$id":""
566
+ title: 'AppbarMenu',
567
+ type: 'array',
568
+ items: [{
569
+ type: 'object',
570
+ properties: baseMenuProperties,
571
+ required: ['key', 'labelAllLocales', 'permissions', 'uriPath']
572
+ }]
573
+ };
574
+
575
+ function validateMenu(menuJson) {
576
+ let schema = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : navbarMenuSchema;
577
+ const validator = new Validator();
578
+ const result = validator.validate(menuJson, schema);
579
+ if (result.valid) {
580
+ return menuJson;
581
+ } else {
582
+ throw new Error('menu.json validation failed\n' + result.errors);
583
+ }
584
+ }
585
+ async function command(cliFlags) {
586
+ const menuJsonPath = cliFlags.inputFile;
587
+ const isAppbarMenu = cliFlags.navigation === 'top';
588
+ if (!menuJsonPath) throw new Error("--input-file cannot be empty. please provide the path of compiled menu.json");
589
+ if (!fs.existsSync(menuJsonPath)) throw new Error("The menu.json file doesn't exist: ".concat(menuJsonPath));
590
+ const menuJson = fs.readFileSync(menuJsonPath, 'utf-8');
591
+ return validateMenu(JSON.parse(menuJson), isAppbarMenu ? appbarMenuSchema : navbarMenuSchema);
592
+ }
593
+
594
+ var pkgJson = {
595
+ name: "@commercetools-frontend/application-cli",
596
+ version: "1.5.0",
597
+ description: "Internal CLI to manage Merchant Center application deployments across various environments.",
598
+ keywords: [
599
+ "commercetools",
600
+ "cli",
601
+ "custom-application"
602
+ ],
603
+ license: "MIT",
604
+ main: "dist/commercetools-frontend-application-cli.cjs.js",
605
+ module: "dist/commercetools-frontend-application-cli.esm.js",
606
+ bin: "bin/cli.js",
607
+ files: [
608
+ "cli",
609
+ "dist",
610
+ "package.json",
611
+ "LICENSE",
612
+ "README.md"
613
+ ],
614
+ scripts: {
615
+ typecheck: "tsc --noEmit"
616
+ },
617
+ dependencies: {
618
+ "@babel/core": "^7.21.0",
619
+ "@babel/runtime": "^7.21.0",
620
+ "@babel/runtime-corejs3": "^7.21.0",
621
+ "@commercetools-frontend/application-config": "22.2.1",
622
+ "@commercetools-frontend/constants": "22.2.1",
623
+ "@manypkg/find-root": "2.1.0",
624
+ cac: "^6.7.14",
625
+ cosmiconfig: "8.1.3",
626
+ dotenv: "16.0.3",
627
+ execa: "5.1.1",
628
+ jsonschema: "^1.4.1",
629
+ listr2: "^6.4.2",
630
+ "node-fetch": "2.6.9"
631
+ },
632
+ devDependencies: {
633
+ "@tsconfig/node18": "1.0.3",
634
+ "@types/node": "18.15.12",
635
+ "@types/node-fetch": "2.6.3",
636
+ typescript: "^4.9.5"
637
+ },
638
+ engines: {
639
+ node: ">=14",
640
+ npm: ">=6"
641
+ },
642
+ publishConfig: {
643
+ access: "public"
644
+ },
645
+ preconstruct: {
646
+ entrypoints: [
647
+ "./cli.ts"
648
+ ]
649
+ }
650
+ };
651
+
652
+ const cli = cac('application-cli');
653
+ const cwd = process.cwd();
654
+ const run = async () => {
655
+ cli.option('--build-revision [git-sha]', '(optional) The git commit SHA which is being built.', {
656
+ default: process.env.CIRCLE_SHA1
657
+ }).option('--build-number [string]', '(optional) A number of the build on the Continuous Integration system.', {
658
+ default: process.env.CIRCLE_BUILD_NUM
659
+ }).option('--package-manager-name [string]', '(optional) Name of the binary of the used package manager (e.g. pnpm).', {
660
+ default: 'yarn'
661
+ });
662
+
663
+ // Default command
664
+ cli.command('').usage('\n\n Compile deployments and menus and create versions for MC applications').action(cli.outputHelp);
665
+ const usageCompileDeployment = 'Compile the deployments for an application for all environments.';
666
+ cli.command('compile-deployments', usageCompileDeployment).usage("compile-deployments \n\n ".concat(usageCompileDeployment)).option('--application-name <string>', '(required) The name of the application being compiled for example application-products.').option('--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 `mc-scripts compile-html` command.').option('--pr-number [string]', '(optional) A pull request number determining a scoped storage bucket for the deployment. Please use it carefully.').option('--mc-url [string]', '(optional) The MC URL of the deployment. This is usually inferred from the env file and overwrites the value. Please use it carefully.').option('--mc-api-url [string]', '(optional) The MC API URL of the deployment. This is usually inferred from the env file and overwrites the value. Please use it carefully.').option('--application-index-out-file [path]', '(optional) The name of the application index file.', {
667
+ default: 'application.html'
668
+ }).option('--application-index-upload-script-out-file [path]', '(optional) The name of the the application index upload script file.', {
669
+ default: 'upload-index.sh'
670
+ }).option('--application-assets-upload-script-out-file [path]', '(optional) The name of the the assets upload script file.', {
671
+ default: 'upload-assets.sh'
672
+ }).option('--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.').option('--skip-menu', '(optional) If provided, it will skip uploading the `menu.json`.', {
673
+ default: false
674
+ }).action(async options => {
675
+ await command$3(options, cwd);
676
+ });
677
+ const usageCompileMenu = 'Compile the menu links of an application into a `menu.json`. This is only required for internal applications';
678
+ cli.command('compile-menu', usageCompileMenu).usage("compile-menu \n\n ".concat(usageCompileMenu)).option('--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.').action(async options => {
679
+ await command$2(options, cwd);
680
+ });
681
+ const usageValidateMenu = 'Validate compiled `menu.json` file';
682
+ cli.command('validate-menu', usageValidateMenu).usage("validate-menu \n\n ".concat(usageValidateMenu)).option('--input-file <path>', '(required) The path to the `menu.json` file to be validated.').option('--navigation [string]', '(optional) Location of the menu navigation. Possible values are `top`.').action(async options => {
683
+ await command(options);
684
+ });
685
+ const usageCreateVersion = 'Output a JSON string about the information in the `version.json` for a deployment, including the updated list of rollbacks.';
686
+ cli.command('create-version', usageCreateVersion).usage("create-version \n\n ".concat(usageCreateVersion)).option('--version-url <url>', "(required) The path of an application's current `version.json` within the storage bucket.").option('--rollbacks [int]', '(optional) The number of max rollbacks to keep', {
687
+ default: 15
688
+ }).option('--out-file [path]', '(optional) The path to the file where to write the JSON. If not specified, the JSON is printed to stdout.').action(async options => {
689
+ await command$1(options);
690
+ });
691
+ cli.help();
692
+ cli.version(pkgJson.version);
693
+ cli.parse(process.argv, {
694
+ run: false
695
+ });
696
+ await cli.runMatchedCommand();
697
+ };
698
+
699
+ export { run };