Skip to content

Commit

Permalink
Add h2 shortcut with shell aliases (#679)
Browse files Browse the repository at this point in the history
* Add h2 alias for bash, zsh and fish

* Support Git Bash in Windows

* Fix Fish alias in Unix

* Support PowerShell and PowerShell 7+ in Windows

* Add test

* refactor: extract utilities

* refactor: easier mocks

* Fix log when alias exists

* Oclif manifest

* Fix issues with echo

* Add generate route shortcut

* Changeset
  • Loading branch information
frandiox authored Apr 3, 2023
1 parent 5e26503 commit 68a6028
Show file tree
Hide file tree
Showing 7 changed files with 332 additions and 5 deletions.
17 changes: 17 additions & 0 deletions .changeset/tricky-llamas-call.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
'@shopify/cli-hydrogen': patch
---

Added a new `shortcut` command that creates a global `h2` alias for the Hydrogen CLI:

```sh
$> npx shopify hydrogen shortcut
```

After that, you can run commands using the new alias:

```sh
$> h2 generate route home
$> h2 g r home # Same as the above
$> h2 check routes
```
6 changes: 2 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,16 @@

[![MIT License](https://img.shields.io/github/license/shopify/hydrogen)](LICENSE.md)
[![npm downloads](https://img.shields.io/npm/dm/@shopify/hydrogen.svg?sanitize=true)](https://npmcharts.com/compare/@shopify/hydrogen?minimal=true)


📚 [Docs](https://shopify.dev/custom-storefronts/hydrogen) | 🗣 [Discord](https://discord.gg/Hefq6w5c5d) | 💬 [Discussions](https://github.com/Shopify/hydrogen/discussions) | 📝 [Changelog](./packages/hydrogen/CHANGELOG.md)


Hydrogen is Shopify’s stack for headless commerce. It provides a set of tools, utilities, and best-in-class examples for building dynamic and performant commerce applications. Hydrogen is designed to dovetail with [Remix](https://remix.run/), Shopify’s full stack web framework, but it also provides a React library portable to other supporting frameworks.
Hydrogen is Shopify’s stack for headless commerce. It provides a set of tools, utilities, and best-in-class examples for building dynamic and performant commerce applications. Hydrogen is designed to dovetail with [Remix](https://remix.run/), Shopify’s full stack web framework, but it also provides a React library portable to other supporting frameworks.

</div>

## Hydrogen Legacy v1

Hydrogen legacy v1 has been moved [to a separate repo](https://github.com/Shopify/hydrogen-v1) and the [docs can be found here](https://shopify.github.io/hydrogen-v1/tutorials/getting-started).
Hydrogen legacy v1 has been moved [to a separate repo](https://github.com/Shopify/hydrogen-v1) and the [docs can be found here](https://shopify.github.io/hydrogen-v1/tutorials/getting-started).

## Getting started with Hydrogen

Expand Down
2 changes: 1 addition & 1 deletion packages/cli/oclif.manifest.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"version":"4.0.9","commands":{"hydrogen:build":{"id":"hydrogen:build","description":"Builds a Hydrogen storefront for production.","strict":true,"pluginName":"@shopify/cli-hydrogen","pluginAlias":"@shopify/cli-hydrogen","pluginType":"core","aliases":[],"flags":{"path":{"name":"path","type":"option","description":"The path to the directory of the Hydrogen storefront. The default is the current directory.","multiple":false},"sourcemap":{"name":"sourcemap","type":"boolean","description":"Generate sourcemaps for the build.","allowNo":false},"disable-route-warning":{"name":"disable-route-warning","type":"boolean","description":"Disable warning about missing standard routes.","allowNo":false},"base":{"name":"base","type":"option","hidden":true,"multiple":false},"entry":{"name":"entry","type":"option","hidden":true,"multiple":false},"target":{"name":"target","type":"option","hidden":true,"multiple":false}},"args":[]},"hydrogen:check":{"id":"hydrogen:check","description":"Returns diagnostic information about a Hydrogen storefront.","strict":true,"pluginName":"@shopify/cli-hydrogen","pluginAlias":"@shopify/cli-hydrogen","pluginType":"core","aliases":[],"flags":{"path":{"name":"path","type":"option","description":"The path to the directory of the Hydrogen storefront. The default is the current directory.","multiple":false}},"args":[{"name":"resource","description":"The resource to check. Currently only 'routes' is supported.","required":true,"options":["routes"]}]},"hydrogen:dev":{"id":"hydrogen:dev","description":"Runs Hydrogen storefront in an Oxygen worker for development.","strict":true,"pluginName":"@shopify/cli-hydrogen","pluginAlias":"@shopify/cli-hydrogen","pluginType":"core","aliases":[],"flags":{"path":{"name":"path","type":"option","description":"The path to the directory of the Hydrogen storefront. The default is the current directory.","multiple":false},"port":{"name":"port","type":"option","description":"Port to run the server on.","multiple":false,"default":3000},"disable-virtual-routes":{"name":"disable-virtual-routes","type":"boolean","description":"Disable rendering fallback routes when a route file doesn't exist","allowNo":false},"host":{"name":"host","type":"option","hidden":true,"multiple":false}},"args":[]},"hydrogen:init":{"id":"hydrogen:init","description":"Creates a new Hydrogen storefront.","strict":true,"pluginName":"@shopify/cli-hydrogen","pluginAlias":"@shopify/cli-hydrogen","pluginType":"core","aliases":[],"flags":{"force":{"name":"force","type":"boolean","char":"f","description":"Overwrite the destination directory and files if they already exist.","allowNo":false},"path":{"name":"path","type":"option","description":"The path to the directory of the new Hydrogen storefront.","multiple":false},"language":{"name":"language","type":"option","description":"Sets the template language to use. One of `js` or `ts`.","multiple":false},"template":{"name":"template","type":"option","description":"Sets the template to use. One of `demo-store` or `hello-world`.","multiple":false},"install-deps":{"name":"install-deps","type":"boolean","description":"Auto install dependencies using the active package manager","allowNo":true}},"args":[]},"hydrogen:preview":{"id":"hydrogen:preview","description":"Runs a Hydrogen storefront in an Oxygen worker for production.","strict":true,"pluginName":"@shopify/cli-hydrogen","pluginAlias":"@shopify/cli-hydrogen","pluginType":"core","aliases":[],"flags":{"path":{"name":"path","type":"option","description":"The path to the directory of the Hydrogen storefront. The default is the current directory.","multiple":false},"port":{"name":"port","type":"option","description":"Port to run the server on.","multiple":false,"default":3000}},"args":[]},"hydrogen:generate:route":{"id":"hydrogen:generate:route","description":"Generates a standard Shopify route.","strict":true,"pluginName":"@shopify/cli-hydrogen","pluginAlias":"@shopify/cli-hydrogen","pluginType":"core","aliases":[],"flags":{"adapter":{"name":"adapter","type":"option","description":"Remix adapter used in the route. The default is `@shopify/remix-oxygen`.","multiple":false},"typescript":{"name":"typescript","type":"boolean","description":"Generate TypeScript files","allowNo":false},"force":{"name":"force","type":"boolean","char":"f","description":"Overwrite the destination directory and files if they already exist.","allowNo":false},"path":{"name":"path","type":"option","description":"The path to the directory of the Hydrogen storefront. The default is the current directory.","multiple":false}},"args":[{"name":"route","description":"The route to generate. One of home,page,cart,products,collections,policies,robots,sitemap,account,all.","required":true,"options":["home","page","cart","products","collections","policies","robots","sitemap","account","all"]}]},"hydrogen:generate:routes":{"id":"hydrogen:generate:routes","description":"Generates all supported standard shopify routes.","strict":true,"pluginName":"@shopify/cli-hydrogen","pluginAlias":"@shopify/cli-hydrogen","pluginType":"core","aliases":[],"flags":{"adapter":{"name":"adapter","type":"option","description":"Remix adapter used in the route. The default is `@shopify/remix-oxygen`.","multiple":false},"typescript":{"name":"typescript","type":"boolean","description":"Generate TypeScript files","allowNo":false},"force":{"name":"force","type":"boolean","char":"f","description":"Overwrite the destination directory and files if they already exist.","allowNo":false},"path":{"name":"path","type":"option","description":"The path to the directory of the Hydrogen storefront. The default is the current directory.","multiple":false}},"args":[]}}}
{"version":"4.0.9","commands":{"hydrogen:build":{"id":"hydrogen:build","description":"Builds a Hydrogen storefront for production.","strict":true,"pluginName":"@shopify/cli-hydrogen","pluginAlias":"@shopify/cli-hydrogen","pluginType":"core","aliases":[],"flags":{"path":{"name":"path","type":"option","description":"The path to the directory of the Hydrogen storefront. The default is the current directory.","multiple":false},"sourcemap":{"name":"sourcemap","type":"boolean","description":"Generate sourcemaps for the build.","allowNo":false},"disable-route-warning":{"name":"disable-route-warning","type":"boolean","description":"Disable warning about missing standard routes.","allowNo":false},"base":{"name":"base","type":"option","hidden":true,"multiple":false},"entry":{"name":"entry","type":"option","hidden":true,"multiple":false},"target":{"name":"target","type":"option","hidden":true,"multiple":false}},"args":[]},"hydrogen:check":{"id":"hydrogen:check","description":"Returns diagnostic information about a Hydrogen storefront.","strict":true,"pluginName":"@shopify/cli-hydrogen","pluginAlias":"@shopify/cli-hydrogen","pluginType":"core","aliases":[],"flags":{"path":{"name":"path","type":"option","description":"The path to the directory of the Hydrogen storefront. The default is the current directory.","multiple":false}},"args":[{"name":"resource","description":"The resource to check. Currently only 'routes' is supported.","required":true,"options":["routes"]}]},"hydrogen:dev":{"id":"hydrogen:dev","description":"Runs Hydrogen storefront in an Oxygen worker for development.","strict":true,"pluginName":"@shopify/cli-hydrogen","pluginAlias":"@shopify/cli-hydrogen","pluginType":"core","aliases":[],"flags":{"path":{"name":"path","type":"option","description":"The path to the directory of the Hydrogen storefront. The default is the current directory.","multiple":false},"port":{"name":"port","type":"option","description":"Port to run the server on.","multiple":false,"default":3000},"disable-virtual-routes":{"name":"disable-virtual-routes","type":"boolean","description":"Disable rendering fallback routes when a route file doesn't exist","allowNo":false},"host":{"name":"host","type":"option","hidden":true,"multiple":false}},"args":[]},"hydrogen:g":{"id":"hydrogen:g","description":"Shortcut for `hydrogen generate`. See `hydrogen generate --help` for more information.","strict":false,"pluginName":"@shopify/cli-hydrogen","pluginAlias":"@shopify/cli-hydrogen","pluginType":"core","hidden":true,"aliases":[],"flags":{},"args":[]},"hydrogen:init":{"id":"hydrogen:init","description":"Creates a new Hydrogen storefront.","strict":true,"pluginName":"@shopify/cli-hydrogen","pluginAlias":"@shopify/cli-hydrogen","pluginType":"core","aliases":[],"flags":{"force":{"name":"force","type":"boolean","char":"f","description":"Overwrite the destination directory and files if they already exist.","allowNo":false},"path":{"name":"path","type":"option","description":"The path to the directory of the new Hydrogen storefront.","multiple":false},"language":{"name":"language","type":"option","description":"Sets the template language to use. One of `js` or `ts`.","multiple":false},"template":{"name":"template","type":"option","description":"Sets the template to use. One of `demo-store` or `hello-world`.","multiple":false},"install-deps":{"name":"install-deps","type":"boolean","description":"Auto install dependencies using the active package manager","allowNo":true}},"args":[]},"hydrogen:preview":{"id":"hydrogen:preview","description":"Runs a Hydrogen storefront in an Oxygen worker for production.","strict":true,"pluginName":"@shopify/cli-hydrogen","pluginAlias":"@shopify/cli-hydrogen","pluginType":"core","aliases":[],"flags":{"path":{"name":"path","type":"option","description":"The path to the directory of the Hydrogen storefront. The default is the current directory.","multiple":false},"port":{"name":"port","type":"option","description":"Port to run the server on.","multiple":false,"default":3000}},"args":[]},"hydrogen:shortcut":{"id":"hydrogen:shortcut","description":"Creates a global `h2` shortcut for the Hydrogen CLI","strict":true,"pluginName":"@shopify/cli-hydrogen","pluginAlias":"@shopify/cli-hydrogen","pluginType":"core","aliases":[],"flags":{},"args":[]},"hydrogen:generate:route":{"id":"hydrogen:generate:route","description":"Generates a standard Shopify route.","strict":true,"pluginName":"@shopify/cli-hydrogen","pluginAlias":"@shopify/cli-hydrogen","pluginType":"core","aliases":[],"flags":{"adapter":{"name":"adapter","type":"option","description":"Remix adapter used in the route. The default is `@shopify/remix-oxygen`.","multiple":false},"typescript":{"name":"typescript","type":"boolean","description":"Generate TypeScript files","allowNo":false},"force":{"name":"force","type":"boolean","char":"f","description":"Overwrite the destination directory and files if they already exist.","allowNo":false},"path":{"name":"path","type":"option","description":"The path to the directory of the Hydrogen storefront. The default is the current directory.","multiple":false}},"args":[{"name":"route","description":"The route to generate. One of home,page,cart,products,collections,policies,robots,sitemap,account,all.","required":true,"options":["home","page","cart","products","collections","policies","robots","sitemap","account","all"]}]},"hydrogen:generate:routes":{"id":"hydrogen:generate:routes","description":"Generates all supported standard shopify routes.","strict":true,"pluginName":"@shopify/cli-hydrogen","pluginAlias":"@shopify/cli-hydrogen","pluginType":"core","aliases":[],"flags":{"adapter":{"name":"adapter","type":"option","description":"Remix adapter used in the route. The default is `@shopify/remix-oxygen`.","multiple":false},"typescript":{"name":"typescript","type":"boolean","description":"Generate TypeScript files","allowNo":false},"force":{"name":"force","type":"boolean","char":"f","description":"Overwrite the destination directory and files if they already exist.","allowNo":false},"path":{"name":"path","type":"option","description":"The path to the directory of the Hydrogen storefront. The default is the current directory.","multiple":false}},"args":[]}}}
21 changes: 21 additions & 0 deletions packages/cli/src/commands/hydrogen/g.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import Command from '@shopify/cli-kit/node/base-command';
import GenerateRoute from './generate/route.js';

export default class GenerateRouteShortcut extends Command {
static description =
'Shortcut for `hydrogen generate`. See `hydrogen generate --help` for more information.';

static strict = false;

static hidden = true;

async run(): Promise<void> {
const [command, ...args] = this.argv;

if (command === 'r' || command === 'route') {
return new GenerateRoute(args, this.config).run();
}

throw new Error(`Invalid command argument "${command}".`);
}
}
86 changes: 86 additions & 0 deletions packages/cli/src/commands/hydrogen/shortcut.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import {describe, it, expect, vi, beforeEach, afterEach} from 'vitest';
import {runCreateShortcut} from './shortcut.js';
import {outputMocker} from '@shopify/cli-kit';
import {isWindows, isGitBash, supportsShell} from '../../utils/shell.js';
import {execSync, exec} from 'child_process';

describe('shortcut', () => {
const outputMock = outputMocker.mockAndCaptureOutput();

beforeEach(() => {
vi.resetAllMocks();
vi.mock('child_process');
vi.mock('../../utils/shell.js', async () => {
return {
isWindows: vi.fn(),
isGitBash: vi.fn(),
supportsShell: vi.fn(),
shellWriteFile: () => true,
shellRunScript: () => true,
hasAlias: () => false,
homeFileExists: () => Promise.resolve(true),
};
});

vi.mocked(supportsShell).mockImplementation(
(shell: string) => !isWindows() || shell === 'bash',
);
});

afterEach(() => {
outputMock.clear();
// Check we are mocking all the things:
expect(execSync).toHaveBeenCalledTimes(0);
expect(exec).toHaveBeenCalledTimes(0);
});

it('creates aliases for Unix', async () => {
// Given
vi.mocked(isWindows).mockReturnValue(false);

// When
await runCreateShortcut();

// Then
expect(outputMock.info()).toMatch(`zsh, bash, fish`);
expect(outputMock.error()).toBeFalsy();
});

it('creates aliases for Windows', async () => {
// Given
vi.mocked(isWindows).mockReturnValue(true);

// When
await runCreateShortcut();

// Then
expect(outputMock.info()).toMatch(`PowerShell, PowerShell 7+`);
expect(outputMock.error()).toBeFalsy();
});

it('creates aliases for Windows in Git Bash', async () => {
// Given
vi.mocked(isWindows).mockReturnValue(true);
vi.mocked(isGitBash).mockReturnValueOnce(true);

// When
await runCreateShortcut();

// Then
expect(outputMock.info()).toMatch('bash');
expect(outputMock.error()).toBeFalsy();
});

it('warns when not finding shells', async () => {
// Given
vi.mocked(isWindows).mockReturnValue(false);
vi.mocked(supportsShell).mockReturnValue(false);

// When
await runCreateShortcut();

// Then
expect(outputMock.info()).toBeFalsy();
expect(outputMock.error()).toBeTruthy();
});
});
Loading

0 comments on commit 68a6028

Please sign in to comment.