@madgex/fert 1.3.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 (120) hide show
  1. package/.eslintignore +8 -0
  2. package/.eslintrc.js +15 -0
  3. package/.github/workflows/commits.yml +14 -0
  4. package/.github/workflows/release.yml +29 -0
  5. package/.husky/commit-msg +4 -0
  6. package/.husky/pre-commit +4 -0
  7. package/.prettierignore +2 -0
  8. package/.prettierrc.js +8 -0
  9. package/README.md +232 -0
  10. package/bin/cli.js +68 -0
  11. package/bin/commands/build-tasks/build-external-assets.js +16 -0
  12. package/bin/commands/build-tasks/build-tokens.js +119 -0
  13. package/bin/commands/build-tasks/bundle-ds-css.js +74 -0
  14. package/bin/commands/build-tasks/bundle-entry.js +43 -0
  15. package/bin/commands/build.js +42 -0
  16. package/bin/commands/dev-server.js +50 -0
  17. package/bin/commands/init.js +207 -0
  18. package/bin/commands/publish-tasks/asset-store-uploader.js +94 -0
  19. package/bin/commands/publish-tasks/get-aws-parameter.js +41 -0
  20. package/bin/commands/publish.js +51 -0
  21. package/bin/utils/cpid-lookup.js +47 -0
  22. package/bin/utils/index.js +121 -0
  23. package/bin/utils/resolve-external-assets.js +77 -0
  24. package/commitlint.config.cjs +1 -0
  25. package/constants.js +36 -0
  26. package/delivery/git.cer +22 -0
  27. package/delivery/jenkinsfile +93 -0
  28. package/package.json +84 -0
  29. package/repo-templates/globals/fert.config.js +4 -0
  30. package/repo-templates/globals/jenkinsfile +4 -0
  31. package/repo-templates/template-basic/_gitignore +5 -0
  32. package/repo-templates/template-basic/brand.json +21 -0
  33. package/repo-templates/template-basic/package.json +13 -0
  34. package/repo-templates/template-basic/public/favicon.ico +0 -0
  35. package/repo-templates/template-basic/public/fonts/mdgx-icons.eot +0 -0
  36. package/repo-templates/template-basic/public/fonts/mdgx-icons.svg +71 -0
  37. package/repo-templates/template-basic/public/fonts/mdgx-icons.ttf +0 -0
  38. package/repo-templates/template-basic/public/fonts/mdgx-icons.woff +0 -0
  39. package/repo-templates/template-basic/public/fonts/my-font.woff +1 -0
  40. package/repo-templates/template-basic/public/images/logo.png +0 -0
  41. package/repo-templates/template-basic/src/css/styles.scss +12 -0
  42. package/repo-templates/template-basic/src/index.js +3 -0
  43. package/repo-templates/template-basic/templates/footer.njk +14 -0
  44. package/repo-templates/template-basic/templates/header.njk +14 -0
  45. package/repo-templates/template-bigworkbag/_gitignore +5 -0
  46. package/repo-templates/template-bigworkbag/brand.json +21 -0
  47. package/repo-templates/template-bigworkbag/package.json +13 -0
  48. package/repo-templates/template-bigworkbag/public/favicon.ico +0 -0
  49. package/repo-templates/template-bigworkbag/public/fonts/mdgx-icons.eot +0 -0
  50. package/repo-templates/template-bigworkbag/public/fonts/mdgx-icons.svg +71 -0
  51. package/repo-templates/template-bigworkbag/public/fonts/mdgx-icons.ttf +0 -0
  52. package/repo-templates/template-bigworkbag/public/fonts/mdgx-icons.woff +0 -0
  53. package/repo-templates/template-bigworkbag/public/fonts/my-font.woff +1 -0
  54. package/repo-templates/template-bigworkbag/public/images/logo.png +0 -0
  55. package/repo-templates/template-bigworkbag/public/images/user-menu-pointer.svg +4 -0
  56. package/repo-templates/template-bigworkbag/src/css/breakpoints.scss +17 -0
  57. package/repo-templates/template-bigworkbag/src/css/clicky-menu.scss +52 -0
  58. package/repo-templates/template-bigworkbag/src/css/desktop-main-nav.scss +25 -0
  59. package/repo-templates/template-bigworkbag/src/css/desktop-user-nav.scss +127 -0
  60. package/repo-templates/template-bigworkbag/src/css/footer.scss +77 -0
  61. package/repo-templates/template-bigworkbag/src/css/header-top.scss +29 -0
  62. package/repo-templates/template-bigworkbag/src/css/leaderboard-ad.scss +22 -0
  63. package/repo-templates/template-bigworkbag/src/css/mobile-main-nav.scss +68 -0
  64. package/repo-templates/template-bigworkbag/src/css/mobile-user-nav.scss +49 -0
  65. package/repo-templates/template-bigworkbag/src/css/reset.scss +91 -0
  66. package/repo-templates/template-bigworkbag/src/css/styles.scss +19 -0
  67. package/repo-templates/template-bigworkbag/src/css/top-bar.scss +13 -0
  68. package/repo-templates/template-bigworkbag/src/css/typography.scss +20 -0
  69. package/repo-templates/template-bigworkbag/src/css/util.scss +28 -0
  70. package/repo-templates/template-bigworkbag/src/css/variables.scss +9 -0
  71. package/repo-templates/template-bigworkbag/src/index.js +3 -0
  72. package/repo-templates/template-bigworkbag/src/js/clicky-menus.js +178 -0
  73. package/repo-templates/template-bigworkbag/src/js/no-js.js +10 -0
  74. package/repo-templates/template-bigworkbag/templates/context/footer-nav.njk +27 -0
  75. package/repo-templates/template-bigworkbag/templates/context/main-nav.njk +32 -0
  76. package/repo-templates/template-bigworkbag/templates/context/user-nav.njk +17 -0
  77. package/repo-templates/template-bigworkbag/templates/footer.njk +68 -0
  78. package/repo-templates/template-bigworkbag/templates/header.njk +54 -0
  79. package/repo-templates/template-bigworkbag/templates/includes/desktop-main-nav-items.njk +18 -0
  80. package/repo-templates/template-bigworkbag/templates/includes/desktop-user-nav/loggedin-items.njk +28 -0
  81. package/repo-templates/template-bigworkbag/templates/includes/desktop-user-nav/loggedout-items.njk +9 -0
  82. package/repo-templates/template-bigworkbag/templates/includes/desktop-user-nav/submenu-items.njk +18 -0
  83. package/repo-templates/template-bigworkbag/templates/includes/desktop-user-nav/user-nav.njk +24 -0
  84. package/repo-templates/template-bigworkbag/templates/includes/dev-console-context.njk +7 -0
  85. package/repo-templates/template-bigworkbag/templates/includes/mobile-main-nav-items.njk +25 -0
  86. package/repo-templates/template-bigworkbag/templates/includes/mobile-user-nav/loggedin-items.njk +33 -0
  87. package/repo-templates/template-bigworkbag/templates/includes/mobile-user-nav/loggedout-items.njk +16 -0
  88. package/repo-templates/template-bigworkbag/templates/includes/mobile-user-nav/user-nav.njk +24 -0
  89. package/repo-templates/template-bigworkbag/templates/svgs/arrow-down.svg +3 -0
  90. package/repo-templates/template-bigworkbag/templates/svgs/arrow-left.svg +3 -0
  91. package/repo-templates/template-bigworkbag/templates/svgs/arrow-right.svg +3 -0
  92. package/repo-templates/template-bigworkbag/templates/svgs/arrow-up.svg +3 -0
  93. package/repo-templates/template-bigworkbag/templates/svgs/cart.svg +3 -0
  94. package/repo-templates/template-bigworkbag/templates/svgs/facebook.svg +3 -0
  95. package/repo-templates/template-bigworkbag/templates/svgs/linkedin.svg +3 -0
  96. package/repo-templates/template-bigworkbag/templates/svgs/profile.svg +5 -0
  97. package/repo-templates/template-bigworkbag/templates/svgs/star-filled.svg +3 -0
  98. package/repo-templates/template-bigworkbag/templates/svgs/star-outline.svg +3 -0
  99. package/repo-templates/template-bigworkbag/templates/svgs/twitter.svg +3 -0
  100. package/repo-templates/template-bigworkbag/templates/svgs/youtube.svg +3 -0
  101. package/repo-templates/template-bigworkbag/templates/translations/da.njk +26 -0
  102. package/repo-templates/template-bigworkbag/templates/translations/de.njk +26 -0
  103. package/repo-templates/template-bigworkbag/templates/translations/en.njk +26 -0
  104. package/repo-templates/template-bigworkbag/templates/translations/es.njk +26 -0
  105. package/repo-templates/template-bigworkbag/templates/translations/fr.njk +26 -0
  106. package/repo-templates/template-bigworkbag/templates/translations/nb.njk +26 -0
  107. package/repo-templates/template-bigworkbag/templates/translations/nl.njk +26 -0
  108. package/repo-templates/template-bigworkbag/templates/translations/sv.njk +26 -0
  109. package/repo-templates/template-bigworkbag/templates/translations/zh-cn.njk +26 -0
  110. package/server/extensions/error-logging.js +25 -0
  111. package/server/index.js +98 -0
  112. package/server/plugins/hapi-vite.js +61 -0
  113. package/server/routes/public.js +29 -0
  114. package/server/routes/views.js +46 -0
  115. package/server/templates/furniture.njk +67 -0
  116. package/server/templates/page1.njk +37 -0
  117. package/server/view-manager.js +59 -0
  118. package/shared/Dockerfile.brandingRepoPublish +28 -0
  119. package/shared/README.md +4 -0
  120. package/vite.config.js +14 -0
@@ -0,0 +1,42 @@
1
+ const path = require('path');
2
+ const { rimraf } = require('rimraf');
3
+ const { resolveConfig, log } = require('../utils');
4
+ const bundleEntry = require('./build-tasks/bundle-entry');
5
+ const buildCssFromTokens = require('./build-tasks/build-tokens');
6
+ const buildExternalAssets = require('./build-tasks/build-external-assets');
7
+
8
+ module.exports = async (root, options = {}) => {
9
+ const fertConfig = await resolveConfig(root, options);
10
+
11
+ if (!options.only) {
12
+ await rimraf(path.resolve(fertConfig.workingDir, 'dist'));
13
+ await exports.buildTokens(fertConfig, options);
14
+ await exports.bundleEntry(fertConfig, options);
15
+
16
+ return;
17
+ }
18
+
19
+ switch (options.only) {
20
+ case 'tokens':
21
+ await exports.buildTokens(fertConfig, options);
22
+ break;
23
+ case 'assets':
24
+ await exports.bundleEntry(fertConfig, options);
25
+ break;
26
+ default:
27
+ log.error('"--only param" not recognixed. Not building.');
28
+ }
29
+ };
30
+
31
+ exports = module.exports; // allow default export & { named } exports
32
+
33
+ // eslint-disable-next-line no-unused-vars
34
+ exports.buildTokens = async (fertConfig, options = {}) => {
35
+ const { workingDir } = fertConfig;
36
+ const tokenDist = path.join(workingDir, `/public/tokens/`);
37
+
38
+ buildCssFromTokens(workingDir, tokenDist);
39
+ };
40
+
41
+ exports.bundleEntry = bundleEntry;
42
+ exports.buildExternalAssets = buildExternalAssets;
@@ -0,0 +1,50 @@
1
+ const path = require('path');
2
+ const chalk = require('chalk');
3
+ const open = require('open');
4
+ const chokidar = require('chokidar');
5
+ const { resolveConfig, log } = require('../utils/index.js');
6
+ const { devServer } = require('../../server');
7
+ const { buildTokens, buildExternalAssets } = require('./build.js');
8
+ const {
9
+ BRAND_JSON_FILENAME,
10
+ FERT_CONFIG_FILENAME,
11
+ } = require('../../constants.js');
12
+
13
+ module.exports = async (root, options = {}) => {
14
+ let fertConfig = await resolveConfig(root, options);
15
+
16
+ console.log(
17
+ `Starting branding server for ${chalk.green(
18
+ fertConfig.client.brandName
19
+ )} ${chalk.dim(`(${fertConfig.clientPropertyId})`)}\n`
20
+ );
21
+
22
+ // watch brand.json & fert.config.js - refresh on change
23
+ const brandPath = path.resolve(fertConfig.workingDir, BRAND_JSON_FILENAME);
24
+ chokidar.watch(brandPath).on('all', async () => {
25
+ await buildTokens(fertConfig);
26
+ });
27
+
28
+ const configPath = path.resolve(fertConfig.workingDir, FERT_CONFIG_FILENAME);
29
+ chokidar.watch(configPath).on('change', async () => {
30
+ console.log('Config changed, reloading…');
31
+ fertConfig = await resolveConfig(root, options);
32
+ await buildExternalAssets(fertConfig);
33
+
34
+ // update server's config & tell Vite to refresh the page
35
+ server.fertConfig({ update: fertConfig });
36
+ server.app.viteServer.ws.send({
37
+ type: 'full-reload',
38
+ path: '*',
39
+ });
40
+ });
41
+
42
+ // start server
43
+ const server = await devServer({ start: true, fertConfig });
44
+
45
+ log.info(`\nDev server: ${chalk.cyan.bold(server.info.uri)}`);
46
+
47
+ if (options.open) {
48
+ await open(server.info.uri);
49
+ }
50
+ };
@@ -0,0 +1,207 @@
1
+ const path = require('path');
2
+ const fs = require('fs');
3
+ const chalk = require('chalk');
4
+ const prompts = require('prompts');
5
+ const uuidValidator = require('uuid-validate');
6
+ const ora = require('ora');
7
+ const { cpidLookup } = require('../utils/cpid-lookup');
8
+
9
+ const { isEmptyDir, formatTargetDir, emptyDir } = require('../utils/index');
10
+ const { TEMPLATES } = require('../../constants');
11
+
12
+ const cwd = process.cwd();
13
+ const templateList = TEMPLATES.map((i) => i.name);
14
+
15
+ module.exports = async (root, options = {}) => {
16
+ const defaultProjectName = 'madgex-{cpid}';
17
+ const argTargetDir = root ? formatTargetDir(root) : null;
18
+ const argTemplate = options.template;
19
+ const argClientPropertyId =
20
+ options.cpid && uuidValidator(options.cpid) ? options.cpid : false;
21
+
22
+ let targetDir = argTargetDir || path.join(cwd, defaultProjectName);
23
+ let result;
24
+
25
+ try {
26
+ result = await prompts(
27
+ [
28
+ {
29
+ type: argClientPropertyId ? null : 'text',
30
+ name: 'cpid',
31
+ message: 'clientPropertyId:',
32
+ validate: (cpid) =>
33
+ uuidValidator(cpid)
34
+ ? true
35
+ : `Invalid UUID, ensure the correct clientPropertyId is entered`,
36
+ },
37
+ {
38
+ type: argTargetDir ? null : 'text',
39
+ name: 'projectName',
40
+ message: 'Project name:',
41
+ initial: (prev) =>
42
+ defaultProjectName.replace('{cpid}', prev || argClientPropertyId),
43
+ onState: (state) => {
44
+ targetDir = formatTargetDir(state.value) || defaultProjectName;
45
+ },
46
+ },
47
+ {
48
+ type:
49
+ argTemplate && templateList.includes(argTemplate) ? null : 'select',
50
+ name: 'template',
51
+ message:
52
+ typeof argTemplate === 'string' &&
53
+ !templateList.includes(argTemplate)
54
+ ? `"${argTemplate}" isn't a valid template. Please choose from below: `
55
+ : 'Select a template:',
56
+ initial: 0,
57
+ choices: TEMPLATES.map((i) => {
58
+ return {
59
+ title: i.display,
60
+ value: i.name,
61
+ };
62
+ }),
63
+ },
64
+ {
65
+ type: () =>
66
+ !fs.existsSync(targetDir) || isEmptyDir(targetDir)
67
+ ? null
68
+ : 'confirm',
69
+ name: 'overwrite',
70
+ message: () =>
71
+ (targetDir === '.'
72
+ ? 'Current directory'
73
+ : `Target directory "${targetDir}"`) +
74
+ ` is not empty. Remove existing files and continue?`,
75
+ },
76
+ {
77
+ type: (_, { overwrite }) => {
78
+ if (overwrite === false) {
79
+ throw new Error(chalk.red('✖') + ' Operation cancelled');
80
+ }
81
+ return null;
82
+ },
83
+ name: 'overwriteChecker',
84
+ },
85
+ ],
86
+ {
87
+ onCancel: () => {
88
+ throw new Error(chalk.red('✖') + ' Operation cancelled');
89
+ },
90
+ }
91
+ );
92
+ } catch (cancelled) {
93
+ console.log(cancelled.message);
94
+ return;
95
+ }
96
+
97
+ const { overwrite, template, cpid } = result;
98
+ const templateName = template || argTemplate;
99
+ const clientPropertyId = cpid || argClientPropertyId;
100
+ const outDir = targetDir;
101
+
102
+ const spinner = ora(
103
+ `Looking up clientPropertyId: ${chalk.yellow(clientPropertyId)}`
104
+ ).start();
105
+ let clientProperties;
106
+ try {
107
+ clientProperties = await cpidLookup(clientPropertyId);
108
+ spinner.succeed(
109
+ `Looking up clientPropertyId: ${chalk.green(clientPropertyId)}`
110
+ );
111
+ } catch (err) {
112
+ spinner.fail(
113
+ `Unable to find client details for '${chalk.red(clientPropertyId)}':`,
114
+ err.message
115
+ );
116
+
117
+ return;
118
+ }
119
+
120
+ if (overwrite) {
121
+ emptyDir(outDir);
122
+ } else if (!fs.existsSync(outDir)) {
123
+ fs.mkdirSync(outDir, { recursive: true });
124
+ }
125
+
126
+ console.log(`\nScaffolding project in ${chalk.bold(outDir)}…`);
127
+
128
+ const templateGlobalsDir = path.resolve(
129
+ __dirname,
130
+ '../../repo-templates',
131
+ `globals`
132
+ );
133
+ const templateDir = path.resolve(
134
+ __dirname,
135
+ '../../repo-templates',
136
+ `template-${templateName}`
137
+ );
138
+
139
+ const renameFiles = {
140
+ _gitignore: '.gitignore',
141
+ };
142
+
143
+ function copy(src, dest) {
144
+ const stat = fs.statSync(src);
145
+ if (stat.isDirectory()) {
146
+ copyDir(src, dest);
147
+ } else {
148
+ fs.copyFileSync(src, dest);
149
+ }
150
+ }
151
+
152
+ function copyDir(srcDir, destDir) {
153
+ fs.mkdirSync(destDir, { recursive: true });
154
+ for (const file of fs.readdirSync(srcDir)) {
155
+ const srcFile = path.resolve(srcDir, file);
156
+ const destFile = path.resolve(destDir, file);
157
+ copy(srcFile, destFile);
158
+ }
159
+ }
160
+
161
+ const write = (file, content) => {
162
+ const targetPath = path.join(outDir, renameFiles[file] ?? file);
163
+ if (content) {
164
+ fs.writeFileSync(targetPath, content);
165
+ } else {
166
+ copy(path.join(templateDir, file), targetPath);
167
+ }
168
+ };
169
+
170
+ copy(templateGlobalsDir, outDir);
171
+
172
+ const files = fs.readdirSync(templateDir);
173
+ for (const file of files.filter((f) => f !== 'package.json')) {
174
+ write(file);
175
+ }
176
+
177
+ // create package.json
178
+ const pkg = JSON.parse(
179
+ fs.readFileSync(path.join(templateDir, 'package.json'), 'utf-8')
180
+ );
181
+ pkg.name = `madgex-${clientPropertyId}`;
182
+ pkg.description = `Branding repo for ${clientProperties.brandName}`;
183
+ write('package.json', JSON.stringify(pkg, null, 2) + '\n');
184
+
185
+ // create fert.config.js
186
+ const fertConfigJs = fs.readFileSync(
187
+ path.join(outDir, 'fert.config.js'),
188
+ 'utf-8'
189
+ );
190
+ write('fert.config.js', fertConfigJs.replace(/{CPID}/gi, clientPropertyId));
191
+
192
+ const cdProjectName = path.relative(cwd, outDir);
193
+
194
+ console.log(`\nDone. Now run:\n`);
195
+
196
+ if (cdProjectName && root !== cwd) {
197
+ console.log(
198
+ ` cd ${
199
+ cdProjectName.includes(' ') ? `"${cdProjectName}"` : cdProjectName
200
+ }`
201
+ );
202
+ }
203
+
204
+ console.log(` npm install`);
205
+ console.log(` npm run dev`);
206
+ console.log();
207
+ };
@@ -0,0 +1,94 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const Hoek = require('@hapi/hoek');
4
+ const { log } = require('../../utils');
5
+ const axios = require('axios');
6
+ const FormData = require('form-data');
7
+ const ora = require('ora');
8
+ const chalk = require('chalk');
9
+ const { ASSET_STORE_USER_GUID } = require('../../../constants');
10
+
11
+ module.exports = class AssetStoreUploader {
12
+ constructor(options = {}) {
13
+ Hoek.assert(options.apiUrl, 'apiUrl required');
14
+ Hoek.assert(options.apiKey, 'apiKey required');
15
+
16
+ this.apiUrl = options.apiUrl;
17
+ this.apiKey = options.apiKey;
18
+ this.basePath = options.basePath ?? '/api/assets';
19
+ }
20
+
21
+ buildAbsolutePath(relativePath, ...parts) {
22
+ return new URL(
23
+ path.posix.join(this.basePath, relativePath, ...parts),
24
+ this.apiUrl
25
+ );
26
+ }
27
+
28
+ async upload(file, dest) {
29
+ const absoluteUrl = this.buildAbsolutePath(dest, path.basename(file));
30
+ const label = absoluteUrl.pathname;
31
+
32
+ log.debug('upload', { file, dest, absoluteUrl });
33
+
34
+ const form = new FormData();
35
+ form.append('data', fs.createReadStream(file));
36
+ form.append('modifiedById', ASSET_STORE_USER_GUID);
37
+
38
+ const spinner = ora(`${chalk.yellow('Uploading')}: ${label}`).start();
39
+
40
+ return axios
41
+ .put(absoluteUrl.href, form, {
42
+ headers: {
43
+ apikey: this.apiKey,
44
+ ...form.getHeaders(),
45
+ },
46
+ })
47
+ .then(() => {
48
+ spinner.succeed(`${label}`);
49
+ })
50
+ .catch((err) => {
51
+ spinner.fail(`Failed to upload '${label}':`, err.message);
52
+
53
+ throw err;
54
+ });
55
+ }
56
+
57
+ async uploadDir(dir, dest = false) {
58
+ Hoek.assert(dir, 'dir missing');
59
+
60
+ const start = process.hrtime();
61
+ const files = this._getFilesFromDir(dir);
62
+
63
+ // Send files consecutively to avoid straining the api
64
+ for (const file of files) {
65
+ const relativeName = path.posix.relative(dir, file);
66
+ const remotePath = path.posix.dirname(dest || relativeName);
67
+
68
+ await this.upload(file, remotePath);
69
+ }
70
+
71
+ const end = process.hrtime(start);
72
+ const duration = end[0] * 1e3 + end[1] * 1e-6;
73
+
74
+ return {
75
+ files,
76
+ duration,
77
+ };
78
+ }
79
+
80
+ // Returns array of all files in provided directory (recursive)
81
+ _getFilesFromDir = (dir, files = []) => {
82
+ fs.readdirSync(dir).forEach((entry) => {
83
+ const name = path.join(dir, entry);
84
+
85
+ if (fs.statSync(name).isDirectory()) {
86
+ this._getFilesFromDir(name, files);
87
+ } else {
88
+ files.push(name);
89
+ }
90
+ });
91
+
92
+ return files;
93
+ };
94
+ };
@@ -0,0 +1,41 @@
1
+ const { SSMClient, GetParameterCommand } = require('@aws-sdk/client-ssm');
2
+ const { fromEnv } = require('@aws-sdk/credential-providers');
3
+ const { AWS_REGION } = require('../../../constants');
4
+
5
+ /**
6
+ * Gets AWS parameter from the parameter store
7
+ *
8
+ * @function getAwsParam
9
+ *
10
+ * @async
11
+ *
12
+ * @param {string} paramName the name of the parameter we want from AWS parameter store.
13
+ *
14
+ * @returns {Promise<{ value: string | undefined }>} the value of the requested AWS parameter
15
+ *
16
+ */
17
+ const getAwsParam = async (paramName) => {
18
+ const client = new SSMClient({
19
+ region: AWS_REGION,
20
+ credentials: fromEnv(),
21
+ });
22
+ const input = {
23
+ Name: paramName,
24
+ WithDecryption: true,
25
+ };
26
+ const command = new GetParameterCommand(input);
27
+ let result;
28
+
29
+ try {
30
+ const response = await client.send(command);
31
+
32
+ result = response.Parameter.Value;
33
+ } catch (error) {
34
+ console.log(`parameter-store error (${paramName}) ${error.message}`, error);
35
+ process.exit(1);
36
+ }
37
+
38
+ return result;
39
+ };
40
+
41
+ module.exports = getAwsParam;
@@ -0,0 +1,51 @@
1
+ const path = require('path');
2
+ const Hoek = require('@hapi/hoek');
3
+ const chalk = require('chalk');
4
+ const { resolveConfig, log } = require('../utils');
5
+ const getAwsParam = require('./publish-tasks/get-aws-parameter');
6
+ const AssetStoreUploader = require('./publish-tasks/asset-store-uploader');
7
+ const {
8
+ ASSET_STORE_API,
9
+ AWS_PARAM_NAME,
10
+ REMOTE_UPLOAD_BASE,
11
+ UPLOAD_DIR,
12
+ } = require('../../constants');
13
+
14
+ module.exports = async (root, options) => {
15
+ const fertConfig = await resolveConfig(root, options);
16
+ const validTargets = ['dev', 'prod'];
17
+
18
+ if (!validTargets.includes(options.target)) {
19
+ throw Error(
20
+ `Missing or invalid --target option. Choose from [${validTargets}]`
21
+ );
22
+ }
23
+
24
+ const templateCtx = {
25
+ env: options.target === 'prod' ? 'job' : 'jb.dev',
26
+ Environement_Name: options.target === 'prod' ? 'production' : 'dev',
27
+ fertConfig,
28
+ };
29
+
30
+ const localDir = path.resolve(fertConfig.workingDir, UPLOAD_DIR);
31
+ const apiUrl = Hoek.reachTemplate(templateCtx, ASSET_STORE_API);
32
+ const remoteBasePath = Hoek.reachTemplate(templateCtx, REMOTE_UPLOAD_BASE);
33
+ const apiKeyParamPath = Hoek.reachTemplate(templateCtx, AWS_PARAM_NAME);
34
+
35
+ const apiKey = await getAwsParam(apiKeyParamPath);
36
+
37
+ log.info(
38
+ `Publishing ${chalk.cyan(localDir)} to ${chalk.green.bold(options.target)}`
39
+ );
40
+
41
+ // send to S3 using the Asset Store API
42
+ const assetStore = new AssetStoreUploader({
43
+ apiUrl,
44
+ apiKey,
45
+ basePath: remoteBasePath,
46
+ });
47
+
48
+ const result = await assetStore.uploadDir(localDir);
49
+
50
+ log.success(`Publish complete in ${(result.duration / 1000).toFixed(0)}s\n`);
51
+ };
@@ -0,0 +1,47 @@
1
+ // Fetch provides support for node 16 and lower
2
+ const fetch = require('node-fetch-commonjs');
3
+ const storage = require('node-persist');
4
+ const dayjs = require('dayjs');
5
+ const duration = require('dayjs/plugin/duration');
6
+ const { PROPERTY_ID_API, TMP_DIR } = require('../../constants');
7
+
8
+ dayjs.extend(duration);
9
+
10
+ exports.doCpidLookup = async (clientPropertyId) => {
11
+ const API_URL = new URL(clientPropertyId, PROPERTY_ID_API).toString();
12
+
13
+ const res = await fetch(API_URL);
14
+
15
+ if (!res.ok) {
16
+ throw Error(`Unable to lookup CPID [${clientPropertyId}]`);
17
+ }
18
+
19
+ const { data: results } = await res.json();
20
+ const { brandName, parentId: parentCpid } = results;
21
+
22
+ return {
23
+ brandName,
24
+ parentCpid: parentCpid ?? false,
25
+ rootClientPropertyId: parentCpid ?? clientPropertyId,
26
+ isAffiliate: !!parentCpid,
27
+ };
28
+ };
29
+
30
+ exports.cpidLookup = async (clientPropertyId) => {
31
+ await storage.init({
32
+ dir: `${TMP_DIR}/fert-cpid-cache`,
33
+ ttl: dayjs.duration(1, 'week').asMilliseconds(),
34
+ });
35
+
36
+ const value = await storage.getItem(clientPropertyId);
37
+
38
+ if (value) {
39
+ return value;
40
+ }
41
+
42
+ const result = await this.doCpidLookup(clientPropertyId);
43
+
44
+ await storage.updateItem(clientPropertyId, result);
45
+
46
+ return result;
47
+ };
@@ -0,0 +1,121 @@
1
+ const path = require('path');
2
+ const fs = require('fs');
3
+ const fsp = require('fs/promises');
4
+ const { VERSION } = require('../../constants');
5
+ const chalk = require('chalk');
6
+ const debug = require('debug');
7
+ const debugLog = debug('fert');
8
+ const uuidValidator = require('uuid-validate');
9
+ const Hoek = require('@hapi/hoek');
10
+ const { cpidLookup } = require('./cpid-lookup');
11
+ const resolveExternalAssets = require('./resolve-external-assets');
12
+
13
+ exports.printBanner = () => {
14
+ console.log(chalk`\n{green.bold Fert} v${VERSION}\n`);
15
+ };
16
+
17
+ exports.log = {
18
+ info: (...args) => {
19
+ console.log(...args);
20
+ },
21
+ success: (...args) => {
22
+ console.log(chalk.green.bold('✔'), ...args);
23
+ },
24
+ error: (...args) => {
25
+ console.log(chalk.red.bold('✖'), ...args);
26
+ },
27
+ debug: (...args) => {
28
+ debugLog(...args);
29
+ },
30
+ };
31
+
32
+ exports.resolveConfig = async (root, options = {}) => {
33
+ const defaults = {
34
+ clientPropertyId: null,
35
+ entry: 'src/index.js',
36
+ externalAssets: {
37
+ links: [],
38
+ scripts: [],
39
+ },
40
+ };
41
+
42
+ const workingDir = root ? path.resolve(root) : path.resolve(process.cwd());
43
+ const fertConfig = Hoek.applyToDefaults(
44
+ defaults,
45
+ this.loadConfigFromFile(workingDir)
46
+ );
47
+ const client = await cpidLookup(fertConfig.clientPropertyId);
48
+
49
+ Hoek.assert(
50
+ uuidValidator(fertConfig.clientPropertyId),
51
+ `Invalid clientPropertyId specified in fert.config.js: ${fertConfig.clientPropertyId}`
52
+ );
53
+
54
+ fertConfig.externalAssets = resolveExternalAssets(fertConfig.externalAssets);
55
+
56
+ return {
57
+ ...fertConfig,
58
+ workingDir,
59
+ cli: {
60
+ ...options,
61
+ },
62
+ client,
63
+ };
64
+ };
65
+
66
+ exports.loadConfigFromFile = (workingDir) => {
67
+ const configPath = path.resolve(workingDir, 'fert.config.js');
68
+
69
+ try {
70
+ // always return fresh config
71
+ if (require.cache[configPath]) {
72
+ delete require.cache[configPath];
73
+ }
74
+
75
+ const fertConfig = require(configPath);
76
+
77
+ return fertConfig;
78
+ } catch (err) {
79
+ this.log.error(
80
+ `Failed to load fert.config.js - ensure you're running fert on a branding repo & config exists.\n`
81
+ );
82
+
83
+ process.exit(1);
84
+ }
85
+ };
86
+
87
+ exports.ensureTrailingSlash = (str) => {
88
+ const lastChar = str.at(-1);
89
+
90
+ return lastChar === '/' ? str : `${str}/`;
91
+ };
92
+
93
+ exports.exists = async (path) => {
94
+ try {
95
+ await fsp.access(path);
96
+ return true;
97
+ } catch {
98
+ return false;
99
+ }
100
+ };
101
+
102
+ exports.isEmptyDir = function isEmptyDir(path) {
103
+ const files = fs.readdirSync(path);
104
+ return files.length === 0 || (files.length === 1 && files[0] === '.git');
105
+ };
106
+
107
+ exports.formatTargetDir = function formatTargetDir(targetDir) {
108
+ return targetDir?.trim().replace(/\/+$/g, '');
109
+ };
110
+
111
+ exports.emptyDir = function emptyDir(dir) {
112
+ if (!fs.existsSync(dir)) {
113
+ return;
114
+ }
115
+ for (const file of fs.readdirSync(dir)) {
116
+ if (file === '.git') {
117
+ continue;
118
+ }
119
+ fs.rmSync(path.resolve(dir, file), { recursive: true, force: true });
120
+ }
121
+ };