Skip to content

Commit f274864

Browse files
author
Ovidiu Barabula
committed
feat(core): add dependencies manager
1 parent 66656fe commit f274864

File tree

2 files changed

+240
-0
lines changed

2 files changed

+240
-0
lines changed
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import { assert, expect } from 'chai';
2+
import 'mocha';
3+
4+
import * as path from 'path';
5+
import { DependenceisManifests, DependenciesManager, DependenciesSubscriber } from '.';
6+
import FileReader from '../util/file-reader';
7+
8+
9+
describe('DependenciesManager', () => {
10+
let fileReader: FileReader;
11+
let depsManager: DependenciesManager;
12+
let subscriber: DependenciesSubscriber;
13+
const manifestName = 'my-manifest';
14+
const manifest: DependenceisManifests = {
15+
dependencies: { 'my-package': '^2.2.2' },
16+
devDependencies: { 'my-dev-package': '^1.1.1' },
17+
};
18+
19+
before(() => {
20+
depsManager = DependenciesManager();
21+
});
22+
23+
beforeEach(() => {
24+
subscriber = depsManager.getSubscriber();
25+
});
26+
27+
28+
describe('getSubscriber()', () => {
29+
it('returns a function', () => {
30+
expect(subscriber).to.be.a('function');
31+
});
32+
33+
34+
it('returns a limited function', () => {
35+
subscriber(manifest, manifestName);
36+
expect(subscriber(manifest, manifestName)).to.be.an('undefined');
37+
});
38+
39+
40+
describe('Subscriber function', () => {
41+
it('subscribes a dependency manifest', () => {
42+
subscriber(manifest, manifestName);
43+
expect(depsManager.isRegistered(manifestName)).to.be.true;
44+
});
45+
46+
47+
it('doesn\'t subscribe an already registered dependency manifest', () => {
48+
// Use the sub fn once
49+
subscriber(manifest, manifestName);
50+
// Get new subscriber
51+
subscriber = depsManager.getSubscriber();
52+
// Subscribe again
53+
subscriber(manifest, manifestName);
54+
expect(depsManager.isRegistered(manifestName)).to.be.true;
55+
});
56+
});
57+
});
58+
59+
60+
describe('install()', () => {
61+
it('registers the dependencies and starts the installation', async () => {
62+
subscriber(manifest, manifestName);
63+
await depsManager.install();
64+
const {
65+
dependencies,
66+
devDependencies,
67+
} = await FileReader(path.join(process.cwd(), 'test.package.json')).read();
68+
69+
expect(dependencies).to.contain.keys('my-package');
70+
expect(devDependencies).to.contain.keys('my-dev-package');
71+
}).timeout(12000);
72+
});
73+
74+
75+
describe('private method registerDependencies()', () => {
76+
it('registeres dependencies manifests with DependenciesInstaller', async () => {
77+
subscriber(manifest, manifestName);
78+
await depsManager.install();
79+
const {
80+
dependencies,
81+
devDependencies,
82+
} = await FileReader(path.join(process.cwd(), 'test.package.json')).read();
83+
84+
expect(dependencies).to.contain.keys('my-package');
85+
expect(devDependencies).to.contain.keys('my-dev-package');
86+
}).timeout(12000);
87+
});
88+
89+
90+
after(async () => {
91+
fileReader = FileReader(path.join(process.cwd(), 'test.package.json'));
92+
const fileContents = await fileReader.read();
93+
delete fileContents.dependencies;
94+
delete fileContents.devDependencies;
95+
await fileReader.write(fileContents);
96+
});
97+
});

src/dependencies-manager/index.ts

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
/**
2+
* Name: index.ts
3+
* Description: Plugin dependencies manager
4+
* Author: Ovidiu Barabula <lectii2008@gmail.com>
5+
* @since 1.1.0
6+
*/
7+
8+
import chalk from 'chalk';
9+
import Logger from '../util/logger';
10+
import { limitFn, pluginName } from '../util/utility-functions';
11+
import InstallerSingleton, {
12+
DependenciesInstaller,
13+
DependenciesManifest,
14+
} from './dependencies-installer';
15+
16+
17+
export type DependenciesSubscriber = (manifest: DependenciesManifest, name: string) => void;
18+
19+
export interface DependenceisManifests {
20+
[key: string]: DependenciesManifest;
21+
}
22+
23+
export interface DependenciesManager {
24+
getSubscriber(): DependenciesSubscriber;
25+
install(): Promise<void>;
26+
/* test:start */
27+
instantiateInstaller?(): Promise<void>;
28+
isRegistered?(name: string): boolean;
29+
registerDependencies?(): Promise<void>;
30+
/* test:end */
31+
}
32+
33+
34+
// Custom messages
35+
const MESSAGES = {
36+
REGISTERING_PLUGIN_DEPS: 'Registering dependencies',
37+
};
38+
39+
40+
// Custom error messages
41+
const ERRORS = {
42+
ALREADY_REGISTERED: 'Sorry, there are dependencies already registered under this name',
43+
};
44+
45+
46+
/**
47+
* DependenciesManager constructor
48+
* The manager will act as a middle-man to delay the (taxing) Dependencies Installer instatiation
49+
*/
50+
export function DependenciesManager(): DependenciesManager {
51+
const logger = Logger.getInstance()('DependenciesManager');
52+
const depsManifests: DependenceisManifests = {};
53+
let depsInstaller: DependenciesInstaller;
54+
55+
56+
/**
57+
* Instantiate Dependencies Installer
58+
*/
59+
async function instantiateInstaller(): Promise<void> {
60+
if (typeof depsInstaller === 'undefined') {
61+
depsInstaller = await InstallerSingleton.getInstance(process.cwd());
62+
}
63+
}
64+
65+
66+
/**
67+
* Register subscribed dependencies to Dependencies Installer
68+
*/
69+
async function registerDependencies(): Promise<void> {
70+
for (const name of Object.keys(depsManifests)) {
71+
await depsInstaller.add(depsManifests[name]);
72+
}
73+
}
74+
75+
76+
/**
77+
* Instantiate, register dependencies and start the Dependencies Installer .run() method
78+
*/
79+
async function install(): Promise<void> {
80+
// Get the Dependencies Installer instance
81+
await instantiateInstaller();
82+
// Register the dependencies with the Dependencies Installer
83+
await registerDependencies();
84+
// Start the installation process
85+
await depsInstaller.run();
86+
}
87+
88+
89+
/**
90+
* Check if manifest is registered
91+
* @param name Manifest name (task or plugin name)
92+
*/
93+
function isRegistered(name: string): boolean {
94+
return Object.keys(depsManifests).includes(name);
95+
}
96+
97+
98+
/**
99+
* Create the dependencies subscriber function
100+
* This function will be provided to all plugins to allow dependencies registration
101+
* The manager will then register each plugin's dependencies to the Dependencies Installer instance
102+
*/
103+
function getSubscriber(): DependenciesSubscriber {
104+
// Return a limited function (1 maximum call) to register plugin dependencies
105+
return limitFn(function (manifest: DependenciesManifest, name: string) {
106+
/* istanbul ignore else */
107+
if (typeof name !== 'undefined') {
108+
logger.debug(`${MESSAGES.REGISTERING_PLUGIN_DEPS} for ${chalk.cyan.bold(pluginName(name))}`);
109+
}
110+
111+
// Check if name is already used
112+
if (isRegistered(name)) {
113+
// Output an error and return
114+
logger.error(`${ERRORS.ALREADY_REGISTERED} ${chalk.cyan.bold(name)}`);
115+
}
116+
117+
// Add the dependencies to the manifests object, if it's not registered already
118+
depsManifests[name] = manifest;
119+
});
120+
}
121+
122+
123+
// Public API object
124+
let publicApi = {
125+
getSubscriber,
126+
install,
127+
};
128+
129+
130+
/* test:start */
131+
// Add private methods for test environment
132+
publicApi = {...publicApi,
133+
instantiateInstaller,
134+
isRegistered,
135+
registerDependencies,
136+
};
137+
/* test:end */
138+
139+
// Return the read-only public API object
140+
return Object.freeze(publicApi);
141+
}
142+
143+
export default DependenciesManager();

0 commit comments

Comments
 (0)