counterfact 0.6.0 → 0.7.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.
- package/.eslintrc.cjs +41 -1
- package/.github/workflows/ci.yaml +1 -1
- package/.github/workflows/coveralls.yaml +1 -1
- package/.vscode/settings.json +4 -12
- package/CHANGELOG.md +20 -0
- package/CNAME +1 -0
- package/README.md +4 -262
- package/bin/counterfact.js +86 -14
- package/demo/openapi.yaml +57 -0
- package/demo/{routes → paths}/count.js +0 -0
- package/demo/{routes → paths}/favicon.ico.js +0 -0
- package/demo/{routes → paths}/hello/kitty.js +0 -0
- package/demo/paths/hello/{name}.js +7 -0
- package/demo/{routes → paths}/user.js +0 -0
- package/demo-ts/package.json +2 -2
- package/demo-ts/path-types/pet/findByStatus.types.ts +22 -2
- package/demo-ts/path-types/pet/findByTags.types.ts +22 -2
- package/demo-ts/path-types/pet/{petId}/uploadImage.types.ts +15 -2
- package/demo-ts/path-types/pet/{petId}.types.ts +42 -4
- package/demo-ts/path-types/pet.types.ts +48 -2
- package/demo-ts/path-types/store/inventory.types.ts +15 -2
- package/demo-ts/path-types/store/order/{orderId}.types.ts +38 -3
- package/demo-ts/path-types/store/order.types.ts +18 -1
- package/demo-ts/path-types/user/createWithList.types.ts +21 -1
- package/demo-ts/path-types/user/login.types.ts +21 -1
- package/demo-ts/path-types/user/logout.types.ts +10 -1
- package/demo-ts/path-types/user/{username}.types.ts +46 -4
- package/demo-ts/path-types/user.types.ts +17 -1
- package/demo-ts/paths/store/inventory.ts +2 -22
- package/demo-ts/yarn.lock +223 -73
- package/docs/koa-plugin.md +35 -0
- package/docs/usage.md +518 -0
- package/jest.config.json +2 -1
- package/package.json +22 -10
- package/src/counterfact.d.ts +144 -0
- package/src/counterfact.js +47 -3
- package/src/dispatcher.js +105 -7
- package/src/module-loader.js +18 -10
- package/src/registry.js +6 -1
- package/src/response-builder.js +81 -0
- package/src/start.js +22 -0
- package/src/transpiler.js +86 -0
- package/src/typescript-generator/init.js +32 -0
- package/src/typescript-generator/operation-type-coder.js +8 -2
- package/src/typescript-generator/repository.js +27 -28
- package/src/typescript-generator/response-type-coder.js +70 -0
- package/src/typescript-generator/schema-type-coder.js +34 -6
- package/templates/typescript/.devcontainer/Dockerfile +14 -0
- package/templates/typescript/.devcontainer/base.Dockerfile +17 -0
- package/templates/typescript/.devcontainer/devcontainer.json +47 -0
- package/templates/typescript/counterfact/context/context.ts +18 -0
- package/templates/typescript/counterfact/public/index.html +17 -0
- package/templates/typescript/counterfact/server.ts +56 -0
- package/templates/typescript/eslintrc.yaml +13 -0
- package/templates/typescript/package.json +28 -0
- package/templates/typescript/tsconfig.json +13 -0
- package/test/counterfact.test.js +51 -6
- package/test/dispatcher.test.js +200 -37
- package/test/response-builder.test.js +129 -0
- package/test/transpiler.test.js +108 -0
- package/test/typescript-generator/__snapshots__/end-to-end.test.js.snap +1927 -837
- package/test/typescript-generator/__snapshots__/operation-type-coder.test.js.snap +40 -2
- package/test/typescript-generator/response-type-coder.test.js +15 -0
- package/test/typescript-generator/schema-type-coder.test.js +99 -2
- package/tsconfig.json +1 -1
- package/demo/routes/hello/[name].js +0 -13
package/.eslintrc.cjs
CHANGED
|
@@ -24,10 +24,25 @@ const rules = {
|
|
|
24
24
|
"node/no-callback-literal": "off",
|
|
25
25
|
"node/file-extension-in-import": "off",
|
|
26
26
|
"node/no-missing-import": "off",
|
|
27
|
+
|
|
28
|
+
// too slow
|
|
29
|
+
"import/no-cycle": "off",
|
|
30
|
+
"import/no-unused-modules": "off",
|
|
31
|
+
"import/namespace": "off",
|
|
32
|
+
"import/no-named-as-default": "off",
|
|
33
|
+
"import/no-deprecated": "off",
|
|
34
|
+
"import/default": "off",
|
|
35
|
+
"import/no-named-as-default-member": "off",
|
|
27
36
|
};
|
|
28
37
|
|
|
29
38
|
module.exports = {
|
|
30
|
-
ignorePatterns: [
|
|
39
|
+
ignorePatterns: [
|
|
40
|
+
"/node_modules/",
|
|
41
|
+
"/coverage/",
|
|
42
|
+
"/reports/",
|
|
43
|
+
"/demo-ts",
|
|
44
|
+
"/templates/",
|
|
45
|
+
],
|
|
31
46
|
|
|
32
47
|
extends: ["hardcore", "hardcore/ts", "hardcore/node"],
|
|
33
48
|
|
|
@@ -35,6 +50,11 @@ module.exports = {
|
|
|
35
50
|
sourceType: "module",
|
|
36
51
|
},
|
|
37
52
|
|
|
53
|
+
env: {
|
|
54
|
+
es2021: true,
|
|
55
|
+
node: true,
|
|
56
|
+
},
|
|
57
|
+
|
|
38
58
|
rules,
|
|
39
59
|
|
|
40
60
|
overrides: [
|
|
@@ -72,6 +92,8 @@ module.exports = {
|
|
|
72
92
|
|
|
73
93
|
"no-magic-numbers": ["off"],
|
|
74
94
|
"id-length": ["off"],
|
|
95
|
+
"max-lines": "off",
|
|
96
|
+
"jest/unbound-method": "off",
|
|
75
97
|
},
|
|
76
98
|
},
|
|
77
99
|
|
|
@@ -143,5 +165,23 @@ module.exports = {
|
|
|
143
165
|
},
|
|
144
166
|
},
|
|
145
167
|
},
|
|
168
|
+
|
|
169
|
+
{
|
|
170
|
+
files: ["templates/**/*.ts"],
|
|
171
|
+
|
|
172
|
+
env: {
|
|
173
|
+
es2021: true,
|
|
174
|
+
node: true,
|
|
175
|
+
},
|
|
176
|
+
|
|
177
|
+
extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
|
|
178
|
+
|
|
179
|
+
parserOptions: {
|
|
180
|
+
ecmaVersion: "latest",
|
|
181
|
+
sourceType: "module",
|
|
182
|
+
},
|
|
183
|
+
|
|
184
|
+
rules: {},
|
|
185
|
+
},
|
|
146
186
|
],
|
|
147
187
|
};
|
package/.vscode/settings.json
CHANGED
|
@@ -3,17 +3,8 @@
|
|
|
3
3
|
"eslint.format.enable": true,
|
|
4
4
|
"eslint.packageManager": "yarn",
|
|
5
5
|
"eslint.codeActionsOnSave.mode": "problems",
|
|
6
|
-
"eslint.
|
|
7
|
-
|
|
8
|
-
"!etc/no-deprecated",
|
|
9
|
-
"!import/no-cycle",
|
|
10
|
-
"!no-explicit-type-exports/no-explicit-type-exports",
|
|
11
|
-
"!import/no-deprecated",
|
|
12
|
-
"!import/no-self-import",
|
|
13
|
-
"!import/default",
|
|
14
|
-
"!import/no-named-as-default",
|
|
15
|
-
"*"
|
|
16
|
-
],
|
|
6
|
+
"eslint.useESLintClass": true,
|
|
7
|
+
"eslint.codeActionsOnSave.rules": ["*"],
|
|
17
8
|
"files.insertFinalNewline": true,
|
|
18
9
|
"jest.nodeEnv": {
|
|
19
10
|
"NODE_OPTIONS": "--experimental-vm-modules"
|
|
@@ -23,5 +14,6 @@
|
|
|
23
14
|
},
|
|
24
15
|
"[typescript]": {
|
|
25
16
|
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
|
|
26
|
-
}
|
|
17
|
+
},
|
|
18
|
+
"cSpell.words": ["bodyparser", "counterfact", "openapi", "transpiles"]
|
|
27
19
|
}
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# counterfact
|
|
2
2
|
|
|
3
|
+
## 0.7.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 4f3c7c9: define a ResponseBuilder type -- to be refined later with one or more generic arguments
|
|
8
|
+
- 5f3ee3f: response builder fluent API which is going to make intellisense with TypeScript awesome
|
|
9
|
+
- 6221e7d: `counterfact start` command to start a server
|
|
10
|
+
- a3dfb48: `npm counterfact init <openapi-file> <destination>` command to build a new package with a Counterfact server
|
|
11
|
+
- 9f91d57: adds a 'go' command that generates code and starts the server in one step
|
|
12
|
+
- 25dcd45: ability to respond with multiple content types, return the best match for the request's accept header
|
|
13
|
+
- c84a4f4: Counterfact is now able to read an OpenAPI document and use it to generate a random response.
|
|
14
|
+
- c0cb938: a function can now return "hello world" as shorthand for `{status: 200, contentType: "text/plain", body: "hello world"}`
|
|
15
|
+
|
|
16
|
+
### Patch Changes
|
|
17
|
+
|
|
18
|
+
- 69b4598: replaced EventEmitter with EventTarget
|
|
19
|
+
- a07c2b2: fix a crash when regenerating code
|
|
20
|
+
- 85c3349: fixed an issue where hot reload did not work in some cases
|
|
21
|
+
- 1551cbb: handle additionalProperties when converting a JSON Schema to TypeScript
|
|
22
|
+
|
|
3
23
|
## 0.6.0
|
|
4
24
|
|
|
5
25
|
### Minor Changes
|
package/CNAME
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
counterfact.dev
|
package/README.md
CHANGED
|
@@ -4,270 +4,12 @@
|
|
|
4
4
|
|
|
5
5
|
[](https://dashboard.stryker-mutator.io/reports/github.com/pmcelhaney/counterfact/main)
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
> This project is a work in progress. As of September 9, 2022, most of the plumbing is in place but the APIs are still solidifying and it's not quite ready for day-to-day use. See the [Version 1.0](https://github.com/pmcelhaney/counterfact/milestone/3) and [Future](https://github.com/pmcelhaney/counterfact/milestone/5) milestones.
|
|
8
8
|
|
|
9
|
-
Counterfact is
|
|
9
|
+
Counterfact is (will be) a tool that helps front end developers and back end developers collaborate by quickly building reference implementations of [OpenAPI](https://www.openapis.org/) documents.
|
|
10
10
|
|
|
11
|
-
Counterfact
|
|
11
|
+
Counterfact makes it easy to build a **fake RESTful back end** to use when **testing the UI**, both manual and automated testing. UI testing can be quite tedious and brittle in part because the _arrange_ part of Arrange, Act, Assert -- getting the server into a particular state -- is often slow, unpredictable, and complex. Counterfact bypasses the accidental complexity associated with arranging a real server's state, making tests that isolate the UI fast, repeatable, and easy to maintain.
|
|
12
12
|
|
|
13
13
|
It accomplishes that without over-isolating. You can run a full scenario: log in, make changes, log in with a different user, and see the effects of those changes. The server you're testing against is a _real_ server from the UI's perspective: it implements business logic and maintains state. It's only not real in the sense that it doesn't have to support 10,000 concurrent users, protect real data, go through a deployment pipeline, or maintain 99.99% uptime.
|
|
14
14
|
|
|
15
|
-
Counterfact is **great** for **communication between front end and back end teams**. OpenAPI does an excellent job defining the inputs and outputs of a RESTful service, but it can't tell us how a server is supposed to behave. Counterfact fills that gap. The feedback cycle is orders of magnitude faster than making changes to a real, scalable back end and then seeing how those changes affect the UI/UX.
|
|
16
|
-
|
|
17
|
-
## Road to 1.0
|
|
18
|
-
|
|
19
|
-
This project is a work in progress. As of July 15, 2022, most of the plumbing is in place. This project will reach 1.0 when mutation test coverage is > 90% and:
|
|
20
|
-
|
|
21
|
-
The following command
|
|
22
|
-
|
|
23
|
-
```sh
|
|
24
|
-
npx counterfact init ./my-petstore https://petstore3.swagger.io/api/v3/openapi.yaml
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
creates or augments an npm package, and after changing to the directory
|
|
28
|
-
|
|
29
|
-
```sh
|
|
30
|
-
cd my-petstore
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
the following command
|
|
34
|
-
|
|
35
|
-
```
|
|
36
|
-
yarn start
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
opens a browser with Swagger-UI pointing to a server that implements the pet store API.
|
|
40
|
-
|
|
41
|
-
Not a mock server that returns canned or randomly generated responses, a [_real_ implementation](./demo-ts/), written in TypeScript.
|
|
42
|
-
|
|
43
|
-
Or at least, the _potential_ for a real implementation. Out of the box, it will return example or random responses. But the code is easy to edit so, e.g., we can change `PUT /pet` to store a `Pet` in memory / a database / a microservice / etc., and `GET /pet` to retrieve that same `Pet`.
|
|
44
|
-
|
|
45
|
-
We can see the effects of the code changes _without restarting the server_.
|
|
46
|
-
|
|
47
|
-
And then after running
|
|
48
|
-
|
|
49
|
-
```sh
|
|
50
|
-
npm run counterfact generate /path/to/copy/of/petstore/with/some/changes.yaml
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
it regenerates the code except for the files that we changed. Of the files we changed, the IDE (via TypeScript and ESLint) tells us if the code needs to be modified to conform to the updated spec.
|
|
54
|
-
|
|
55
|
-
_Again, without restarting the server._
|
|
56
|
-
|
|
57
|
-
## Beyond 1.0
|
|
58
|
-
|
|
59
|
-
The scenario above describes a zero-configuration happy path, adhering to the philosophy of convention over configuration.
|
|
60
|
-
|
|
61
|
-
The next phase is to add configuration options that define things like what port the server runs on, when generated files are overwritten, where those files live, whether to add `start` and `generate` scripts and what they should be named, whether to build a soup-to-nuts server or only make Express / Koa / Connect middleware available, etc. These configurations may be set in a `.counterfactrc.json` file, environment variables, and or command line options, whatever makes sense.
|
|
62
|
-
|
|
63
|
-
(By the way, generating code is optional. When this project started, that wasn't even part of the scope. If you don't have an OpenAPI spec, or don't want to use it, Counterfact is still an ergonomic platform on which to build quick and dirty prototypes of RESTful services in TypeScript or JavaScript.)
|
|
64
|
-
|
|
65
|
-
After that, more features that improve developer experience. For example, say you're demoing an iOS music player app and someone asks about edge cases. You'll be able answer questions immediately by going into a REPL and typing things like:
|
|
66
|
-
|
|
67
|
-
```ts
|
|
68
|
-
context.addABunchOfSongsToCatalog(1_000_000);
|
|
69
|
-
|
|
70
|
-
context.setAlbumTitle(
|
|
71
|
-
123,
|
|
72
|
-
"This Is is a Very Long Name That is Definitely Not Going to Fit in the Allotted Space and it has Emoji 🦧"
|
|
73
|
-
);
|
|
74
|
-
|
|
75
|
-
network.setLatency("1-5s");
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
## Installation
|
|
79
|
-
|
|
80
|
-
```sh
|
|
81
|
-
npm install --save-dev counterfact
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
or
|
|
85
|
-
|
|
86
|
-
```sh
|
|
87
|
-
yarn add -D counterfact
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
## Usage
|
|
91
|
-
|
|
92
|
-
See the [demo-ts](./demo-ts/README.md) directory for an example.
|
|
93
|
-
|
|
94
|
-
### Using the Koa plugin
|
|
95
|
-
|
|
96
|
-
(Currently this is the only way to run Counterfact. A stand-alone CLI is coming soon.)
|
|
97
|
-
|
|
98
|
-
```js
|
|
99
|
-
import { fileURLToPath } from "node:url";
|
|
100
|
-
|
|
101
|
-
import Koa from "koa";
|
|
102
|
-
import { counterfact } from "counterfact";
|
|
103
|
-
|
|
104
|
-
const PORT = 3100;
|
|
105
|
-
|
|
106
|
-
const app = new Koa();
|
|
107
|
-
|
|
108
|
-
const initialContext = {};
|
|
109
|
-
|
|
110
|
-
const { koaMiddleware } = await counterfact(
|
|
111
|
-
fileURLToPath(new URL("paths/", import.meta.url)),
|
|
112
|
-
initialContext
|
|
113
|
-
);
|
|
114
|
-
|
|
115
|
-
app.use(koaMiddleware);
|
|
116
|
-
|
|
117
|
-
app.listen(PORT);
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
### Setting up paths
|
|
121
|
-
|
|
122
|
-
The first thing you need to do is create a directory where you will put your paths implementations.
|
|
123
|
-
|
|
124
|
-
```sh
|
|
125
|
-
mkdir paths
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
To mock an API at `/hello/world`, create a file called `./paths/hello/world.js`.
|
|
129
|
-
|
|
130
|
-
Inside that file, output a function that handles a GET request.
|
|
131
|
-
|
|
132
|
-
```js
|
|
133
|
-
export function GET() {
|
|
134
|
-
return {
|
|
135
|
-
status: 200, // optional HTTP status code (200 is the default)
|
|
136
|
-
body: "hello world", // HTTP response body
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
Now when you run your server and call "GET /hello/world" you should get a 200 response with "hello world" in the body.
|
|
142
|
-
|
|
143
|
-
### Reading the query string
|
|
144
|
-
|
|
145
|
-
The get function has one parameter, a context object that contains metadata about the request. We can use that to read a the query string from `/hello/world?greeting=Hi`
|
|
146
|
-
|
|
147
|
-
```js
|
|
148
|
-
export function GET(context) {
|
|
149
|
-
return {
|
|
150
|
-
body: "${context.query.greeting} world",
|
|
151
|
-
};
|
|
152
|
-
}
|
|
153
|
-
```
|
|
154
|
-
|
|
155
|
-
In practice, tend to use destructuring syntax, which is the closest thing we have in JavaScript to named arguments.
|
|
156
|
-
|
|
157
|
-
```js
|
|
158
|
-
export function GET({ query }) {
|
|
159
|
-
return {
|
|
160
|
-
body: "${query.greeting} world",
|
|
161
|
-
};
|
|
162
|
-
}
|
|
163
|
-
```
|
|
164
|
-
|
|
165
|
-
### Dynamic routes
|
|
166
|
-
|
|
167
|
-
Create another file called `./routes/hello/[name].js`. This file will match `/hello/universe`, `/hello/friends`, and `/hello/whatever-you-want-here`.
|
|
168
|
-
|
|
169
|
-
```js
|
|
170
|
-
export function GET({ greeting, path }) {
|
|
171
|
-
return {
|
|
172
|
-
body: "${query.greeting}, ${path.name}!",
|
|
173
|
-
};
|
|
174
|
-
}
|
|
175
|
-
```
|
|
176
|
-
|
|
177
|
-
The `path` object is analogous to the `query` object, holding values in the dynamic parts of the path.
|
|
178
|
-
|
|
179
|
-
(Note that `/hello/world` is still handled by `/hello/world.js` -- static routes take precedence over dynamic ones.)
|
|
180
|
-
|
|
181
|
-
This feature was [inspired by Next.js](https://nextjs.org/docs/routing/dynamic-routes).
|
|
182
|
-
|
|
183
|
-
### State
|
|
184
|
-
|
|
185
|
-
State management is handled through a plain old JavaScript object called `context`, which is passed as the second argument to the `counterfact()` function (default value = `{}`). You can modify the context object however you like. Changes will persist from one request to another as long as the server is running.
|
|
186
|
-
|
|
187
|
-
There are no rules around how you manipulate the context. Yes, you read that right.
|
|
188
|
-
|
|
189
|
-
```js
|
|
190
|
-
export function GET({ greeting, path, context }) {
|
|
191
|
-
context.visits ??= {};
|
|
192
|
-
context.visits[path.name] ??= 0;
|
|
193
|
-
context.visits[path.name] += 1;
|
|
194
|
-
|
|
195
|
-
return {
|
|
196
|
-
body: "${query.greeting}, ${path.name}!",
|
|
197
|
-
};
|
|
198
|
-
}
|
|
199
|
-
```
|
|
200
|
-
|
|
201
|
-
### Request Methods
|
|
202
|
-
|
|
203
|
-
So far we've only covered `GET` requests. What about `POST`, `PUT`, `PATCH` and `DELETE`? All HTTP request methods are supported. It's a matter exporting functions with the corresponding names.
|
|
204
|
-
|
|
205
|
-
```js
|
|
206
|
-
export function GET({ path, context }) {
|
|
207
|
-
|
|
208
|
-
context.friends ??= {};
|
|
209
|
-
context.friends[path.name] ??= {
|
|
210
|
-
appearance: "lovely"
|
|
211
|
-
};
|
|
212
|
-
|
|
213
|
-
return {
|
|
214
|
-
body: "Hello, ${path.name}. You look ${context.friends[name].appearance} today!"
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
export function POST(path, context, body) {
|
|
219
|
-
|
|
220
|
-
context.friends ??= {};
|
|
221
|
-
context.friends[path.name] ??= {};
|
|
222
|
-
context.friends[appearance] = body.appearance;
|
|
223
|
-
|
|
224
|
-
return {
|
|
225
|
-
body: {
|
|
226
|
-
"Okay, I'll remember that ${path.name} is ${body.appearance}!"
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
```
|
|
231
|
-
|
|
232
|
-
### Asynchronous Handlers
|
|
233
|
-
|
|
234
|
-
If you need to do work asynchronously, return a promise or use async / await.
|
|
235
|
-
|
|
236
|
-
```js
|
|
237
|
-
export function PUT({ body }) {
|
|
238
|
-
return doSomeStuffWith(body).then(() => {
|
|
239
|
-
body: "Successfully did an async PUT";
|
|
240
|
-
});
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
export async function DELETE() {
|
|
244
|
-
await deleteMe();
|
|
245
|
-
return {
|
|
246
|
-
body: "Took a while, but now it's deleted.",
|
|
247
|
-
};
|
|
248
|
-
}
|
|
249
|
-
```
|
|
250
|
-
|
|
251
|
-
### Coming soon
|
|
252
|
-
|
|
253
|
-
Headers, content-type, etc. are not supported yet but coming soon. Hopefully by now you can guess what those APIs are going to look like.
|
|
254
|
-
|
|
255
|
-
---
|
|
256
|
-
|
|
257
|
-
## Development Setup
|
|
258
|
-
|
|
259
|
-
Looking to get involved? You can begin by cloning the project and using one of the following setup options.
|
|
260
|
-
|
|
261
|
-
### Installation (devcontainer)
|
|
262
|
-
|
|
263
|
-
> **BETA**: We are working through using a devcontainer for development. If you have [Docker](https://docker.com) on your machine and would like to give it a go and give us some feedback, please clone the repo, and then follow the instructions on the following page in place of the Installation instructions below.
|
|
264
|
-
>
|
|
265
|
-
> - [Quick start: Open an existing folder in a container.](https://code.visualstudio.com/docs/remote/containers#_quick-start-open-an-existing-folder-in-a-container)
|
|
266
|
-
|
|
267
|
-
### Installation (Original Method)
|
|
268
|
-
|
|
269
|
-
Installation will set up Git hooks and install all of the Node packages.
|
|
270
|
-
|
|
271
|
-
1. Make sure you have [Node 16](https://nodejs.org/en/) installed.
|
|
272
|
-
2. Install yarn by running `npm install -g yarn`.
|
|
273
|
-
3. Install the dependencies by running `yarn install`.
|
|
15
|
+
Counterfact is **great** for **communication between front end and back end teams** and **prototyping workflows**. OpenAPI does an excellent job defining the inputs and outputs of a RESTful service, but it can't tell us how a server is supposed to behave. Counterfact fills that gap. The feedback cycle is orders of magnitude faster than making changes to a real, scalable back end and then seeing how those changes affect the UI/UX.
|
package/bin/counterfact.js
CHANGED
|
@@ -1,22 +1,94 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
/* eslint-disable no-magic-numbers */
|
|
3
|
+
import fs from "node:fs";
|
|
2
4
|
import nodePath from "node:path";
|
|
3
5
|
|
|
4
6
|
import { generate } from "../src/typescript-generator/generate.js";
|
|
7
|
+
import { init } from "../src/typescript-generator/init.js";
|
|
8
|
+
import { start } from "../src/start.js";
|
|
5
9
|
|
|
6
|
-
|
|
10
|
+
// eslint-disable-next-line max-statements, sonarjs/cognitive-complexity, complexity
|
|
11
|
+
async function main() {
|
|
12
|
+
// eslint-disable-next-line prefer-destructuring
|
|
13
|
+
const command = process.argv[2];
|
|
7
14
|
|
|
8
|
-
if (
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
)
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
if (command === "start") {
|
|
16
|
+
const basePath = process.argv[3]
|
|
17
|
+
? nodePath.resolve(process.argv[3])
|
|
18
|
+
: process.cwd();
|
|
19
|
+
const port = process.argv[4] ?? 3100;
|
|
20
|
+
|
|
21
|
+
const pathsRoot = nodePath.join(basePath, "paths");
|
|
22
|
+
|
|
23
|
+
// eslint-disable-next-line node/no-sync
|
|
24
|
+
if (fs.existsSync(pathsRoot)) {
|
|
25
|
+
await start(basePath, port);
|
|
26
|
+
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
process.stdout.write(
|
|
31
|
+
`Error: expected to find a directory at ${pathsRoot}\n`
|
|
32
|
+
);
|
|
33
|
+
process.stdout.write(
|
|
34
|
+
"This directory should contain JS files corresponding to REST endpoints.\n\n"
|
|
35
|
+
);
|
|
36
|
+
process.exitCode = 1;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (command === "generate" && process.argv.length === 5) {
|
|
40
|
+
const [, source, destination] = process.argv
|
|
41
|
+
.slice(2)
|
|
42
|
+
.map((pathString) => nodePath.join(process.cwd(), pathString));
|
|
43
|
+
|
|
44
|
+
await generate(source, destination);
|
|
45
|
+
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (command === "init" && process.argv.length === 5) {
|
|
50
|
+
const [, source, destination] = process.argv
|
|
51
|
+
.slice(2)
|
|
52
|
+
.map((pathString) => nodePath.join(process.cwd(), pathString));
|
|
53
|
+
|
|
54
|
+
// eslint-disable-next-line node/no-sync
|
|
55
|
+
if (fs.existsSync(destination)) {
|
|
56
|
+
process.stdout.write(`Destination already exists: ${destination}\n`);
|
|
57
|
+
process.exitCode = 1;
|
|
17
58
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
await init(source, destination);
|
|
63
|
+
await generate(source, nodePath.join(destination, "counterfact"));
|
|
64
|
+
|
|
65
|
+
process.stdout.write(`Created a new project at ${destination}!\n\n`);
|
|
66
|
+
process.stdout.write("Next steps: \n");
|
|
67
|
+
process.stdout.write(` cd ${destination}\n`);
|
|
68
|
+
process.stdout.write(" npm install\n");
|
|
69
|
+
process.stdout.write(" npm start -- --open\n");
|
|
70
|
+
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (command === "go" && process.argv.length === 5) {
|
|
75
|
+
const [, source, destination = "."] = process.argv
|
|
76
|
+
.slice(2)
|
|
77
|
+
.map((pathString) => nodePath.join(process.cwd(), pathString));
|
|
78
|
+
|
|
79
|
+
await generate(source, destination);
|
|
80
|
+
|
|
81
|
+
const basePath = nodePath.resolve(destination);
|
|
82
|
+
|
|
83
|
+
await start(basePath, 3100);
|
|
84
|
+
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
process.stdout.write("Usage:\n");
|
|
89
|
+
process.stdout.write(" counterfact start [basePath] [port]\n");
|
|
90
|
+
process.stdout.write(" counterfact generate [source] [destination]\n");
|
|
91
|
+
process.stdout.write(" counterfact init [source] [destination]\n");
|
|
92
|
+
}
|
|
21
93
|
|
|
22
|
-
|
|
94
|
+
await main();
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
openapi: 3.0.3
|
|
2
|
+
info:
|
|
3
|
+
version: 1.0.0
|
|
4
|
+
title: Sample API
|
|
5
|
+
description: A sample API to illustrate OpenAPI concepts
|
|
6
|
+
paths:
|
|
7
|
+
/count:
|
|
8
|
+
get:
|
|
9
|
+
description: outputs the number of time each URL was visited
|
|
10
|
+
responses:
|
|
11
|
+
default:
|
|
12
|
+
description: Successful response
|
|
13
|
+
content:
|
|
14
|
+
application/json:
|
|
15
|
+
schema:
|
|
16
|
+
type:
|
|
17
|
+
string
|
|
18
|
+
examples:
|
|
19
|
+
no visits:
|
|
20
|
+
value: You have not visited anyone yet
|
|
21
|
+
/hello/kitty:
|
|
22
|
+
get:
|
|
23
|
+
description: HTML with a hello kitty image
|
|
24
|
+
responses:
|
|
25
|
+
default:
|
|
26
|
+
description: Successful response
|
|
27
|
+
content:
|
|
28
|
+
application/json:
|
|
29
|
+
schema:
|
|
30
|
+
type:
|
|
31
|
+
string
|
|
32
|
+
examples:
|
|
33
|
+
hello kitty:
|
|
34
|
+
value: >-
|
|
35
|
+
<img
|
|
36
|
+
src="https://upload.wikimedia.org/wikipedia/en/0/05/Hello_kitty_character_portrait.png">
|
|
37
|
+
/hello/{name}:
|
|
38
|
+
get:
|
|
39
|
+
parameters:
|
|
40
|
+
- in: path
|
|
41
|
+
name: name
|
|
42
|
+
required: true
|
|
43
|
+
schema:
|
|
44
|
+
type: string
|
|
45
|
+
description: says hello to the name
|
|
46
|
+
description: says hello to someone
|
|
47
|
+
responses:
|
|
48
|
+
default:
|
|
49
|
+
description: Successful response
|
|
50
|
+
content:
|
|
51
|
+
application/json:
|
|
52
|
+
schema:
|
|
53
|
+
type:
|
|
54
|
+
string
|
|
55
|
+
examples:
|
|
56
|
+
hello-world:
|
|
57
|
+
value: Hello, world
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/demo-ts/package.json
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"main": "index.js",
|
|
8
8
|
"dependencies": {
|
|
9
9
|
"@types/koa-bodyparser": "^4.3.7",
|
|
10
|
-
"counterfact": "
|
|
10
|
+
"counterfact": "../",
|
|
11
11
|
"json-schema-faker": "^0.5.0-rcv.44",
|
|
12
12
|
"koa": "^2.13.4",
|
|
13
13
|
"koa-bodyparser": "^4.3.0",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"npm": "^8.13.1"
|
|
21
21
|
},
|
|
22
22
|
"scripts": {
|
|
23
|
-
"start": "ts-node index.ts",
|
|
23
|
+
"start": "ts-node -T index.ts",
|
|
24
24
|
"test": "echo \"Error: no test specified\" && exit 1"
|
|
25
25
|
},
|
|
26
26
|
"author": "",
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { Context } from "../../context/context.js";
|
|
2
|
-
import type {
|
|
2
|
+
import type { ResponseBuilderBuilder } from "counterfact";
|
|
3
|
+
import type { HttpStatusCode } from "counterfact";
|
|
3
4
|
import type { Pet } from "./../../components/Pet.js";
|
|
5
|
+
import type { Tools } from "./../../internal/tools.js";
|
|
4
6
|
export type HTTP_GET = ({
|
|
5
7
|
query,
|
|
6
8
|
path,
|
|
@@ -14,6 +16,23 @@ export type HTTP_GET = ({
|
|
|
14
16
|
header: never;
|
|
15
17
|
body: undefined;
|
|
16
18
|
context: Context;
|
|
19
|
+
response: ResponseBuilderBuilder<{
|
|
20
|
+
200: {
|
|
21
|
+
headers: {};
|
|
22
|
+
content: {
|
|
23
|
+
"application/xml": {
|
|
24
|
+
schema: Array<Pet>;
|
|
25
|
+
};
|
|
26
|
+
"application/json": {
|
|
27
|
+
schema: Array<Pet>;
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
400: {
|
|
32
|
+
headers: {};
|
|
33
|
+
content: {};
|
|
34
|
+
};
|
|
35
|
+
}>;
|
|
17
36
|
tools: Tools;
|
|
18
37
|
}) =>
|
|
19
38
|
| {
|
|
@@ -29,4 +48,5 @@ export type HTTP_GET = ({
|
|
|
29
48
|
| {
|
|
30
49
|
status: 400;
|
|
31
50
|
}
|
|
32
|
-
| { status: 415; contentType: "text/plain"; body: string }
|
|
51
|
+
| { status: 415; contentType: "text/plain"; body: string }
|
|
52
|
+
| void;
|