Skip to content

Commit 7eead66

Browse files
committed
MUSE-1252 added MSP mgmt API
1 parent b2a6e52 commit 7eead66

14 files changed

Lines changed: 664 additions & 3 deletions

workspace/packages/muse-core/lib/index.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ var originalRequire = Module.prototype.require;
55

66
// Ensure @ebay/muse-core only has one instance
77
const __museCoreSingleton = module.exports;
8-
Module.prototype.require = function() {
8+
Module.prototype.require = function () {
99
if (arguments[0] === '@ebay/muse-core') return __museCoreSingleton;
1010
return originalRequire.apply(this, arguments);
1111
};
@@ -18,7 +18,7 @@ const plugin = require('js-plugin');
1818
const envFile1 = path.join(process.cwd(), '.muse.env');
1919
const envFile2 = path.join(os.homedir(), '.muse.env');
2020

21-
[envFile1, envFile2].some(envFile => {
21+
[envFile1, envFile2].some((envFile) => {
2222
if (fs.existsSync(envFile)) {
2323
require('dotenv').config({ path: envFile });
2424
return true;
@@ -29,7 +29,7 @@ const config = require('./config');
2929

3030
module.exports.config = config;
3131
module.exports.logger = require('./logger');
32-
module.exports.registerPlugin = p => {
32+
module.exports.registerPlugin = (p) => {
3333
if (config.__pluginLoaded) {
3434
throw new Error(
3535
`You can only register a plugin before initialization. Usually you should register a plugin in the global scope in your code.`,
@@ -47,6 +47,7 @@ module.exports.pm = require('./pm');
4747
module.exports.req = require('./req');
4848
module.exports.data = require('./data');
4949
module.exports.storage = require('./storage');
50+
module.exports.msp = require('./msp');
5051
module.exports.utils = require('./utils');
5152
module.exports.plugin = plugin;
5253

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
const { asyncInvoke, osUsername, validate } = require('../utils');
2+
const { registry } = require('../storage');
3+
const getMsp = require('./getMsp');
4+
const schema = require('../schemas/msp/addPreset.json');
5+
const logger = require('../logger').createLogger('muse.msp.addPreset');
6+
7+
/**
8+
* @module muse-core/msp/addPreset
9+
*/
10+
11+
/**
12+
* @description Add a new preset to /msp.yaml.
13+
* @param {object} params
14+
* @param {string} params.name The preset name.
15+
* @param {object} params.preset The preset object.
16+
* @param {string} [params.preset.extends] Parent preset name.
17+
* @param {object} [params.preset.versions] Package versions for this preset.
18+
* @param {string} [params.author=osUsername]
19+
* @param {string} [params.msg] Commit message.
20+
* @returns {object} The created preset.
21+
*/
22+
module.exports = async (params = {}) => {
23+
validate(schema, params);
24+
const ctx = {};
25+
if (!params.author) params.author = osUsername;
26+
const { name, preset, author, msg } = params;
27+
logger.info(`Adding preset ${name}...`);
28+
await asyncInvoke('museCore.msp.beforeAddPreset', ctx, params);
29+
30+
let msp = await getMsp();
31+
if (!msp) msp = {};
32+
if (msp[name]) throw new Error(`Preset ${name} already exists.`);
33+
34+
ctx.preset = {
35+
creation: new Date().toJSON(),
36+
...preset,
37+
};
38+
39+
try {
40+
msp[name] = ctx.preset;
41+
await asyncInvoke('museCore.msp.addPreset', ctx, params);
42+
await registry.setYaml('/msp.yaml', msp, msg || `Add preset ${name} by ${author}`);
43+
} catch (err) {
44+
ctx.error = err;
45+
await asyncInvoke('museCore.msp.failedAddPreset', ctx, params);
46+
throw err;
47+
}
48+
await asyncInvoke('museCore.msp.afterAddPreset', ctx, params);
49+
logger.info(`Add preset success: ${name}.`);
50+
return ctx.preset;
51+
};
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
const { vol } = require('memfs');
2+
const plugin = require('js-plugin');
3+
const muse = require('../');
4+
const { registry } = require('../storage');
5+
6+
const testJsPlugin = {
7+
name: 'test-addPreset',
8+
museCore: {
9+
msp: {
10+
addPreset: jest.fn(),
11+
beforeAddPreset: jest.fn(),
12+
afterAddPreset: jest.fn(),
13+
},
14+
},
15+
};
16+
plugin.register(testJsPlugin);
17+
18+
describe('addPreset tests.', () => {
19+
beforeEach(() => {
20+
vol.reset();
21+
});
22+
23+
it('should create msp.yaml and add preset when file does not exist', async () => {
24+
const preset = await muse.msp.addPreset({
25+
name: 'default',
26+
preset: { versions: { '@ebay/muse-core': '1.0.45' } },
27+
author: 'nate',
28+
});
29+
30+
expect(preset).toMatchObject({ versions: { '@ebay/muse-core': '1.0.45' } });
31+
expect(preset.creation).toBeDefined();
32+
33+
const msp = await registry.getJsonByYaml('/msp.yaml');
34+
expect(msp.default).toMatchObject({ versions: { '@ebay/muse-core': '1.0.45' } });
35+
36+
expect(testJsPlugin.museCore.msp.addPreset).toBeCalledTimes(1);
37+
expect(testJsPlugin.museCore.msp.beforeAddPreset).toBeCalledTimes(1);
38+
expect(testJsPlugin.museCore.msp.afterAddPreset).toBeCalledTimes(1);
39+
});
40+
41+
it('should add preset to existing msp.yaml', async () => {
42+
await registry.set('/msp.yaml', 'default:\n versions:\n "@ebay/muse-core": "1.0.45"\n');
43+
44+
await muse.msp.addPreset({
45+
name: 'muse-react',
46+
preset: { extends: 'default', versions: { '@ebay/muse-lib-react': '2.0.3' } },
47+
author: 'nate',
48+
});
49+
50+
const msp = await registry.getJsonByYaml('/msp.yaml');
51+
expect(Object.keys(msp)).toEqual(['default', 'muse-react']);
52+
expect(msp['muse-react'].extends).toBe('default');
53+
});
54+
55+
it('should throw if preset already exists', async () => {
56+
await registry.set('/msp.yaml', 'default:\n versions: {}\n');
57+
58+
try {
59+
await muse.msp.addPreset({ name: 'default', preset: {}, author: 'nate' });
60+
expect(true).toBe(false);
61+
} catch (err) {
62+
expect(err.message).toMatch('already exists');
63+
}
64+
});
65+
66+
it('should throw and invoke failedAddPreset on error', async () => {
67+
const testJsPluginFails = {
68+
name: 'test-addPreset-fail',
69+
museCore: {
70+
msp: {
71+
addPreset: jest.fn().mockRejectedValue(new Error('storage error')),
72+
beforeAddPreset: jest.fn(),
73+
afterAddPreset: jest.fn(),
74+
failedAddPreset: jest.fn(),
75+
},
76+
},
77+
};
78+
plugin.register(testJsPluginFails);
79+
80+
try {
81+
await muse.msp.addPreset({ name: 'new-preset', preset: {}, author: 'nate' });
82+
expect(true).toBe(false);
83+
} catch (e) {
84+
expect(e.message).toEqual('storage error');
85+
}
86+
expect(testJsPluginFails.museCore.msp.failedAddPreset).toBeCalledTimes(1);
87+
expect(testJsPluginFails.museCore.msp.afterAddPreset).toBeCalledTimes(0);
88+
});
89+
});
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
const { asyncInvoke, osUsername, validate } = require('../utils');
2+
const { registry } = require('../storage');
3+
const getMsp = require('./getMsp');
4+
const schema = require('../schemas/msp/deletePreset.json');
5+
const logger = require('../logger').createLogger('muse.msp.deletePreset');
6+
7+
/**
8+
* @module muse-core/msp/deletePreset
9+
*/
10+
11+
/**
12+
* @description Delete a preset from /msp.yaml.
13+
* @param {object} params
14+
* @param {string} params.name The preset name to delete.
15+
* @param {string} [params.author=osUsername]
16+
* @param {string} [params.msg] Commit message.
17+
*/
18+
module.exports = async (params = {}) => {
19+
validate(schema, params);
20+
const ctx = {};
21+
if (!params.author) params.author = osUsername;
22+
const { name, author, msg } = params;
23+
logger.info(`Deleting preset ${name}...`);
24+
await asyncInvoke('museCore.msp.beforeDeletePreset', ctx, params);
25+
26+
const msp = await getMsp();
27+
if (!msp) throw new Error('msp.yaml does not exist.');
28+
if (!msp[name]) throw new Error(`Preset ${name} does not exist.`);
29+
30+
ctx.preset = msp[name];
31+
32+
try {
33+
delete msp[name];
34+
await asyncInvoke('museCore.msp.deletePreset', ctx, params);
35+
await registry.setYaml('/msp.yaml', msp, msg || `Delete preset ${name} by ${author}`);
36+
} catch (err) {
37+
ctx.error = err;
38+
await asyncInvoke('museCore.msp.failedDeletePreset', ctx, params);
39+
throw err;
40+
}
41+
await asyncInvoke('museCore.msp.afterDeletePreset', ctx, params);
42+
logger.info(`Delete preset success: ${name}.`);
43+
return ctx;
44+
};
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
const { vol } = require('memfs');
2+
const plugin = require('js-plugin');
3+
const muse = require('../');
4+
const { registry } = require('../storage');
5+
6+
const testJsPlugin = {
7+
name: 'test-deletePreset',
8+
museCore: {
9+
msp: {
10+
deletePreset: jest.fn(),
11+
beforeDeletePreset: jest.fn(),
12+
afterDeletePreset: jest.fn(),
13+
},
14+
},
15+
};
16+
plugin.register(testJsPlugin);
17+
18+
describe('deletePreset tests.', () => {
19+
beforeEach(() => {
20+
vol.reset();
21+
});
22+
23+
it('should delete a preset from msp.yaml', async () => {
24+
await registry.set(
25+
'/msp.yaml',
26+
`
27+
default:
28+
versions:
29+
"@ebay/muse-core": "1.0.45"
30+
muse-react:
31+
extends: default
32+
versions:
33+
"@ebay/muse-lib-react": "2.0.3"
34+
`,
35+
);
36+
37+
await muse.msp.deletePreset({ name: 'muse-react', author: 'nate' });
38+
39+
const msp = await registry.getJsonByYaml('/msp.yaml');
40+
expect(msp['muse-react']).toBeUndefined();
41+
expect(msp.default).toBeDefined();
42+
43+
expect(testJsPlugin.museCore.msp.deletePreset).toBeCalledTimes(1);
44+
expect(testJsPlugin.museCore.msp.beforeDeletePreset).toBeCalledTimes(1);
45+
expect(testJsPlugin.museCore.msp.afterDeletePreset).toBeCalledTimes(1);
46+
});
47+
48+
it('should throw if msp.yaml does not exist', async () => {
49+
try {
50+
await muse.msp.deletePreset({ name: 'default', author: 'nate' });
51+
expect(true).toBe(false);
52+
} catch (err) {
53+
expect(err.message).toMatch('msp.yaml does not exist');
54+
}
55+
});
56+
57+
it('should throw if preset does not exist', async () => {
58+
await registry.set('/msp.yaml', 'default:\n versions: {}\n');
59+
60+
try {
61+
await muse.msp.deletePreset({ name: 'no-such-preset', author: 'nate' });
62+
expect(true).toBe(false);
63+
} catch (err) {
64+
expect(err.message).toMatch('does not exist');
65+
}
66+
});
67+
68+
it('should throw and invoke failedDeletePreset on error', async () => {
69+
const testJsPluginFails = {
70+
name: 'test-deletePreset-fail',
71+
museCore: {
72+
msp: {
73+
deletePreset: jest.fn().mockRejectedValue(new Error('storage error')),
74+
beforeDeletePreset: jest.fn(),
75+
afterDeletePreset: jest.fn(),
76+
failedDeletePreset: jest.fn(),
77+
},
78+
},
79+
};
80+
plugin.register(testJsPluginFails);
81+
await registry.set('/msp.yaml', 'default:\n versions: {}\n');
82+
83+
try {
84+
await muse.msp.deletePreset({ name: 'default', author: 'nate' });
85+
expect(true).toBe(false);
86+
} catch (e) {
87+
expect(e.message).toEqual('storage error');
88+
}
89+
expect(testJsPluginFails.museCore.msp.failedDeletePreset).toBeCalledTimes(1);
90+
expect(testJsPluginFails.museCore.msp.afterDeletePreset).toBeCalledTimes(0);
91+
});
92+
});
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
const { asyncInvoke, validate } = require('../utils');
2+
const { registry } = require('../storage');
3+
const schema = require('../schemas/msp/getMsp.json');
4+
const logger = require('../logger').createLogger('muse.msp.getMsp');
5+
6+
/**
7+
* @module muse-core/msp/getMsp
8+
*/
9+
10+
/**
11+
* @description Get all MSP presets as raw JSON from /msp.yaml.
12+
* @param {object} [params={}]
13+
* @returns {object|null} The parsed msp.yaml content, or null if it doesn't exist.
14+
*/
15+
module.exports = async (params = {}) => {
16+
validate(schema, params);
17+
const ctx = {};
18+
logger.verbose('Getting msp...');
19+
await asyncInvoke('museCore.msp.beforeGetMsp', ctx, params);
20+
21+
try {
22+
ctx.msp = await registry.getJsonByYaml('/msp.yaml');
23+
await asyncInvoke('museCore.msp.getMsp', ctx, params);
24+
} catch (err) {
25+
ctx.error = err;
26+
await asyncInvoke('museCore.msp.failedGetMsp', ctx, params);
27+
throw err;
28+
}
29+
await asyncInvoke('museCore.msp.afterGetMsp', ctx, params);
30+
logger.verbose('Get msp success.');
31+
return ctx.msp;
32+
};

0 commit comments

Comments
 (0)