Skip to content

Commit

Permalink
Merge pull request #21 from emulsify-ds/feat/small-improvements
Browse files Browse the repository at this point in the history
feat: minor improvements
  • Loading branch information
patrickocoffeyo authored Sep 25, 2021
2 parents ccb72fe + c94d6d2 commit 5516133
Show file tree
Hide file tree
Showing 16 changed files with 131 additions and 28 deletions.
12 changes: 11 additions & 1 deletion USAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ Options:

Commands:
list|ls Lists all of the components that are available for installation within your project based on the system and variant you selected
install|i [name] Install a component from within the current project's system and variant
install|i [name] [options] Install a component from within the current project's system and variant
help [command] display help for command
```
Expand Down Expand Up @@ -179,4 +179,14 @@ emulsify component install card
> Success! The card component has been added to your project.
```

If you attempt to install a component that already exists within your project, you the installation process will throw an error. However, if you wish to overwrite the existing component, pass the `--force` flag.

```bash
emulsify component install card
> Error: The component "card" already exists, and force was not passed (--force).
emulsify component install card --force
> Success! The card component has been added to your project.
```

That's pretty much it. Have fun, and please feel free to open issues if you discover a bug, or have an improvement to suggest!
3 changes: 3 additions & 0 deletions jest.setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ jest.mock('simple-git', () => {
branch: jest.fn(),
checkout: jest.fn(),
fetch: jest.fn(),
pull: jest.fn(),
};

return jest.fn(() => mockGit);
Expand All @@ -23,6 +24,8 @@ jest.mock('fs', () => ({

jest.mock('fs-extra', () => ({
copy: jest.fn(),
remove: jest.fn(),
pathExists: jest.fn(),
}));

jest.mock('child_process');
20 changes: 14 additions & 6 deletions src/handlers/componentInstall.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
EMULSIFY_PROJECT_CONFIG_FILE,
} from '../lib/constants';
import type { EmulsifySystem } from '@emulsify-cli/config';
import type { InstallComponentHandlerOptions } from '@emulsify-cli/handlers';
import getGitRepoNameFromUrl from '../util/getGitRepoNameFromUrl';
import getEmulsifyConfig from '../util/project/getEmulsifyConfig';
import getJsonFromCachedFile from '../util/cache/getJsonFromCachedFile';
Expand All @@ -14,7 +15,10 @@ import cloneIntoCache from '../util/cache/cloneIntoCache';
/**
* Handler for the `component install` command.
*/
export default async function componentInstall(name: string): Promise<void> {
export default async function componentInstall(
name: string,
{ force }: InstallComponentHandlerOptions
): Promise<void> {
const emulsifyConfig = await getEmulsifyConfig();
if (!emulsifyConfig) {
return log(
Expand Down Expand Up @@ -86,9 +90,13 @@ export default async function componentInstall(name: string): Promise<void> {
);
}

await installComponentFromCache(systemConf, variantConf, name);
return log(
'success',
`Success! The ${name} component has been added to your project.`
);
try {
await installComponentFromCache(systemConf, variantConf, name, force);
return log(
'success',
`Success! The ${name} component has been added to your project.`
);
} catch (e) {
return log('error', (e as Error).toString());
}
}
11 changes: 2 additions & 9 deletions src/handlers/init.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ describe('init', () => {
});

it('can clone an Emulsify starter based on CLI input, and log a success message upon completion', async () => {
expect.assertions(3);
expect.assertions(2);
await init('cornflake', `${root}/themes/subDir`, {
starter: 'https://github.com/cornflake-ds/cornflake-drupal.git',
checkout: '5.6x',
Expand All @@ -77,14 +77,7 @@ describe('init', () => {
'/home/uname/Projects/cornflake/themes/subDir/cornflake',
{ '--branch': '5.6x' }
);
expect(logMock).toHaveBeenCalledWith(
'success',
'Created an Emulsify project in /home/uname/Projects/cornflake/themes/subDir/cornflake.'
);
expect(logMock).toHaveBeenCalledWith(
'info',
`Emulsify does not come with components by default.\nPlease use "emulsify system install" to select a design system you'd like to use.\nDoing so will install the system's default components, and allow you to install any other components made available by the design system.\nTo see a list of out-of-the-box design systems, run: "emulsify system ls"`
);
expect(logMock).toHaveBeenCalledTimes(5);
});

it('can clone an Emulsify starter without a provided checkout', async () => {
Expand Down
23 changes: 22 additions & 1 deletion src/handlers/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import R from 'ramda';
import { join } from 'path';
import { existsSync, promises as fs } from 'fs';
import simpleGit from 'simple-git';
import { cyan } from 'chalk';

import type { EmulsifyProjectConfiguration } from '@emulsify-cli/config';
import type { InitHandlerOptions } from '@emulsify-cli/handlers';
Expand Down Expand Up @@ -134,7 +135,27 @@ export default async function init(
log('success', `Created an Emulsify project in ${target}.`);
log(
'info',
`Emulsify does not come with components by default.\nPlease use "emulsify system install" to select a design system you'd like to use.\nDoing so will install the system's default components, and allow you to install any other components made available by the design system.\nTo see a list of out-of-the-box design systems, run: "emulsify system ls"`
`Make sure you install the modules your Emulsify-based theme requires in order to function.`
);
log(
'verbose',
`
- composer require drupal/components
- composer require drupal/emulsify_twig
- drush en components emulsify_twig -y
`
);
log(
'info',
`Once the requirements have been installed, you will need to select a system to use, as Emulsify does not come with components by default.`
);
log(
'verbose',
`
${cyan('List systems')}: emulsify system list
${cyan('Install a system')}: emulsify system install "system-name"
${cyan('Install default system')}: emulsify system install compound
`
);
} catch (e) {
log('error', `Unable to pull down ${repository}: ${String(e)}`, EXIT_ERROR);
Expand Down
7 changes: 6 additions & 1 deletion src/handlers/systemInstall.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,12 @@ export default async function systemInstall(
({ required }) => required === true
);
for (const component of requiredComponents) {
await installComponentFromCache(systemConf, variantConf, component.name);
await installComponentFromCache(
systemConf,
variantConf,
component.name,
true
);
}

// Install all global files and folders.
Expand Down
4 changes: 4 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@ component
.action(componentList);
component
.command('install [name]')
.option(
'-f --force',
'Use this to overwrite a component that is already installed'
)
.alias('i')
.description(
"Install a component from within the current project's system and variant"
Expand Down
4 changes: 4 additions & 0 deletions src/types/handlers.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,8 @@ declare module '@emulsify-cli/handlers' {
checkout?: string | void;
variant?: string | void;
};

export type InstallComponentHandlerOptions = {
force?: boolean;
};
}
5 changes: 4 additions & 1 deletion src/util/cache/cloneIntoCache.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const mkdirMock = fs.promises.mkdir as jest.Mock;
const gitCloneMock = git().clone as jest.Mock;
const gitFetchMock = git().fetch as jest.Mock;
const gitCheckoutMock = git().checkout as jest.Mock;
const gitPullMock = git().pull as jest.Mock;

(findFileInCurrentPath as jest.Mock).mockReturnValue(
'/home/uname/projects/emulsify'
Expand All @@ -25,6 +26,7 @@ describe('cloneIntoCache', () => {
gitCloneMock.mockClear();
gitFetchMock.mockClear();
gitCheckoutMock.mockClear();
gitPullMock.mockClear();
});

const cloneOptions = {
Expand All @@ -33,11 +35,12 @@ describe('cloneIntoCache', () => {
};

it('can ensure that the correct branch is checked out, and return early if the cache item already exists', async () => {
expect.assertions(4);
expect.assertions(5);
existsSyncMock.mockReturnValueOnce(true);
await cloneIntoCache('systems', ['cornflake'])(cloneOptions);
expect(existsSyncMock).toHaveBeenCalledTimes(1);
expect(gitFetchMock).toHaveBeenCalledTimes(1);
expect(gitPullMock).toHaveBeenCalledTimes(1);
expect(gitCheckoutMock).toHaveBeenCalledWith('branch-name');
expect(gitCloneMock).not.toHaveBeenCalled();
});
Expand Down
1 change: 1 addition & 0 deletions src/util/cache/cloneIntoCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export default function cloneIntoCache(
git = simpleGit(destination);
await git.fetch();
await git.checkout(checkout);
await git.pull();
}
return;
}
Expand Down
13 changes: 12 additions & 1 deletion src/util/cache/copyItemFromCache.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ jest.mock('./getCachedItemPath', () =>
'/home/uname/.emulsify/cache/systems/12345/compound/components/00-base/colors'
)
);
import { copy } from 'fs-extra';
import { copy, remove } from 'fs-extra';
import copyItemFromCache from './copyItemFromCache';

describe('copyItemFromCache', () => {
Expand All @@ -19,4 +19,15 @@ describe('copyItemFromCache', () => {
'/home/uname/Projects/drupal/web/themes/custom/cornflake/components/00-base/colors'
);
});
it('can remove a destination before copying items from the cache if "force" is true', async () => {
await copyItemFromCache(
'systems',
['compound', 'components', '00-base', 'colors'],
'/home/uname/Projects/drupal/web/themes/custom/cornflake/components/00-base/colors',
true
);
expect(remove).toHaveBeenCalledWith(
'/home/uname/Projects/drupal/web/themes/custom/cornflake/components/00-base/colors'
);
});
});
11 changes: 9 additions & 2 deletions src/util/cache/copyItemFromCache.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { CacheBucket, CacheItemPath } from '@emulsify-cli/cache';
import { copy } from 'fs-extra';
import { copy, remove } from 'fs-extra';
import getCachedItemPath from './getCachedItemPath';

/**
Expand All @@ -10,12 +10,19 @@ import getCachedItemPath from './getCachedItemPath';
* @param itemPath array containing segments of the path to the cached item within the specified bucket.
* @param item name of the cached item.
* @param destination full path to the destination to which the cached item should be copied.
* @param force if true, removes any file/folder at the destination before copying.
*/
export default async function copyFileFromCache(
bucket: CacheBucket,
itemPath: CacheItemPath,
destination: string
destination: string,
force = false
): Promise<void> {
const source = getCachedItemPath(bucket, itemPath);

if (force) {
await remove(destination);
}

return copy(source, destination);
}
15 changes: 14 additions & 1 deletion src/util/project/installComponentFromCache.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
jest.mock('../cache/copyItemFromCache', () => jest.fn());
jest.mock('../fs/findFileInCurrentPath', () => jest.fn());

import { pathExists } from 'fs-extra';
import type { EmulsifySystem, EmulsifyVariant } from '@emulsify-cli/config';
import copyItemFromCache from '../cache/copyItemFromCache';
import findFileInCurrentPath from '../fs/findFileInCurrentPath';
Expand All @@ -9,6 +10,7 @@ import installComponentFromCache from './installComponentFromCache';
const findFileMock = (findFileInCurrentPath as jest.Mock).mockReturnValue(
'/home/uname/Projects/cornflake/web/themes/custom/cornflake/project.emulsify.json'
);
const pathExistsMock = (pathExists as jest.Mock).mockResolvedValue(false);

describe('installComponentFromCache', () => {
const system = {
Expand Down Expand Up @@ -69,13 +71,24 @@ describe('installComponentFromCache', () => {
);
});

it('throws an error if the component is already installed, and force is false', async () => {
expect.assertions(1);
pathExistsMock.mockResolvedValueOnce(true);
await expect(
installComponentFromCache(system, variant, 'link', false)
).rejects.toThrow(
'The component "link" already exists, and force was not passed (--force).'
);
});

it('copies the component from the cached item into the correct destination', async () => {
expect.assertions(1);
await installComponentFromCache(system, variant, 'link');
expect(copyItemFromCache as jest.Mock).toHaveBeenCalledWith(
'systems',
['compound', './components/00-base', 'link'],
'/home/uname/Projects/cornflake/web/themes/custom/cornflake/components/00-base/link'
'/home/uname/Projects/cornflake/web/themes/custom/cornflake/components/00-base/link',
false
);
});
});
17 changes: 15 additions & 2 deletions src/util/project/installComponentFromCache.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { pathExists } from 'fs-extra';
import type { EmulsifySystem, EmulsifyVariant } from '@emulsify-cli/config';
import { join, dirname } from 'path';
import { EMULSIFY_PROJECT_CONFIG_FILE } from '../../lib/constants';
Expand All @@ -10,12 +11,14 @@ import copyItemFromCache from '../cache/copyItemFromCache';
* @param system EmulsifySystem object depicting the system from which the component should be installed.
* @param variant EmulsifyVariant object containing information about the component, where it lives, and how it should be installed.
* @param componentName string name of the component that should be installed.
* @param force if true, replaces an existing component (if any).
* @returns
*/
export default async function installComponentFromCache(
system: EmulsifySystem,
variant: EmulsifyVariant,
componentName: string
componentName: string,
force = false
): Promise<void> {
// Gather information about the current Emulsify project. If none exists,
// throw an error.
Expand Down Expand Up @@ -51,9 +54,19 @@ export default async function installComponentFromCache(
// Calculate the destination path based on the path to the Emulsify project, the structure of the
// component, and the component's name.
const destination = join(dirname(path), structure.directory, component.name);

// If the component already exists within the project, and force is not true,
// throw an error.
if ((await pathExists(destination)) && !force) {
throw new Error(
`The component "${component.name}" already exists, and force was not passed (--force).`
);
}

return copyItemFromCache(
'systems',
[system.name, structure.directory, component.name],
destination
destination,
force
);
}
6 changes: 4 additions & 2 deletions src/util/project/installGeneralAssetsFromCache.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,15 @@ describe('installGeneralAssetsFromCache', () => {
1,
'systems',
['compound', './components/00-base/00-defaults'],
'/home/uname/Projects/cornflake/web/themes/custom/cornflake/components/00-base/00-defaults'
'/home/uname/Projects/cornflake/web/themes/custom/cornflake/components/00-base/00-defaults',
true
);
expect(copyItemMock).toHaveBeenNthCalledWith(
2,
'systems',
['compound', './components/style.scss'],
'/home/uname/Projects/cornflake/web/themes/custom/cornflake/components/style.scss'
'/home/uname/Projects/cornflake/web/themes/custom/cornflake/components/style.scss',
true
);
});

Expand Down
7 changes: 6 additions & 1 deletion src/util/project/installGeneralAssetsFromCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,12 @@ export default async function installGeneralAssetsFromCache(
const destination = join(dirname(path), asset.destinationPath);
promises.push(
catchLater(
copyItemFromCache('systems', [system.name, asset.path], destination)
copyItemFromCache(
'systems',
[system.name, asset.path],
destination,
true
)
)
);
}
Expand Down

0 comments on commit 5516133

Please sign in to comment.