api 4.5.1 → 5.0.0-beta.2

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 (70) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +32 -162
  3. package/bin/api +2 -0
  4. package/dist/bin.d.ts +1 -0
  5. package/dist/bin.js +91 -0
  6. package/dist/cache.d.ts +30 -0
  7. package/dist/cache.js +217 -0
  8. package/dist/cli/codegen/index.d.ts +4 -0
  9. package/dist/cli/codegen/index.js +23 -0
  10. package/dist/cli/codegen/language.d.ts +27 -0
  11. package/dist/cli/codegen/language.js +19 -0
  12. package/dist/cli/codegen/languages/typescript.d.ts +99 -0
  13. package/dist/cli/codegen/languages/typescript.js +769 -0
  14. package/dist/cli/commands/index.d.ts +4 -0
  15. package/dist/cli/commands/index.js +9 -0
  16. package/dist/cli/commands/install.d.ts +3 -0
  17. package/dist/cli/commands/install.js +230 -0
  18. package/dist/cli/lib/prompt.d.ts +9 -0
  19. package/dist/cli/lib/prompt.js +81 -0
  20. package/dist/cli/logger.d.ts +1 -0
  21. package/dist/cli/logger.js +16 -0
  22. package/dist/cli/storage.d.ts +105 -0
  23. package/dist/cli/storage.js +264 -0
  24. package/dist/core/getJSONSchemaDefaults.d.ts +15 -0
  25. package/dist/core/getJSONSchemaDefaults.js +62 -0
  26. package/dist/core/index.d.ts +32 -0
  27. package/dist/core/index.js +143 -0
  28. package/dist/core/parseResponse.d.ts +1 -0
  29. package/dist/core/parseResponse.js +65 -0
  30. package/dist/core/prepareAuth.d.ts +5 -0
  31. package/dist/core/prepareAuth.js +55 -0
  32. package/dist/core/prepareParams.d.ts +24 -0
  33. package/dist/core/prepareParams.js +351 -0
  34. package/dist/core/prepareServer.d.ts +13 -0
  35. package/dist/core/prepareServer.js +50 -0
  36. package/dist/fetcher.d.ts +54 -0
  37. package/dist/fetcher.js +165 -0
  38. package/dist/index.d.ts +6 -0
  39. package/dist/index.js +276 -0
  40. package/dist/packageInfo.d.ts +2 -0
  41. package/dist/packageInfo.js +6 -0
  42. package/package.json +67 -28
  43. package/src/.sink.d.ts +1 -0
  44. package/src/bin.ts +20 -0
  45. package/src/cache.ts +212 -0
  46. package/src/cli/codegen/index.ts +31 -0
  47. package/src/cli/codegen/language.ts +47 -0
  48. package/src/cli/codegen/languages/typescript.ts +807 -0
  49. package/src/cli/commands/index.ts +5 -0
  50. package/src/cli/commands/install.ts +196 -0
  51. package/src/cli/lib/prompt.ts +29 -0
  52. package/src/cli/logger.ts +10 -0
  53. package/src/cli/storage.ts +297 -0
  54. package/src/core/getJSONSchemaDefaults.ts +74 -0
  55. package/src/core/index.ts +108 -0
  56. package/src/{lib/parseResponse.js → core/parseResponse.ts} +5 -7
  57. package/src/core/prepareAuth.ts +85 -0
  58. package/src/core/prepareParams.ts +338 -0
  59. package/src/{lib/prepareServer.js → core/prepareServer.ts} +13 -12
  60. package/src/fetcher.ts +141 -0
  61. package/src/index.ts +212 -0
  62. package/src/packageInfo.ts +3 -0
  63. package/src/typings.d.ts +3 -0
  64. package/tsconfig.json +24 -0
  65. package/src/cache.js +0 -225
  66. package/src/index.js +0 -177
  67. package/src/lib/getSchema.js +0 -34
  68. package/src/lib/index.js +0 -11
  69. package/src/lib/prepareAuth.js +0 -69
  70. package/src/lib/prepareParams.js +0 -198
@@ -0,0 +1,6 @@
1
+ import type { OASDocument } from 'oas/@types/rmoas.types';
2
+ interface SDKOptions {
3
+ cacheDir?: string;
4
+ }
5
+ declare const _default: (uri: string | OASDocument, opts?: SDKOptions) => any;
6
+ export = _default;
package/dist/index.js ADDED
@@ -0,0 +1,276 @@
1
+ "use strict";
2
+ var __assign = (this && this.__assign) || function () {
3
+ __assign = Object.assign || function(t) {
4
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
5
+ s = arguments[i];
6
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
7
+ t[p] = s[p];
8
+ }
9
+ return t;
10
+ };
11
+ return __assign.apply(this, arguments);
12
+ };
13
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
14
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
15
+ return new (P || (P = Promise))(function (resolve, reject) {
16
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
17
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
18
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
19
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
20
+ });
21
+ };
22
+ var __generator = (this && this.__generator) || function (thisArg, body) {
23
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
24
+ return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
25
+ function verb(n) { return function (v) { return step([n, v]); }; }
26
+ function step(op) {
27
+ if (f) throw new TypeError("Generator is already executing.");
28
+ while (_) try {
29
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
30
+ if (y = 0, t) op = [op[0] & 2, t.value];
31
+ switch (op[0]) {
32
+ case 0: case 1: t = op; break;
33
+ case 4: _.label++; return { value: op[1], done: false };
34
+ case 5: _.label++; y = op[1]; op = [0]; continue;
35
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
36
+ default:
37
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
38
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
39
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
40
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
41
+ if (t[2]) _.ops.pop();
42
+ _.trys.pop(); continue;
43
+ }
44
+ op = body.call(thisArg, _);
45
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
46
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
47
+ }
48
+ };
49
+ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
50
+ if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
51
+ if (ar || !(i in from)) {
52
+ if (!ar) ar = Array.prototype.slice.call(from, 0, i);
53
+ ar[i] = from[i];
54
+ }
55
+ }
56
+ return to.concat(ar || Array.prototype.slice.call(from));
57
+ };
58
+ var __importDefault = (this && this.__importDefault) || function (mod) {
59
+ return (mod && mod.__esModule) ? mod : { "default": mod };
60
+ };
61
+ var oas_1 = __importDefault(require("oas"));
62
+ var core_1 = __importDefault(require("./core"));
63
+ var cache_1 = __importDefault(require("./cache"));
64
+ var packageInfo_1 = require("./packageInfo");
65
+ var Sdk = /** @class */ (function () {
66
+ function Sdk(uri, opts) {
67
+ if (opts === void 0) { opts = {}; }
68
+ this.uri = uri;
69
+ this.userAgent = "".concat(packageInfo_1.PACKAGE_NAME, " (node)/").concat(packageInfo_1.PACKAGE_VERSION);
70
+ this.cacheDir = opts.cacheDir ? opts.cacheDir : false;
71
+ }
72
+ Sdk.prototype.load = function () {
73
+ var cache = new cache_1["default"](this.uri, this.cacheDir);
74
+ var userAgent = this.userAgent;
75
+ var core = new core_1["default"]();
76
+ core.setUserAgent(userAgent);
77
+ var isLoaded = false;
78
+ var isCached = cache.isCached();
79
+ var sdk = {};
80
+ /**
81
+ * Create dynamic accessors for every HTTP method that the OpenAPI specification supports.
82
+ *
83
+ * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#fixed-fields-7}
84
+ * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#fixed-fields-7}
85
+ */
86
+ function loadMethods() {
87
+ return ['get', 'put', 'post', 'delete', 'options', 'head', 'patch', 'trace']
88
+ .map(function (httpVerb) {
89
+ var _a;
90
+ return _a = {},
91
+ _a[httpVerb] = (function (method, path) {
92
+ var args = [];
93
+ for (var _i = 2; _i < arguments.length; _i++) {
94
+ args[_i - 2] = arguments[_i];
95
+ }
96
+ return core.fetch.apply(core, __spreadArray([path, method], args, false));
97
+ }).bind(null, httpVerb),
98
+ _a;
99
+ })
100
+ .reduce(function (prev, next) { return Object.assign(prev, next); });
101
+ }
102
+ /**
103
+ * Create dynamic accessors for every operation with a defined operation ID. If an operation
104
+ * does not have an operation ID it can be accessed by its `.method('/path')` accessor instead.
105
+ *
106
+ * @param spec
107
+ */
108
+ function loadOperations(spec) {
109
+ return Object.entries(spec.getPaths())
110
+ .map(function (_a) {
111
+ var operations = _a[1];
112
+ return Object.values(operations);
113
+ })
114
+ .reduce(function (prev, next) { return prev.concat(next); }, [])
115
+ .filter(function (operation) { return operation.hasOperationId(); })
116
+ .reduce(function (prev, next) {
117
+ var _a;
118
+ // `getOperationId()` creates dynamic operation IDs when one isn't available but we need
119
+ // to know here if we actually have one present or not. The `camelCase` option here also
120
+ // cleans up any `operationId` that we might have into something that can be used as a
121
+ // valid JS method.
122
+ var operationId = next.getOperationId({ camelCase: true });
123
+ return Object.assign(prev, (_a = {},
124
+ _a[operationId] = (function (operation) {
125
+ var args = [];
126
+ for (var _i = 1; _i < arguments.length; _i++) {
127
+ args[_i - 1] = arguments[_i];
128
+ }
129
+ return core.fetchOperation.apply(core, __spreadArray([operation], args, false));
130
+ }).bind(null, next),
131
+ _a));
132
+ }, {});
133
+ }
134
+ function loadFromCache() {
135
+ return __awaiter(this, void 0, void 0, function () {
136
+ var cachedSpec, spec;
137
+ return __generator(this, function (_a) {
138
+ switch (_a.label) {
139
+ case 0:
140
+ if (!isCached) return [3 /*break*/, 2];
141
+ return [4 /*yield*/, cache.get()];
142
+ case 1:
143
+ cachedSpec = _a.sent();
144
+ return [3 /*break*/, 4];
145
+ case 2: return [4 /*yield*/, cache.load()];
146
+ case 3:
147
+ cachedSpec = _a.sent();
148
+ isCached = true;
149
+ _a.label = 4;
150
+ case 4:
151
+ spec = new oas_1["default"](cachedSpec);
152
+ core.setSpec(spec);
153
+ sdk = Object.assign(sdk, __assign(__assign({}, loadMethods()), loadOperations(spec)));
154
+ isLoaded = true;
155
+ return [2 /*return*/];
156
+ }
157
+ });
158
+ });
159
+ }
160
+ var sdkProxy = {
161
+ // @give this a better type than any
162
+ get: function (target, method) {
163
+ // Since auth returns a self-proxy, we **do not** want it to fall through into the async
164
+ // function below as when that'll happen, instead of returning a self-proxy, it'll end up
165
+ // returning a Promise. When that happens, chaining `sdk.auth().operationId()` will fail.
166
+ if (['auth', 'config'].includes(method)) {
167
+ // @todo split this up so we have better types for `auth` and `config`
168
+ return function () {
169
+ var args = [];
170
+ for (var _i = 0; _i < arguments.length; _i++) {
171
+ args[_i] = arguments[_i];
172
+ }
173
+ return target[method].apply(this, args);
174
+ };
175
+ }
176
+ return function () {
177
+ var args = [];
178
+ for (var _i = 0; _i < arguments.length; _i++) {
179
+ args[_i] = arguments[_i];
180
+ }
181
+ return __awaiter(this, void 0, void 0, function () {
182
+ return __generator(this, function (_a) {
183
+ switch (_a.label) {
184
+ case 0:
185
+ if (!!(method in target)) return [3 /*break*/, 2];
186
+ // If this method doesn't exist on the proxy, have we loaded the SDK? If we have, then
187
+ // this method isn't valid.
188
+ if (isLoaded) {
189
+ throw new Error("Sorry, `".concat(method, "` does not appear to be a valid operation on this API."));
190
+ }
191
+ return [4 /*yield*/, loadFromCache()];
192
+ case 1:
193
+ _a.sent();
194
+ // If after loading the SDK and this method still doesn't exist, then it's not real!
195
+ if (!(method in sdk)) {
196
+ throw new Error("Sorry, `".concat(method, "` does not appear to be a valid operation on this API."));
197
+ }
198
+ // @todo give sdk a better type
199
+ return [2 /*return*/, sdk[method].apply(this, args)];
200
+ case 2: return [2 /*return*/, target[method].apply(this, args)];
201
+ }
202
+ });
203
+ });
204
+ };
205
+ }
206
+ };
207
+ sdk = {
208
+ /**
209
+ * If the API you're using requires authentication you can supply the required credentials
210
+ * through this method and the library will magically determine how they should be used
211
+ * within your API request.
212
+ *
213
+ * With the exception of OpenID and MutualTLS, it supports all forms of authentication
214
+ * supported by the OpenAPI specification.
215
+ *
216
+ * @example <caption>HTTP Basic auth</caption>
217
+ * sdk.auth('username', 'password');
218
+ *
219
+ * @example <caption>Bearer tokens (HTTP or OAuth 2)</caption>
220
+ * sdk.auth('myBearerToken');
221
+ *
222
+ * @example <caption>API Keys</caption>
223
+ * sdk.auth('myApiKey');
224
+ *
225
+ * @see {@link https://spec.openapis.org/oas/v3.0.3#fixed-fields-22}
226
+ * @see {@link https://spec.openapis.org/oas/v3.1.0#fixed-fields-22}
227
+ * @param values Your auth credentials for the API. Can specify up to two strings or numbers.
228
+ */
229
+ auth: function () {
230
+ var values = [];
231
+ for (var _i = 0; _i < arguments.length; _i++) {
232
+ values[_i] = arguments[_i];
233
+ }
234
+ core.setAuth.apply(core, values);
235
+ },
236
+ /**
237
+ * Optionally configure various options, such as response parsing, that the SDK allows.
238
+ *
239
+ * @param config Object of supported SDK options and toggles.
240
+ * @param config.parseResponse If responses are parsed according to its `Content-Type` header.
241
+ */
242
+ config: function (config) {
243
+ core.setConfig(config);
244
+ },
245
+ /**
246
+ * If the API you're using offers alternate server URLs, and server variables, you can tell
247
+ * the SDK which one to use with this method. To use it you can supply either one of the
248
+ * server URLs that are contained within the OpenAPI definition (along with any server
249
+ * variables), or you can pass it a fully qualified URL to use (that may or may not exist
250
+ * within the OpenAPI definition).
251
+ *
252
+ * @example <caption>Server URL with server variables</caption>
253
+ * sdk.server('https://{region}.api.example.com/{basePath}', {
254
+ * name: 'eu',
255
+ * basePath: 'v14',
256
+ * });
257
+ *
258
+ * @example <caption>Fully qualified server URL</caption>
259
+ * sdk.server('https://eu.api.example.com/v14');
260
+ *
261
+ * @param url Server URL
262
+ * @param variables An object of variables to replace into the server URL.
263
+ */
264
+ server: function (url, variables) {
265
+ if (variables === void 0) { variables = {}; }
266
+ core.setServer(url, variables);
267
+ }
268
+ };
269
+ return new Proxy(sdk, sdkProxy);
270
+ };
271
+ return Sdk;
272
+ }());
273
+ module.exports = function (uri, opts) {
274
+ if (opts === void 0) { opts = {}; }
275
+ return new Sdk(uri, opts).load();
276
+ };
@@ -0,0 +1,2 @@
1
+ export declare const PACKAGE_NAME = "api";
2
+ export declare const PACKAGE_VERSION = "5.0.0-beta.2";
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ exports.__esModule = true;
3
+ exports.PACKAGE_VERSION = exports.PACKAGE_NAME = void 0;
4
+ // This file is automatically updated by the build script.
5
+ exports.PACKAGE_NAME = 'api';
6
+ exports.PACKAGE_VERSION = '5.0.0-beta.2';
package/package.json CHANGED
@@ -1,56 +1,95 @@
1
1
  {
2
2
  "name": "api",
3
- "version": "4.5.1",
4
- "description": "Generate an SDK from an OpenAPI definition",
5
- "main": "src/index.js",
3
+ "version": "5.0.0-beta.2",
4
+ "description": "Magical SDK generation from an OpenAPI definition 🪄",
5
+ "main": "./dist/index.js",
6
+ "types": "./dist/index.d.ts",
7
+ "bin": {
8
+ "api": "./bin/api"
9
+ },
6
10
  "scripts": {
7
- "lint": "eslint .",
8
- "pretest": "npm run lint",
9
- "prettier": "prettier --list-different --write \"./**/**.js\"",
10
- "test": "jest --coverage"
11
+ "build": "tsc",
12
+ "debug:bin": "node -r ts-node/register src/bin.ts",
13
+ "prebuild": "rm -rf dist/; npm run prebuild.packageConfig",
14
+ "prebuild.packageConfig": "node -p \"'// This file is automatically updated by the build script.\\nexport const PACKAGE_NAME = \\'' + require('./package.json').name + '\\';\\nexport const PACKAGE_VERSION = \\'' + require('./package.json').version + '\\';'\" > src/packageInfo.ts; git add src/packageInfo.ts",
15
+ "prepack": "npm run build",
16
+ "test": "nyc mocha \"test/**/*.test.ts\""
11
17
  },
12
18
  "repository": {
13
19
  "type": "git",
14
20
  "url": "https://github.com/readmeio/api.git",
15
21
  "directory": "packages/api"
16
22
  },
23
+ "homepage": "https://api.readme.dev",
17
24
  "bugs": {
18
25
  "url": "https://github.com/readmeio/api/issues"
19
26
  },
20
27
  "author": "Jon Ursenbach <jon@readme.io>",
21
28
  "license": "MIT",
22
29
  "engines": {
23
- "node": "^12 || ^14 || ^16"
30
+ "node": ">=14"
24
31
  },
32
+ "keywords": [
33
+ "api",
34
+ "openapi",
35
+ "sdk",
36
+ "swagger"
37
+ ],
25
38
  "dependencies": {
26
- "@readme/oas-to-har": "^16.1.0",
27
- "@readme/openapi-parser": "^2.1.0",
39
+ "@readme/oas-to-har": "^17.0.8",
40
+ "@readme/openapi-parser": "^2.2.0",
41
+ "chalk": "^4.1.2",
42
+ "commander": "^9.2.0",
28
43
  "datauri": "^4.1.0",
29
- "fetch-har": "^5.0.5",
44
+ "execa": "^5.1.1",
45
+ "fetch-har": "^8.0.3",
46
+ "figures": "^3.2.0",
30
47
  "find-cache-dir": "^3.3.1",
31
- "form-data": "^4.0.0",
32
- "get-stream": "^6.0.0",
48
+ "form-data-encoder": "^1.7.2",
49
+ "formdata-node": "^4.3.2",
50
+ "get-stream": "^6.0.1",
51
+ "isomorphic-fetch": "^3.0.0",
33
52
  "js-yaml": "^4.1.0",
53
+ "json-schema-to-typescript": "^10.1.5",
54
+ "json-schema-traverse": "^1.0.0",
55
+ "lodash.merge": "^4.6.2",
34
56
  "make-dir": "^3.1.0",
35
- "mimer": "^2.0.2",
36
- "node-fetch": "^2.6.0",
37
- "oas": "^18.1.0"
57
+ "oas": "^18.3.3",
58
+ "object-hash": "^3.0.0",
59
+ "ora": "^5.4.1",
60
+ "prompts": "^2.4.2",
61
+ "ssri": "^9.0.0",
62
+ "ts-morph": "^15.1.0",
63
+ "validate-npm-package-name": "^4.0.0"
38
64
  },
39
65
  "devDependencies": {
40
- "@readme/eslint-config": "^8.0.2",
41
- "@readme/oas-examples": "^4.3.3",
42
- "eslint": "^8.3.0",
43
- "jest": "^27.3.1",
44
- "memfs": "^3.2.4",
45
- "nock": "^13.1.3",
46
- "prettier": "^2.4.1"
66
+ "@readme/oas-examples": "^5.4.1",
67
+ "@types/chai": "^4.3.1",
68
+ "@types/find-cache-dir": "^3.2.1",
69
+ "@types/js-yaml": "^4.0.5",
70
+ "@types/lodash.merge": "^4.6.7",
71
+ "@types/mocha": "^9.1.1",
72
+ "@types/object-hash": "^2.2.1",
73
+ "@types/prompts": "^2.0.14",
74
+ "@types/sinon-chai": "^3.2.8",
75
+ "@types/ssri": "^7.1.1",
76
+ "@types/validate-npm-package-name": "^4.0.0",
77
+ "chai": "^4.3.6",
78
+ "fetch-mock": "^9.11.0",
79
+ "mocha": "^10.0.0",
80
+ "mock-require": "^3.0.3",
81
+ "nyc": "^15.1.0",
82
+ "sinon": "^14.0.0",
83
+ "sinon-chai": "^3.7.0",
84
+ "typescript": "^4.7.4",
85
+ "unique-temp-dir": "^1.0.0"
47
86
  },
48
87
  "prettier": "@readme/eslint-config/prettier",
49
- "jest": {
50
- "testPathIgnorePatterns": [
51
- "__tests__/__fixtures__/",
52
- ".api-test/"
88
+ "nyc": {
89
+ "exclude": [
90
+ "dist/",
91
+ "test/"
53
92
  ]
54
93
  },
55
- "gitHead": "5c3490291498c1ac7f2f9dfe2ad99afff9010689"
94
+ "gitHead": "aa738b1bf46b447afed38507d3fc49acbbad6309"
56
95
  }
package/src/.sink.d.ts ADDED
@@ -0,0 +1 @@
1
+ declare module 'form-data-encoder';
package/src/bin.ts ADDED
@@ -0,0 +1,20 @@
1
+ import { Command } from 'commander';
2
+ import * as pkg from './packageInfo';
3
+ import commands from './cli/commands';
4
+
5
+ (async () => {
6
+ const program = new Command();
7
+ program.name(pkg.PACKAGE_NAME);
8
+ program.version(pkg.PACKAGE_VERSION);
9
+
10
+ /**
11
+ * Instead of using Commander's `executableDir` API for loading in external command files we're
12
+ * programatically doing it like this because it's cleaner for us to let Commander manage option
13
+ * and argument parsing within this file than having each command manage that itself.
14
+ */
15
+ Object.entries(commands).forEach(([, cmd]: [string, Command]) => {
16
+ program.addCommand(cmd);
17
+ });
18
+
19
+ await program.parseAsync(process.argv);
20
+ })();
package/src/cache.ts ADDED
@@ -0,0 +1,212 @@
1
+ import type { OASDocument } from 'oas/@types/rmoas.types';
2
+
3
+ import 'isomorphic-fetch';
4
+ import OpenAPIParser from '@readme/openapi-parser';
5
+ import crypto from 'crypto';
6
+ import findCacheDir from 'find-cache-dir';
7
+ import fs from 'fs';
8
+ import os from 'os';
9
+ import path from 'path';
10
+ import makeDir from 'make-dir';
11
+
12
+ import { PACKAGE_NAME } from './packageInfo';
13
+
14
+ import Fetcher from './fetcher';
15
+
16
+ type CacheStore = Record<
17
+ string,
18
+ {
19
+ hash: string;
20
+ path?: string; // Deprecated in v4.5.0 in favor of `hash`.
21
+ original: string | OASDocument;
22
+ title?: string;
23
+ version?: string;
24
+ }
25
+ >;
26
+
27
+ export default class Cache {
28
+ static dir: string;
29
+
30
+ static cacheStore: string;
31
+
32
+ static specsCache: string;
33
+
34
+ uri: string | OASDocument;
35
+
36
+ uriHash: string;
37
+
38
+ cached: false | CacheStore;
39
+
40
+ fetcher: Fetcher;
41
+
42
+ constructor(uri: string | OASDocument, cacheDir: string | false = false) {
43
+ Cache.setCacheDir(cacheDir);
44
+ Cache.cacheStore = path.join(Cache.dir, 'cache.json');
45
+ Cache.specsCache = path.join(Cache.dir, 'specs');
46
+
47
+ this.fetcher = new Fetcher(uri);
48
+
49
+ this.uri = this.fetcher.uri;
50
+ this.uriHash = Cache.getCacheHash(this.uri);
51
+
52
+ // This should default to false so we have awareness if we've looked at the cache yet.
53
+ this.cached = false;
54
+ }
55
+
56
+ static getCacheHash(file: string | OASDocument) {
57
+ let data: string;
58
+ if (typeof file === 'object') {
59
+ // Under certain unit testing circumstances, we might be supplying the class with a raw JSON
60
+ // object so we'll need to convert it to a string in order to hand it off to the crypto
61
+ // module.
62
+ data = JSON.stringify(file);
63
+ } else {
64
+ data = file;
65
+ }
66
+
67
+ return crypto.createHash('md5').update(data).digest('hex');
68
+ }
69
+
70
+ static setCacheDir(dir?: string | false) {
71
+ if (dir) {
72
+ Cache.dir = dir;
73
+ return;
74
+ } else if (Cache.dir) {
75
+ // If we already have a cache dir set and aren't explicitly it to something new then we
76
+ // shouldn't overwrite what we've already got.
77
+ return;
78
+ }
79
+
80
+ Cache.dir = findCacheDir({ name: PACKAGE_NAME });
81
+ if (typeof Cache.dir === 'undefined') {
82
+ // The `find-cache-dir` module returns `undefined` if the `node_modules/` directory isn't
83
+ // writable, or there's no `package.json` in the root-most directory. If this happens, we can
84
+ // instead adhoc create a cache directory in the users OS temp directory and store our data
85
+ // there.
86
+ //
87
+ // @link https://github.com/avajs/find-cache-dir/issues/29
88
+ Cache.dir = makeDir.sync(path.join(os.tmpdir(), PACKAGE_NAME));
89
+ }
90
+ }
91
+
92
+ static async reset() {
93
+ if (Cache.cacheStore) {
94
+ await fs.promises.rm(Cache.cacheStore).catch(() => {
95
+ // no-op
96
+ });
97
+ }
98
+
99
+ if (Cache.specsCache) {
100
+ await fs.promises.rm(Cache.specsCache, { recursive: true }).catch(() => {
101
+ // no-op
102
+ });
103
+ }
104
+ }
105
+
106
+ static validate(json: any) {
107
+ if (json.swagger) {
108
+ throw new Error('Sorry, this module only supports OpenAPI definitions.');
109
+ }
110
+
111
+ // The `validate` method handles dereferencing for us.
112
+ return OpenAPIParser.validate(json, {
113
+ dereference: {
114
+ // If circular `$refs` are ignored they'll remain in the API definition as `$ref: String`.
115
+ // This allows us to not only do easy circular reference detection but also stringify and
116
+ // save dereferenced API definitions back into the cache directory.
117
+ circular: 'ignore',
118
+ },
119
+ }).catch(err => {
120
+ if (/is not a valid openapi definition/i.test(err.message)) {
121
+ throw new Error("Sorry, that doesn't look like a valid OpenAPI definition.");
122
+ }
123
+
124
+ throw err;
125
+ });
126
+ }
127
+
128
+ isCached() {
129
+ const cache = this.getCache();
130
+ return cache && this.uriHash in cache;
131
+ }
132
+
133
+ getCache() {
134
+ if (typeof this.cached === 'object') {
135
+ return this.cached;
136
+ }
137
+
138
+ this.cached = {};
139
+
140
+ if (fs.existsSync(Cache.cacheStore)) {
141
+ this.cached = JSON.parse(fs.readFileSync(Cache.cacheStore, 'utf8')) as CacheStore;
142
+ }
143
+
144
+ return this.cached;
145
+ }
146
+
147
+ get() {
148
+ // If the class was supplied a raw object, just go ahead and bypass the caching system and
149
+ // return that.
150
+ if (typeof this.uri === 'object') {
151
+ return this.uri;
152
+ }
153
+
154
+ if (!this.isCached()) {
155
+ throw new Error(`${this.uri} has not been cached yet and must do so before being retrieved.`);
156
+ }
157
+
158
+ const cache = this.getCache();
159
+
160
+ // Prior to v4.5.0 we were putting a fully resolved path to the API definition in the cache
161
+ // store but if you had specified a custom caching directory and would generate the cache on
162
+ // your system, that filepath would obviously not be the same in other environments. For this
163
+ // reason the `path` was removed from the cache store in favor of storing the `hash` instead.
164
+ //
165
+ // If we still have `path` in the config cache for backwards compatibility we should use it.
166
+ if ('path' in cache[this.uriHash]) {
167
+ return JSON.parse(fs.readFileSync(cache[this.uriHash].path, 'utf8'));
168
+ }
169
+
170
+ return JSON.parse(fs.readFileSync(path.join(Cache.specsCache, `${cache[this.uriHash].hash}.json`), 'utf8'));
171
+ }
172
+
173
+ async load() {
174
+ // If the class was supplied a raw object, just go ahead and bypass the caching system and
175
+ // return that.
176
+ if (typeof this.uri === 'object') {
177
+ return this.uri;
178
+ }
179
+
180
+ return this.fetcher.load().then(async spec => this.save(spec));
181
+ }
182
+
183
+ save(spec: OASDocument) {
184
+ if (!fs.existsSync(Cache.dir)) {
185
+ fs.mkdirSync(Cache.dir, { recursive: true });
186
+ }
187
+
188
+ if (!fs.existsSync(Cache.specsCache)) {
189
+ fs.mkdirSync(Cache.specsCache, { recursive: true });
190
+ }
191
+
192
+ const cache = this.getCache();
193
+ if (!(this.uriHash in cache)) {
194
+ const saved = JSON.stringify(spec, null, 2);
195
+ const fileHash = crypto.createHash('md5').update(saved).digest('hex');
196
+
197
+ cache[this.uriHash] = {
198
+ hash: fileHash,
199
+ original: this.uri,
200
+ title: 'title' in spec.info ? spec.info.title : undefined,
201
+ version: 'version' in spec.info ? spec.info.version : undefined,
202
+ };
203
+
204
+ fs.writeFileSync(path.join(Cache.specsCache, `${fileHash}.json`), saved);
205
+ fs.writeFileSync(Cache.cacheStore, JSON.stringify(cache, null, 2));
206
+
207
+ this.cached = cache;
208
+ }
209
+
210
+ return spec;
211
+ }
212
+ }