@madgex/fert 4.2.6 → 5.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +54 -23
- package/bin/cli.js +15 -11
- package/bin/commands/_service-command-bootstrap.js +71 -0
- package/bin/commands/build-tasks/bundle-entry.js +1 -1
- package/bin/commands/build.js +6 -3
- package/bin/commands/configs.js +44 -12
- package/bin/commands/configs.test.js +2 -1
- package/bin/commands/dev-server.js +25 -10
- package/bin/commands/init.js +8 -3
- package/bin/commands/publish-tasks/get-aws-parameter.js +1 -3
- package/bin/commands/publish.js +27 -9
- package/bin/utils/configs.js +40 -29
- package/bin/utils/configs.test.js +12 -4
- package/bin/utils/cpid-matches-git-remote.js +9 -3
- package/bin/utils/index.js +76 -20
- package/constants.js +24 -9
- package/package.json +6 -6
- package/repo-templates/globals/fert.config.js +1 -0
- package/repo-templates/template-basic/_gitignore +2 -1
- package/repo-templates/template-bigworkbag/_gitignore +2 -1
- package/server/index.js +6 -10
- package/server/plugins/hapi-vite.js +1 -0
- package/server/routes/public.js +2 -2
- package/server/routes/views.js +4 -1
- package/shared/Dockerfile.brandingRepoPublish +1 -1
package/README.md
CHANGED
|
@@ -42,9 +42,25 @@ To empower & serve its users, we should design this CLI with the users in mind.
|
|
|
42
42
|
|
|
43
43
|
# Configuration
|
|
44
44
|
|
|
45
|
-
There are **
|
|
45
|
+
There are **two required files** in the root of the branding repo, and **two required files** in the service folder:
|
|
46
46
|
|
|
47
|
-
|
|
47
|
+
`/fert.config.js` [required] – a configuration file in the root of the branding repo specifying the following config to both the Fert dev server and the production asset build:
|
|
48
|
+
|
|
49
|
+
| Option | |
|
|
50
|
+
| --------------------------- | ------------------------------------------------------------------- |
|
|
51
|
+
| `clientPropertyId` <String> | The `clientPropertyId` associated with the branding repo (required) |
|
|
52
|
+
|
|
53
|
+
e.g.
|
|
54
|
+
|
|
55
|
+
```js
|
|
56
|
+
module.exports = {
|
|
57
|
+
clientPropertyId: 'ff6102ff-0f4b-43d1-a2c7-83b835b8dee5',
|
|
58
|
+
};
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
`/jenkinsfile` - A jenkins file in the root of the repo containing the pipeline which will build and send the assets to S3. Every push to the repo will trigger a jenkins build automatically. Changes on **master** will trigger a build and send assets to the **production** bucket. Any other branches will send the assets to the jb.dev bucket.
|
|
62
|
+
|
|
63
|
+
`/services/<service-folder>/brand.json` [required] – a JSON file in a service folder that supplies the base branding tokens. These values will be merged with the Design System tokens & a full set of branding tokens will be created. [see here for format](https://github.com/wiley/madgex-design-system/tree/master/packages/%40madgex.design-system/src/tokens)
|
|
48
64
|
|
|
49
65
|
e.g.
|
|
50
66
|
|
|
@@ -61,13 +77,13 @@ e.g.
|
|
|
61
77
|
}
|
|
62
78
|
```
|
|
63
79
|
|
|
64
|
-
|
|
80
|
+
`/services/<service-folder>/fert.service.config.js` [required] – a service configuration file in the service folder, specifying the following config to both the Fert dev server and the production asset build:
|
|
65
81
|
|
|
66
|
-
| Option
|
|
67
|
-
|
|
|
68
|
-
| `
|
|
69
|
-
| `entry` <String>
|
|
70
|
-
| `
|
|
82
|
+
| Option | |
|
|
83
|
+
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
84
|
+
| `serviceName` <String> | Which service to display and deploy, e.g. `jobseekers-frontend`, `recruiterservices-frontend`. |
|
|
85
|
+
| `entry` <String> | The JavaScript entry file. Any assets (CSS/SCSS/SVGs) included will be processed and be part of the output. Defaults to `['src/index.js']` |
|
|
86
|
+
| `externalAssets` <Object> | |
|
|
71
87
|
|
|
72
88
|
The `entry` will be used by Vite to create production bundles.
|
|
73
89
|
|
|
@@ -75,14 +91,15 @@ e.g.
|
|
|
75
91
|
|
|
76
92
|
```js
|
|
77
93
|
module.exports = {
|
|
78
|
-
|
|
94
|
+
serviceName: 'jobseekers-frontend',
|
|
79
95
|
entry: './src/index.js',
|
|
80
|
-
|
|
96
|
+
externalAssets: {
|
|
97
|
+
links: [],
|
|
98
|
+
scripts: [],
|
|
99
|
+
},
|
|
81
100
|
};
|
|
82
101
|
```
|
|
83
102
|
|
|
84
|
-
`jenkinsfile` - A jenkins file containing the pipeline which will build and send the assets to S3. Every push to the repo will trigger a jenkins build automatically. Changes on **master** will trigger a build and send assets to the **production** bucket. Any other branches will send the assets to the jb.dev bucket.
|
|
85
|
-
|
|
86
103
|
# CLI commands
|
|
87
104
|
|
|
88
105
|
The CLI will support 3 main commands: dev server (default command), scaffolding a new project and building production assets.
|
|
@@ -97,13 +114,16 @@ The CLI will support 3 main commands: dev server (default command), scaffolding
|
|
|
97
114
|
|
|
98
115
|
## Dev server
|
|
99
116
|
|
|
100
|
-
|
|
117
|
+
Start the Fert dev server for 1 or all services:
|
|
118
|
+
`fert` | `fert --service-name=jobseekers-frontend`
|
|
101
119
|
|
|
102
|
-
Start the Fert dev server
|
|
120
|
+
Start the Fert dev server for 1 service from the service folder:
|
|
121
|
+
`cd services/jobseekers-frontend`
|
|
122
|
+
`fert`
|
|
103
123
|
|
|
104
124
|
**Usage**
|
|
105
125
|
|
|
106
|
-
`fert
|
|
126
|
+
`fert `
|
|
107
127
|
|
|
108
128
|
**Outline of implementation**
|
|
109
129
|
|
|
@@ -145,13 +165,18 @@ The `init` command is intended for an automation tool which will create a new br
|
|
|
145
165
|
|
|
146
166
|
## Build
|
|
147
167
|
|
|
148
|
-
`fert build`
|
|
149
|
-
|
|
150
168
|
Process CSS/JS assets specified in `fert.config.js` and copy static assets, including templates for distribution.
|
|
151
169
|
|
|
170
|
+
Run for all or a specific service
|
|
171
|
+
`fert build` | `fert build --service-name=jobseekers-frontend`
|
|
172
|
+
|
|
173
|
+
Run for 1 service from the service folder:
|
|
174
|
+
`cd services/jobseekers-frontend`
|
|
175
|
+
`fert build`
|
|
176
|
+
|
|
152
177
|
**Usage**
|
|
153
178
|
|
|
154
|
-
`fert build
|
|
179
|
+
`fert build --only [tokens | assets]`
|
|
155
180
|
|
|
156
181
|
| Option | |
|
|
157
182
|
| ----------------- | ---------------------------------------------------------------------------------------------------------- |
|
|
@@ -172,15 +197,21 @@ Templates & CSS may be processed using the [CSS Modules](https://github.com/css-
|
|
|
172
197
|
|
|
173
198
|
## Publish
|
|
174
199
|
|
|
200
|
+
From root, run for all services or a specific service:
|
|
201
|
+
`fert publish --target=prod` | `fert publish --target=prod --service-name=jobseekers-frontend`
|
|
202
|
+
|
|
203
|
+
Run for 1 service from the service folder:
|
|
204
|
+
`cd services/jobseekers-frontend`
|
|
175
205
|
`fert publish`
|
|
176
206
|
|
|
177
207
|
**Usage**
|
|
178
208
|
|
|
179
|
-
`fert publish
|
|
209
|
+
`fert publish --target [target]`
|
|
180
210
|
|
|
181
|
-
| Option
|
|
182
|
-
|
|
|
183
|
-
| `--target`
|
|
211
|
+
| Option | |
|
|
212
|
+
| ----------- | ---------------------------------------------------------------------------- |
|
|
213
|
+
| `--target` | Where to publish the `dist` directory assets.<br/>`dev` \| `prod` (`string`) |
|
|
214
|
+
| `--dry-run` | Dry run, dont actually upload anything |
|
|
184
215
|
|
|
185
216
|
Send all files and directories created in the `dist` directory to either development or production versions of the Asset Store API. Files uploaded to the Asset Store API are available via a CloudFront-based CDN.
|
|
186
217
|
|
|
@@ -198,7 +229,7 @@ A set of commands to help with publishing, downloading & referencing project con
|
|
|
198
229
|
|
|
199
230
|
**Usage**
|
|
200
231
|
|
|
201
|
-
`fert configs
|
|
232
|
+
`fert configs --[query|download|publish] [env]`
|
|
202
233
|
|
|
203
234
|
| Option | Description |
|
|
204
235
|
| -------------------- | --------------------------------------------------------------------------------------- |
|
package/bin/cli.js
CHANGED
|
@@ -6,40 +6,44 @@ const cli = require('cac')('fert');
|
|
|
6
6
|
const { VERSION } = require('../constants');
|
|
7
7
|
const { printBanner } = require('./utils/index.js');
|
|
8
8
|
|
|
9
|
+
const {
|
|
10
|
+
serivceCommandBootstrap,
|
|
11
|
+
} = require('./commands/_service-command-bootstrap.js');
|
|
12
|
+
|
|
9
13
|
const run = () => {
|
|
10
14
|
printBanner();
|
|
11
15
|
|
|
12
16
|
cli
|
|
13
17
|
.command(
|
|
14
|
-
'
|
|
18
|
+
'',
|
|
15
19
|
'Start layout server. Can supply a branding directory if running FERT standalone'
|
|
16
20
|
)
|
|
17
21
|
.alias('dev')
|
|
18
22
|
.option('--open', 'Automatically open the dev server in your browser')
|
|
19
23
|
.option('--host [host]', '[string] specify hostname')
|
|
20
24
|
.option('--port <port>', '[number] specify port')
|
|
21
|
-
.option(
|
|
22
|
-
|
|
23
|
-
'Use jobseeker or recruiter site, "js" or "rs"'
|
|
24
|
-
)
|
|
25
|
-
.action((...args) => require('./commands/dev-server')(...args));
|
|
25
|
+
.option('--service-name <serviceName>', '[string] run a single service')
|
|
26
|
+
.action((...args) => serivceCommandBootstrap('dev', ...args));
|
|
26
27
|
|
|
27
28
|
cli
|
|
28
29
|
.command(
|
|
29
|
-
'build
|
|
30
|
+
'build',
|
|
30
31
|
'Build project. Can supply a branding directory if running FERT standalone'
|
|
31
32
|
)
|
|
32
33
|
.option('--only <task>', `Only run part of the build [ 'assets', 'tokens']`)
|
|
33
34
|
.option('--config [dir]', 'Use custom rollup config file')
|
|
34
|
-
.
|
|
35
|
+
.option('--service-name <serviceName>', '[string] run a single service')
|
|
36
|
+
.action((...args) => serivceCommandBootstrap('build', ...args));
|
|
35
37
|
|
|
36
38
|
cli
|
|
37
|
-
.command('publish
|
|
39
|
+
.command('publish', 'Publish the project')
|
|
38
40
|
.option('--target <env>', 'Environment to publish to, "dev" or "prod"')
|
|
39
|
-
.
|
|
41
|
+
.option('--service-name <serviceName>', '[string] run a single service')
|
|
42
|
+
.option('--dry-run', 'Dry run, dont actually upload anything')
|
|
43
|
+
.action((...args) => serivceCommandBootstrap('publish', ...args));
|
|
40
44
|
|
|
41
45
|
cli
|
|
42
|
-
.command('configs
|
|
46
|
+
.command('configs', 'Query/Publish project configs')
|
|
43
47
|
.option(
|
|
44
48
|
'--publish <env>',
|
|
45
49
|
'Publish configs to Configuration API environment "dev" or "production"'
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
loadServiceConfigFiles,
|
|
5
|
+
loadConfigFromFile,
|
|
6
|
+
} = require('../utils/index.js');
|
|
7
|
+
|
|
8
|
+
const commandDevSever = require('./dev-server.js');
|
|
9
|
+
const commandBuild = require('./build.js');
|
|
10
|
+
const commandPublish = require('./publish.js');
|
|
11
|
+
const { log } = require('../utils/logging.js');
|
|
12
|
+
const { FERT_SERVICE_CONFIG_FILENAME } = require('../../constants.js');
|
|
13
|
+
|
|
14
|
+
const commandMap = {
|
|
15
|
+
dev: commandDevSever,
|
|
16
|
+
build: commandBuild,
|
|
17
|
+
publish: commandPublish,
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* determine if we are running a single service, otherwise search for all services
|
|
22
|
+
* Then run command
|
|
23
|
+
*/
|
|
24
|
+
module.exports.serivceCommandBootstrap = async function serivceCommandBootstrap(
|
|
25
|
+
command,
|
|
26
|
+
options
|
|
27
|
+
) {
|
|
28
|
+
// explicitly run a single service - via CLI option
|
|
29
|
+
if (options.serviceName) {
|
|
30
|
+
console.log(`🔦 ${chalk.green('Running single service mode')}`);
|
|
31
|
+
await commandMap[command](options);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// explicitly run a single service - if we are inside a service folder
|
|
36
|
+
try {
|
|
37
|
+
// try to load service config in process.cwd
|
|
38
|
+
const serviceConfig = await loadConfigFromFile({
|
|
39
|
+
dir: process.cwd(),
|
|
40
|
+
filename: FERT_SERVICE_CONFIG_FILENAME,
|
|
41
|
+
silent: true,
|
|
42
|
+
});
|
|
43
|
+
if (serviceConfig) {
|
|
44
|
+
console.log(`🔦 ${chalk.green('Running single service mode')}`);
|
|
45
|
+
await commandMap[command]({
|
|
46
|
+
...options,
|
|
47
|
+
serviceName: serviceConfig.serviceName,
|
|
48
|
+
});
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
} catch {
|
|
52
|
+
// no service config file in cwd
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// not single service - try to find and load all services
|
|
56
|
+
const serviceConfigs = await loadServiceConfigFiles();
|
|
57
|
+
console.log(
|
|
58
|
+
`🔦 ${chalk.green('Running multi service mode')}, searching for services`
|
|
59
|
+
);
|
|
60
|
+
for (const { serviceConfig } of serviceConfigs) {
|
|
61
|
+
try {
|
|
62
|
+
await commandMap[command]({
|
|
63
|
+
...options,
|
|
64
|
+
serviceName: serviceConfig.serviceName,
|
|
65
|
+
});
|
|
66
|
+
} catch (error) {
|
|
67
|
+
log.error('Failed to run command', command, options, serviceConfig);
|
|
68
|
+
console.error(error);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
};
|
|
@@ -16,7 +16,7 @@ module.exports = async (fertConfig, options = {}) => {
|
|
|
16
16
|
|
|
17
17
|
dynamicOptions = {
|
|
18
18
|
root: path.resolve(fertConfig.workingDir),
|
|
19
|
-
base:
|
|
19
|
+
base: `/_/${fertConfig.serviceName}/assets/`,
|
|
20
20
|
build: {
|
|
21
21
|
cssCodeSplit: false, // important: to get a single, style.css file output
|
|
22
22
|
outDir: path.resolve(fertConfig.workingDir, 'dist'),
|
package/bin/commands/build.js
CHANGED
|
@@ -7,9 +7,12 @@ const buildCssFromTokens = require('./build-tasks/build-tokens');
|
|
|
7
7
|
const buildExternalAssets = require('./build-tasks/build-external-assets');
|
|
8
8
|
const { validateLocalConfigs } = require('../utils/configs.js');
|
|
9
9
|
|
|
10
|
-
module.exports = async (
|
|
11
|
-
const fertConfig = await resolveConfig(
|
|
12
|
-
await validateLocalConfigs(
|
|
10
|
+
module.exports = async (options = {}) => {
|
|
11
|
+
const fertConfig = await resolveConfig(options);
|
|
12
|
+
await validateLocalConfigs({
|
|
13
|
+
workingDir: fertConfig.rootDir,
|
|
14
|
+
clientPropertyId: fertConfig.clientPropertyId,
|
|
15
|
+
});
|
|
13
16
|
|
|
14
17
|
if (!options.only) {
|
|
15
18
|
await rimraf(path.resolve(fertConfig.workingDir, 'dist'));
|
package/bin/commands/configs.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const {
|
|
1
|
+
const { loadConfigFromFile, findFertConfigDir } = require('../utils/index.js');
|
|
2
2
|
const { getConfigAPI, updateProjectConfigs } = require('../utils/configs.js');
|
|
3
3
|
const chalk = require('chalk');
|
|
4
4
|
const { log } = require('../utils/logging.js');
|
|
@@ -8,7 +8,7 @@ const { mkdir, writeFile } = require('node:fs/promises');
|
|
|
8
8
|
const Path = require('node:path');
|
|
9
9
|
const { CONFIG_DIR } = require('../../constants.js');
|
|
10
10
|
|
|
11
|
-
const handleDownload = async (
|
|
11
|
+
const handleDownload = async ({ workingDir }, api, options) => {
|
|
12
12
|
const validTargets = ['dev', 'production'];
|
|
13
13
|
|
|
14
14
|
if (!validTargets.includes(options.download)) {
|
|
@@ -77,13 +77,17 @@ const handleDownload = async (fertConfig, api, options) => {
|
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
for (const [schemaName, data] of Object.entries(resultObj)) {
|
|
80
|
-
const filePath = Path.join(CONFIG_DIR, `${schemaName}.json`);
|
|
80
|
+
const filePath = Path.join(workingDir, CONFIG_DIR, `${schemaName}.json`);
|
|
81
81
|
await writeFile(filePath, JSON.stringify(data, null, 2));
|
|
82
82
|
console.log(` - ${schemaName}.json`);
|
|
83
83
|
}
|
|
84
84
|
};
|
|
85
85
|
|
|
86
|
-
const handlePublish = async (
|
|
86
|
+
const handlePublish = async (
|
|
87
|
+
{ workingDir, clientPropertyId },
|
|
88
|
+
api,
|
|
89
|
+
options
|
|
90
|
+
) => {
|
|
87
91
|
const validTargets = ['dev', 'production'];
|
|
88
92
|
|
|
89
93
|
if (!validTargets.includes(options.publish)) {
|
|
@@ -93,14 +97,23 @@ const handlePublish = async (fertConfig, api, options) => {
|
|
|
93
97
|
}
|
|
94
98
|
|
|
95
99
|
try {
|
|
96
|
-
await updateProjectConfigs(
|
|
100
|
+
await updateProjectConfigs({
|
|
101
|
+
workingDir,
|
|
102
|
+
clientPropertyId,
|
|
103
|
+
environment: options.publish,
|
|
104
|
+
});
|
|
105
|
+
console.log('upload');
|
|
97
106
|
} catch (err) {
|
|
98
107
|
log.debug(err);
|
|
99
108
|
process.exit(1);
|
|
100
109
|
}
|
|
101
110
|
};
|
|
102
111
|
|
|
103
|
-
const handleQuery = async (
|
|
112
|
+
const handleQuery = async (
|
|
113
|
+
/* { workingDir, clientPropertyId } */ _,
|
|
114
|
+
api,
|
|
115
|
+
options
|
|
116
|
+
) => {
|
|
104
117
|
try {
|
|
105
118
|
if (options.query !== true) {
|
|
106
119
|
console.log(
|
|
@@ -141,9 +154,16 @@ const handleQuery = async (fertConfig, api, options) => {
|
|
|
141
154
|
}
|
|
142
155
|
};
|
|
143
156
|
|
|
144
|
-
module.exports = async (
|
|
145
|
-
const
|
|
146
|
-
|
|
157
|
+
module.exports = async (options) => {
|
|
158
|
+
const fertConfigDir = await findFertConfigDir();
|
|
159
|
+
|
|
160
|
+
const { clientPropertyId } = await loadConfigFromFile({
|
|
161
|
+
dir: fertConfigDir,
|
|
162
|
+
});
|
|
163
|
+
const api = await getConfigAPI({
|
|
164
|
+
clientPropertyId,
|
|
165
|
+
environment: options.environment,
|
|
166
|
+
});
|
|
147
167
|
|
|
148
168
|
if (!options.publish && !options.query && !options.download) {
|
|
149
169
|
console.log(
|
|
@@ -153,11 +173,23 @@ module.exports = async (root, options) => {
|
|
|
153
173
|
}
|
|
154
174
|
|
|
155
175
|
if (options.download) {
|
|
156
|
-
await handleDownload(
|
|
176
|
+
await handleDownload(
|
|
177
|
+
{ clientPropertyId, workingDir: fertConfigDir },
|
|
178
|
+
api,
|
|
179
|
+
options
|
|
180
|
+
);
|
|
157
181
|
} else if (options.publish) {
|
|
158
|
-
await handlePublish(
|
|
182
|
+
await handlePublish(
|
|
183
|
+
{ clientPropertyId, workingDir: fertConfigDir },
|
|
184
|
+
api,
|
|
185
|
+
options
|
|
186
|
+
);
|
|
159
187
|
} else if (options.query) {
|
|
160
|
-
await handleQuery(
|
|
188
|
+
await handleQuery(
|
|
189
|
+
{ clientPropertyId, workingDir: fertConfigDir },
|
|
190
|
+
api,
|
|
191
|
+
options
|
|
192
|
+
);
|
|
161
193
|
}
|
|
162
194
|
};
|
|
163
195
|
|
|
@@ -15,6 +15,7 @@ describe('configs', () => {
|
|
|
15
15
|
|
|
16
16
|
beforeEach(async (t) => {
|
|
17
17
|
fertConfig = {
|
|
18
|
+
workingDir: 'working-dir',
|
|
18
19
|
clientPropertyId: 'ff6102ff-0f4b-43d1-a2c7-83b835b8dee5',
|
|
19
20
|
};
|
|
20
21
|
|
|
@@ -82,7 +83,7 @@ describe('configs', () => {
|
|
|
82
83
|
|
|
83
84
|
assert.strictEqual(
|
|
84
85
|
writeFileMock.mock.calls[0].arguments[0],
|
|
85
|
-
Path.join('config', 'jsfe-config.json')
|
|
86
|
+
Path.join('working-dir', 'config', 'jsfe-config.json')
|
|
86
87
|
);
|
|
87
88
|
});
|
|
88
89
|
|
|
@@ -2,7 +2,7 @@ const path = require('node:path');
|
|
|
2
2
|
const chalk = require('chalk');
|
|
3
3
|
const open = require('open');
|
|
4
4
|
const chokidar = require('chokidar');
|
|
5
|
-
const { resolveConfig } = require('../utils/index.js');
|
|
5
|
+
const { resolveConfig, findFertConfigDir } = require('../utils/index.js');
|
|
6
6
|
const { log } = require('../utils/logging.js');
|
|
7
7
|
const { validateLocalConfigs } = require('../utils/configs.js');
|
|
8
8
|
const { devServer } = require('../../server');
|
|
@@ -10,26 +10,37 @@ const { buildTokens, buildExternalAssets } = require('./build.js');
|
|
|
10
10
|
const {
|
|
11
11
|
BRAND_JSON_FILENAME,
|
|
12
12
|
FERT_CONFIG_FILENAME,
|
|
13
|
+
FERT_SERVICE_CONFIG_FILENAME,
|
|
13
14
|
} = require('../../constants.js');
|
|
14
15
|
|
|
15
|
-
module.exports = async (
|
|
16
|
-
let fertConfig = await resolveConfig(
|
|
16
|
+
module.exports = async (options = {}) => {
|
|
17
|
+
let fertConfig = await resolveConfig(options);
|
|
17
18
|
|
|
18
19
|
console.log(
|
|
19
20
|
`Starting branding server for ${chalk.green(
|
|
20
21
|
fertConfig.client.brandName
|
|
21
|
-
)} [${fertConfig.
|
|
22
|
+
)} [${fertConfig.serviceName}] ${chalk.dim(
|
|
22
23
|
`(${fertConfig.clientPropertyId})`
|
|
23
24
|
)}\n`
|
|
24
25
|
);
|
|
25
26
|
|
|
26
|
-
await validateLocalConfigs(
|
|
27
|
+
await validateLocalConfigs({
|
|
28
|
+
workingDir: fertConfig.rootDir,
|
|
29
|
+
clientPropertyId: fertConfig.clientPropertyId,
|
|
30
|
+
throwable: false,
|
|
31
|
+
});
|
|
27
32
|
await buildTokens(fertConfig);
|
|
28
33
|
|
|
34
|
+
const fertConfigDir = await findFertConfigDir();
|
|
35
|
+
|
|
29
36
|
// watch local configs - revalidate on change
|
|
30
|
-
const configsPath = path.resolve(
|
|
37
|
+
const configsPath = path.resolve(fertConfigDir, './config/*.json');
|
|
31
38
|
chokidar.watch(configsPath, { ignoreInitial: true }).on('all', async () => {
|
|
32
|
-
await validateLocalConfigs(
|
|
39
|
+
await validateLocalConfigs({
|
|
40
|
+
workingDir: fertConfig.rootDir,
|
|
41
|
+
clientPropertyId: fertConfig.clientPropertyId,
|
|
42
|
+
throwable: false,
|
|
43
|
+
});
|
|
33
44
|
});
|
|
34
45
|
|
|
35
46
|
// watch brand.json & fert.config.js - refresh on change
|
|
@@ -38,10 +49,14 @@ module.exports = async (root, options = {}) => {
|
|
|
38
49
|
await buildTokens(fertConfig);
|
|
39
50
|
});
|
|
40
51
|
|
|
41
|
-
const configPath = path.resolve(
|
|
42
|
-
|
|
52
|
+
const configPath = path.resolve(fertConfigDir, FERT_CONFIG_FILENAME);
|
|
53
|
+
const serviceConfigPath = path.resolve(
|
|
54
|
+
fertConfig.workingDir,
|
|
55
|
+
FERT_SERVICE_CONFIG_FILENAME
|
|
56
|
+
);
|
|
57
|
+
chokidar.watch([configPath, serviceConfigPath]).on('change', async () => {
|
|
43
58
|
console.log('Config changed, reloading…');
|
|
44
|
-
fertConfig = await resolveConfig(
|
|
59
|
+
fertConfig = await resolveConfig(options);
|
|
45
60
|
await buildExternalAssets(fertConfig);
|
|
46
61
|
|
|
47
62
|
// update server's config & tell Vite to refresh the page
|
package/bin/commands/init.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/* eslint-disable no-unreachable */
|
|
1
2
|
const path = require('node:path');
|
|
2
3
|
const fs = require('node:fs');
|
|
3
4
|
const chalk = require('chalk');
|
|
@@ -7,12 +8,13 @@ const ora = require('ora');
|
|
|
7
8
|
const { cpidLookup } = require('../utils/cpid-lookup');
|
|
8
9
|
|
|
9
10
|
const { isEmptyDir, formatTargetDir, emptyDir } = require('../utils/index');
|
|
10
|
-
const { TEMPLATES } = require('../../constants');
|
|
11
|
+
const { TEMPLATES, FERT_CONFIG_FILENAME } = require('../../constants');
|
|
11
12
|
|
|
12
13
|
const cwd = process.cwd();
|
|
13
14
|
const templateList = TEMPLATES.map((i) => i.name);
|
|
14
15
|
|
|
15
16
|
module.exports = async (root, options = {}) => {
|
|
17
|
+
throw new Error('TODO: init new monorepo template');
|
|
16
18
|
const defaultProjectName = 'madgex-{cpid}';
|
|
17
19
|
const argTargetDir = root ? formatTargetDir(root) : null;
|
|
18
20
|
const argTemplate = options.template;
|
|
@@ -184,10 +186,13 @@ module.exports = async (root, options = {}) => {
|
|
|
184
186
|
|
|
185
187
|
// create fert.config.js
|
|
186
188
|
const fertConfigJs = fs.readFileSync(
|
|
187
|
-
path.join(outDir,
|
|
189
|
+
path.join(outDir, FERT_CONFIG_FILENAME),
|
|
188
190
|
'utf-8'
|
|
189
191
|
);
|
|
190
|
-
write(
|
|
192
|
+
write(
|
|
193
|
+
FERT_CONFIG_FILENAME,
|
|
194
|
+
fertConfigJs.replace(/{CPID}/gi, clientPropertyId)
|
|
195
|
+
);
|
|
191
196
|
|
|
192
197
|
const cdProjectName = path.relative(cwd, outDir);
|
|
193
198
|
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
const { SSMClient, GetParameterCommand } = require('@aws-sdk/client-ssm');
|
|
2
|
-
const { fromEnv } = require('@aws-sdk/credential-providers');
|
|
3
2
|
const { AWS_REGION } = require('../../../constants');
|
|
4
3
|
|
|
5
4
|
/**
|
|
@@ -17,7 +16,6 @@ const { AWS_REGION } = require('../../../constants');
|
|
|
17
16
|
const getAwsParam = async (paramName) => {
|
|
18
17
|
const client = new SSMClient({
|
|
19
18
|
region: AWS_REGION,
|
|
20
|
-
credentials: fromEnv(),
|
|
21
19
|
});
|
|
22
20
|
const input = {
|
|
23
21
|
Name: paramName,
|
|
@@ -32,7 +30,7 @@ const getAwsParam = async (paramName) => {
|
|
|
32
30
|
result = response.Parameter.Value;
|
|
33
31
|
} catch (error) {
|
|
34
32
|
console.log(`parameter-store error (${paramName}) ${error.message}`, error);
|
|
35
|
-
|
|
33
|
+
throw error;
|
|
36
34
|
}
|
|
37
35
|
|
|
38
36
|
return result;
|
package/bin/commands/publish.js
CHANGED
|
@@ -5,7 +5,10 @@ const { resolveConfig } = require('../utils');
|
|
|
5
5
|
const { log } = require('../utils/logging');
|
|
6
6
|
const getAwsParam = require('./publish-tasks/get-aws-parameter');
|
|
7
7
|
const AssetStoreUploader = require('./publish-tasks/asset-store-uploader');
|
|
8
|
-
const {
|
|
8
|
+
const {
|
|
9
|
+
updateProjectConfigs,
|
|
10
|
+
validateLocalConfigs,
|
|
11
|
+
} = require('../utils/configs.js');
|
|
9
12
|
const {
|
|
10
13
|
getCloudFrontDistributionsForDomain,
|
|
11
14
|
} = require('../utils/lookup-cf-distribution-ids');
|
|
@@ -18,8 +21,8 @@ const {
|
|
|
18
21
|
BRANDED_SITE_INVALIDATION_PATH,
|
|
19
22
|
} = require('../../constants');
|
|
20
23
|
|
|
21
|
-
module.exports = async (
|
|
22
|
-
const fertConfig = await resolveConfig(
|
|
24
|
+
module.exports = async (options) => {
|
|
25
|
+
const fertConfig = await resolveConfig(options);
|
|
23
26
|
const validTargets = ['dev', 'prod'];
|
|
24
27
|
|
|
25
28
|
if (!validTargets.includes(options.target)) {
|
|
@@ -28,6 +31,10 @@ module.exports = async (root, options) => {
|
|
|
28
31
|
);
|
|
29
32
|
}
|
|
30
33
|
|
|
34
|
+
if (options.dryRun) {
|
|
35
|
+
console.log(chalk.green('Running Publish in Dry Mode'));
|
|
36
|
+
}
|
|
37
|
+
|
|
31
38
|
const templateCtx = {
|
|
32
39
|
env: options.target === 'prod' ? 'job' : 'jb.dev',
|
|
33
40
|
Environment_Name: options.target === 'prod' ? 'production' : 'dev',
|
|
@@ -71,15 +78,26 @@ module.exports = async (root, options) => {
|
|
|
71
78
|
basePath: remoteBasePath,
|
|
72
79
|
});
|
|
73
80
|
|
|
74
|
-
|
|
75
|
-
|
|
81
|
+
let uploadResult;
|
|
82
|
+
if (!options.dryRun) {
|
|
83
|
+
uploadResult = await assetStore.uploadDir(localDir);
|
|
84
|
+
}
|
|
76
85
|
// validate & upload local configs to configuration API
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
86
|
+
if (!options.dryRun) {
|
|
87
|
+
await updateProjectConfigs({
|
|
88
|
+
workingDir: fertConfig.rootDir,
|
|
89
|
+
clientPropertyId: fertConfig.clientPropertyId,
|
|
90
|
+
environment: fertConfig.currBranch === 'master' ? 'production' : 'dev',
|
|
91
|
+
});
|
|
92
|
+
} else {
|
|
93
|
+
await validateLocalConfigs({
|
|
94
|
+
workingDir: fertConfig.rootDir,
|
|
95
|
+
clientPropertyId: fertConfig.clientPropertyId,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
80
98
|
|
|
81
99
|
log.success(
|
|
82
|
-
`Publish complete in ${(uploadResult
|
|
100
|
+
`Publish complete in ${((uploadResult?.duration || 1) / 1000).toFixed(0)}s\n`
|
|
83
101
|
);
|
|
84
102
|
|
|
85
103
|
// invalidate cloudfront cache
|
package/bin/utils/configs.js
CHANGED
|
@@ -9,17 +9,23 @@ const { CONFIG_DIR } = require('../../constants');
|
|
|
9
9
|
/**
|
|
10
10
|
* Validates the local configuration files against the provided schema.
|
|
11
11
|
*
|
|
12
|
-
* @param {Object}
|
|
13
|
-
* @param {
|
|
14
|
-
* @param {
|
|
12
|
+
* @param {Object} options - required, The options object.
|
|
13
|
+
* @param {string} options.workingDir - required, Working dir for configs - should usually be 'root' where fert.config.js is
|
|
14
|
+
* @param {string} options.clientPropertyId - required, clientPropertyId
|
|
15
|
+
* @param {boolean} options.throwable - default: true, Whether to throw validation errors or just log them.
|
|
15
16
|
*
|
|
16
17
|
* @returns {Promise<void>} - Returns a promise that resolves if all configurations are valid.
|
|
17
18
|
* @throws {Error} - Throws an error if any configuration is invalid.
|
|
18
19
|
*/
|
|
19
|
-
const validateLocalConfigs = async (
|
|
20
|
-
|
|
20
|
+
const validateLocalConfigs = async ({
|
|
21
|
+
workingDir,
|
|
22
|
+
clientPropertyId,
|
|
23
|
+
throwable = true,
|
|
24
|
+
}) => {
|
|
25
|
+
Hoek.assert(clientPropertyId, 'clientPropertyId is required');
|
|
26
|
+
Hoek.assert(workingDir, 'workingDir is required');
|
|
21
27
|
|
|
22
|
-
const dir = path.join(
|
|
28
|
+
const dir = path.join(workingDir, CONFIG_DIR);
|
|
23
29
|
if (!fs.existsSync(dir)) {
|
|
24
30
|
log.debug(`Directory does not exist: ${dir}`);
|
|
25
31
|
|
|
@@ -27,9 +33,9 @@ const validateLocalConfigs = async (fertConfig, options = { catch: true }) => {
|
|
|
27
33
|
}
|
|
28
34
|
|
|
29
35
|
const api = await getConfigAPI({
|
|
30
|
-
clientPropertyId
|
|
36
|
+
clientPropertyId,
|
|
31
37
|
});
|
|
32
|
-
const localConfigs = loadLocalConfigs(
|
|
38
|
+
const localConfigs = loadLocalConfigs(workingDir);
|
|
33
39
|
|
|
34
40
|
const validationPromises = Object.keys(localConfigs).map(
|
|
35
41
|
async (configName) => {
|
|
@@ -66,7 +72,7 @@ const validateLocalConfigs = async (fertConfig, options = { catch: true }) => {
|
|
|
66
72
|
} catch (err) {
|
|
67
73
|
log.error(err.message);
|
|
68
74
|
|
|
69
|
-
if (
|
|
75
|
+
if (throwable) {
|
|
70
76
|
throw err;
|
|
71
77
|
}
|
|
72
78
|
}
|
|
@@ -92,8 +98,14 @@ const getConfigAPI = async ({
|
|
|
92
98
|
return api;
|
|
93
99
|
};
|
|
94
100
|
|
|
95
|
-
|
|
96
|
-
|
|
101
|
+
/**
|
|
102
|
+
* loading all configs from a directory (usually root /configs folder)
|
|
103
|
+
*
|
|
104
|
+
* @param {string} workingDir
|
|
105
|
+
* @returns {Promise<Object>} - Object map of configs loaded from files {filename:configJSON,...}
|
|
106
|
+
*/
|
|
107
|
+
const loadLocalConfigs = (workingDir) => {
|
|
108
|
+
const dir = path.join(workingDir, CONFIG_DIR);
|
|
97
109
|
const configs = {};
|
|
98
110
|
const files = fs.readdirSync(dir);
|
|
99
111
|
|
|
@@ -125,17 +137,22 @@ const loadLocalConfigs = (fertConfig) => {
|
|
|
125
137
|
/**
|
|
126
138
|
* Handles the removal and setting of project configs in the Configuration API.
|
|
127
139
|
*
|
|
128
|
-
* @param {Object}
|
|
140
|
+
* @param {Object} options - required, The options object.
|
|
141
|
+
* @param {string} options.workingDir - required, Working dir for configs - should usually be 'root' where fert.config.js is
|
|
142
|
+
* @param {string} options.clientPropertyId - required, clientPropertyId
|
|
143
|
+
* @param {string} options.environment - default=production, clientPropertyId
|
|
129
144
|
*
|
|
130
145
|
* @returns {Promise<boolean>} - Returns a promise that resolves to true if the update is successful.
|
|
131
146
|
*/
|
|
132
|
-
const updateProjectConfigs = async (
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
147
|
+
const updateProjectConfigs = async ({
|
|
148
|
+
clientPropertyId,
|
|
149
|
+
workingDir,
|
|
150
|
+
environment = 'production',
|
|
151
|
+
} = {}) => {
|
|
152
|
+
Hoek.assert(clientPropertyId, 'clientPropertyId is required');
|
|
153
|
+
Hoek.assert(workingDir, 'workingDir is required');
|
|
137
154
|
|
|
138
|
-
const dir = path.join(
|
|
155
|
+
const dir = path.join(workingDir, CONFIG_DIR);
|
|
139
156
|
let spinner;
|
|
140
157
|
|
|
141
158
|
if (!fs.existsSync(dir)) {
|
|
@@ -145,15 +162,11 @@ const updateProjectConfigs = async (
|
|
|
145
162
|
}
|
|
146
163
|
|
|
147
164
|
try {
|
|
148
|
-
await validateLocalConfigs(
|
|
149
|
-
|
|
150
|
-
const api = await getConfigAPI({
|
|
151
|
-
clientPropertyId: fertConfig.clientPropertyId,
|
|
152
|
-
environment,
|
|
153
|
-
});
|
|
165
|
+
await validateLocalConfigs({ clientPropertyId, workingDir });
|
|
166
|
+
const api = await getConfigAPI({ clientPropertyId, environment });
|
|
154
167
|
spinner = ora('Fetching local configs').start();
|
|
155
168
|
|
|
156
|
-
const localConfigs = loadLocalConfigs(
|
|
169
|
+
const localConfigs = loadLocalConfigs(workingDir);
|
|
157
170
|
const { toRemove, toSet } = collateConfigs(api, localConfigs);
|
|
158
171
|
|
|
159
172
|
spinner.text = 'Updating configs…';
|
|
@@ -227,13 +240,12 @@ const getUnsetKeysWithDefaults = (defaults = {}, config = {}) => {
|
|
|
227
240
|
return diff;
|
|
228
241
|
};
|
|
229
242
|
|
|
230
|
-
const removeConfigs = async (api, toRemove
|
|
243
|
+
const removeConfigs = async (api, toRemove) => {
|
|
231
244
|
const deletePromises = Object.entries(toRemove).flatMap(
|
|
232
245
|
([configName, keys]) =>
|
|
233
246
|
keys.map(async (key) => {
|
|
234
247
|
try {
|
|
235
248
|
await api.deleteConfig(configName, key);
|
|
236
|
-
spinner.text = `Removed key ${chalk.bold(key)} from config: ${chalk.bold(configName)}`;
|
|
237
249
|
} catch (error) {
|
|
238
250
|
log.error('Failed to remove config', { configName, key, error });
|
|
239
251
|
}
|
|
@@ -243,12 +255,11 @@ const removeConfigs = async (api, toRemove, spinner) => {
|
|
|
243
255
|
await Promise.all(deletePromises);
|
|
244
256
|
};
|
|
245
257
|
|
|
246
|
-
const setConfigs = async (api, toSet
|
|
258
|
+
const setConfigs = async (api, toSet) => {
|
|
247
259
|
const setPromises = Object.entries(toSet).flatMap(([configName, config]) =>
|
|
248
260
|
Object.entries(config).map(async ([key, value]) => {
|
|
249
261
|
try {
|
|
250
262
|
await api.setConfig(configName, key, value);
|
|
251
|
-
spinner.text = `Set key ${chalk.bold(key)} for config: ${chalk.bold(configName)}`;
|
|
252
263
|
} catch (error) {
|
|
253
264
|
log.error('Failed to set config', { configName, key, value, error });
|
|
254
265
|
}
|
|
@@ -9,10 +9,14 @@ describe('configs', () => {
|
|
|
9
9
|
it('logs debug message if config directory does not exist', async (t) => {
|
|
10
10
|
const fsSpy = t.mock.method(fs, 'existsSync');
|
|
11
11
|
const logSpy = t.mock.method(log, 'debug');
|
|
12
|
-
const
|
|
12
|
+
const workingDir = '/non/existent/dir';
|
|
13
|
+
const clientPropertyId = 'cpid';
|
|
13
14
|
let calls;
|
|
14
15
|
|
|
15
|
-
await validateLocalConfigs(
|
|
16
|
+
await validateLocalConfigs({
|
|
17
|
+
workingDir,
|
|
18
|
+
clientPropertyId,
|
|
19
|
+
});
|
|
16
20
|
|
|
17
21
|
calls = fsSpy.mock.calls;
|
|
18
22
|
assert.strictEqual(calls[0].arguments[0], '/non/existent/dir/config');
|
|
@@ -28,10 +32,14 @@ describe('configs', () => {
|
|
|
28
32
|
it('logs debug message if config directory does not exist', async (t) => {
|
|
29
33
|
const fsSpy = t.mock.method(fs, 'existsSync');
|
|
30
34
|
const logSpy = t.mock.method(log, 'debug');
|
|
31
|
-
const
|
|
35
|
+
const workingDir = '/non/existent/dir';
|
|
36
|
+
const clientPropertyId = 'cpid';
|
|
32
37
|
let calls;
|
|
33
38
|
|
|
34
|
-
await updateProjectConfigs(
|
|
39
|
+
await updateProjectConfigs({
|
|
40
|
+
workingDir,
|
|
41
|
+
clientPropertyId,
|
|
42
|
+
});
|
|
35
43
|
|
|
36
44
|
calls = fsSpy.mock.calls;
|
|
37
45
|
assert.strictEqual(calls[0].arguments[0], '/non/existent/dir/config');
|
|
@@ -2,6 +2,7 @@ const simpleGit = require('simple-git');
|
|
|
2
2
|
const chalk = require('chalk');
|
|
3
3
|
const Hoek = require('@hapi/hoek');
|
|
4
4
|
const dedent = require('dedent');
|
|
5
|
+
const utils = require('./index.js');
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Validates client property ID found in the fert config against the git url and the package name.
|
|
@@ -17,13 +18,18 @@ const dedent = require('dedent');
|
|
|
17
18
|
*
|
|
18
19
|
*/
|
|
19
20
|
|
|
20
|
-
exports.cpIdMatchesGitRemote = async (fertConfig
|
|
21
|
-
|
|
21
|
+
exports.cpIdMatchesGitRemote = async (fertConfig) => {
|
|
22
|
+
const fertConfigDir = await utils.findFertConfigDir();
|
|
23
|
+
|
|
24
|
+
// Validate that the fert config directory is a git repository
|
|
22
25
|
const simpleGitOptions = {
|
|
23
26
|
baseDir: fertConfig.workingDir,
|
|
24
27
|
};
|
|
25
28
|
const git = simpleGit(simpleGitOptions);
|
|
26
|
-
Hoek.assert(
|
|
29
|
+
Hoek.assert(
|
|
30
|
+
await git.checkIsRepo(),
|
|
31
|
+
`detected fert config in${fertConfigDir}, but it is not a git repository`
|
|
32
|
+
);
|
|
27
33
|
|
|
28
34
|
// Validate that the CPID in the fert config matches the CPID in the git remote URL
|
|
29
35
|
try {
|
package/bin/utils/index.js
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
const path = require('node:path');
|
|
2
2
|
const fs = require('node:fs');
|
|
3
|
-
const {
|
|
3
|
+
const { glob } = require('node:fs/promises');
|
|
4
|
+
const {
|
|
5
|
+
VERSION,
|
|
6
|
+
FERT_CONFIG_FILENAME,
|
|
7
|
+
FERT_SERVICE_CONFIG_FILENAME,
|
|
8
|
+
} = require('../../constants');
|
|
4
9
|
const chalk = require('chalk');
|
|
5
10
|
const uuidValidator = require('uuid-validate');
|
|
6
11
|
const Hoek = require('@hapi/hoek');
|
|
@@ -31,9 +36,10 @@ exports.getConfig = async (cpid, configNames = []) => {
|
|
|
31
36
|
return result;
|
|
32
37
|
};
|
|
33
38
|
|
|
34
|
-
exports.resolveConfig = async (
|
|
39
|
+
exports.resolveConfig = async (options = {}) => {
|
|
35
40
|
const defaults = {
|
|
36
41
|
clientPropertyId: null,
|
|
42
|
+
serviceName: null,
|
|
37
43
|
entry: 'src/index.js',
|
|
38
44
|
externalAssets: {
|
|
39
45
|
links: [],
|
|
@@ -41,12 +47,22 @@ exports.resolveConfig = async (root, options = {}) => {
|
|
|
41
47
|
},
|
|
42
48
|
};
|
|
43
49
|
|
|
44
|
-
const
|
|
45
|
-
const git = simpleGit({ baseDir:
|
|
50
|
+
const rootDir = await this.findFertConfigDir();
|
|
51
|
+
const git = simpleGit({ baseDir: rootDir });
|
|
46
52
|
|
|
53
|
+
const serviceConfigs = await this.loadServiceConfigFiles();
|
|
54
|
+
|
|
55
|
+
// workingDir based on service folder
|
|
56
|
+
const { dir: workingDir, serviceConfig } = serviceConfigs.find(
|
|
57
|
+
(item) => item.serviceConfig.serviceName === options.serviceName
|
|
58
|
+
);
|
|
47
59
|
const fertConfig = Hoek.applyToDefaults(
|
|
48
60
|
defaults,
|
|
49
|
-
|
|
61
|
+
// merge fert config with service config
|
|
62
|
+
{
|
|
63
|
+
...this.loadConfigFromFile({ dir: rootDir }),
|
|
64
|
+
...serviceConfig,
|
|
65
|
+
}
|
|
50
66
|
);
|
|
51
67
|
|
|
52
68
|
// Check Fert cpid is valid
|
|
@@ -55,14 +71,16 @@ exports.resolveConfig = async (root, options = {}) => {
|
|
|
55
71
|
`Invalid clientPropertyId specified in fert.config.js: ${fertConfig.clientPropertyId}`
|
|
56
72
|
);
|
|
57
73
|
|
|
74
|
+
// Check Fert serviceName is valid
|
|
75
|
+
Hoek.assert(
|
|
76
|
+
typeof fertConfig.serviceName === 'string',
|
|
77
|
+
`serviceName required: ${fertConfig.serviceName}`
|
|
78
|
+
);
|
|
58
79
|
// get & store the current git branch
|
|
59
80
|
fertConfig.currBranch = (await git.branch()).current;
|
|
60
81
|
|
|
61
82
|
// Check Fert cpid matches git's remote
|
|
62
|
-
const fertCpidMatchesGitRemote = await cpIdMatchesGitRemote(
|
|
63
|
-
fertConfig,
|
|
64
|
-
workingDir
|
|
65
|
-
);
|
|
83
|
+
const fertCpidMatchesGitRemote = await cpIdMatchesGitRemote(fertConfig);
|
|
66
84
|
|
|
67
85
|
Hoek.assert(
|
|
68
86
|
fertCpidMatchesGitRemote,
|
|
@@ -78,11 +96,9 @@ exports.resolveConfig = async (root, options = {}) => {
|
|
|
78
96
|
// 'SiteName',
|
|
79
97
|
]);
|
|
80
98
|
|
|
81
|
-
fertConfig.assumeSite =
|
|
82
|
-
SITE[options.assumeSite] || SITE[fertConfig.assumeSite] || SITE.js;
|
|
83
|
-
|
|
84
99
|
return {
|
|
85
100
|
...fertConfig,
|
|
101
|
+
rootDir,
|
|
86
102
|
workingDir,
|
|
87
103
|
cli: {
|
|
88
104
|
...options,
|
|
@@ -91,9 +107,48 @@ exports.resolveConfig = async (root, options = {}) => {
|
|
|
91
107
|
};
|
|
92
108
|
};
|
|
93
109
|
|
|
94
|
-
|
|
95
|
-
|
|
110
|
+
/**
|
|
111
|
+
* Find the root fert config in this project. This is required to
|
|
112
|
+
* determine where our 'root' is for the project even if you're inside a service folder.
|
|
113
|
+
*
|
|
114
|
+
* @returns {string} closest fert config directory in this folder, or above
|
|
115
|
+
*/
|
|
116
|
+
exports.findFertConfigDir = async () => {
|
|
117
|
+
const { findUp } = await import('find-up-simple');
|
|
118
|
+
const fertConfigPath = await findUp(FERT_CONFIG_FILENAME);
|
|
119
|
+
if (!fertConfigPath) throw new Error(`couldn't find ${FERT_CONFIG_FILENAME}`);
|
|
120
|
+
return path.resolve(path.dirname(fertConfigPath));
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* find and load all service config files, return config and location
|
|
125
|
+
* @param {string} root directory path
|
|
126
|
+
* @returns
|
|
127
|
+
*/
|
|
128
|
+
exports.loadServiceConfigFiles = async () => {
|
|
129
|
+
const rootDir = await this.findFertConfigDir();
|
|
130
|
+
|
|
131
|
+
const serviceConfigs = [];
|
|
132
|
+
for await (const filePath of glob(`**/${FERT_SERVICE_CONFIG_FILENAME}`, {
|
|
133
|
+
cwd: path.resolve(rootDir),
|
|
134
|
+
exclude: (item) => ['node_modules', 'dist'].includes(item),
|
|
135
|
+
})) {
|
|
136
|
+
const dir = path.resolve(rootDir, path.dirname(filePath));
|
|
137
|
+
const serviceConfig = await this.loadConfigFromFile({
|
|
138
|
+
dir,
|
|
139
|
+
filename: FERT_SERVICE_CONFIG_FILENAME,
|
|
140
|
+
});
|
|
141
|
+
serviceConfigs.push({ dir, serviceConfig });
|
|
142
|
+
}
|
|
143
|
+
return serviceConfigs;
|
|
144
|
+
};
|
|
96
145
|
|
|
146
|
+
exports.loadConfigFromFile = ({
|
|
147
|
+
dir = process.cwd(),
|
|
148
|
+
filename = FERT_CONFIG_FILENAME,
|
|
149
|
+
silent = false,
|
|
150
|
+
}) => {
|
|
151
|
+
const configPath = path.resolve(dir, filename);
|
|
97
152
|
try {
|
|
98
153
|
// always return fresh config
|
|
99
154
|
if (require.cache[configPath]) {
|
|
@@ -104,13 +159,14 @@ exports.loadConfigFromFile = (workingDir) => {
|
|
|
104
159
|
|
|
105
160
|
return fertConfig;
|
|
106
161
|
} catch (err) {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
console.error(err);
|
|
162
|
+
if (!silent) {
|
|
163
|
+
log.error(
|
|
164
|
+
`Failed to load ${filename} - ensure you're running fert on a branding repo & config exists.\n`
|
|
165
|
+
);
|
|
112
166
|
|
|
113
|
-
|
|
167
|
+
console.error(err);
|
|
168
|
+
throw err;
|
|
169
|
+
}
|
|
114
170
|
}
|
|
115
171
|
};
|
|
116
172
|
|
package/constants.js
CHANGED
|
@@ -15,21 +15,40 @@ module.exports = {
|
|
|
15
15
|
VERSION,
|
|
16
16
|
TMP_DIR: fs.realpathSync(os.tmpdir()),
|
|
17
17
|
FERT_PACKAGE_DIR: path.resolve('./'),
|
|
18
|
-
DEFAULT_DEV_PORT: 3000,
|
|
19
18
|
AWS_REGION: 'eu-west-1',
|
|
20
19
|
PROPERTY_ID_API:
|
|
21
20
|
'https://property-identification-api.cs.madgexhosting.net/properties/',
|
|
22
21
|
ASSETS_API_URL: 'https://asset-store.job.madgexhosting.net',
|
|
23
22
|
BRAND_JSON_FILENAME: 'brand.json',
|
|
24
23
|
FERT_CONFIG_FILENAME: 'fert.config.js',
|
|
24
|
+
FERT_SERVICE_CONFIG_FILENAME: 'fert.service.config.js',
|
|
25
25
|
ASSET_STORE_API: 'https://asset-store-api.{env}.madgexhosting.net/',
|
|
26
26
|
UPLOAD_DIR: 'dist',
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
/**
|
|
28
|
+
* use `jobseekers-frontend` AWS keys for asset-store access, even for all services (e.g. recruiterservices-frontend).
|
|
29
|
+
* 💡 Technically this should be `fert-service` key. It does not make sense to create `recruiterservices-frontend` with access too.
|
|
30
|
+
* Possibly one to refactor in the future.
|
|
31
|
+
* */
|
|
29
32
|
AWS_PARAM_NAME:
|
|
30
33
|
'/{Environment_Name}/jobboard/kong/internal/consumer-key/jobseekers-frontend_{fertConfig.client.rootClientPropertyId}',
|
|
31
|
-
|
|
32
|
-
|
|
34
|
+
/**
|
|
35
|
+
* important to have the correct service name and cpid so assets are uploaded to the correct unique place per-service.
|
|
36
|
+
*/
|
|
37
|
+
REMOTE_UPLOAD_BASE:
|
|
38
|
+
'/api/assets/{fertConfig.serviceName}/{fertConfig.clientPropertyId}',
|
|
39
|
+
/**
|
|
40
|
+
* invalidates the real asset store key path, which is related to `REMOTE_UPLOAD_BASE`
|
|
41
|
+
* asset-store puts things in `rootClientPropertyId` based on the AWS_PARAM_NAME used, and the rest of the path relates to `REMOTE_UPLOAD_BASE`
|
|
42
|
+
*/
|
|
43
|
+
ASSET_STORE_INVALIDATION_PATH: `/{fertConfig.client.rootClientPropertyId}/{fertConfig.serviceName}/{fertConfig.clientPropertyId}/*`,
|
|
44
|
+
/**
|
|
45
|
+
* cloud backend cache busting for updated assets.
|
|
46
|
+
* assumption that assets are accessed on each service via a similar root.
|
|
47
|
+
* e.g.
|
|
48
|
+
* www.bigworkbag.com/_/jobseekers-frontend/assets/my-asset.png
|
|
49
|
+
* recruiter.bigworkbag.com/_/recruiterservices-frontend/assets/my-asset.png
|
|
50
|
+
*/
|
|
51
|
+
BRANDED_SITE_INVALIDATION_PATH: `/_/{fertConfig.serviceName}/assets/*`,
|
|
33
52
|
ASSET_STORE_USER_GUID: 'a386d4b6-f2df-4b80-ad1f-0349e23f530b',
|
|
34
53
|
TEMPLATES: [
|
|
35
54
|
{
|
|
@@ -41,10 +60,6 @@ module.exports = {
|
|
|
41
60
|
display: 'BigWorkBag - populated, example project',
|
|
42
61
|
},
|
|
43
62
|
],
|
|
44
|
-
SITE: {
|
|
45
|
-
js: 'jobseekers-frontend',
|
|
46
|
-
rs: 'recruiterservices-frontend',
|
|
47
|
-
},
|
|
48
63
|
ONE_MINUTE,
|
|
49
64
|
ONE_HOUR,
|
|
50
65
|
ONE_DAY,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@madgex/fert",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.0.1",
|
|
4
4
|
"description": "Tool to help build the V6 branding",
|
|
5
5
|
"bin": {
|
|
6
6
|
"fert": "./bin/cli.js"
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"url": "https://github.com/wiley/madgex-frontend-rollout-tool.git"
|
|
19
19
|
},
|
|
20
20
|
"engines": {
|
|
21
|
-
"node": ">=
|
|
21
|
+
"node": ">=22"
|
|
22
22
|
},
|
|
23
23
|
"author": "Madgex",
|
|
24
24
|
"license": "UNLICENSED",
|
|
@@ -31,16 +31,16 @@
|
|
|
31
31
|
"@hapi/inert": "^7.1.0",
|
|
32
32
|
"@hapi/vision": "^7.0.3",
|
|
33
33
|
"@hapipal/toys": "^4.0.0",
|
|
34
|
-
"@madgex/config-api-sdk": "^1.6.
|
|
34
|
+
"@madgex/config-api-sdk": "^1.6.2",
|
|
35
35
|
"@madgex/design-system": "^9.1.9",
|
|
36
36
|
"@private/header-footer-podlet-server": "github:wiley/madgex-header-footer-podlet",
|
|
37
37
|
"axios": "^1.7.9",
|
|
38
38
|
"cac": "^6.7.14",
|
|
39
39
|
"chalk": "4.1.2",
|
|
40
40
|
"chokidar": "^3.5.3",
|
|
41
|
-
"dayjs": "^1.11.13",
|
|
42
41
|
"debug": "^4.4.0",
|
|
43
42
|
"dedent": "^1.5.3",
|
|
43
|
+
"find-up-simple": "^1.0.0",
|
|
44
44
|
"flat-cache": "^4.0.0",
|
|
45
45
|
"form-data": "^4.0.1",
|
|
46
46
|
"lodash": "^4.17.21",
|
|
@@ -49,12 +49,12 @@
|
|
|
49
49
|
"ora": "5.4.1",
|
|
50
50
|
"prompts": "^2.4.2",
|
|
51
51
|
"rimraf": "^5.0.5",
|
|
52
|
-
"sass": "^1.83.
|
|
52
|
+
"sass": "^1.83.4",
|
|
53
53
|
"simple-git": "^3.27.0",
|
|
54
54
|
"simple-update-notifier": "^2.0.0",
|
|
55
55
|
"style-dictionary": "3.9.0",
|
|
56
56
|
"uuid-validate": "^0.0.3",
|
|
57
|
-
"vite": "^4.
|
|
57
|
+
"vite": "^4.5.9",
|
|
58
58
|
"vite-plugin-static-copy": "^0.17.1"
|
|
59
59
|
},
|
|
60
60
|
"devDependencies": {
|
package/server/index.js
CHANGED
|
@@ -3,17 +3,12 @@ const Toys = require('@hapipal/toys');
|
|
|
3
3
|
const path = require('node:path');
|
|
4
4
|
const { log } = require('../bin/utils/logging');
|
|
5
5
|
const podletServer = require('@private/header-footer-podlet-server');
|
|
6
|
-
const {
|
|
7
|
-
VERSION,
|
|
8
|
-
DEFAULT_DEV_PORT,
|
|
9
|
-
PROPERTY_ID_API,
|
|
10
|
-
ASSETS_API_URL,
|
|
11
|
-
} = require('../constants');
|
|
6
|
+
const { VERSION, PROPERTY_ID_API, ASSETS_API_URL } = require('../constants');
|
|
12
7
|
|
|
13
8
|
exports.devServer = async ({ start, fertConfig = {} } = {}) => {
|
|
14
9
|
const server = Hapi.server({
|
|
15
|
-
port:
|
|
16
|
-
host: fertConfig.cli
|
|
10
|
+
port: 0, // 0 === random unused port
|
|
11
|
+
host: fertConfig.cli?.host ?? 'localhost',
|
|
17
12
|
});
|
|
18
13
|
|
|
19
14
|
// "the path prefix used to locate static resources (files and view templates) when relative paths are used"
|
|
@@ -40,7 +35,7 @@ exports.devServer = async ({ start, fertConfig = {} } = {}) => {
|
|
|
40
35
|
templatePath: [path.resolve(fertConfig.workingDir, 'templates')],
|
|
41
36
|
clientPropertiesApi: PROPERTY_ID_API,
|
|
42
37
|
assetsApiUrl: ASSETS_API_URL,
|
|
43
|
-
layout: fertConfig.
|
|
38
|
+
layout: fertConfig.serviceName,
|
|
44
39
|
},
|
|
45
40
|
routes: { prefix: '/_podlet' },
|
|
46
41
|
});
|
|
@@ -56,6 +51,7 @@ exports.devServer = async ({ start, fertConfig = {} } = {}) => {
|
|
|
56
51
|
watch: ['/templates/**/*', '/public/tokens/**/*'].map((p) =>
|
|
57
52
|
path.join(fertConfig.workingDir, p)
|
|
58
53
|
),
|
|
54
|
+
root: path.resolve(fertConfig.workingDir),
|
|
59
55
|
entry: path.join(fertConfig.workingDir, fertConfig.entry),
|
|
60
56
|
log: log.debug,
|
|
61
57
|
},
|
|
@@ -82,7 +78,7 @@ exports.devServer = async ({ start, fertConfig = {} } = {}) => {
|
|
|
82
78
|
});
|
|
83
79
|
|
|
84
80
|
// routes
|
|
85
|
-
server.route(require('./routes/public'));
|
|
81
|
+
server.route(require('./routes/public')({ fertConfig }));
|
|
86
82
|
server.route(require('./routes/views'));
|
|
87
83
|
server.ext(require('./extensions/error-logging'));
|
|
88
84
|
|
package/server/routes/public.js
CHANGED
package/server/routes/views.js
CHANGED
|
@@ -16,7 +16,10 @@ module.exports = {
|
|
|
16
16
|
const podletResponse = await request.server.inject({
|
|
17
17
|
url: `/_podlet${request.path}`,
|
|
18
18
|
method: request.method,
|
|
19
|
-
headers:
|
|
19
|
+
headers: {
|
|
20
|
+
...filteredHeaders,
|
|
21
|
+
'podium-requested-by': fertConfig.serviceName,
|
|
22
|
+
},
|
|
20
23
|
auth: request.auth.isAuthenticated ? request.auth : null,
|
|
21
24
|
app: {
|
|
22
25
|
clientPropertyId: fertConfig.clientPropertyId,
|