counterfact 0.11.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/.eslintrc.cjs +8 -1
  2. package/CHANGELOG.md +6 -0
  3. package/CONTRIBUTING.md +30 -0
  4. package/README.md +17 -15
  5. package/_config.yaml +12 -0
  6. package/_includes/head-custom-google-analytics.html +13 -0
  7. package/bin/counterfact.js +11 -2
  8. package/package.json +4 -4
  9. package/src/client/index.html +43 -0
  10. package/src/{context-registry.js → server/context-registry.js} +0 -0
  11. package/src/{counterfact.js → server/counterfact.js} +9 -3
  12. package/src/{dispatcher.js → server/dispatcher.js} +0 -0
  13. package/src/{koa-middleware.js → server/koa-middleware.js} +0 -0
  14. package/src/{module-loader.js → server/module-loader.js} +0 -0
  15. package/src/{registry.js → server/registry.js} +0 -0
  16. package/src/{response-builder.js → server/response-builder.js} +0 -0
  17. package/src/{start.js → server/start.js} +17 -37
  18. package/src/{tools.js → server/tools.js} +0 -0
  19. package/src/{transpiler.js → server/transpiler.js} +0 -0
  20. package/src/typescript-generator/specification.js +1 -1
  21. package/src/{read-file.js → util/read-file.js} +0 -0
  22. package/test/{context-registry.test.js → server/context-registry.test.js} +4 -1
  23. package/test/{counterfact.test.js → server/counterfact.test.js} +2 -3
  24. package/test/{dispatcher.test.js → server/dispatcher.test.js} +3 -3
  25. package/test/{koa-middleware.test.js → server/koa-middleware.test.js} +4 -4
  26. package/test/{module-loader.test.js → server/module-loader.test.js} +4 -5
  27. package/test/{registry.test.js → server/registry.test.js} +1 -1
  28. package/test/{response-builder.test.js → server/response-builder.test.js} +1 -1
  29. package/test/server/start.test.js +10 -0
  30. package/test/{tools.test.js → server/tools.test.js} +1 -1
  31. package/test/{transpiler.test.js → server/transpiler.test.js} +2 -3
  32. package/test/start.test.js +0 -10
package/.eslintrc.cjs CHANGED
@@ -36,7 +36,14 @@ const rules = {
36
36
  };
37
37
 
38
38
  module.exports = {
39
- ignorePatterns: ["/node_modules/", "/coverage/", "/reports/", "/out/"],
39
+ ignorePatterns: [
40
+ "/node_modules/",
41
+ "/coverage/",
42
+ "/reports/",
43
+ "/out/",
44
+ "_includes",
45
+ ".stryker-tmp",
46
+ ],
40
47
 
41
48
  extends: ["hardcore", "hardcore/ts", "hardcore/node"],
42
49
 
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # counterfact
2
2
 
3
+ ## 0.12.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 1a622d2: a REPL allows you to interact with the server's context programatically
8
+
3
9
  ## 0.11.0
4
10
 
5
11
  ### Minor Changes
@@ -0,0 +1,30 @@
1
+ # Contributing
2
+
3
+ ## Where I need help
4
+
5
+ - **Feedback:** First and foremost, send me feedback. What makes sense? What's confusing? What's missing? What's broken? [Send me an email](pmcelhaney@gmail.com), [open an issue](https://github.com/pmcelhaney/counterfact/issues/new), or [start a discussion](https://github.com/pmcelhaney/counterfact/discussions).
6
+ - **Documentation:** There are probably typos in this very document. Please send PRs, large and small. You can do it right from your browser. If you're viewing this document in Github, click the pencil button in the top right corner.
7
+ - **Graphic design:** I'm terrible at graphic design. I know good design when I see it, and I can tell you why it's good, but struggle with the creative process. Whether it's building a web site, designing a logo, brainstorming on a GUI, or fixing up some ugly Markdown code, I'll take whatever help I can get!
8
+ - **Tests:** Test coverage is pretty good, but there are gaps. Filling those gaps is an easy way to gain some familiarity with the codebase.
9
+ - **Code Generation:** As of this writing, the code that writes the code works okay, but it's kind of sloppy. Instead of printing strings of source code directly, I'd like to refactor everything to build ASTs. I'd also like to bring in [json-schema-to-typescript](https://github.com/bcherny/json-schema-to-typescript), which is more accurate than my hand-rolled MVP.
10
+ - **Convert to TypeScript**: While Counterfact generates and runs TypeScript, ironically the code itself is in JavaScript. That's partly because running the unit tests in Jest requires [--experimental-vm-modules](https://jestjs.io/docs/ecmascript-modules). Getting Jest working with that and TypeScript at the same time was too much trouble when the project was getting off the ground.
11
+ - **New features and bug fixes:** See the [issues list](https://github.com/pmcelhaney/counterfact/issues). If you plan on working on something, please add a comment and/or assign yourself.
12
+ - **Spread the word!** If you find this project useful, please let others know about it. Share it in your team Slack, on social media, etc.
13
+
14
+ ## Development
15
+
16
+ This is a pretty straightforward NodeJS project.
17
+
18
+ ```sh
19
+ git clone git@github.com:pmcelhaney/counterfact.git
20
+ cd counterfact
21
+ npm install
22
+ npm lint
23
+ npm test
24
+ ```
25
+
26
+ The [code generator](./src/typescript-generator/README.md) is under `src/typescript-generator`. The server is directly under `src`. I'm planning to move it to `src/server`.
27
+
28
+ Testing and linting changes is important, but at this point I'm more concerned about changing the word "I" in this page to "we", so don't hesitate to create a PR, even it's not "finished".
29
+
30
+ Thanks in advance!
package/README.md CHANGED
@@ -1,24 +1,21 @@
1
- <div align="right">
2
1
 
3
- [![Coverage Status](https://coveralls.io/repos/github/pmcelhaney/counterfact/badge.svg)](https://coveralls.io/github/pmcelhaney/counterfact) [![Mutation testing badge](https://img.shields.io/endpoint?style=flat&url=https%3A%2F%2Fbadge-api.stryker-mutator.io%2Fgithub.com%2Fpmcelhaney%2Fcounterfact%2Fmain)](https://dashboard.stryker-mutator.io/reports/github.com/pmcelhaney/counterfact/main) ![MIT License](https://img.shields.io/badge/license-MIT-blue)
4
-
5
- </div>
6
2
 
7
- <div align="center">
3
+ <div align="center" markdown="1">
8
4
 
9
5
  # Counterfact
10
6
 
11
7
  _Front end development without back end headaches_
12
8
 
13
- </div>
9
+ [Quick Start](#quick-start) | [Documentation](./docs/usage.md) | [Contributing](CONTRIBUTING.md)
10
+
11
+ Counterfact is a stand-in REST server powered by Node, TypeScript, and OpenAPI.<br>
12
+ It enables you to write front end code and test UX flows without a complete back end.
14
13
 
15
- <div align="center">
16
- Counterfact is a stand-in REST server powered by Node, TypeScript, and OpenAPI.<br>Write front end code and test the user experience without a complete back end.
17
14
  </div>
18
15
 
19
16
  <br>
20
17
 
21
- <table align="center" cols="2">
18
+ <table align="center" cols="2" markdown="1">
22
19
 
23
20
  <tr>
24
21
  <td>
@@ -41,9 +38,16 @@ Counterfact is a stand-in REST server powered by Node, TypeScript, and OpenAPI.<
41
38
 
42
39
  </table>
43
40
 
44
- <h2 id="quick-start">Got 60 Seconds? Try it!</h2>
41
+ <div align="center" markdown="1">
42
+
43
+ [![Coverage Status](https://coveralls.io/repos/github/pmcelhaney/counterfact/badge.svg)](https://coveralls.io/github/pmcelhaney/counterfact) [![Mutation testing badge](https://img.shields.io/endpoint?style=flat&url=https%3A%2F%2Fbadge-api.stryker-mutator.io%2Fgithub.com%2Fpmcelhaney%2Fcounterfact%2Fmain)](https://dashboard.stryker-mutator.io/reports/github.com/pmcelhaney/counterfact/main) ![MIT License](https://img.shields.io/badge/license-MIT-blue)
45
44
 
46
- Copy this into your terminal. The only prerequisite is Node 16+.
45
+
46
+ </div>
47
+
48
+ <h2 id="quick-start">Got 60 Seconds? Try me!</h2>
49
+
50
+ Copy the following command into your terminal. The only prerequisite is Node 16+.
47
51
 
48
52
  ```sh copy
49
53
  npx counterfact@latest https://petstore3.swagger.io/api/v3/openapi.json api --open
@@ -66,8 +70,6 @@ You can use Swagger to try out the auto-generated API. Out of the box, it return
66
70
 
67
71
  📗 See [Usage](./docs/usage.md) for detailed documentation.
68
72
 
69
- <br>
70
-
71
- ## Support
73
+ ---
72
74
 
73
- Counterfact is brand new as of October 3, 2022. Please send feedback / questions to pmcelhaney@gmail.com or [create a new issue](https://github.com/pmcelhaney/counterfact/issues/new). If you like what you see, please give this project a star!
75
+ Counterfact is brand new as of October 5, 2022. Please send feedback / questions to pmcelhaney@gmail.com or [create a new issue](https://github.com/pmcelhaney/counterfact/issues/new). If you like what you see, please give this project a star!
package/_config.yaml ADDED
@@ -0,0 +1,12 @@
1
+ remote_theme: pages-themes/dinky@v0.2.0
2
+ plugins:
3
+ - jekyll-remote-theme
4
+ - jekyll-seo-tag
5
+ markdown: GFM
6
+ theme: jekyll-theme-cayman
7
+ title: Counterfact
8
+ description: OpenAPI to TypeScript generator and mock server
9
+ url: https://counterfact.dev
10
+ author: Partrick McElhaney
11
+ google_analytics: G-2QHMETF62T
12
+
@@ -0,0 +1,13 @@
1
+ <script
2
+ async
3
+ src="https://www.googletagmanager.com/gtag/js?id={{ site.google_analytics }}"
4
+ ></script>
5
+ <script>
6
+ window.dataLayer = window.dataLayer || [];
7
+ function gtag() {
8
+ dataLayer.push(arguments);
9
+ }
10
+ gtag("js", new Date());
11
+
12
+ gtag("config", "{{ site.google_analytics }}");
13
+ </script>
@@ -1,12 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import nodePath from "node:path";
4
+ import repl from "node:repl";
4
5
 
5
6
  import { program } from "commander";
6
7
  import open from "open";
7
8
 
8
9
  import { generate } from "../src/typescript-generator/generate.js";
9
- import { start } from "../src/start.js";
10
+ import { start } from "../src/server/start.js";
10
11
 
11
12
  const DEFAULT_PORT = 3100;
12
13
 
@@ -23,7 +24,7 @@ async function main(source, destination) {
23
24
  const startServer = options.server || includeSwagger;
24
25
 
25
26
  if (startServer) {
26
- await start({
27
+ const { contextRegistry } = await start({
27
28
  basePath,
28
29
  port: options.port,
29
30
  openApiPath: source,
@@ -33,6 +34,14 @@ async function main(source, destination) {
33
34
  process.stdout.write(
34
35
  `API is running at http://localhost:${options.port}.\n`
35
36
  );
37
+
38
+ process.stdout.write(
39
+ '\n\nStarting REPL... (start with `const context = loadContext("/")`)\n'
40
+ );
41
+
42
+ const replServer = repl.start("> ");
43
+
44
+ replServer.context.loadContext = (path) => contextRegistry.find(path);
36
45
  }
37
46
 
38
47
  if (openBrowser) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "counterfact",
3
- "version": "0.11.0",
3
+ "version": "0.12.0",
4
4
  "description": "a library for building a fake REST API for testing",
5
5
  "type": "module",
6
6
  "main": "./src/counterfact.js",
@@ -40,8 +40,8 @@
40
40
  "@stryker-mutator/jest-runner": "6.2.2",
41
41
  "@types/koa": "2.13.5",
42
42
  "@types/koa-static": "^4.0.2",
43
- "eslint": "8.24.0",
44
- "eslint-config-hardcore": "25.0.0",
43
+ "eslint": "8.25.0",
44
+ "eslint-config-hardcore": "25.1.0",
45
45
  "eslint-formatter-github-annotations": "0.1.0",
46
46
  "eslint-import-resolver-typescript": "^3.2.5",
47
47
  "eslint-plugin-etc": "^2.0.2",
@@ -53,7 +53,7 @@
53
53
  "jest": "28.1.2",
54
54
  "nodemon": "2.0.20",
55
55
  "stryker-cli": "1.0.2",
56
- "supertest": "6.2.4"
56
+ "supertest": "6.3.0"
57
57
  },
58
58
  "dependencies": {
59
59
  "@hapi/accept": "^6.0.0",
@@ -0,0 +1,43 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <title>Counterfact</title>
5
+ <meta name="description" content="Counterfact dashboard" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
7
+ <style type="text/css">
8
+ body {
9
+ font-family: sans-serif;
10
+
11
+ margin: 20vh;
12
+ font-size: 3vh;
13
+ text-align: center;
14
+ }
15
+
16
+ ul {
17
+ list-style: none;
18
+ margin: 0;
19
+ line-height: 2em;
20
+ }
21
+ </style>
22
+ </head>
23
+ <body>
24
+ <h1>Counterfact is running!</h1>
25
+ <ul>
26
+ <li>
27
+ The generated code is at<br /><a href="vscode://file{{basePath}}"
28
+ >{{basePath}}</a
29
+ >
30
+ </li>
31
+ <li>
32
+ You can explore the API using
33
+ <a href="/counterfact/swagger">Swagger UI</a>
34
+ </li>
35
+ <li>
36
+ <a
37
+ href="https://github.com/pmcelhaney/counterfact/blob/main/docs/usage.md#generated-code-"
38
+ >How does this work?</a
39
+ >
40
+ </li>
41
+ </ul>
42
+ </body>
43
+ </html>
@@ -6,7 +6,8 @@ import os from "node:os";
6
6
  import $RefParser from "json-schema-ref-parser";
7
7
  import yaml from "js-yaml";
8
8
 
9
- import { readFile } from "./read-file.js";
9
+ import { readFile } from "../util/read-file.js";
10
+
10
11
  import { Registry } from "./registry.js";
11
12
  import { Dispatcher } from "./dispatcher.js";
12
13
  import { koaMiddleware } from "./koa-middleware.js";
@@ -39,7 +40,7 @@ export async function counterfact(
39
40
  )}/`;
40
41
 
41
42
  fs.copyFile(
42
- nodePath.join(__dirname, "../templates/response-builder-factory.ts"),
43
+ nodePath.join(__dirname, "../../templates/response-builder-factory.ts"),
43
44
  nodePath.join(basePath, "response-builder-factory.ts")
44
45
  );
45
46
 
@@ -69,5 +70,10 @@ export async function counterfact(
69
70
 
70
71
  await moduleLoader.watch();
71
72
 
72
- return { koaMiddleware: koaMiddleware(dispatcher), registry, moduleLoader };
73
+ return {
74
+ koaMiddleware: koaMiddleware(dispatcher),
75
+ registry,
76
+ moduleLoader,
77
+ contextRegistry,
78
+ };
73
79
  }
File without changes
File without changes
@@ -5,8 +5,12 @@ import bodyParser from "koa-bodyparser";
5
5
  import { koaSwagger } from "koa2-swagger-ui";
6
6
  import yaml from "js-yaml";
7
7
 
8
+ import { readFile } from "../util/read-file.js";
9
+
8
10
  import { counterfact } from "./counterfact.js";
9
- import { readFile } from "./read-file.js";
11
+
12
+ // eslint-disable-next-line no-underscore-dangle
13
+ const __dirname = nodePath.dirname(new URL(import.meta.url).pathname);
10
14
 
11
15
  const DEFAULT_PORT = 3100;
12
16
 
@@ -43,46 +47,17 @@ function swaggerUi(app, openApiPath, port) {
43
47
  );
44
48
  }
45
49
 
46
- export function landingPageBody(basePath) {
47
- return `
48
- <html>
49
- <head>
50
- <title>Counterfact</title>
51
- <style type="text/css">
52
- body {
53
- font-family: sans-serif;
54
-
55
- margin: 20vh;
56
- font-size: 3vh;
57
- text-align: center;
58
- }
59
-
60
- ul {
61
- list-style: none;
62
- margin: 0;
63
- line-height: 2em;
64
- }
65
-
66
-
67
- </style>
68
- </head>
69
- <body>
70
- <h1>Counterfact is running!</h1>
71
- <ul>
72
-
73
- <li>The generated code is at<br><a href="vscode://file${basePath}">${basePath}</a></li>
74
- <li>You can explore the API using <a href="/counterfact/swagger">Swagger UI</a></li>
75
- <li><a href="https://github.com/pmcelhaney/counterfact/blob/main/docs/usage.md#generated-code-">How does this work?</a></li>
76
- </ul>
77
- </body>
78
- </html>
79
- `;
50
+ export async function landingPageBody(basePath) {
51
+ const body = await readFile(nodePath.join(__dirname, "../client/index.html"));
52
+
53
+ return body.replaceAll("{{basePath}}", basePath);
80
54
  }
81
55
 
82
56
  export function landingPage(app, basePath) {
83
57
  app.use(async (ctx, next) => {
84
58
  if (ctx.URL.pathname === "/counterfact") {
85
- ctx.body = landingPageBody(basePath);
59
+ // eslint-disable-next-line require-atomic-updates
60
+ ctx.body = await landingPageBody(basePath);
86
61
 
87
62
  return;
88
63
  }
@@ -108,9 +83,14 @@ export async function start({
108
83
 
109
84
  app.use(bodyParser());
110
85
 
111
- const { koaMiddleware } = await counterfact(basePath, openApiPath);
86
+ const { koaMiddleware, contextRegistry } = await counterfact(
87
+ basePath,
88
+ openApiPath
89
+ );
112
90
 
113
91
  app.use(koaMiddleware);
114
92
 
115
93
  app.listen(port);
94
+
95
+ return { contextRegistry };
116
96
  }
File without changes
File without changes
@@ -2,7 +2,7 @@ import nodePath from "node:path";
2
2
 
3
3
  import yaml from "js-yaml";
4
4
 
5
- import { readFile } from "../read-file.js";
5
+ import { readFile } from "../util/read-file.js";
6
6
 
7
7
  import { Requirement } from "./requirement.js";
8
8
 
File without changes
@@ -1,4 +1,7 @@
1
- import { ContextRegistry, parentPath } from "../src/context-registry.js";
1
+ import {
2
+ ContextRegistry,
3
+ parentPath,
4
+ } from "../../src/server/context-registry.js";
2
5
 
3
6
  describe("moduleRegistry", () => {
4
7
  it("finds a context that exactly matches the path", () => {
@@ -1,9 +1,8 @@
1
1
  import supertest from "supertest";
2
2
  import Koa from "koa";
3
3
 
4
- import { counterfact } from "../src/counterfact.js";
5
-
6
- import { withTemporaryFiles } from "./lib/with-temporary-files.js";
4
+ import { counterfact } from "../../src/server/counterfact.js";
5
+ import { withTemporaryFiles } from "../lib/with-temporary-files.js";
7
6
 
8
7
  describe("integration test", () => {
9
8
  it("finds a path", async () => {
@@ -1,6 +1,6 @@
1
- import { Dispatcher } from "../src/dispatcher.js";
2
- import { ContextRegistry } from "../src/context-registry.js";
3
- import { Registry } from "../src/registry.js";
1
+ import { Dispatcher } from "../../src/server/dispatcher.js";
2
+ import { ContextRegistry } from "../../src/server/context-registry.js";
3
+ import { Registry } from "../../src/server/registry.js";
4
4
 
5
5
  describe("a dispatcher", () => {
6
6
  it("dispatches a get request to a server and returns the response", async () => {
@@ -1,7 +1,7 @@
1
- import { Registry } from "../src/registry.js";
2
- import { Dispatcher } from "../src/dispatcher.js";
3
- import { koaMiddleware } from "../src/koa-middleware.js";
4
- import { ContextRegistry } from "../src/context-registry.js";
1
+ import { Registry } from "../../src/server/registry.js";
2
+ import { Dispatcher } from "../../src/server/dispatcher.js";
3
+ import { koaMiddleware } from "../../src/server/koa-middleware.js";
4
+ import { ContextRegistry } from "../../src/server/context-registry.js";
5
5
 
6
6
  describe("koa middleware", () => {
7
7
  it("passes the request to the dispatcher and returns the response", async () => {
@@ -1,10 +1,9 @@
1
1
  import { once } from "node:events";
2
2
 
3
- import { ModuleLoader } from "../src/module-loader.js";
4
- import { Registry } from "../src/registry.js";
5
- import { ContextRegistry } from "../src/context-registry.js";
6
-
7
- import { withTemporaryFiles } from "./lib/with-temporary-files.js";
3
+ import { ModuleLoader } from "../../src/server/module-loader.js";
4
+ import { Registry } from "../../src/server/registry.js";
5
+ import { ContextRegistry } from "../../src/server/context-registry.js";
6
+ import { withTemporaryFiles } from "../lib/with-temporary-files.js";
8
7
 
9
8
  describe("a module loader", () => {
10
9
  it("finds a file and adds it to the registry", async () => {
@@ -1,4 +1,4 @@
1
- import { Registry } from "../src/registry.js";
1
+ import { Registry } from "../../src/server/registry.js";
2
2
 
3
3
  describe("a scripted server", () => {
4
4
  it("knows if a handler exists for a request method at a path", () => {
@@ -1,4 +1,4 @@
1
- import { createResponseBuilder } from "../src/response-builder.js";
1
+ import { createResponseBuilder } from "../../src/server/response-builder.js";
2
2
 
3
3
  describe("a response builder", () => {
4
4
  it("starts building a response object when the status is selected", () => {
@@ -0,0 +1,10 @@
1
+ import { landingPageBody } from "../../src/server/start.js";
2
+
3
+ describe("start", () => {
4
+ it("renders the landing page", async () => {
5
+ const basePath = "/home/user/counterfact";
6
+ const result = await landingPageBody(basePath);
7
+
8
+ expect(result).toContain(basePath);
9
+ });
10
+ });
@@ -1,4 +1,4 @@
1
- import { Tools } from "../src/tools.js";
1
+ import { Tools } from "../../src/server/tools.js";
2
2
 
3
3
  describe("tools", () => {
4
4
  it("oneOf()", () => {
@@ -2,9 +2,8 @@ import { once } from "node:events";
2
2
  import fs from "node:fs/promises";
3
3
  import { constants as fsConstants } from "node:fs";
4
4
 
5
- import { Transpiler } from "../src/transpiler.js";
6
-
7
- import { withTemporaryFiles } from "./lib/with-temporary-files.js";
5
+ import { Transpiler } from "../../src/server/transpiler.js";
6
+ import { withTemporaryFiles } from "../lib/with-temporary-files.js";
8
7
 
9
8
  const TYPESCRIPT_SOURCE = "const x:number = 1;\n";
10
9
  const JAVASCRIPT_SOURCE = "var x = 1;\n";
@@ -1,10 +0,0 @@
1
- import { landingPageBody } from "../src/start.js";
2
-
3
- describe("start", () => {
4
- it("renders the landing page", () => {
5
- const basePath = "/home/user/counterfact";
6
- const result = landingPageBody(basePath);
7
-
8
- expect(result).toContain(basePath);
9
- });
10
- });