Skip to content

Commit

Permalink
feat(cli): add compas watch [..sub-commands] and --watch (#1587)
Browse files Browse the repository at this point in the history
* feat(cli): add watch command

Closes #1509

* chore: add some documentation

* chore: sync cli reference

* fix: remove comment in json block

* chore: fix docs build

* docs: add config document

* fix types
  • Loading branch information
dirkdev98 committed Feb 9, 2022
1 parent 5a2e6bf commit b1f0762
Show file tree
Hide file tree
Showing 32 changed files with 1,181 additions and 65 deletions.
9 changes: 8 additions & 1 deletion config/compas.json
@@ -1 +1,8 @@
{}
{
"cli": {
"globalWatchOptions": {
"extensions": [],
"ignorePatterns": ["__fixtures__", "test/tmp"]
}
}
}
15 changes: 13 additions & 2 deletions docs/.vitepress/config.js
Expand Up @@ -130,10 +130,21 @@ function getHomeSidebar() {
},
],
},

{
text: "CLI Reference",
link: "/references/cli.html",
text: "References",
children: [
{
text: "Compas configuration",
link: "/references/compas-config.html",
},
{
text: "CLI Reference",
link: "/references/cli.html",
},
],
},

{
text: "Migrations",
link: "/migrations/index.html",
Expand Down
17 changes: 17 additions & 0 deletions docs/features/extending-the-cli.md
Expand Up @@ -61,6 +61,10 @@ export const cliDefinition = {
// When set to true, instead of matching on the name any value can be passed, i.e `compas run generate`, `compas run foo`.
// Defaults to false.
isDynamic: false,

// When set to true, this command allows '--watch' and `cli watch [command.name]`, see 'watchSettings' below, to tune the behaviour.
// Defaults to false.
isWatchable: false,
},

// Optional flag array. Sub commands also allow the flags defined by their parents.
Expand Down Expand Up @@ -133,6 +137,19 @@ export const cliDefinition = {
// An optional executor. If a command does not have an executor, the executor of it's (recursive) parent is used.
executor: cliExecutor,

// Extra properties for 'modifiers.isWatchable' commands
// Defaults to
// { extensions: ["js", "json"], ignorePatterns: [".cache", "coverage", "node_modules"], }
watchSettings: {
// Specific extensions to watch for
// Is optional
extensions: [ "js", "json" ],

// Specific patterns to filter out.
// Use this if your program or another program writes files that you don't want this command to be restarted for.
ignorePatterns: [ ".cache" ],
},

// Extra prperties for 'modifiers.isDynamic' commands
dynamicValue: {
// Called when parsing the command. May return a Promise.
Expand Down
76 changes: 68 additions & 8 deletions docs/references/cli.md
Expand Up @@ -32,9 +32,10 @@ Configure shell auto-complete for this CLI.

Run all '.bench.js' files in this project.

| Option | Description |
| ---------- | -------------------------------------------------------- |
| -h, --help | Display information about the current command. (boolean) |
| Option | Description |
| ---------- | ---------------------------------------------------------------------------------------------------------------- |
| --watch | Run the command, restarting it when file changes happen. See 'compas help watch' for more information. (boolean) |
| -h, --help | Display information about the current command. (boolean) |

## `compas check-env`

Expand Down Expand Up @@ -198,6 +199,7 @@ time. To disable this behaviour when the command enters watch mode,
| --connection-settings | Specify a path that contains the PostgreSQL connection object. (string) |
| --keep-alive | Keep the service running, by maintaining a single idle SQL connection. (boolean) |
| --without-lock | Drop the migration lock, before entering the keep-alive state. Only used when `--keep-alive` is passed as well (boolean) |
| --watch | Run the command, restarting it when file changes happen. See 'compas help watch' for more information. (boolean) |
| -h, --help | Display information about the current command. (boolean) |

### `compas migrate info`
Expand All @@ -209,6 +211,7 @@ Print the current migration state.
| --connection-settings | Specify a path that contains the PostgreSQL connection object. (string) |
| --keep-alive | Keep the service running, by maintaining a single idle SQL connection. (boolean) |
| --without-lock | Drop the migration lock, before entering the keep-alive state. Only used when `--keep-alive` is passed as well (boolean) |
| --watch | Run the command, restarting it when file changes happen. See 'compas help watch' for more information. (boolean) |
| -h, --help | Display information about the current command. (boolean) |

### `compas migrate rebuild`
Expand All @@ -220,6 +223,7 @@ Recreate migration state based on the file system.
| --connection-settings | Specify a path that contains the PostgreSQL connection object. (string) |
| --keep-alive | Keep the service running, by maintaining a single idle SQL connection. (boolean) |
| --without-lock | Drop the migration lock, before entering the keep-alive state. Only used when `--keep-alive` is passed as well (boolean) |
| --watch | Run the command, restarting it when file changes happen. See 'compas help watch' for more information. (boolean) |
| -h, --help | Display information about the current command. (boolean) |

## `compas proxy`
Expand Down Expand Up @@ -255,11 +259,12 @@ located in the scripts directory.

The file or script to run.

| Option | Description |
| ------------- | -------------------------------------------------------- |
| --script-args | undefined (string) |
| --node-args | undefined (string) |
| -h, --help | Display information about the current command. (boolean) |
| Option | Description |
| ------------- | ---------------------------------------------------------------------------------------------------------------- |
| --watch | Run the command, restarting it when file changes happen. See 'compas help watch' for more information. (boolean) |
| --script-args | undefined (string) |
| --node-args | undefined (string) |
| -h, --help | Display information about the current command. (boolean) |

## `compas test`

Expand Down Expand Up @@ -297,6 +302,7 @@ https://www.npmjs.com/package/c8 for more information.
| --parallel-count | The number of workers to use, when running in parallel. Defaulting to (the number of CPU cores - 1) or 4, whichever is lower. (number) |
| --randomize-rounds | Runs test the specified amount of times, shuffling the test file order between runs. (number) |
| --coverage | Collect coverage information while running the tests. (boolean) |
| --watch | Run the command, restarting it when file changes happen. See 'compas help watch' for more information. (boolean) |
| -h, --help | Display information about the current command. (boolean) |

## `compas version`
Expand Down Expand Up @@ -335,3 +341,57 @@ Display help for any of the available commands.
| Option | Description |
| ---------- | -------------------------------------------------------- |
| -h, --help | Display information about the current command. (boolean) |

## `compas watch`

Run the command, restarting it when file changes happen.

Some commands in this CLI can be watched. They can be executed via
`compas watch [..subCommand]` or by adding the '--watch' flag when invoking the
command.

The watching happens by monitoring all the files in your project and restarting
the command once files are changed. Manually restarting is also possible by
sending `rs<enter>` to the program.

Watch behaviour can be tuned by commands. Setting 'modifiers.isWatchable' to
'true' is necessary for it to allow watching, and 'watchSettings' can be
specified with custom extensions to be watched or specific directories to
ignore. When watch behavior is needed for custom scripts, following the steps in
[extending the cli](https://compasjs.com/features/extending-the-cli.html) is
mandatory.

```js
export const cliDefinition = {
name: "my-command",
shortDescription: "My command",
modifiers: {
isWatchable: true, // This is mandatory
},
watchSettings: {
extensions: ["js", "ts"], // Defaults to '["js", "json"]'
ignorePatterns: ["__fixtures__"], // Defaults to '[".cache", "coverage", "node_modules"]'
},
};
```

You can also add a compas config file at 'config/compas.{js,json}' to specify
project specific items. They are appended to the specification of the command
and can be used if your tests write files that may trigger the watcher. See the
[config loader](https://compasjs.com/features/config-files.html#config-loader)
for more information about config files.

```json
{
"cli": {
"globalWatchOptions": {
"extensions": [],
"ignorePatterns": ["__fixtures__", "test/tmp"]
}
}
}
```

| Option | Description |
| ---------- | -------------------------------------------------------- |
| -h, --help | Display information about the current command. (boolean) |
46 changes: 46 additions & 0 deletions docs/references/compas-config.md
@@ -0,0 +1,46 @@
# Compas configuration

Most features work without external configuration. They are either relatively
strict to enforce a way of working, or are configurable via their api's.
However, some features are better configured via a global configuration file.
This file is optional, but if exists should be located at
`config/compas.{js,mjs,json}`. The file is loaded via the
[config loader](/features/config-files.html#config-loader).

## Contents

All keys are optional

- **cli** (object): root property for configuration for the CLI package
- **commandDirectories** (string[]): Array of directories relative to the
project root. All JavaScript files will be imported by the CLI and checked
if it exports a 'cliDefinition'. See
[extending the cli](https://compasjs.com/features/extending-the-cli.html)
for more information. The loader does not recurse in to sub directories.
- **globalWatchOptions** (object): Project level watch options, applied to all
commands running in 'watch' mode via the Compas CLI. The values here are
appended to the defaults of the specific command that is being watched.
- **extensions** (string[]): Add file extensions that should be watched. Say
that you are creating a static site generator, then you most likely also
want to restart if markdown files are changed.
- **ignorePatterns** (string[]): Remove directories from being watched, this
has precedence over the included extensions. Useful to ignore build output
directories and other temporary output created by the command, so it is
not restarted because of it's own changes.

### Example

```js
export function config() {
return {
cli: {
commandDirectories: ["./src/my-commands"],

globalWatchOptions: {
extensions: ["md"],
ignorePatterns: ["dist"],
},
},
};
}
```
15 changes: 15 additions & 0 deletions gen/cli.js
Expand Up @@ -84,11 +84,13 @@ export function applyCliStructure(app) {
.keys({
isDynamic: T.bool().default(false),
isCosmetic: T.bool().default(false),
isWatchable: T.bool().default(false),
})
.default(
JSON.stringify({
isDynamic: false,
isCosmetic: false,
isWatchable: false,
}),
),
dynamicValue: T.object()
Expand All @@ -107,6 +109,19 @@ export function applyCliStructure(app) {
.optional(),
})
.default("{}"),
watchSettings: T.object()
.keys({
extensions: T.array().values(T.string()).default(`["js", "json"]`),
ignorePatterns: T.array()
.values(T.string())
.default(`[".cache", "coverage", "node_modules"]`),
})
.default(
JSON.stringify({
extensions: ["js", "json"],
ignorePatterns: [".cache", "coverage", "node_modules"],
}),
),
subCommands: T.array()
.values(T.reference("cli", "commandDefinition"))
.default("[]"),
Expand Down
4 changes: 3 additions & 1 deletion packages/cli/package.json
Expand Up @@ -28,8 +28,10 @@
"dependencies": {
"@compas/stdlib": "0.0.184",
"c8": "7.11.0",
"chokidar": "3.5.3",
"http-proxy": "1.18.1",
"recast": "0.20.5"
"recast": "0.20.5",
"tree-kill": "1.2.2"
},
"maintainers": [
{
Expand Down
6 changes: 6 additions & 0 deletions packages/cli/src/cli/command.js
Expand Up @@ -6,6 +6,7 @@ import {
} from "@compas/stdlib";
import { cliHelpShouldRun } from "./help.js";
import { cliParserParseCommand, cliParserSplitArgs } from "./parser.js";
import { cliWatchShouldRun } from "./watch.js";

/**
* Get the CLI root, skips 'help'.
Expand Down Expand Up @@ -53,6 +54,11 @@ export async function cliCommandDetermine(event, cli, input) {
return {
value: cli.subCommands.find((it) => it.name === "help"),
};
} else if (cliWatchShouldRun(commandArgs, flagArgs)) {
// @ts-ignore
return {
value: cli.subCommands.find((it) => it.name === "watch"),
};
}

eventStop(event);
Expand Down
12 changes: 10 additions & 2 deletions packages/cli/src/cli/help.js
Expand Up @@ -32,6 +32,11 @@ export function cliHelpInit(cli) {
modifiers: {
isCosmetic: false,
isDynamic: false,
isWatchable: false,
},
watchSettings: {
extensions: [],
ignorePatterns: [],
},
dynamicValue: {},
executor: () => {
Expand Down Expand Up @@ -93,8 +98,10 @@ export function cliHelpCheckForReservedKeys(command) {
});
}

for (const cmd of command.subCommands) {
cliHelpCheckForReservedKeys(cmd);
if (!["help", "watch"].includes(command.name)) {
for (const cmd of command.subCommands) {
cliHelpCheckForReservedKeys(cmd);
}
}
}

Expand Down Expand Up @@ -143,6 +150,7 @@ export async function cliHelpGetMessage(event, cli, userInput) {
);

if (command.error) {
eventStop(event);
return command;
}

Expand Down
5 changes: 3 additions & 2 deletions packages/cli/src/cli/init.js
Expand Up @@ -11,6 +11,7 @@ import { validateCliCommandDefinition } from "../generated/cli/validators.js";
import { cliHelpInit } from "./help.js";
import { cliLoaderLoadDirectories } from "./loader.js";
import { lowerCaseFirst } from "./utils.js";
import { cliWatchInit } from "./watch.js";

/**
*
Expand Down Expand Up @@ -47,7 +48,7 @@ export async function cliInit(event, root, options) {
cliInitValidateFlags(cli);

cliHelpInit(cli);
// TODO: cliWatchInit
cliWatchInit(cli);

cliInitAddDefaultCompletions(cli);

Expand Down Expand Up @@ -199,7 +200,7 @@ function cliInitAddDefaultCompletions(command) {
};
}

if (command.name !== "help") {
if (!["help", "watch"].includes(command.name)) {
for (const cmd of command.subCommands) {
cliInitAddDefaultCompletions(cmd);
}
Expand Down
3 changes: 1 addition & 2 deletions packages/cli/src/cli/parser.js
Expand Up @@ -198,8 +198,7 @@ export async function cliParserParseFlags(event, command, userInput) {
}
} else if (
!flagDefinition.modifiers.isRepeatable &&
!isNil(result[flagDefinition.name]) &&
flagDefinition.name !== "help"
!isNil(result[flagDefinition.name])
) {
return {
error: {
Expand Down

0 comments on commit b1f0762

Please sign in to comment.