Skip to content

Commit

Permalink
Merge pull request #886 from pmcelhaney/821-add-cli-option-to-skip-ge…
Browse files Browse the repository at this point in the history
…nerating-types
  • Loading branch information
pmcelhaney authored May 9, 2024
2 parents 9e55c63 + bc90107 commit e5a4488
Show file tree
Hide file tree
Showing 13 changed files with 324 additions and 95 deletions.
5 changes: 5 additions & 0 deletions .changeset/curvy-wasps-compare.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"counterfact": minor
---

Add command line options for granular control of the different parts of CF
108 changes: 102 additions & 6 deletions bin/counterfact.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#!/usr/bin/env node
/* eslint-disable complexity */

import { readFile } from "node:fs/promises";
import nodePath from "node:path";
Expand Down Expand Up @@ -43,18 +44,81 @@ function padTagLine(tagLine) {
return `${padding}${tagLine}`;
}

// eslint-disable-next-line max-statements
function createWatchMessage(config) {
let watchMessage = "";

switch (true) {
case config.watch.routes && config.watch.types: {
watchMessage = "Watching for changes";

break;
}
case config.watch.routes: {
watchMessage = "Watching routes for changes";

break;
}
case config.watch.types: {
watchMessage = "Watching types for changes";

break;
}

default: {
watchMessage = undefined;
}
}

if (!watchMessage) {
switch (true) {
case config.generate.routes && config.generate.types: {
watchMessage = "Generating routes and types";

break;
}
case config.generate.routes: {
watchMessage = "Generating routes";

break;
}
case config.generate.types: {
watchMessage = "Generating types";

break;
}

default: {
watchMessage = undefined;
}
}
}

return watchMessage;
}

// eslint-disable-next-line max-statements
async function main(source, destination) {
debug("executing the main function");

const options = program.opts();
// eslint-disable-next-line sonar/process-argv
const args = process.argv;

const destinationPath = nodePath
.join(process.cwd(), destination)
.replaceAll("\\", "/");

const basePath = nodePath.resolve(destinationPath).replaceAll("\\", "/");

// If no options are provided, default to all options
if (!args.some((argument) => argument.startsWith("-"))) {
options.repl = true;
options.serve = true;
options.watch = true;
options.generate = true;
}

debug("options: %o", options);
debug("source: %s", source);
debug("destination: %s", destination);
Expand All @@ -69,12 +133,34 @@ async function main(source, destination) {

const config = {
basePath,

generate: {
routes:
options.generate ||
options.generateRoutes ||
options.watch ||
options.watchRoutes,

types:
options.generate ||
options.generateTypes ||
options.watch ||
options.watchTypes,
},

includeSwaggerUi: true,
openApiPath: source,
port: options.port,
proxyEnabled: Boolean(options.proxyUrl),
proxyUrl: options.proxyUrl,
routePrefix: options.prefix,
startRepl: options.repl,
startServer: options.serve,

watch: {
routes: options.watch || options.watchRoutes,
types: options.watch || options.watchTypes,
},
};

debug("loading counterfact (%o)", config);
Expand All @@ -83,6 +169,8 @@ async function main(source, destination) {

debug("loaded counterfact", config);

const watchMessage = createWatchMessage(config);

const introduction = [
"____ ____ _ _ _ _ ___ ____ ____ ____ ____ ____ ___",
"|___ [__] |__| |\\| | |=== |--< |--- |--| |___ | ",
Expand All @@ -97,7 +185,9 @@ async function main(source, destination) {
"🎉 VERSION 1.0 IS COMING! LEARN MORE:",
" https://github.com/pmcelhaney/counterfact/issues/823",
"",
"Starting REPL, type .help for more info",
watchMessage,
config.startServer ? "Starting server" : undefined,
config.startRepl ? "Starting REPL, type .help for more info" : undefined,
];

process.stdout.write(
Expand All @@ -107,9 +197,7 @@ async function main(source, destination) {
process.stdout.write("\n\n");

debug("starting server");

await start();

await start(config);
debug("started server");

if (openBrowser) {
Expand All @@ -130,9 +218,17 @@ program
"_",
)
.argument("[destination]", "path to generated code", ".")
.option("--port <number>", "server port number", DEFAULT_PORT)
.option("-p, --port <number>", "server port number", DEFAULT_PORT)
.option("--swagger", "include swagger-ui")
.option("--open", "open a browser")
.option("-o, --open", "open a browser")
.option("-g, --generate", "generate all code for both routes and types")
.option("--generate-types", "generate types")
.option("--generate-routes", "generate routes")
.option("-w, --watch", "generate + watch all code for changes")
.option("--watch-types", "generate + watch types for changes")
.option("--watch-routes", "generate + watch routes for changes")
.option("-s, --serve", "start the server")
.option("-r, --repl", "start the REPL")
.option("--proxy-url <string>", "proxy URL")
.option(
"--prefix <string>",
Expand Down
10 changes: 10 additions & 0 deletions docs/faq-generated-code.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,16 @@ If you have control over the OpenAPI (i.e. you're not getting it from a third pa

No. The fact that you can change the code while the server is running is what makes mocking in Counterfact so pleasant. It watches for file changes and updates automatically. And the state of your context objects is preserved. You can even change the definition of a context object (`_.context.js`) and Counterfact will preserve the values of any properties you didn't explicitly change.

## Can I have more granular control over when the generated code is created?

Yes. Maybe you're working on a feature and a recent Counterfact bump has caused your types to get updated and it's cluttering your staging area. No problem. You can use the `--generate`, `--generate-types` and `--generate-routes` options to have more control over when the generated code is created. This allows you to generate the types and routes separately and to run the server without generating the code.

```bash
npx counterfact my-api.yml --generate-types
```

You can use the `--watch`, `--watch-types` and `--watch-routes` flags to have Counterfact watch the types and routes files for changes and regenerate the code when they change. Watching will always include generating the code.

## After reading this FAQ and now I have even more questions!

Please reach out by [creating an issue](https://github.com/pmcelhaney/counterfact/issues).
Expand Down
12 changes: 10 additions & 2 deletions docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,15 @@ Arguments:
Options:
--port <number> server port number (default: 3100)
--swagger include swagger-ui
--open open a browser
-o, --open open a browser
-g, --generate generate all code for both routes and types
--generate-types generate types
--generate-routes generate routes
-w, --watch generate + watch all code for changes
--watch-types generate + watch types for changes
--watch-routes generate + watch routes for changes
-s, --serve start the mock server
-r, --repl start the REPL
--proxy-url <string> proxy URL
--prefix <string> base path from which routes will be served (e.g. /api/v1)
-h, --help display help for command
Expand Down Expand Up @@ -70,7 +78,7 @@ After generating code you should have three directories:
- 📂 **paths** contains the implementation of each endpoint, a.k.a. routes. Counterfact uses the word "paths" because it corresponds to the `/paths` section of the spec.
- 📂 **path-types** contains the type information for paths.

The code under `components` and `path-types` is regenerated every time you run Counterfact, so that the types can stay in sync with any OpenAPI changes. The code under paths is minimal boilerplate that you're meant to edit by hand. Counterfact will not overwrite your changes in the `paths` directory, but it will add new files when necessary.
When you launch Counterfact with no command line options, the code under `components` and `path-types` is regenerated every time you run Counterfact, so that the types can stay in sync with any OpenAPI changes. The code under paths is minimal boilerplate that you're meant to edit by hand. Counterfact will not overwrite your changes in the `paths` directory, but it will add new files when necessary. If you use any of the command line options then it will only regenerate the code when you tell it to via the `--watch` or `--generate` options.

See also [Generated Code FAQ](./faq-generated-code.md)

Expand Down
36 changes: 25 additions & 11 deletions src/server/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import { createHttpTerminator, type HttpTerminator } from "http-terminator";
import yaml from "js-yaml";
import $RefParser from "json-schema-ref-parser";

import { CodeGenerator } from "../typescript-generator/code-generator.js";
import { readFile } from "../util/read-file.js";
import { CodeGenerator } from "./code-generator.js";
import type { Config } from "./config.js";
import { ContextRegistry } from "./context-registry.js";
import { createKoaApp } from "./create-koa-app.js";
Expand Down Expand Up @@ -44,7 +44,11 @@ export async function counterfact(config: Config) {

const contextRegistry = new ContextRegistry();

const codeGenerator = new CodeGenerator(config.openApiPath, config.basePath);
const codeGenerator = new CodeGenerator(
config.openApiPath,
config.basePath,
config.generate,
);

const dispatcher = new Dispatcher(
registry,
Expand All @@ -69,18 +73,28 @@ export async function counterfact(config: Config) {
const koaApp = createKoaApp(registry, middleware, config);

// eslint-disable-next-line max-statements
async function start(options: { http?: boolean } = {}) {
const http = options.http ?? true;

await codeGenerator.watch();
await transpiler.watch();
await moduleLoader.load();
await moduleLoader.watch();
async function start(options: Config) {
const {
generate,
startRepl: shouldStartRepl,
startServer,
watch,
} = options;

if (generate.routes || generate.types) {
await codeGenerator.generate();
}
if (watch.routes || watch.types) {
await codeGenerator.watch();
}

// eslint-disable-next-line @typescript-eslint/init-declarations
let httpTerminator: HttpTerminator | undefined;

if (http) {
if (startServer) {
await transpiler.watch();
await moduleLoader.load();
await moduleLoader.watch();
const server = koaApp.listen({
port: config.port,
});
Expand All @@ -90,7 +104,7 @@ export async function counterfact(config: Config) {
});
}

const replServer = startRepl(contextRegistry, config);
const replServer = shouldStartRepl && startRepl(contextRegistry, config);

return {
replServer,
Expand Down
50 changes: 0 additions & 50 deletions src/server/code-generator.ts

This file was deleted.

10 changes: 10 additions & 0 deletions src/server/config.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,20 @@
export interface Config {
basePath: string;
generate: {
routes: boolean;
types: boolean;
};
openApiPath: string;
port: number;
proxyEnabled: boolean;
proxyUrl: string;
routePrefix: string;
startRepl: boolean;
startServer: boolean;
watch: {
routes: boolean;
types: boolean;
};
}

export const DUMMY_EXPORT_FOR_TEST_COVERAGE = 1;
Loading

0 comments on commit e5a4488

Please sign in to comment.