@madgex/fert 4.2.2 → 4.2.5

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.
@@ -28,6 +28,9 @@ jobs:
28
28
  ssh-agent -a $SSH_AUTH_SOCK > /dev/null
29
29
  ssh-add - <<< "${{ secrets.MADGEX_GITHUB_SSH_KEY }}"
30
30
 
31
+ - name: Authenticate with private NPM package
32
+ run: echo "//registry.npmjs.org/:_authToken=${{ secrets.MADGEX_NPM_TOKEN }}" > /home/runner/.npmrc
33
+
31
34
  - name: Install dependencies
32
35
  run: npm ci
33
36
  env:
@@ -1,12 +1,8 @@
1
1
  const { resolveConfig } = require('../utils/index.js');
2
- const {
3
- getConfigAPI,
4
- validateLocalConfigs,
5
- updateProjectConfigs,
6
- } = require('../utils/configs.js');
2
+ const { getConfigAPI, updateProjectConfigs } = require('../utils/configs.js');
7
3
  const chalk = require('chalk');
8
4
  const { log } = require('../utils/logging.js');
9
- const { getSchemaKeyMeta } = require('../utils/getSchemaKeyMeta.js');
5
+ const { getSchemaMeta } = require('../utils/getSchemaMeta.js');
10
6
  const ora = require('ora');
11
7
  const { mkdir, writeFile } = require('node:fs/promises');
12
8
  const Path = require('node:path');
@@ -26,34 +22,34 @@ const handleDownload = async (fertConfig, api, options) => {
26
22
 
27
23
  try {
28
24
  for (const [schemaName, schema] of Object.entries(api.schemas)) {
29
- const configs = await api.getSchemaKeys(schemaName);
25
+ const schemaMeta = getSchemaMeta(api.getSchema(schemaName));
26
+
27
+ if (schemaMeta.readOnly) {
28
+ // skip read-only config files
29
+ continue;
30
+ }
31
+
32
+ const configs = api.getSchemaKeys(schemaName);
30
33
 
31
34
  for (const configKey of configs) {
32
- const meta = getSchemaKeyMeta(schema, configKey);
35
+ const meta = getSchemaMeta(schema, configKey);
33
36
  if (meta.readOnly) {
37
+ // skip read-only config keys
34
38
  continue;
35
39
  }
36
40
 
37
41
  try {
38
- const data = await api.getConfig(schemaName, configKey, {
39
- useSchema: false,
40
- });
41
- if (!data.isDefault) {
42
- let result = data.result;
43
- try {
44
- result = JSON.parse(result);
45
- } catch (error) {
46
- log.debug(
47
- `Unable to JSON parse ${chalk.cyan(data.response?.url || schemaName + '/' + configKey)} error:`,
48
- error.message
49
- );
50
- }
51
-
52
- resultObj[schemaName] = {
53
- ...result[schemaName],
54
- [configKey]: result,
55
- };
42
+ const data = await api.getConfig(schemaName, configKey);
43
+
44
+ if (data.isDefault) {
45
+ continue;
56
46
  }
47
+
48
+ if (!resultObj[schemaName]) {
49
+ resultObj[schemaName] = {};
50
+ }
51
+
52
+ resultObj[schemaName][configKey] = data.result;
57
53
  } catch (error) {
58
54
  log.debug(
59
55
  `Unable to retrieve ${chalk.cyan(error.url || schemaName + '/' + configKey)}`,
@@ -97,9 +93,7 @@ const handlePublish = async (fertConfig, api, options) => {
97
93
  }
98
94
 
99
95
  try {
100
- await validateLocalConfigs(fertConfig, { catch: false });
101
96
  await updateProjectConfigs(fertConfig, { environment: options.publish });
102
- console.log('upload');
103
97
  } catch (err) {
104
98
  log.debug(err);
105
99
  process.exit(1);
@@ -11,7 +11,7 @@ describe('configs', () => {
11
11
  let writeFileMock;
12
12
  let handleDownload;
13
13
  let handleQuery;
14
- let handlePublish;
14
+ let collateConfigs;
15
15
 
16
16
  beforeEach(async (t) => {
17
17
  fertConfig = {
@@ -26,6 +26,10 @@ describe('configs', () => {
26
26
  schemas: {
27
27
  'jsfe-config': Joi.object({
28
28
  StrConfig: Joi.string().default('My new JSFE config'),
29
+ NumConfig: Joi.number().default(0),
30
+ StrConfigReadOnly: Joi.string()
31
+ .default('I am read-only')
32
+ .meta({ readOnly: true }),
29
33
  }),
30
34
  },
31
35
  fetch: async () => ({
@@ -49,7 +53,10 @@ describe('configs', () => {
49
53
 
50
54
  // Clear require cache to ensure we get a fresh copy with our mocks
51
55
  delete require.cache[require.resolve('./configs')];
52
- ({ handleDownload, handleQuery, handlePublish } = require('./configs'));
56
+ ({ handleDownload, handleQuery } = require('./configs'));
57
+
58
+ delete require.cache[require.resolve('../utils/configs')];
59
+ ({ collateConfigs } = require('../utils/configs'));
53
60
  });
54
61
 
55
62
  describe('handleDownload', () => {
@@ -115,18 +122,30 @@ describe('configs', () => {
115
122
  });
116
123
  });
117
124
 
118
- describe.skip('handlePublish', () => {
119
- let options;
125
+ describe('handlePublish', () => {
126
+ it('sets and deletes correct configs', async () => {
127
+ const localConfigs = {
128
+ 'jsfe-config': {
129
+ path: '',
130
+ data: {
131
+ StrConfig: 'default value',
132
+ // NumConfig is purposefully omitted & should be deleted from the api
133
+ },
134
+ },
135
+ };
120
136
 
121
- beforeEach(async () => {
122
- options = { publish: true };
123
- });
137
+ const { toSet, toRemove } = await collateConfigs(api, localConfigs);
138
+
139
+ assert.deepStrictEqual(toSet, {
140
+ 'jsfe-config': {
141
+ StrConfig: 'default value',
142
+ },
143
+ });
124
144
 
125
- it('publishes the configs', async () => {
126
- await handlePublish(fertConfig, api, options);
145
+ assert.deepStrictEqual(toRemove, { 'jsfe-config': ['NumConfig'] });
127
146
  });
128
147
  });
129
- describe.skip('handleQuery', () => {
148
+ describe('handleQuery', () => {
130
149
  let options;
131
150
 
132
151
  beforeEach(async () => {
@@ -5,10 +5,7 @@ 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 {
9
- validateLocalConfigs,
10
- updateProjectConfigs,
11
- } = require('../utils/configs.js');
8
+ const { updateProjectConfigs } = require('../utils/configs.js');
12
9
  const {
13
10
  getCloudFrontDistributionsForDomain,
14
11
  } = require('../utils/lookup-cf-distribution-ids');
@@ -77,11 +74,9 @@ module.exports = async (root, options) => {
77
74
  const uploadResult = await assetStore.uploadDir(localDir);
78
75
 
79
76
  // validate & upload local configs to configuration API
80
- await validateLocalConfigs(fertConfig);
81
- await updateProjectConfigs(fertConfig),
82
- {
83
- environment: fertConfig.currBranch === 'master' ? 'production' : 'dev',
84
- };
77
+ await updateProjectConfigs(fertConfig, {
78
+ environment: fertConfig.currBranch === 'master' ? 'production' : 'dev',
79
+ });
85
80
 
86
81
  log.success(
87
82
  `Publish complete in ${(uploadResult.duration / 1000).toFixed(0)}s\n`
@@ -35,6 +35,15 @@ const validateLocalConfigs = async (fertConfig, options = { catch: true }) => {
35
35
  async (configName) => {
36
36
  try {
37
37
  const schema = api.getSchema(configName);
38
+ const isSchemaReadOnly = api._isSchemaReadOnly(schema);
39
+
40
+ if (isSchemaReadOnly) {
41
+ throw Error(
42
+ `You're trying to set values on a read-only (usually V5) config schema: ${configName}`
43
+ );
44
+ }
45
+
46
+ // check the entire config against the config validation
38
47
  const { error } = schema.validate(localConfigs[configName].data);
39
48
  if (error) {
40
49
  throw new Error(
@@ -42,6 +51,17 @@ const validateLocalConfigs = async (fertConfig, options = { catch: true }) => {
42
51
  );
43
52
  }
44
53
 
54
+ // validate all keys individually against the schema keys to ensure we're not trying to set any read-only keys
55
+ const config = localConfigs[configName].data;
56
+ for (const key in config) {
57
+ const keySchema = schema.extract(key);
58
+ if (api._isSchemaReadOnly(keySchema)) {
59
+ throw new Error(
60
+ `You're trying to set a read-only key on a config schema: ${configName}/${key}`
61
+ );
62
+ }
63
+ }
64
+
45
65
  log.success(`Config validated:`, chalk.bold(configName));
46
66
  } catch (err) {
47
67
  log.error(err.message);
@@ -125,6 +145,8 @@ const updateProjectConfigs = async (
125
145
  }
126
146
 
127
147
  try {
148
+ await validateLocalConfigs(fertConfig, { catch: false });
149
+
128
150
  const api = await getConfigAPI({
129
151
  clientPropertyId: fertConfig.clientPropertyId,
130
152
  environment,
@@ -154,12 +176,29 @@ const collateConfigs = (api, localConfigs) => {
154
176
 
155
177
  Object.entries(localConfigs).forEach(
156
178
  ([configName, { data: config = {} } = {}]) => {
179
+ const schema = api.getSchema(configName);
180
+ const isSchemaReadOnly = api._isSchemaReadOnly(schema);
181
+
182
+ if (isSchemaReadOnly) {
183
+ log.warn(`Skipping read-only schema: ${configName}`);
184
+ return;
185
+ }
186
+
157
187
  const configDefault = api._getDefaultConfig(configName);
158
188
 
159
189
  const unsetKeysWithDefaults = getUnsetKeysWithDefaults(
160
190
  configDefault,
161
191
  config
162
192
  );
193
+
194
+ // remove any keys that are read-only from the unset keys collection
195
+ // we don't want to be deleting those!
196
+ for (const key in unsetKeysWithDefaults) {
197
+ if (api._isSchemaReadOnly(schema.extract(key))) {
198
+ delete unsetKeysWithDefaults[key];
199
+ }
200
+ }
201
+
163
202
  toRemove[configName] = Object.keys(unsetKeysWithDefaults);
164
203
  toSet[configName] = config;
165
204
  }
@@ -217,4 +256,5 @@ module.exports = {
217
256
  getConfigAPI,
218
257
  validateLocalConfigs,
219
258
  updateProjectConfigs,
259
+ collateConfigs,
220
260
  };
@@ -0,0 +1,21 @@
1
+ const getSchemaMeta = function (schema, key = null) {
2
+ if (!schema || typeof schema.describe !== 'function') {
3
+ throw new Error('Schema is required');
4
+ }
5
+
6
+ try {
7
+ if (key) {
8
+ const keySchema = schema.extract(key);
9
+ if (!keySchema) {
10
+ throw new Error(`Key "${key}" not found in schema`);
11
+ }
12
+ return keySchema.describe().metas?.[0] || {};
13
+ }
14
+
15
+ return schema.describe().metas?.[0] || {};
16
+ } catch (error) {
17
+ throw new Error('Invalid Joi schema', { cause: error });
18
+ }
19
+ };
20
+
21
+ module.exports = { getSchemaMeta };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@madgex/fert",
3
- "version": "4.2.2",
3
+ "version": "4.2.5",
4
4
  "description": "Tool to help build the V6 branding",
5
5
  "bin": {
6
6
  "fert": "./bin/cli.js"
@@ -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.0",
34
+ "@madgex/config-api-sdk": "^1.6.1",
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",
@@ -69,6 +69,7 @@
69
69
  "eslint-plugin-prettier": "^5.2.1",
70
70
  "globals": "^15.14.0",
71
71
  "husky": "^9.1.7",
72
+ "joi": "^17.13.3",
72
73
  "lint-staged": "^15.3.0",
73
74
  "prettier": "^3.4.2",
74
75
  "semantic-release": "^24.2.0"
@@ -1,14 +0,0 @@
1
- const getSchemaKeyMeta = function (schema, key) {
2
- if (!schema) throw new Error('Schema is required');
3
- if (!key) throw new Error('Key is required');
4
-
5
- const keySchema = schema.extract(key);
6
- if (!keySchema) {
7
- throw new Error(`Key "${key}" not found in schema`);
8
- }
9
-
10
- const metas = keySchema.describe().metas;
11
- return Array.isArray(metas) && metas.length > 0 ? metas[0] : {};
12
- };
13
-
14
- module.exports = { getSchemaKeyMeta };