@commercetools-frontend/application-cli 1.4.0 → 1.6.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.
- package/README.md +1 -1
- package/bin/cli.js +9 -0
- package/cli/dist/commercetools-frontend-application-cli-cli.cjs.d.ts +2 -0
- package/cli/dist/commercetools-frontend-application-cli-cli.cjs.d.ts.map +1 -0
- package/cli/dist/commercetools-frontend-application-cli-cli.cjs.dev.js +725 -0
- package/cli/dist/commercetools-frontend-application-cli-cli.cjs.js +7 -0
- package/cli/dist/commercetools-frontend-application-cli-cli.cjs.prod.js +725 -0
- package/cli/dist/commercetools-frontend-application-cli-cli.esm.js +699 -0
- package/cli/package.json +4 -0
- package/dist/declarations/src/cli.d.ts +2 -0
- package/dist/declarations/src/commands/compile-deployments.d.ts +3 -0
- package/dist/declarations/src/commands/compile-menu.d.ts +3 -0
- package/dist/declarations/src/commands/create-version.d.ts +3 -0
- package/dist/declarations/src/commands/validate-menu.d.ts +5 -0
- package/dist/declarations/src/schema.d.ts +228 -0
- package/dist/declarations/src/types.d.ts +92 -0
- package/dist/declarations/src/utils/create-application-assets-upload-script.d.ts +3 -0
- package/dist/declarations/src/utils/create-application-index-upload-script.d.ts +3 -0
- package/dist/declarations/src/utils/get-application-directory.d.ts +2 -0
- package/dist/declarations/src/utils/is-ci.d.ts +2 -0
- package/dist/declarations/src/utils/load-dotenv-files.d.ts +5 -0
- package/dist/declarations/src/utils/resolve-in-application.d.ts +2 -0
- package/package.json +31 -13
- package/src/bin/cli.js +0 -100
- package/src/commands/compile-deployments.js +0 -283
- package/src/commands/compile-menu.js +0 -126
- package/src/commands/create-version.js +0 -43
- package/src/commands/validate-menu.js +0 -33
- package/src/commands/validate-menu.spec.js +0 -43
- package/src/schema.js +0 -112
- package/src/utils/create-application-assets-upload-script.js +0 -62
- package/src/utils/create-application-index-upload-script.js +0 -41
- package/src/utils/get-application-directory.js +0 -7
- package/src/utils/is-ci.js +0 -5
- package/src/utils/load-dotenv-files.js +0 -48
- package/src/utils/resolve-in-application.js +0 -8
|
@@ -1,283 +0,0 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import Listr from 'listr';
|
|
4
|
-
import { execa } from 'execa';
|
|
5
|
-
import { cosmiconfig } from 'cosmiconfig';
|
|
6
|
-
import { findRootSync } from '@manypkg/find-root';
|
|
7
|
-
import ListrVerboseRenderer from 'listr-verbose-renderer';
|
|
8
|
-
import resolveInApplication from '../utils/resolve-in-application.js';
|
|
9
|
-
import getApplicationDirectory from '../utils/get-application-directory.js';
|
|
10
|
-
import isCI from '../utils/is-ci.js';
|
|
11
|
-
import createApplicationIndexUploadScript from '../utils/create-application-index-upload-script.js';
|
|
12
|
-
import createApplicationAssetsUploadScript from '../utils/create-application-assets-upload-script.js';
|
|
13
|
-
import loadDotenvFiles from '../utils/load-dotenv-files.js';
|
|
14
|
-
|
|
15
|
-
const buckedConfigExplorer = cosmiconfig('google-storage-buckets');
|
|
16
|
-
|
|
17
|
-
function writeUploadScriptFile({ fileName, fileContent, filePath }) {
|
|
18
|
-
fs.writeFileSync(path.join(filePath, fileName), fileContent, {
|
|
19
|
-
// Make the script executable
|
|
20
|
-
mode: 0o755,
|
|
21
|
-
encoding: 'utf8',
|
|
22
|
-
});
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function getBucketNamespace(prNumber) {
|
|
26
|
-
if (!prNumber) return;
|
|
27
|
-
if (prNumber === 'merchant-center-preview') return prNumber;
|
|
28
|
-
return `mc-${prNumber}`;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Construct the storage bucket URL for the specific application and cloud environment.
|
|
33
|
-
*
|
|
34
|
-
* 1. Static assets are uploaded to `:bucketRegion/:prNumber?/:applicationName`
|
|
35
|
-
* 2. The application index is uploaded to `:bucketRegion/:prNumber?/:applicationName/:cloudEnvironment`
|
|
36
|
-
*
|
|
37
|
-
* This allows all cloud environments sharing the same static assets while each application's index
|
|
38
|
-
* is uploaded with different headers (e.g. CSP rules).
|
|
39
|
-
*/
|
|
40
|
-
function getApplicationAssetsBucketUrl({
|
|
41
|
-
bucketRegion,
|
|
42
|
-
prNumber,
|
|
43
|
-
applicationName,
|
|
44
|
-
}) {
|
|
45
|
-
const applicationAssetsBucketUrl = [
|
|
46
|
-
`gs://${bucketRegion}`,
|
|
47
|
-
getBucketNamespace(prNumber),
|
|
48
|
-
applicationName,
|
|
49
|
-
].filter(Boolean);
|
|
50
|
-
|
|
51
|
-
return applicationAssetsBucketUrl.join('/');
|
|
52
|
-
}
|
|
53
|
-
function getApplicationIndexBucketUrl({
|
|
54
|
-
bucketRegion,
|
|
55
|
-
prNumber,
|
|
56
|
-
applicationName,
|
|
57
|
-
cloudEnvironment,
|
|
58
|
-
}) {
|
|
59
|
-
const applicationAssetsBucketUrl = getApplicationAssetsBucketUrl({
|
|
60
|
-
bucketRegion,
|
|
61
|
-
prNumber,
|
|
62
|
-
applicationName,
|
|
63
|
-
});
|
|
64
|
-
const applicationIndexBucketUrl = `${applicationAssetsBucketUrl}/${cloudEnvironment}`;
|
|
65
|
-
|
|
66
|
-
return applicationIndexBucketUrl;
|
|
67
|
-
}
|
|
68
|
-
function getCdnUrl({ bucketRegion, prNumber, applicationName }) {
|
|
69
|
-
return [
|
|
70
|
-
`https://storage.googleapis.com/${bucketRegion}`,
|
|
71
|
-
getBucketNamespace(prNumber),
|
|
72
|
-
applicationName,
|
|
73
|
-
]
|
|
74
|
-
.filter(Boolean)
|
|
75
|
-
.join('/');
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
async function compileApplicationAssets({ cliFlags, bucketRegion, paths }) {
|
|
79
|
-
const applicationAssetsUploadScriptContent =
|
|
80
|
-
createApplicationAssetsUploadScript({
|
|
81
|
-
bucketUrl: getApplicationAssetsBucketUrl({
|
|
82
|
-
bucketRegion,
|
|
83
|
-
prNumber: cliFlags['pr-number'],
|
|
84
|
-
applicationName: cliFlags['application-name'],
|
|
85
|
-
}),
|
|
86
|
-
assetsPath: paths.assetsPath,
|
|
87
|
-
skipMenu: cliFlags['skip-menu'],
|
|
88
|
-
});
|
|
89
|
-
const parsedApplicationAssetsUploadScriptFile = path.parse(
|
|
90
|
-
cliFlags['application-assets-upload-script-out-file']
|
|
91
|
-
);
|
|
92
|
-
const applicationAssetsUploadScriptFileName = `${parsedApplicationAssetsUploadScriptFile.name}-${bucketRegion}${parsedApplicationAssetsUploadScriptFile.ext}`;
|
|
93
|
-
|
|
94
|
-
writeUploadScriptFile({
|
|
95
|
-
fileName: applicationAssetsUploadScriptFileName,
|
|
96
|
-
fileContent: applicationAssetsUploadScriptContent,
|
|
97
|
-
filePath: paths.deploymentsPath,
|
|
98
|
-
});
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
async function compileEnvironmentApplicationIndexes({
|
|
102
|
-
cliFlags,
|
|
103
|
-
paths,
|
|
104
|
-
bucketRegion,
|
|
105
|
-
cloudEnvironment,
|
|
106
|
-
}) {
|
|
107
|
-
const cloudEnvironmentDeploymentPath = path.join(
|
|
108
|
-
paths.deploymentsPath,
|
|
109
|
-
cloudEnvironment
|
|
110
|
-
);
|
|
111
|
-
// Ensure the folder exists
|
|
112
|
-
const createDeploymentsFolderResult = await execa(
|
|
113
|
-
'mkdir',
|
|
114
|
-
['-p', cloudEnvironmentDeploymentPath],
|
|
115
|
-
{ encoding: 'utf8' }
|
|
116
|
-
);
|
|
117
|
-
if (createDeploymentsFolderResult.failed) {
|
|
118
|
-
throw new Error(createDeploymentsFolderResult.stderr);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// Construct the proper CDN URL for the specific application
|
|
122
|
-
const cdnUrl = getCdnUrl({
|
|
123
|
-
bucketRegion,
|
|
124
|
-
prNumber: cliFlags['pr-number'],
|
|
125
|
-
applicationName: cliFlags['application-name'],
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
const environmentVariablesForCompilation = {
|
|
129
|
-
...loadDotenvFiles({
|
|
130
|
-
dotenvPath: paths.dotenvPath,
|
|
131
|
-
cloudEnvironment,
|
|
132
|
-
}),
|
|
133
|
-
// The trailing slash is important to indicate to the CSP directive that all the resources
|
|
134
|
-
// under that path should be allowed.
|
|
135
|
-
MC_CDN_URL: `${cdnUrl}/`,
|
|
136
|
-
...(cliFlags['mc-url']
|
|
137
|
-
? {
|
|
138
|
-
MC_URL: cliFlags['mc-url'],
|
|
139
|
-
}
|
|
140
|
-
: {}),
|
|
141
|
-
...(cliFlags['mc-api-url']
|
|
142
|
-
? {
|
|
143
|
-
MC_API_URL: cliFlags['mc-api-url'],
|
|
144
|
-
}
|
|
145
|
-
: {}),
|
|
146
|
-
// Will be used by the Application Kit for Sentry and exposed on `window.app.revision`.
|
|
147
|
-
REVISION: cliFlags['build-revision'],
|
|
148
|
-
};
|
|
149
|
-
|
|
150
|
-
/// Sentry and GTM is disabled on branch deployments
|
|
151
|
-
if (cliFlags['pr-number']) {
|
|
152
|
-
process.env.TRACKING_SENTRY = null;
|
|
153
|
-
process.env.TRACKING_GTM = null;
|
|
154
|
-
environmentVariablesForCompilation.TRACKING_SENTRY = null;
|
|
155
|
-
environmentVariablesForCompilation.TRACKING_GTM = null;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// Compile the application using the loaded environment values
|
|
159
|
-
const compileResult = await execa('mc-scripts', ['compile-html'], {
|
|
160
|
-
encoding: 'utf8',
|
|
161
|
-
preferLocal: true,
|
|
162
|
-
extendEnv: true,
|
|
163
|
-
env: environmentVariablesForCompilation,
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
if (compileResult.failed) {
|
|
167
|
-
throw new Error(compileResult.stderr);
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
const applicationIndexUploadScriptContent =
|
|
171
|
-
createApplicationIndexUploadScript({
|
|
172
|
-
packageManagerName: cliFlags['package-manager-name'],
|
|
173
|
-
bucketUrl: getApplicationIndexBucketUrl({
|
|
174
|
-
bucketRegion,
|
|
175
|
-
prNumber: cliFlags['pr-number'],
|
|
176
|
-
applicationName: cliFlags['application-name'],
|
|
177
|
-
cloudEnvironment,
|
|
178
|
-
}),
|
|
179
|
-
cdnUrl,
|
|
180
|
-
cloudEnvironment,
|
|
181
|
-
buildRevision: cliFlags['build-revision'],
|
|
182
|
-
buildNumber: cliFlags['build-number'],
|
|
183
|
-
applicationIndexOutFile: cliFlags['application-index-out-file'],
|
|
184
|
-
versionJsonOutFile: cliFlags['version-json-out-file'],
|
|
185
|
-
});
|
|
186
|
-
// Generate bash scripts to run the `gsutil` upload command.
|
|
187
|
-
writeUploadScriptFile({
|
|
188
|
-
fileName: cliFlags['application-index-upload-script-out-file'],
|
|
189
|
-
fileContent: applicationIndexUploadScriptContent,
|
|
190
|
-
filePath: cloudEnvironmentDeploymentPath,
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
// Move the compiled `index.html` to the deployments folder of the related cloud environment.
|
|
194
|
-
const moveResult = await execa('mv', [
|
|
195
|
-
path.join(paths.publicAssetsPath, 'index.html'),
|
|
196
|
-
path.join(
|
|
197
|
-
cloudEnvironmentDeploymentPath,
|
|
198
|
-
cliFlags['application-index-out-file']
|
|
199
|
-
),
|
|
200
|
-
]);
|
|
201
|
-
|
|
202
|
-
if (moveResult.failed) {
|
|
203
|
-
throw new Error(moveResult.stderr);
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
async function command(cliFlags, cwd) {
|
|
208
|
-
let cloudEnvironmentsGroupedByBucketRegions;
|
|
209
|
-
try {
|
|
210
|
-
// This is the list of the supported cloud environments and their related bucket location.
|
|
211
|
-
cloudEnvironmentsGroupedByBucketRegions =
|
|
212
|
-
await buckedConfigExplorer.search();
|
|
213
|
-
} catch (e) {
|
|
214
|
-
throw new Error(
|
|
215
|
-
'Failed loading a Google Bucket configuration. Create a cosmiconfig for `google-storage-buckets` for example `google-storage-buckets.config.cjs`.'
|
|
216
|
-
);
|
|
217
|
-
}
|
|
218
|
-
const applicationDirectory = getApplicationDirectory(cwd);
|
|
219
|
-
let assetsPath;
|
|
220
|
-
if (cliFlags['ci-assets-root-path'] && isCI()) {
|
|
221
|
-
assetsPath = applicationDirectory.replace(
|
|
222
|
-
'/home/circleci/',
|
|
223
|
-
cliFlags['ci-assets-root-path']
|
|
224
|
-
);
|
|
225
|
-
} else {
|
|
226
|
-
assetsPath = applicationDirectory;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
const monorepoRoot = findRootSync(cwd);
|
|
230
|
-
const paths = {
|
|
231
|
-
publicAssetsPath: resolveInApplication('public', cwd),
|
|
232
|
-
deploymentsPath: resolveInApplication('deployments', cwd),
|
|
233
|
-
dotenvPath:
|
|
234
|
-
cliFlags['dotenv-folder'] &&
|
|
235
|
-
path.join(monorepoRoot.rootDir, cliFlags['dotenv-folder']),
|
|
236
|
-
assetsPath,
|
|
237
|
-
};
|
|
238
|
-
|
|
239
|
-
async function execute() {
|
|
240
|
-
const taskList = new Listr(
|
|
241
|
-
Object.entries(cloudEnvironmentsGroupedByBucketRegions.config).map(
|
|
242
|
-
([bucketRegion, cloudEnvironments]) => ({
|
|
243
|
-
title: `Compiling application for bucket ${bucketRegion}`,
|
|
244
|
-
task: () =>
|
|
245
|
-
new Listr(
|
|
246
|
-
cloudEnvironments
|
|
247
|
-
.map((cloudEnvironment) => ({
|
|
248
|
-
title: `Compiling ${cloudEnvironment} application index`,
|
|
249
|
-
task: async () => {
|
|
250
|
-
await compileEnvironmentApplicationIndexes({
|
|
251
|
-
cliFlags,
|
|
252
|
-
paths,
|
|
253
|
-
bucketRegion,
|
|
254
|
-
cloudEnvironment,
|
|
255
|
-
});
|
|
256
|
-
return 'Done';
|
|
257
|
-
},
|
|
258
|
-
}))
|
|
259
|
-
.concat({
|
|
260
|
-
title: `Compiling application assets`,
|
|
261
|
-
task: () => {
|
|
262
|
-
compileApplicationAssets({
|
|
263
|
-
cliFlags,
|
|
264
|
-
bucketRegion,
|
|
265
|
-
paths,
|
|
266
|
-
});
|
|
267
|
-
},
|
|
268
|
-
})
|
|
269
|
-
),
|
|
270
|
-
})
|
|
271
|
-
),
|
|
272
|
-
{
|
|
273
|
-
renderer: isCI() ? ListrVerboseRenderer : 'default',
|
|
274
|
-
nonTTYRenderer: ListrVerboseRenderer,
|
|
275
|
-
}
|
|
276
|
-
);
|
|
277
|
-
await taskList.run();
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
await execute();
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
export default command;
|
|
@@ -1,126 +0,0 @@
|
|
|
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
|
-
const monorepoRoot = findRootSync(cwd);
|
|
88
|
-
const dotenvPath =
|
|
89
|
-
cliFlags['dotenv-folder'] &&
|
|
90
|
-
path.join(monorepoRoot.rootDir, 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,43 +0,0 @@
|
|
|
1
|
-
import fetch from 'node-fetch';
|
|
2
|
-
import fs from 'fs';
|
|
3
|
-
|
|
4
|
-
async function command(cliFlags) {
|
|
5
|
-
const numberOfRollbacks = cliFlags.rollbacks - 1;
|
|
6
|
-
|
|
7
|
-
let nextRollbacks;
|
|
8
|
-
try {
|
|
9
|
-
// The last build's JSON becomes the first rollback
|
|
10
|
-
// while all previous rollbacks remain but are sliced.
|
|
11
|
-
const lastVersionResponse = await fetch(cliFlags['version-url']);
|
|
12
|
-
const lastVersionJson = await lastVersionResponse.json();
|
|
13
|
-
|
|
14
|
-
const previousBuild = lastVersionJson && {
|
|
15
|
-
buildNumber: lastVersionJson.buildNumber,
|
|
16
|
-
revision: lastVersionJson.revision,
|
|
17
|
-
deployedAt: lastVersionJson.deployedAt,
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
nextRollbacks = [previousBuild, ...lastVersionJson.rollbacks]
|
|
21
|
-
.filter(Boolean)
|
|
22
|
-
.slice(0, numberOfRollbacks);
|
|
23
|
-
} catch (error) {
|
|
24
|
-
nextRollbacks = [];
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const nextBuild = {
|
|
28
|
-
buildNumber: cliFlags['build-number'],
|
|
29
|
-
revision: cliFlags['build-revision'],
|
|
30
|
-
deployedAt: new Date().toISOString(),
|
|
31
|
-
rollbacks: nextRollbacks,
|
|
32
|
-
};
|
|
33
|
-
const formattedJson = JSON.stringify(nextBuild, null, 2);
|
|
34
|
-
// Logging to stdout which is from where it will be picked
|
|
35
|
-
// up by the caller (a bash script).
|
|
36
|
-
if (cliFlags['out-file']) {
|
|
37
|
-
fs.writeFileSync(cliFlags['out-file'], formattedJson, { encoding: 'utf8' });
|
|
38
|
-
} else {
|
|
39
|
-
console.log(formattedJson);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export default command;
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
|
-
import { Validator } from 'jsonschema';
|
|
3
|
-
import { navbarMenuSchema, appbarMenuSchema } from '../schema.js';
|
|
4
|
-
|
|
5
|
-
export function validateMenu(menuJson, schema = navbarMenuSchema) {
|
|
6
|
-
const validator = new Validator();
|
|
7
|
-
const result = validator.validate(menuJson, schema);
|
|
8
|
-
if (result.valid) {
|
|
9
|
-
return menuJson;
|
|
10
|
-
} else {
|
|
11
|
-
throw new Error('menu.json validation failed\n' + result.errors);
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
async function command(cliFlags, cwd) {
|
|
16
|
-
const menuJsonPath = cliFlags['input-file'];
|
|
17
|
-
const isAppbarMenu = cliFlags['navigation'] === 'top';
|
|
18
|
-
|
|
19
|
-
if (!menuJsonPath)
|
|
20
|
-
throw new Error(
|
|
21
|
-
`--input-file cannot be empty. please provide the path of compiled menu.json`
|
|
22
|
-
);
|
|
23
|
-
if (!fs.existsSync(menuJsonPath))
|
|
24
|
-
throw new Error(`The menu.json file doesn't exist: ${menuJsonPath}`);
|
|
25
|
-
const menuJson = fs.readFileSync(menuJsonPath, 'utf-8');
|
|
26
|
-
|
|
27
|
-
return validateMenu(
|
|
28
|
-
JSON.parse(menuJson),
|
|
29
|
-
isAppbarMenu ? appbarMenuSchema : navbarMenuSchema
|
|
30
|
-
);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export default command;
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import { validateMenu } from './validate-menu.js';
|
|
2
|
-
|
|
3
|
-
describe('validate menu', () => {
|
|
4
|
-
test.each([
|
|
5
|
-
123,
|
|
6
|
-
'{value:123}',
|
|
7
|
-
{},
|
|
8
|
-
{ uriPath: 'products', icon: 'ProductsIcon', submenu: [] },
|
|
9
|
-
{
|
|
10
|
-
uriPath: 123,
|
|
11
|
-
intlMessage: 123,
|
|
12
|
-
icon: 123,
|
|
13
|
-
permissions: '',
|
|
14
|
-
submenu: {},
|
|
15
|
-
key: {},
|
|
16
|
-
labelAllLocales: {},
|
|
17
|
-
},
|
|
18
|
-
])('should return null for invalid menu configs', (json) => {
|
|
19
|
-
try {
|
|
20
|
-
expect(validateMenu(json)).toEqual([]);
|
|
21
|
-
} catch (error) {
|
|
22
|
-
// catch block is required otherwise the test fails
|
|
23
|
-
// due to `validateMenu` throwing error for invalid menu
|
|
24
|
-
}
|
|
25
|
-
});
|
|
26
|
-
it('should return menu json for valid menu configs', async () => {
|
|
27
|
-
const menuJson = {
|
|
28
|
-
uriPath: 'products',
|
|
29
|
-
intlMessage: 'Products',
|
|
30
|
-
icon: 'ProductsIcon',
|
|
31
|
-
permissions: ['ViewProducts'],
|
|
32
|
-
submenu: [],
|
|
33
|
-
key: 'product',
|
|
34
|
-
labelAllLocales: [
|
|
35
|
-
{
|
|
36
|
-
locale: 'en',
|
|
37
|
-
value: 'product',
|
|
38
|
-
},
|
|
39
|
-
],
|
|
40
|
-
};
|
|
41
|
-
expect(validateMenu(menuJson)).toEqual(menuJson);
|
|
42
|
-
});
|
|
43
|
-
});
|
package/src/schema.js
DELETED
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
const baseMenuProperties = {
|
|
2
|
-
key: {
|
|
3
|
-
type: 'string',
|
|
4
|
-
},
|
|
5
|
-
uriPath: {
|
|
6
|
-
type: 'string',
|
|
7
|
-
},
|
|
8
|
-
icon: {
|
|
9
|
-
type: 'string',
|
|
10
|
-
},
|
|
11
|
-
featureToggle: {
|
|
12
|
-
type: ['string', 'null'],
|
|
13
|
-
},
|
|
14
|
-
labelAllLocales: {
|
|
15
|
-
type: 'array',
|
|
16
|
-
items: [
|
|
17
|
-
{
|
|
18
|
-
type: 'object',
|
|
19
|
-
properties: {
|
|
20
|
-
locale: { type: 'string' },
|
|
21
|
-
value: { type: 'string' },
|
|
22
|
-
},
|
|
23
|
-
required: ['locale', 'value'],
|
|
24
|
-
},
|
|
25
|
-
],
|
|
26
|
-
},
|
|
27
|
-
menuVisibility: {
|
|
28
|
-
type: ['string', 'null'],
|
|
29
|
-
},
|
|
30
|
-
permissions: {
|
|
31
|
-
type: 'array',
|
|
32
|
-
items: {
|
|
33
|
-
type: 'string',
|
|
34
|
-
},
|
|
35
|
-
},
|
|
36
|
-
dataFences: {
|
|
37
|
-
type: ['array', 'null'],
|
|
38
|
-
items: [
|
|
39
|
-
{
|
|
40
|
-
type: ['object'],
|
|
41
|
-
properties: {
|
|
42
|
-
group: {
|
|
43
|
-
type: 'string',
|
|
44
|
-
},
|
|
45
|
-
name: {
|
|
46
|
-
type: 'string',
|
|
47
|
-
},
|
|
48
|
-
type: {
|
|
49
|
-
type: 'string',
|
|
50
|
-
},
|
|
51
|
-
},
|
|
52
|
-
},
|
|
53
|
-
],
|
|
54
|
-
},
|
|
55
|
-
actionRights: {
|
|
56
|
-
type: ['array', 'null'],
|
|
57
|
-
items: [
|
|
58
|
-
{
|
|
59
|
-
type: ['object'],
|
|
60
|
-
properties: {
|
|
61
|
-
group: {
|
|
62
|
-
type: 'string',
|
|
63
|
-
},
|
|
64
|
-
name: {
|
|
65
|
-
type: 'string',
|
|
66
|
-
},
|
|
67
|
-
},
|
|
68
|
-
},
|
|
69
|
-
],
|
|
70
|
-
},
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
export const navbarMenuSchema = {
|
|
74
|
-
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
75
|
-
// "$id":""
|
|
76
|
-
title: 'NavbarMenu',
|
|
77
|
-
type: 'object',
|
|
78
|
-
properties: {
|
|
79
|
-
...baseMenuProperties,
|
|
80
|
-
submenu: {
|
|
81
|
-
type: 'array',
|
|
82
|
-
items: [
|
|
83
|
-
{
|
|
84
|
-
type: 'object',
|
|
85
|
-
properties: baseMenuProperties,
|
|
86
|
-
},
|
|
87
|
-
],
|
|
88
|
-
},
|
|
89
|
-
},
|
|
90
|
-
required: [
|
|
91
|
-
'icon',
|
|
92
|
-
'key',
|
|
93
|
-
'labelAllLocales',
|
|
94
|
-
'permissions',
|
|
95
|
-
'submenu',
|
|
96
|
-
'uriPath',
|
|
97
|
-
],
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
export const appbarMenuSchema = {
|
|
101
|
-
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
102
|
-
// "$id":""
|
|
103
|
-
title: 'AppbarMenu',
|
|
104
|
-
type: 'array',
|
|
105
|
-
items: [
|
|
106
|
-
{
|
|
107
|
-
type: 'object',
|
|
108
|
-
properties: baseMenuProperties,
|
|
109
|
-
required: ['key', 'labelAllLocales', 'permissions', 'uriPath'],
|
|
110
|
-
},
|
|
111
|
-
],
|
|
112
|
-
};
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
function createApplicationAssetsUploadScript({
|
|
2
|
-
bucketUrl,
|
|
3
|
-
assetsPath,
|
|
4
|
-
skipMenu,
|
|
5
|
-
}) {
|
|
6
|
-
const uploadScriptContent = `#!/usr/bin/env bash
|
|
7
|
-
|
|
8
|
-
set -e
|
|
9
|
-
|
|
10
|
-
# NOTES:
|
|
11
|
-
# https://cloud.google.com/storage/docs/gsutil/commands/cp#options
|
|
12
|
-
# 1. The '-z' option triggers compressing the assets before
|
|
13
|
-
# uploading them and sets the 'Content-Encoding' to 'gzip'.
|
|
14
|
-
# 2. The 'Accept-encoding: gzip' is set automatically by the 'gsutils'.
|
|
15
|
-
# 3. The 'max-age' is set to 1 year which is considered the maximum
|
|
16
|
-
# "valid" lifetime of an asset to be cached.
|
|
17
|
-
# 5. The '-n' will skip uploading existing files and prevents them to
|
|
18
|
-
# be overwritten
|
|
19
|
-
echo "Uploading static assets to bucket ${bucketUrl}"
|
|
20
|
-
|
|
21
|
-
gsutil -m \\
|
|
22
|
-
-h "Cache-Control: public, max-age=31536000, no-transform" \\
|
|
23
|
-
cp -z js,css \\
|
|
24
|
-
-n \\
|
|
25
|
-
${assetsPath}/public/{*.css,*.js,*.js.map,*.png,*.html,robots.txt} \\
|
|
26
|
-
"${bucketUrl}"
|
|
27
|
-
|
|
28
|
-
if ${skipMenu}; then
|
|
29
|
-
echo "Skipping menu.json upload"
|
|
30
|
-
else
|
|
31
|
-
echo "Uploading menu.json to bucket ${bucketUrl}"
|
|
32
|
-
# NOTE: somehow the 'cache-control:private' doesn't work.
|
|
33
|
-
# I mean, the file is uploaded with the correct metadata but when I fetch
|
|
34
|
-
# the file the response contains the header
|
|
35
|
-
# 'cache-control: public, max-age=31536000, no-transform', even though the
|
|
36
|
-
# documentation clearly states that by marking the header as 'private' will
|
|
37
|
-
# disable the cache (for publicly readable objects).
|
|
38
|
-
# https://cloud.google.com/storage/docs/gsutil/addlhelp/WorkingWithObjectMetadata#cache-control
|
|
39
|
-
# However, I found out that, by requesting the file with any RANDOM
|
|
40
|
-
# query parameter, will instruct the storage to return a 'fresh' object
|
|
41
|
-
# (without any cache control).
|
|
42
|
-
# Unofficial source: https://stackoverflow.com/a/49052895
|
|
43
|
-
# This seems to be the 'easiest' option to 'disable' the cache for public
|
|
44
|
-
# objects. Other alternative approaces are:
|
|
45
|
-
# * make the object private with some simple ACL (private objects are not cached)
|
|
46
|
-
# * suffix the file name with e.g. the git SHA, so we have different files
|
|
47
|
-
# for each upload ('index.html.template-\${CIRCLE_SHA1}'). The server knows
|
|
48
|
-
# the git SHA on runtime and can get the correct file when it starts.
|
|
49
|
-
# * find out why the 'private' cache control does not work
|
|
50
|
-
gsutil \\
|
|
51
|
-
-h "Content-Type: application/json" \\
|
|
52
|
-
-h "Cache-Control: private, max-age=0, no-transform" \\
|
|
53
|
-
cp -z json \\
|
|
54
|
-
${assetsPath}/menu.json \\
|
|
55
|
-
${bucketUrl}
|
|
56
|
-
fi
|
|
57
|
-
`;
|
|
58
|
-
|
|
59
|
-
return uploadScriptContent;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export default createApplicationAssetsUploadScript;
|