@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.
- package/.github/workflows/release.yml +3 -0
- package/bin/commands/configs.js +22 -28
- package/bin/commands/configs.test.js +29 -10
- package/bin/commands/publish.js +4 -9
- package/bin/utils/configs.js +40 -0
- package/bin/utils/getSchemaMeta.js +21 -0
- package/package.json +3 -2
- package/bin/utils/getSchemaKeyMeta.js +0 -14
|
@@ -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:
|
package/bin/commands/configs.js
CHANGED
|
@@ -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 {
|
|
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
|
|
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 =
|
|
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
|
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
|
|
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
|
|
119
|
-
|
|
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
|
-
|
|
122
|
-
|
|
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
|
-
|
|
126
|
-
await handlePublish(fertConfig, api, options);
|
|
145
|
+
assert.deepStrictEqual(toRemove, { 'jsfe-config': ['NumConfig'] });
|
|
127
146
|
});
|
|
128
147
|
});
|
|
129
|
-
describe
|
|
148
|
+
describe('handleQuery', () => {
|
|
130
149
|
let options;
|
|
131
150
|
|
|
132
151
|
beforeEach(async () => {
|
package/bin/commands/publish.js
CHANGED
|
@@ -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
|
|
81
|
-
|
|
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`
|
package/bin/utils/configs.js
CHANGED
|
@@ -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.
|
|
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.
|
|
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 };
|