Skip to content

Commit

Permalink
Release v1.0.0 (#23)
Browse files Browse the repository at this point in the history
* Add unit tests with Jest
* Convert code to ESNext
* Replace all callbacks with Promises
* Improve command API
  • Loading branch information
erickzhao authored Aug 24, 2018
1 parent c8c8764 commit 3edbaec
Show file tree
Hide file tree
Showing 21 changed files with 8,435 additions and 1,985 deletions.
8 changes: 5 additions & 3 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
{
"extends": [
"plugin:shopify/es5",
"plugin:shopify/node",
"plugin:shopify/mocha"
"plugin:shopify/esnext",
"plugin:shopify/node"
],
"rules": {
"no-console": 0,
"comma-dangle": 0
},
"env": {
"jest": true
}
}
97 changes: 44 additions & 53 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
[![npm version](https://badge.fury.io/js/%40shopify%2Fthemekit.svg)](https://badge.fury.io/js/%40shopify%2Fthemekit)

# @shopify/themekit

Node version of [Theme Kit](http://shopify.github.io/themekit/).
Node wrapper for [Theme Kit](http://shopify.github.io/themekit/).


## Table Of Contents

- [Installation](#installation)
- [Examples](#examples)
- [API](#api)
+ [`commands(options, callback)`](#themekitcommandsargs)
- [CLI](#cli)
- [License](http://github.com/Shopify/node-themekit/blob/master/LICENSE.md)

Expand All @@ -17,49 +19,33 @@ Node version of [Theme Kit](http://shopify.github.io/themekit/).
$ npm install @shopify/themekit
```

## Usage
```javascript
const themeKit = require('@shopify/themekit');

await themeKit.command('version');
//=> ThemeKit 0.8.0 darwin/amd64
```

## Examples

### Run commands

Here are a collection of examples to run Theme Kit commands.
This wrapper exposes a single function in its API which allows it to run any command available in the original Theme Kit CLI. Here are a collection of examples to run Theme Kit commands.

For a complete list of commands and args: [shopify.github.io/themekit/commands](http://shopify.github.io/themekit/commands).

#### Example 1

Print Theme Kit version info.

```javascript
var command = require('@shopify/themekit').command;

command({
args: ['version']
}, function(err) {
if (err) {
console.error(err);
return;
}

console.log('Theme Kit command has completed.');
});
```

#### Example 2
#### Example 1

Remove specific files from development environment.

```javascript
var command = require('@shopify/themekit').command;

command({
args: ['remove', '--env', 'development', 'snippets/pagination.liquid', 'snippets/date.liquid']
}, function(err) {
if (err) {
console.error(err);
return;
}
const themeKit = require('@shopify/themekit');

console.log('Theme Kit command has completed.');
await themeKit.command('remove', {
env: 'development',
files: ['snippets/pagination.liquid', 'snippets/date.liquid']
});
```

Expand All @@ -68,17 +54,10 @@ command({
Deploy all files to staging environment.

```javascript
var command = require('@shopify/themekit').command;

command({
args: ['deploy', '--env', 'staging']
}, function(err) {
if (err) {
console.error(err);
return;
}
const themeKit = require('@shopify/themekit');

console.log('Theme Kit command has completed.');
themeKit.command('deploy', {
env: 'staging'
});
```

Expand All @@ -90,7 +69,7 @@ Deploy theme to production via NPM scripts.

```json
"dependencies": {
"@shopify/themekit": "0.4.3"
"@shopify/themekit": "1.0.0"
},
"scripts": {
"deploy": "shopify-themekit replace --env production"
Expand All @@ -99,36 +78,48 @@ Deploy theme to production via NPM scripts.

## API

### `command(options, callback)`
### `command(command[, flags][, options)`

Executes command with arguments using the Theme Kit binary.

- options `<Object>`
- command `<String>`

Theme Kit command to run.

- flags `<Object>`

Flags to pass into the command.

All flags specified in the Theme Kit documentation are available, but in `camelCase` rather than in `--flag-form`.
```javascript
{
args: <Array>, // arguments to execute | ['version']
logLevel: <String> // Set level additional output info | 'silent', 'error', 'all', 'silly'
noIgnore: true, // --no-ignore
env: 'development' // --env=development
}
```

- callback `<Function>`
Additional flags:
- `files`: Specify an array of target files to upload.
- `ignoredFiles`: Like `ignoredFile`, but takes in an array of files to ignore.

```javascript
function(error) {

- options `<Object>`

```javascript
{
cwd: <String>, // Hard-code a working directory to run the binary from
logLevel: <String> // Set level additional output info | 'silent', 'error', 'all', 'silly'
}
```

For a complete list of commands and args: [shopify.github.io/themekit/commands/](http://shopify.github.io/themekit/commands/).
For a complete list of commands and flags, see the [Theme Kit documentation](http://shopify.github.io/themekit/commands/).

## CLI

```bash
$ shopify-themekit <args>
```

This CLI component of this package is intended to be used with NPM scripts. If you plan on using the command line interface heavily, please refer to: [shopify.github.io/themekit](http://shopify.github.io/themekit).
This CLI component of this package is intended to be used with NPM scripts. It functions exactly the same as the original Theme Kit binary. If you plan on using the command line interface heavily, please refer to [the original Theme Kit repository](http://shopify.github.io/themekit).

## License

Expand Down
18 changes: 18 additions & 0 deletions __mocks__/child_process.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
module.exports = {
spawn: jest.fn(() => ({
on: jest.fn((evt, cb) => {
if (evt === 'close') {
return cb();
}
return () => { /* noop*/ };
}),
stdout: {
setEncoding: jest.fn(),
on: jest.fn()
},
stderr: {
on: jest.fn()
},
addListener: jest.fn()
}))
};
35 changes: 35 additions & 0 deletions __tests__/command.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
const themekit = require('../lib/themekit');
const runExecutable = require('../lib/run-executable');

jest.mock('../lib/run-executable');

describe('command', () => {
test('forces no-update-notifier flag', async () => {
const args = ['version', '--some-flag', '--no-update-notifier'];

await themekit.command('version', {
someFlag: true
});

expect(runExecutable).toBeCalledWith(args, expect.any(String), null);
});

test('passes cwd and logLevel properly to runExecutable', async () => {
const cwd = process.cwd();
const logLevel = 'info';

await themekit.command('version', null, {cwd, logLevel});

expect(runExecutable).toBeCalledWith(expect.any(Array), cwd, logLevel);
});

test('does not mutate input param', async () => {
const flags = {someFlag: true};
const flagsCopy = JSON.parse(JSON.stringify(flags));

await themekit.command('version', flags);

expect(flags).toMatchObject(flagsCopy);
});
});

8 changes: 8 additions & 0 deletions __tests__/postinstall.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
const themekit = require('../lib/themekit');
const {version} = require('../lib/config');

test('successfully runs binary', async () => {
global.process.stdout.write = jest.fn();
await themekit.command('version');
expect(global.process.stdout.write).toHaveBeenLastCalledWith(expect.stringContaining(version));
});
20 changes: 20 additions & 0 deletions __tests__/run-executable.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
const path = require('path');

const cfg = require('../lib/config');
const runExecutable = require('../lib/run-executable');

jest.mock('child_process');

describe('runExecutable', () => {
test('spawns child process with correct arguments', async () => {
const {spawn} = require('child_process');

const pathToExecutable = path.join(cfg.destination, cfg.binName);
const args = ['arg1', 'arg2', 'arg3'];
const cwd = process.cwd();

await runExecutable(args, cwd);

expect(spawn).toBeCalledWith(pathToExecutable, args, {cwd});
});
});
111 changes: 111 additions & 0 deletions __tests__/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
const fsMock = require('mock-fs');
const fs = require('fs');
const {cleanFile, getFlagArrayFromObject} = require('../lib/utils');

describe('getFlagArrayFromObject', () => {
test('converts string flags correctly', () => {
const input = {
flagOne: 'value1',
flagTwo: 'value2',
};
const expectedOutput = ['--flag-one', 'value1', '--flag-two', 'value2'];

const output = getFlagArrayFromObject(input);

expect(output).toBeInstanceOf(Array);
expect(output).toHaveLength(expectedOutput.length);
expect(output).toEqual(expect.arrayContaining(expectedOutput));
});

test('converts boolean flags correctly', () => {
const input = {
flagOne: true,
flagTwo: false,
flagThree: true,
};
const expectedOutput = ['--flag-one', '--flag-three'];

const output = getFlagArrayFromObject(input);

expect(output).toBeInstanceOf(Array);
expect(output).toHaveLength(expectedOutput.length);
expect(output).toEqual(expect.arrayContaining(expectedOutput));
});

test('correctly deconstructs ignoredFiles flag', () => {
const input = {
ignoredFiles: ['file1', 'file2']
};
const expectedOutput = ['--ignored-file', 'file1', '--ignored-file', 'file2'];

const output = getFlagArrayFromObject(input);

expect(output).toBeInstanceOf(Array);
expect(output).toHaveLength(expectedOutput.length);
expect(output).toEqual(expect.arrayContaining(expectedOutput));
});

test('correctly deconstructs files flag', () => {
const input = {
files: ['file1', 'file2']
};
const expectedOutput = ['file1', 'file2'];

const output = getFlagArrayFromObject(input);

expect(output).toBeInstanceOf(Array);
expect(output).toHaveLength(expectedOutput.length);
expect(output).toEqual(expect.arrayContaining(expectedOutput));
});
});

describe('cleanFile', () => {
afterEach(() => {
fsMock.restore();
});

test('successfully removes file if it exists', () => {
fsMock({
'path/to/executable': {
'my-exec': '...',
}
});

const unlink = jest.spyOn(fs, 'unlinkSync');
const pathToExecutable = 'path/to/executable/my-exec';

function removeFile() {
cleanFile(pathToExecutable);
}
function removeFileAndAccess() {
cleanFile(pathToExecutable);
fs.statSync(pathToExecutable);
}

expect(removeFile).not.toThrow();
expect(removeFileAndAccess).toThrow('ENOENT');
expect(unlink).toBeCalledWith(pathToExecutable);
});

test('does not throw if path does not exist', () => {
fsMock({
'path/to/executable': {
'not-my-exec': '...',
}
});

const unlink = jest.spyOn(fs, 'unlinkSync');
const pathToExecutable = 'path/to/executable/my-exec';

function removeFile() {
cleanFile(pathToExecutable);
}
function access() {
fs.statSync(pathToExecutable);
}

expect(unlink).toBeCalledWith(pathToExecutable);
expect(removeFile).not.toThrow();
expect(access).toThrow('ENOENT');
});
});
4 changes: 1 addition & 3 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1 @@
module.exports = {
command: require('./lib/command')
};
module.exports = require('./lib/themekit');
Loading

0 comments on commit 3edbaec

Please sign in to comment.