Skip to content

Commit bdc0b9d

Browse files
author
Ovidiu Barabula
committed
feat(core): add method for dependencies installer manifest object validation
1 parent 306b578 commit bdc0b9d

File tree

2 files changed

+178
-2
lines changed

2 files changed

+178
-2
lines changed

src/util/dependencies-installer.spec.ts

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,14 @@ describe('DependenciesInstaller', () => {
110110
const { devDependencies } = await fileReader.read();
111111
expect(devDependencies['my-dev-package-1']).to.equal('^1.0.0');
112112
});
113+
114+
115+
it('doesn\'t do anything if <manifest> is not valid', async () => {
116+
await installer.add({});
117+
const { devDependencies } = await fileReader.read();
118+
expect(devDependencies['my-dev-package-1']).to.equal('^1.0.0');
119+
});
120+
});
113121
});
114122

115123

@@ -157,6 +165,75 @@ describe('DependenciesInstaller', () => {
157165
});
158166

159167

168+
describe('private method isManifestValid()', () => {
169+
beforeEach(async () => {
170+
installer = await InstallerSingleton.getInstance(process.cwd());
171+
});
172+
173+
174+
it('returns true if <manifest> has dependencies object', () => {
175+
expect(installer.isManifestValid({ dependencies: { 'my-package': '1.0.0' } })).to.be.true;
176+
expect(installer.isManifestValid({
177+
dependencies: { 'my-package': '1.0.0' },
178+
devDependencies: {},
179+
})).to.be.true;
180+
});
181+
182+
183+
it('returns true if <manifest> has devDependencies object', () => {
184+
expect(installer.isManifestValid({
185+
dependencies: {},
186+
devDependencies: { 'my-package': '1.0.0' },
187+
})).to.be.true;
188+
});
189+
190+
191+
it('returns false if <manifest> argument is not an object', () => {
192+
expect(installer.isManifestValid(1)).to.be.false;
193+
});
194+
195+
196+
it('returns false if <manifest> argument is empty', () => {
197+
expect(installer.isManifestValid({})).to.be.false;
198+
});
199+
200+
201+
it('returns false if <manifest> argument is missing both dependencies and devDependencies keys', () => {
202+
expect(installer.isManifestValid({ 'some-key': 'value' })).to.be.false;
203+
});
204+
205+
206+
it('returns false if <manifest> argument has non-object dependencies', () => {
207+
expect(installer.isManifestValid({ dependencies: 1 })).to.be.false;
208+
});
209+
210+
211+
it('returns false if <manifest> argument has non-object devDependencies', () => {
212+
expect(installer.isManifestValid({ devDependencies: 1 })).to.be.false;
213+
});
214+
215+
216+
it('returns false if <manifest> argument has non-object dependencies and devDependencies', () => {
217+
expect(installer.isManifestValid({ dependencies: 1, devDependencies: 1 })).to.be.false;
218+
});
219+
220+
221+
it('returns false if <manifest> argument has dependencies but is empty', () => {
222+
expect(installer.isManifestValid({ dependencies: {} })).to.be.false;
223+
});
224+
225+
226+
it('returns false if <manifest> argument has devDependencies but is empty', () => {
227+
expect(installer.isManifestValid({ devDependencies: {} })).to.be.false;
228+
});
229+
230+
231+
it('returns false if <manifest> argument has both dependencies and devDependencies empty', () => {
232+
expect(installer.isManifestValid({ dependencies: {}, devDependencies: {} })).to.be.false;
233+
});
234+
});
235+
236+
160237
describe('private method logError()', () => {
161238
beforeEach(async () => {
162239
installer = await InstallerSingleton.getInstance(process.cwd());

src/util/dependencies-installer.ts

Lines changed: 101 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,14 @@ import { Stream } from 'stream';
1212
import { Config } from '../config-manager';
1313
import FileReader from './file-reader';
1414
import Logger, { ILogger } from './logger';
15-
import { required, sortObjectKeys } from './utility-functions';
15+
import {
16+
isObject,
17+
isObjectEmpty,
18+
limitFn,
19+
pluginName,
20+
required,
21+
sortObjectKeys,
22+
} from './utility-functions';
1623

1724

1825
export type PackageManager = 'yarn' | 'npm';
@@ -39,13 +46,16 @@ export interface DependenciesInstallerDefaults {
3946
managers: PackageManagersList;
4047
}
4148

49+
export type DependenciesSubscriber = (manifest: DependenciesManifest, name: string) => void;
50+
4251
export interface DependenciesInstaller {
4352
add(manifest: DependenciesManifest): Promise<void>;
4453
run(): Promise<void>;
4554
/* test:start */
4655
checkForManagers?(): Promise<void>;
4756
hasNoManagers?(): boolean;
4857
isManagerInstalled?(manager: PackageManager): Promise<boolean>;
58+
isManifestValid?(manifest: DependenciesManifest): boolean;
4959
logError?(stream: Stream): void;
5060
logOutput?(stream: Stream): void;
5161
/* test:end */
@@ -57,7 +67,9 @@ const MESSAGES = {
5767
INSTALLING: 'Installing dependencies using',
5868
LOOKING_FOR_MANAGERS: 'Looking for package managers\u2026',
5969
MANAGER_FOUND: 'Package manager found',
70+
MANIFEST_SCHEMA: 'When registering your plugin dependencies use the "package.json" schema',
6071
MANUAL_VERSION_CHANGE_REQUIRED: 'Unexpected behaviour or errors might occur. Please change the version of the package manually!',
72+
REGISTERING_PLUGIN_DEPS: 'Registering dependencies',
6173
};
6274

6375
// Custom error messages
@@ -66,6 +78,7 @@ export const ERRORS = {
6678
DEPENDENCY_ALREADY_ADDED: 'Package already exists',
6779
DEPENDENCY_VERSION_MISMATCH: 'Package already exists with different version',
6880
MANAGERS_REQUIRED: 'You need at least one package manager on your system (e.g. \'yarn\', \'npm\')',
81+
MANIFEST_INVALID: 'Dependencies <manifest> object is invalid',
6982
NO_MANAGERS: 'Wow, no package managers were found on your system',
7083
};
7184

@@ -80,7 +93,7 @@ export async function Installer(
8093
options?: DependenciesInstallerOptions,
8194
): Promise<DependenciesInstaller> {
8295
const defaultOptions: DependenciesInstallerDefaults = {
83-
logChannel: 'PackageInstaller',
96+
logChannel: 'DepsInstaller',
8497
managers: ['yarn', 'npm'],
8598
};
8699

@@ -111,11 +124,96 @@ export async function Installer(
111124
let availableManagers: PackageManagersList = [];
112125

113126

127+
/**
128+
* Check validity of <manifest> object
129+
* @param manifest Dependencies manifest object
130+
*/
131+
function isManifestValid(manifest: DependenciesManifest) {
132+
if (!isObject(manifest)) {
133+
logger.error(`${ERRORS.MANIFEST_INVALID}: not an object`);
134+
return false;
135+
}
136+
137+
if (isObjectEmpty(manifest)) {
138+
logger.error(`${ERRORS.MANIFEST_INVALID}: object is empty`);
139+
return false;
140+
}
141+
142+
// Not valid if both "devDependencies" and "dependencies" keys are missing
143+
if (!Object.keys(manifest).some(key => ['dependencies', 'devDependencies'].includes(key))) {
144+
logger.error(`${ERRORS.MANIFEST_INVALID}: object should have at least one of the following keys: 'dependencies', 'devDependencies'`);
145+
return false;
146+
}
147+
148+
// If "dependencies" or "devDependencies" are not objects
149+
if (
150+
(typeof manifest.dependencies !== 'undefined' && !isObject(manifest.dependencies)) ||
151+
(typeof manifest.devDependencies !== 'undefined' && !isObject(manifest.devDependencies))
152+
) {
153+
logger.error(`${ERRORS.MANIFEST_INVALID}: manifest's 'dependencies' and 'devDependencies' should be objects`);
154+
return false;
155+
}
156+
157+
// If manifest contains "devDependencies" or "dependencies" keys, but are empty
158+
if (
159+
// Has "devDependencies", but is empty
160+
(
161+
typeof manifest.dependencies === 'undefined' &&
162+
(
163+
typeof manifest.devDependencies !== 'undefined' &&
164+
isObject(manifest.devDependencies) &&
165+
isObjectEmpty(manifest.devDependencies)
166+
)
167+
) ||
168+
// Has "dependencies", but is empty
169+
(
170+
typeof manifest.devDependencies === 'undefined' &&
171+
(
172+
typeof manifest.dependencies !== 'undefined' &&
173+
isObject(manifest.dependencies) &&
174+
isObjectEmpty(manifest.dependencies)
175+
)
176+
) ||
177+
// Has both "dependencies" and "devDependencies" empty
178+
(
179+
typeof manifest.dependencies !== 'undefined' &&
180+
isObject(manifest.dependencies) &&
181+
isObjectEmpty(manifest.dependencies) &&
182+
typeof manifest.devDependencies !== 'undefined' &&
183+
isObject(manifest.devDependencies) &&
184+
isObjectEmpty(manifest.devDependencies)
185+
)
186+
) {
187+
logger.error(
188+
`${ERRORS.MANIFEST_INVALID}: manifest's 'dependencies' or 'devDependencies' objects should not be empty`,
189+
);
190+
return false;
191+
}
192+
193+
return true;
194+
}
195+
196+
114197
/**
115198
* Add dependencies to package.json
116199
* @param dependencies Object with "dependencies" and "devDependencies"
117200
*/
118201
async function add(manifest: DependenciesManifest): Promise<void> {
202+
// Validate manifest parameter
203+
if (!isManifestValid(manifest)) {
204+
const schema = {
205+
dependencies: {
206+
package: '^1.0.1',
207+
package2: '^2.2.0',
208+
},
209+
devDependencies: {
210+
'dev-package': '~3.1.4',
211+
},
212+
};
213+
logger.warn(`${MESSAGES.MANIFEST_SCHEMA}:\n${JSON.stringify(schema, null, 2)}`);
214+
return undefined;
215+
}
216+
119217
try {
120218
// Get current dependencies
121219
const projectConfig = await fileReader.read();
@@ -306,6 +404,7 @@ export async function Installer(
306404
checkForManagers,
307405
hasNoManagers,
308406
isManagerInstalled,
407+
isManifestValid,
309408
logError,
310409
logOutput,
311410
};

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy