@madgex/fert 4.2.6 → 5.0.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 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 **three required files** in the root of the branding repo:
45
+ There are **two required files** in the root of the branding repo, and **two required files** in the service folder:
46
46
 
47
- `brand.json` [required] – a JSON file 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)
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
- `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:
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
- | `clientPropertyId` <String> | The `clientPropertyId` associated with the branding repo (required) |
69
- | `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']` |
70
- | `assumeSite` <String> | Which site we want to display, jobseeker site or recruiter site. Values are either `js` or `rs`. |
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
- clientPropertyId: 'ff6102ff-0f4b-43d1-a2c7-83b835b8dee5',
94
+ name: 'jobseekers-frontend',
79
95
  entry: './src/index.js',
80
- assumeSite: 'js',
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
- `fert`
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 in the current directory.
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 [root]`
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 [root] --only [tokens | assets]`
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 [root] --target [target]`
209
+ `fert publish --target [target]`
180
210
 
181
- | Option | |
182
- | ---------- | ---------------------------------------------------------------------------- |
183
- | `--target` | Where to publish the `dist` directory assets.<br/>`dev` \| `prod` (`string`) |
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 [root] --[query|download|publish] [env]`
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
- '[root]',
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
- '--assume-site [site]',
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 [root]',
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
- .action((...args) => require('./commands/build.js')(...args));
35
+ .option('--service-name <serviceName>', '[string] run a single service')
36
+ .action((...args) => serivceCommandBootstrap('build', ...args));
35
37
 
36
38
  cli
37
- .command('publish [root]', 'Publish the project')
39
+ .command('publish', 'Publish the project')
38
40
  .option('--target <env>', 'Environment to publish to, "dev" or "prod"')
39
- .action((...args) => require('./commands/publish')(...args));
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 [root]', 'Query/Publish project 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: '/_/jobseekers-frontend/assets/',
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'),
@@ -7,9 +7,15 @@ 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 (root, options = {}) => {
11
- const fertConfig = await resolveConfig(root, options);
12
- await validateLocalConfigs(fertConfig, { catch: false });
10
+ module.exports = async (options = {}) => {
11
+ const fertConfig = await resolveConfig(options);
12
+ await validateLocalConfigs(
13
+ {
14
+ workingDir: fertConfig.rootDir,
15
+ clientPropertyId: fertConfig.clientPropertyId,
16
+ },
17
+ { throwable: true }
18
+ );
13
19
 
14
20
  if (!options.only) {
15
21
  await rimraf(path.resolve(fertConfig.workingDir, 'dist'));
@@ -1,4 +1,4 @@
1
- const { resolveConfig } = require('../utils/index.js');
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 (fertConfig, api, options) => {
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 (fertConfig, api, options) => {
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(fertConfig, { environment: options.publish });
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 (fertConfig, api, options) => {
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 (root, options) => {
145
- const fertConfig = await resolveConfig(root, options);
146
- const api = await getConfigAPI(fertConfig);
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(fertConfig, api, options);
176
+ await handleDownload(
177
+ { clientPropertyId, workingDir: fertConfigDir },
178
+ api,
179
+ options
180
+ );
157
181
  } else if (options.publish) {
158
- await handlePublish(fertConfig, api, options);
182
+ await handlePublish(
183
+ { clientPropertyId, workingDir: fertConfigDir },
184
+ api,
185
+ options
186
+ );
159
187
  } else if (options.query) {
160
- await handleQuery(fertConfig, api, options);
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,15 +10,16 @@ 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 (root, options = {}) => {
16
- let fertConfig = await resolveConfig(root, options);
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.assumeSite}] ${chalk.dim(
22
+ )} [${fertConfig.serviceName}] ${chalk.dim(
22
23
  `(${fertConfig.clientPropertyId})`
23
24
  )}\n`
24
25
  );
@@ -26,8 +27,10 @@ module.exports = async (root, options = {}) => {
26
27
  await validateLocalConfigs(fertConfig);
27
28
  await buildTokens(fertConfig);
28
29
 
30
+ const fertConfigDir = await findFertConfigDir();
31
+
29
32
  // watch local configs - revalidate on change
30
- const configsPath = path.resolve(fertConfig.workingDir, './config/*.json');
33
+ const configsPath = path.resolve(fertConfigDir, './config/*.json');
31
34
  chokidar.watch(configsPath, { ignoreInitial: true }).on('all', async () => {
32
35
  await validateLocalConfigs(fertConfig);
33
36
  });
@@ -38,10 +41,14 @@ module.exports = async (root, options = {}) => {
38
41
  await buildTokens(fertConfig);
39
42
  });
40
43
 
41
- const configPath = path.resolve(fertConfig.workingDir, FERT_CONFIG_FILENAME);
42
- chokidar.watch(configPath).on('change', async () => {
44
+ const configPath = path.resolve(fertConfigDir, FERT_CONFIG_FILENAME);
45
+ const serviceConfigPath = path.resolve(
46
+ fertConfig.workingDir,
47
+ FERT_SERVICE_CONFIG_FILENAME
48
+ );
49
+ chokidar.watch([configPath, serviceConfigPath]).on('change', async () => {
43
50
  console.log('Config changed, reloading…');
44
- fertConfig = await resolveConfig(root, options);
51
+ fertConfig = await resolveConfig(options);
45
52
  await buildExternalAssets(fertConfig);
46
53
 
47
54
  // update server's config & tell Vite to refresh the page
@@ -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, 'fert.config.js'),
189
+ path.join(outDir, FERT_CONFIG_FILENAME),
188
190
  'utf-8'
189
191
  );
190
- write('fert.config.js', fertConfigJs.replace(/{CPID}/gi, clientPropertyId));
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
- process.exit(1);
33
+ throw error;
36
34
  }
37
35
 
38
36
  return result;
@@ -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 { updateProjectConfigs } = require('../utils/configs.js');
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 (root, options) => {
22
- const fertConfig = await resolveConfig(root, options);
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,27 @@ module.exports = async (root, options) => {
71
78
  basePath: remoteBasePath,
72
79
  });
73
80
 
74
- const uploadResult = await assetStore.uploadDir(localDir);
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
- await updateProjectConfigs(fertConfig, {
78
- environment: fertConfig.currBranch === 'master' ? 'production' : 'dev',
79
- });
86
+ if (!options.dryRun) {
87
+ await updateProjectConfigs(fertConfig, {
88
+ environment: fertConfig.currBranch === 'master' ? 'production' : 'dev',
89
+ });
90
+ } else {
91
+ await validateLocalConfigs(
92
+ {
93
+ workingDir: fertConfig.rootDir,
94
+ clientPropertyId: fertConfig.clientPropertyId,
95
+ },
96
+ { throwable: true }
97
+ );
98
+ }
80
99
 
81
100
  log.success(
82
- `Publish complete in ${(uploadResult.duration / 1000).toFixed(0)}s\n`
101
+ `Publish complete in ${((uploadResult?.duration || 1) / 1000).toFixed(0)}s\n`
83
102
  );
84
103
 
85
104
  // invalidate cloudfront cache
@@ -16,10 +16,15 @@ const { CONFIG_DIR } = require('../../constants');
16
16
  * @returns {Promise<void>} - Returns a promise that resolves if all configurations are valid.
17
17
  * @throws {Error} - Throws an error if any configuration is invalid.
18
18
  */
19
- const validateLocalConfigs = async (fertConfig, options = { catch: true }) => {
20
- Hoek.assert(fertConfig, 'fertConfig is required');
19
+ const validateLocalConfigs = async ({
20
+ workingDir,
21
+ clientPropertyId,
22
+ throwable = false,
23
+ }) => {
24
+ Hoek.assert(clientPropertyId, 'clientPropertyId is required');
25
+ Hoek.assert(workingDir, 'workingDir is required');
21
26
 
22
- const dir = path.join(fertConfig.workingDir, CONFIG_DIR);
27
+ const dir = path.join(workingDir, CONFIG_DIR);
23
28
  if (!fs.existsSync(dir)) {
24
29
  log.debug(`Directory does not exist: ${dir}`);
25
30
 
@@ -27,9 +32,9 @@ const validateLocalConfigs = async (fertConfig, options = { catch: true }) => {
27
32
  }
28
33
 
29
34
  const api = await getConfigAPI({
30
- clientPropertyId: fertConfig.clientPropertyId,
35
+ clientPropertyId,
31
36
  });
32
- const localConfigs = loadLocalConfigs(fertConfig);
37
+ const localConfigs = loadLocalConfigs(workingDir);
33
38
 
34
39
  const validationPromises = Object.keys(localConfigs).map(
35
40
  async (configName) => {
@@ -66,7 +71,7 @@ const validateLocalConfigs = async (fertConfig, options = { catch: true }) => {
66
71
  } catch (err) {
67
72
  log.error(err.message);
68
73
 
69
- if (!options.catch) {
74
+ if (throwable) {
70
75
  throw err;
71
76
  }
72
77
  }
@@ -92,8 +97,8 @@ const getConfigAPI = async ({
92
97
  return api;
93
98
  };
94
99
 
95
- const loadLocalConfigs = (fertConfig) => {
96
- const dir = path.join(fertConfig.workingDir, CONFIG_DIR);
100
+ const loadLocalConfigs = (workingDir) => {
101
+ const dir = path.join(workingDir, CONFIG_DIR);
97
102
  const configs = {};
98
103
  const files = fs.readdirSync(dir);
99
104
 
@@ -129,13 +134,14 @@ const loadLocalConfigs = (fertConfig) => {
129
134
  *
130
135
  * @returns {Promise<boolean>} - Returns a promise that resolves to true if the update is successful.
131
136
  */
132
- const updateProjectConfigs = async (
133
- fertConfig,
134
- { environment = 'production' } = {}
135
- ) => {
136
- Hoek.assert(fertConfig, 'fertConfig is required');
137
+ const updateProjectConfigs = async ({
138
+ clientPropertyId,
139
+ workingDir,
140
+ environment = 'production',
141
+ } = {}) => {
142
+ Hoek.assert(clientPropertyId, 'clientPropertyId is required');
137
143
 
138
- const dir = path.join(fertConfig.workingDir, CONFIG_DIR);
144
+ const dir = path.join(workingDir, CONFIG_DIR);
139
145
  let spinner;
140
146
 
141
147
  if (!fs.existsSync(dir)) {
@@ -145,15 +151,14 @@ const updateProjectConfigs = async (
145
151
  }
146
152
 
147
153
  try {
148
- await validateLocalConfigs(fertConfig, { catch: false });
149
-
150
- const api = await getConfigAPI({
151
- clientPropertyId: fertConfig.clientPropertyId,
152
- environment,
153
- });
154
+ await validateLocalConfigs(
155
+ { clientPropertyId, workingDir },
156
+ { catch: false }
157
+ );
158
+ const api = await getConfigAPI({ clientPropertyId, environment });
154
159
  spinner = ora('Fetching local configs').start();
155
160
 
156
- const localConfigs = loadLocalConfigs(fertConfig);
161
+ const localConfigs = loadLocalConfigs(workingDir);
157
162
  const { toRemove, toSet } = collateConfigs(api, localConfigs);
158
163
 
159
164
  spinner.text = 'Updating configs…';
@@ -227,13 +232,12 @@ const getUnsetKeysWithDefaults = (defaults = {}, config = {}) => {
227
232
  return diff;
228
233
  };
229
234
 
230
- const removeConfigs = async (api, toRemove, spinner) => {
235
+ const removeConfigs = async (api, toRemove) => {
231
236
  const deletePromises = Object.entries(toRemove).flatMap(
232
237
  ([configName, keys]) =>
233
238
  keys.map(async (key) => {
234
239
  try {
235
240
  await api.deleteConfig(configName, key);
236
- spinner.text = `Removed key ${chalk.bold(key)} from config: ${chalk.bold(configName)}`;
237
241
  } catch (error) {
238
242
  log.error('Failed to remove config', { configName, key, error });
239
243
  }
@@ -243,12 +247,11 @@ const removeConfigs = async (api, toRemove, spinner) => {
243
247
  await Promise.all(deletePromises);
244
248
  };
245
249
 
246
- const setConfigs = async (api, toSet, spinner) => {
250
+ const setConfigs = async (api, toSet) => {
247
251
  const setPromises = Object.entries(toSet).flatMap(([configName, config]) =>
248
252
  Object.entries(config).map(async ([key, value]) => {
249
253
  try {
250
254
  await api.setConfig(configName, key, value);
251
- spinner.text = `Set key ${chalk.bold(key)} for config: ${chalk.bold(configName)}`;
252
255
  } catch (error) {
253
256
  log.error('Failed to set config', { configName, key, value, error });
254
257
  }
@@ -9,7 +9,10 @@ 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 fertConfig = { workingDir: '/non/existent/dir' };
12
+ const fertConfig = {
13
+ workingDir: '/non/existent/dir',
14
+ clientPropertyId: 'cpid',
15
+ };
13
16
  let calls;
14
17
 
15
18
  await validateLocalConfigs(fertConfig);
@@ -28,7 +31,10 @@ describe('configs', () => {
28
31
  it('logs debug message if config directory does not exist', async (t) => {
29
32
  const fsSpy = t.mock.method(fs, 'existsSync');
30
33
  const logSpy = t.mock.method(log, 'debug');
31
- const fertConfig = { workingDir: '/non/existent/dir' };
34
+ const fertConfig = {
35
+ workingDir: '/non/existent/dir',
36
+ clientPropertyId: 'cpid',
37
+ };
32
38
  let calls;
33
39
 
34
40
  await updateProjectConfigs(fertConfig);
@@ -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, workingDir) => {
21
- // Validate that the working directory is a git repository
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(await git.checkIsRepo(), `${workingDir} is not a git repository`);
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 {
@@ -1,6 +1,11 @@
1
1
  const path = require('node:path');
2
2
  const fs = require('node:fs');
3
- const { VERSION, SITE } = require('../../constants');
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 (root, options = {}) => {
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 workingDir = root ? path.resolve(root) : path.resolve(process.cwd());
45
- const git = simpleGit({ baseDir: workingDir });
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
- this.loadConfigFromFile(workingDir)
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
- exports.loadConfigFromFile = (workingDir) => {
95
- const configPath = path.resolve(workingDir, 'fert.config.js');
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
- log.error(
108
- `Failed to load fert.config.js - ensure you're running fert on a branding repo & config exists.\n`
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
- process.exit(1);
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
- REMOTE_UPLOAD_BASE:
28
- '/api/assets/jobseekers-frontend/{fertConfig.clientPropertyId}',
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
- ASSET_STORE_INVALIDATION_PATH: `/{fertConfig.client.rootClientPropertyId}/jobseekers-frontend/{fertConfig.clientPropertyId}/*`,
32
- BRANDED_SITE_INVALIDATION_PATH: `/_/jobseekers-frontend/assets/*`,
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": "4.2.6",
3
+ "version": "5.0.0",
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": ">=18.20.0"
21
+ "node": ">=22"
22
22
  },
23
23
  "author": "Madgex",
24
24
  "license": "UNLICENSED",
@@ -31,7 +31,7 @@
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.1",
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",
@@ -41,6 +41,7 @@
41
41
  "dayjs": "^1.11.13",
42
42
  "debug": "^4.4.0",
43
43
  "dedent": "^1.5.3",
44
+ "find-up-simple": "^1.0.0",
44
45
  "flat-cache": "^4.0.0",
45
46
  "form-data": "^4.0.1",
46
47
  "lodash": "^4.17.21",
@@ -49,12 +50,12 @@
49
50
  "ora": "5.4.1",
50
51
  "prompts": "^2.4.2",
51
52
  "rimraf": "^5.0.5",
52
- "sass": "^1.83.0",
53
+ "sass": "^1.83.4",
53
54
  "simple-git": "^3.27.0",
54
55
  "simple-update-notifier": "^2.0.0",
55
56
  "style-dictionary": "3.9.0",
56
57
  "uuid-validate": "^0.0.3",
57
- "vite": "^4.4.9",
58
+ "vite": "^4.5.9",
58
59
  "vite-plugin-static-copy": "^0.17.1"
59
60
  },
60
61
  "devDependencies": {
@@ -1,5 +1,6 @@
1
1
  module.exports = {
2
2
  clientPropertyId: '{CPID}',
3
+ serviceName: null,
3
4
  entry: './src/index.js',
4
5
  externalAssets: {
5
6
  links: [],
@@ -2,4 +2,5 @@
2
2
  .tmp
3
3
  node_modules
4
4
  .DS_Store
5
- dist
5
+ dist
6
+ **/public/tokens
@@ -2,4 +2,5 @@
2
2
  .tmp
3
3
  node_modules
4
4
  .DS_Store
5
- dist
5
+ dist
6
+ **/public/tokens
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: fertConfig.cli.port ?? DEFAULT_DEV_PORT,
16
- host: fertConfig.cli.host ?? 'localhost',
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.assumeSite,
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
 
@@ -21,6 +21,7 @@ module.exports = {
21
21
  type: 'onPostStart',
22
22
  method: async (server) => {
23
23
  server.app.viteServer = await createServer({
24
+ root: options.root,
24
25
  server: {
25
26
  origin: server.info.uri,
26
27
  watch: {
@@ -1,7 +1,7 @@
1
- module.exports = [
1
+ module.exports = ({ fertConfig }) => [
2
2
  {
3
3
  method: 'get',
4
- path: '/_/jobseekers-frontend/assets/{p*}',
4
+ path: `/_/${fertConfig.serviceName}/assets/{p*}`,
5
5
  handler: {
6
6
  directory: {
7
7
  path: ['./dist', './public'],
@@ -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: filteredHeaders,
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,
@@ -1,4 +1,4 @@
1
- FROM node:18-alpine
1
+ FROM node:22-alpine
2
2
 
3
3
  ENV HOME=/app/
4
4