api 5.0.8 → 6.1.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/dist/cache.d.ts +4 -1
- package/dist/cli/codegen/languages/typescript.d.ts +22 -5
- package/dist/cli/codegen/languages/typescript.js +57 -14
- package/dist/cli/commands/install.js +33 -31
- package/dist/cli/storage.d.ts +10 -10
- package/dist/cli/storage.js +14 -1
- package/dist/core/errors/fetchError.d.ts +4 -4
- package/dist/core/getJSONSchemaDefaults.d.ts +0 -1
- package/dist/core/getJSONSchemaDefaults.js +0 -1
- package/dist/core/index.d.ts +1 -1
- package/dist/core/prepareAuth.d.ts +1 -1
- package/dist/core/prepareServer.d.ts +0 -3
- package/dist/core/prepareServer.js +0 -3
- package/dist/index.js +0 -1
- package/dist/packageInfo.d.ts +1 -1
- package/dist/packageInfo.js +1 -1
- package/package.json +9 -20
- package/src/cache.ts +4 -1
- package/src/cli/codegen/languages/typescript.ts +95 -24
- package/src/cli/commands/install.ts +12 -15
- package/src/cli/storage.ts +27 -10
- package/src/core/errors/fetchError.ts +4 -4
- package/src/core/getJSONSchemaDefaults.ts +0 -1
- package/src/core/index.ts +7 -2
- package/src/core/prepareAuth.ts +1 -1
- package/src/core/prepareParams.ts +1 -1
- package/src/core/prepareServer.ts +0 -3
- package/src/index.ts +0 -1
- package/src/packageInfo.ts +1 -1
package/dist/cache.d.ts
CHANGED
|
@@ -3,8 +3,11 @@ import 'isomorphic-fetch';
|
|
|
3
3
|
import Fetcher from './fetcher';
|
|
4
4
|
type CacheStore = Record<string, {
|
|
5
5
|
hash: string;
|
|
6
|
-
path?: string;
|
|
7
6
|
original: string | OASDocument;
|
|
7
|
+
/**
|
|
8
|
+
* @deprecated Deprecated in v4.5.0 in favor of `hash`.
|
|
9
|
+
*/
|
|
10
|
+
path?: string;
|
|
8
11
|
title?: string;
|
|
9
12
|
version?: string;
|
|
10
13
|
}>;
|
|
@@ -2,19 +2,22 @@ import type Storage from '../../storage';
|
|
|
2
2
|
import type { InstallerOptions } from '../language';
|
|
3
3
|
import type Oas from 'oas';
|
|
4
4
|
import type { Operation } from 'oas';
|
|
5
|
-
import type { ClassDeclaration } from 'ts-morph';
|
|
5
|
+
import type { ClassDeclaration, JSDocStructure, JSDocTagStructure, OptionalKind } from 'ts-morph';
|
|
6
6
|
import { Project } from 'ts-morph';
|
|
7
7
|
import CodeGeneratorLanguage from '../language';
|
|
8
8
|
export interface TSGeneratorOptions {
|
|
9
|
-
outputJS?: boolean;
|
|
10
9
|
compilerTarget?: 'cjs' | 'esm';
|
|
10
|
+
outputJS?: boolean;
|
|
11
11
|
}
|
|
12
12
|
interface OperationTypeHousing {
|
|
13
|
+
operation: Operation;
|
|
13
14
|
types: {
|
|
14
15
|
params?: false | Record<'body' | 'formData' | 'metadata', string>;
|
|
15
|
-
responses?: Record<string,
|
|
16
|
+
responses?: Record<string | number, {
|
|
17
|
+
description?: string;
|
|
18
|
+
type: string;
|
|
19
|
+
}>;
|
|
16
20
|
};
|
|
17
|
-
operation: Operation;
|
|
18
21
|
}
|
|
19
22
|
export default class TSGenerator extends CodeGeneratorLanguage {
|
|
20
23
|
project: Project;
|
|
@@ -57,6 +60,17 @@ export default class TSGenerator extends CodeGeneratorLanguage {
|
|
|
57
60
|
* @see {@link https://npm.im/json-schema-to-ts}
|
|
58
61
|
*/
|
|
59
62
|
createTypesFile(): import("ts-morph").SourceFile;
|
|
63
|
+
/**
|
|
64
|
+
* Add a new JSDoc `@tag` to an existing docblock.
|
|
65
|
+
*
|
|
66
|
+
*/
|
|
67
|
+
static addTagToDocblock(docblock: OptionalKind<JSDocStructure>, tag: OptionalKind<JSDocTagStructure>): {
|
|
68
|
+
tags: OptionalKind<JSDocTagStructure>[];
|
|
69
|
+
description?: string | import("ts-morph").WriterFunction;
|
|
70
|
+
leadingTrivia?: string | import("ts-morph").WriterFunction | (string | import("ts-morph").WriterFunction)[];
|
|
71
|
+
trailingTrivia?: string | import("ts-morph").WriterFunction | (string | import("ts-morph").WriterFunction)[];
|
|
72
|
+
kind?: import("ts-morph").StructureKind.JSDoc;
|
|
73
|
+
};
|
|
60
74
|
/**
|
|
61
75
|
* Create operation accessors on the SDK.
|
|
62
76
|
*
|
|
@@ -83,7 +97,10 @@ export default class TSGenerator extends CodeGeneratorLanguage {
|
|
|
83
97
|
*
|
|
84
98
|
*/
|
|
85
99
|
prepareResponseTypesForOperation(operation: Operation, operationId: string): {
|
|
86
|
-
[x: string]:
|
|
100
|
+
[x: string]: {
|
|
101
|
+
type: string;
|
|
102
|
+
description: any;
|
|
103
|
+
};
|
|
87
104
|
};
|
|
88
105
|
/**
|
|
89
106
|
* Add a given schema into our schema dataset that we'll be be exporting as types.
|
|
@@ -163,11 +163,18 @@ var TSGenerator = /** @class */ (function (_super) {
|
|
|
163
163
|
// This will install the installed SDK as a dependency within the current working directory,
|
|
164
164
|
// adding `@api/<sdk identifier>` as a dependency there so you can load it with
|
|
165
165
|
// `require('@api/<sdk identifier>)`.
|
|
166
|
-
return [2 /*return*/, (0, execa_1["default"])('npm', __spreadArray(__spreadArray([], npmInstall, true), [installDir], false).filter(Boolean))
|
|
166
|
+
return [2 /*return*/, (0, execa_1["default"])('npm', __spreadArray(__spreadArray([], npmInstall, true), [installDir], false).filter(Boolean))
|
|
167
|
+
.then(function (res) {
|
|
167
168
|
if (opts.dryRun) {
|
|
168
169
|
(opts.logger ? opts.logger : logger_1["default"])(res.command);
|
|
169
170
|
(opts.logger ? opts.logger : logger_1["default"])(res.stdout);
|
|
170
171
|
}
|
|
172
|
+
})["catch"](function (err) {
|
|
173
|
+
if (opts.dryRun) {
|
|
174
|
+
(opts.logger ? opts.logger : logger_1["default"])(err.message);
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
throw err;
|
|
171
178
|
})];
|
|
172
179
|
}
|
|
173
180
|
});
|
|
@@ -464,6 +471,16 @@ var TSGenerator = /** @class */ (function (_super) {
|
|
|
464
471
|
});
|
|
465
472
|
return sourceFile;
|
|
466
473
|
};
|
|
474
|
+
/**
|
|
475
|
+
* Add a new JSDoc `@tag` to an existing docblock.
|
|
476
|
+
*
|
|
477
|
+
*/
|
|
478
|
+
TSGenerator.addTagToDocblock = function (docblock, tag) {
|
|
479
|
+
var _a;
|
|
480
|
+
var tags = (_a = docblock.tags) !== null && _a !== void 0 ? _a : [];
|
|
481
|
+
tags.push(tag);
|
|
482
|
+
return __assign(__assign({}, docblock), { tags: tags });
|
|
483
|
+
};
|
|
467
484
|
/**
|
|
468
485
|
* Create operation accessors on the SDK.
|
|
469
486
|
*
|
|
@@ -488,7 +505,10 @@ var TSGenerator = /** @class */ (function (_super) {
|
|
|
488
505
|
return writer;
|
|
489
506
|
};
|
|
490
507
|
if (summary && description) {
|
|
491
|
-
docblock
|
|
508
|
+
docblock = TSGenerator.addTagToDocblock(docblock, {
|
|
509
|
+
tagName: 'summary',
|
|
510
|
+
text: (0, util_1.docblockEscape)((0, util_1.wordWrap)(summary))
|
|
511
|
+
});
|
|
492
512
|
}
|
|
493
513
|
}
|
|
494
514
|
var hasOptionalBody = false;
|
|
@@ -516,9 +536,9 @@ var TSGenerator = /** @class */ (function (_super) {
|
|
|
516
536
|
}
|
|
517
537
|
var returnType = 'Promise<FetchResponse<number, unknown>>';
|
|
518
538
|
if (responseTypes) {
|
|
519
|
-
|
|
539
|
+
var returnTypes = Object.entries(responseTypes)
|
|
520
540
|
.map(function (_a) {
|
|
521
|
-
var status = _a[0],
|
|
541
|
+
var status = _a[0], _b = _a[1], responseDescription = _b.description, responseType = _b.type;
|
|
522
542
|
if (status.toLowerCase() === 'default') {
|
|
523
543
|
return "FetchResponse<number, ".concat(responseType, ">");
|
|
524
544
|
}
|
|
@@ -529,17 +549,42 @@ var TSGenerator = /** @class */ (function (_super) {
|
|
|
529
549
|
// it and should instead fall back to treating it as an unknown number.
|
|
530
550
|
return "FetchResponse<number, ".concat(responseType, ">");
|
|
531
551
|
}
|
|
552
|
+
if (Number(statusPrefix) >= 4) {
|
|
553
|
+
docblock = TSGenerator.addTagToDocblock(docblock, {
|
|
554
|
+
tagName: 'throws',
|
|
555
|
+
text: "FetchError<".concat(status, ", ").concat(responseType, ">").concat(responseDescription ? (0, util_1.docblockEscape)((0, util_1.wordWrap)(" ".concat(responseDescription))) : '')
|
|
556
|
+
});
|
|
557
|
+
return false;
|
|
558
|
+
}
|
|
532
559
|
_this.usesHTTPMethodRangeInterface = true;
|
|
533
560
|
return "FetchResponse<HTTPMethodRange<".concat(statusPrefix, "00, ").concat(statusPrefix, "99>, ").concat(responseType, ">");
|
|
534
561
|
}
|
|
562
|
+
// 400 and 500 status code families are thrown as exceptions so adding them as a possible
|
|
563
|
+
// return type isn't valid.
|
|
564
|
+
if (Number(status) >= 400) {
|
|
565
|
+
docblock = TSGenerator.addTagToDocblock(docblock, {
|
|
566
|
+
tagName: 'throws',
|
|
567
|
+
text: "FetchError<".concat(status, ", ").concat(responseType, ">").concat(responseDescription ? (0, util_1.docblockEscape)((0, util_1.wordWrap)(" ".concat(responseDescription))) : '')
|
|
568
|
+
});
|
|
569
|
+
return false;
|
|
570
|
+
}
|
|
535
571
|
return "FetchResponse<".concat(status, ", ").concat(responseType, ">");
|
|
536
572
|
})
|
|
537
|
-
.
|
|
573
|
+
.filter(Boolean)
|
|
574
|
+
.join(' | ');
|
|
575
|
+
// If all of our documented responses are for error status codes then all we can document for
|
|
576
|
+
// anything else that might happen is `unknown`.
|
|
577
|
+
returnType = "Promise<".concat(returnTypes.length ? returnTypes : 'FetchResponse<number, unknown>', ">");
|
|
538
578
|
}
|
|
579
|
+
var shouldAddAltTypedOverloads = Object.keys(parameters).length === 2 && hasOptionalBody && !hasOptionalMetadata;
|
|
539
580
|
var operationIdAccessor = this.sdk.addMethod({
|
|
540
581
|
name: operationId,
|
|
541
582
|
returnType: returnType,
|
|
542
|
-
|
|
583
|
+
// If we're going to be creating typed method overloads for optional body an metadata handling
|
|
584
|
+
// we should only add a docblock to the first overload we create because IDE Intellisense will
|
|
585
|
+
// always use that and adding a docblock to all three will bloat the SDK with unused and
|
|
586
|
+
// unsurfaced method documentation.
|
|
587
|
+
docs: shouldAddAltTypedOverloads ? null : Object.keys(docblock).length ? [docblock] : null,
|
|
543
588
|
statements: function (writer) {
|
|
544
589
|
/**
|
|
545
590
|
* @example return this.core.fetch('/pet/findByStatus', 'get', body, metadata);
|
|
@@ -569,10 +614,6 @@ var TSGenerator = /** @class */ (function (_super) {
|
|
|
569
614
|
// If we have both body and metadata parameters but only body is optional we need to create
|
|
570
615
|
// a couple function overloads as Typescript doesn't let us have an optional method parameter
|
|
571
616
|
// come before one that's required.
|
|
572
|
-
//
|
|
573
|
-
// None of these accessor overloads will receive a docblock because the original will have
|
|
574
|
-
// that covered.
|
|
575
|
-
var shouldAddAltTypedOverloads = Object.keys(parameters).length === 2 && hasOptionalBody && !hasOptionalMetadata;
|
|
576
617
|
if (shouldAddAltTypedOverloads) {
|
|
577
618
|
// Create an overload that has both `body` and `metadata` parameters as required.
|
|
578
619
|
operationIdAccessor.addOverload({
|
|
@@ -586,8 +627,7 @@ var TSGenerator = /** @class */ (function (_super) {
|
|
|
586
627
|
// Create an overload that just has a single `metadata` parameter.
|
|
587
628
|
operationIdAccessor.addOverload({
|
|
588
629
|
parameters: [__assign({}, parameters.metadata)],
|
|
589
|
-
returnType: returnType
|
|
590
|
-
docs: Object.keys(docblock).length ? [docblock] : null
|
|
630
|
+
returnType: returnType
|
|
591
631
|
});
|
|
592
632
|
// Create an overload that has both `body` and `metadata` parameters as optional. Even though
|
|
593
633
|
// our `metadata` parameter is actually required for this operation this is the only way we're
|
|
@@ -739,7 +779,7 @@ var TSGenerator = /** @class */ (function (_super) {
|
|
|
739
779
|
var res = Object.entries(schemas)
|
|
740
780
|
.map(function (_a) {
|
|
741
781
|
var _b;
|
|
742
|
-
var status = _a[0],
|
|
782
|
+
var status = _a[0], _c = _a[1], description = _c.description, schema = _c.schema;
|
|
743
783
|
var typeName;
|
|
744
784
|
if (typeof schema === 'string' && schema.startsWith('::convert::')) {
|
|
745
785
|
// If this schema is a string and has our conversion prefix then we've already created
|
|
@@ -756,7 +796,10 @@ var TSGenerator = /** @class */ (function (_super) {
|
|
|
756
796
|
return _b = {},
|
|
757
797
|
// Types are prefixed with `types.` because that's how we're importing them from
|
|
758
798
|
// `types.d.ts`.
|
|
759
|
-
_b[status] =
|
|
799
|
+
_b[status] = {
|
|
800
|
+
type: "types.".concat(typeName),
|
|
801
|
+
description: description
|
|
802
|
+
},
|
|
760
803
|
_b;
|
|
761
804
|
})
|
|
762
805
|
.reduce(function (prev, next) { return Object.assign(prev, next); }, {});
|
|
@@ -43,7 +43,6 @@ var commander_1 = require("commander");
|
|
|
43
43
|
var figures_1 = __importDefault(require("figures"));
|
|
44
44
|
var oas_1 = __importDefault(require("oas"));
|
|
45
45
|
var ora_1 = __importDefault(require("ora"));
|
|
46
|
-
var validate_npm_package_name_1 = __importDefault(require("validate-npm-package-name"));
|
|
47
46
|
var fetcher_1 = __importDefault(require("../../fetcher"));
|
|
48
47
|
var codegen_1 = __importDefault(require("../codegen"));
|
|
49
48
|
var prompt_1 = __importDefault(require("../lib/prompt"));
|
|
@@ -55,6 +54,7 @@ cmd
|
|
|
55
54
|
.name('install')
|
|
56
55
|
.description('install an API SDK into your codebase')
|
|
57
56
|
.argument('<uri>', 'an API to install')
|
|
57
|
+
.option('-i, --identifier <identifier>', 'API identifier (eg. `@api/petstore`)')
|
|
58
58
|
.addOption(new commander_1.Option('-l, --lang <language>', 'SDK language').choices([
|
|
59
59
|
'js',
|
|
60
60
|
'js-cjs',
|
|
@@ -107,10 +107,17 @@ cmd
|
|
|
107
107
|
// @todo
|
|
108
108
|
// logger(`It looks like you already have this API installed. Would you like to update it?`);
|
|
109
109
|
}
|
|
110
|
-
if (!
|
|
110
|
+
if (!options.identifier) return [3 /*break*/, 6];
|
|
111
|
+
// `Storage.isIdentifierValid` will throw an exception if an identifier is invalid.
|
|
112
|
+
if (storage_1["default"].isIdentifierValid(options.identifier)) {
|
|
113
|
+
identifier = options.identifier;
|
|
114
|
+
}
|
|
115
|
+
return [3 /*break*/, 9];
|
|
116
|
+
case 6:
|
|
117
|
+
if (!fetcher_1["default"].isAPIRegistryUUID(uri)) return [3 /*break*/, 7];
|
|
111
118
|
identifier = fetcher_1["default"].getProjectPrefixFromRegistryUUID(uri);
|
|
112
|
-
return [3 /*break*/,
|
|
113
|
-
case
|
|
119
|
+
return [3 /*break*/, 9];
|
|
120
|
+
case 7: return [4 /*yield*/, (0, prompt_1["default"])({
|
|
114
121
|
type: 'text',
|
|
115
122
|
name: 'value',
|
|
116
123
|
message: 'What would you like to identify this API as? This will be how you import the SDK. (e.g. entering `petstore` would result in `@api/petstore`)',
|
|
@@ -118,23 +125,18 @@ cmd
|
|
|
118
125
|
if (!value) {
|
|
119
126
|
return false;
|
|
120
127
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
return "\"".concat(value, "\" is already taken in your `.api/` directory. Please enter another identifier.");
|
|
128
|
+
try {
|
|
129
|
+
return storage_1["default"].isIdentifierValid(value, true);
|
|
124
130
|
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
// `prompts` doesn't support surfacing multiple errors in a `validate` call so we can
|
|
128
|
-
// only surface the first to the user.
|
|
129
|
-
return isValidForNPM.errors[0];
|
|
131
|
+
catch (err) {
|
|
132
|
+
return err.message;
|
|
130
133
|
}
|
|
131
|
-
return true;
|
|
132
134
|
}
|
|
133
135
|
})];
|
|
134
|
-
case 7:
|
|
135
|
-
(identifier = (_a.sent()).value);
|
|
136
|
-
_a.label = 8;
|
|
137
136
|
case 8:
|
|
137
|
+
(identifier = (_a.sent()).value);
|
|
138
|
+
_a.label = 9;
|
|
139
|
+
case 9:
|
|
138
140
|
if (!identifier) {
|
|
139
141
|
(0, logger_1["default"])('You must tell us what you would like to identify this API as in order to install it.', true);
|
|
140
142
|
process.exit(1);
|
|
@@ -153,7 +155,7 @@ cmd
|
|
|
153
155
|
(0, logger_1["default"])(err.message, true);
|
|
154
156
|
process.exit(1);
|
|
155
157
|
})];
|
|
156
|
-
case
|
|
158
|
+
case 10:
|
|
157
159
|
oas = _a.sent();
|
|
158
160
|
// @todo look for a prettier config and if we find one ask them if we should use it
|
|
159
161
|
spinner = (0, ora_1["default"])('Generating your SDK').start();
|
|
@@ -169,7 +171,7 @@ cmd
|
|
|
169
171
|
(0, logger_1["default"])(err.message, true);
|
|
170
172
|
process.exit(1);
|
|
171
173
|
})];
|
|
172
|
-
case
|
|
174
|
+
case 11:
|
|
173
175
|
sdkSource = _a.sent();
|
|
174
176
|
spinner = (0, ora_1["default"])('Saving your SDK into your codebase').start();
|
|
175
177
|
return [4 /*yield*/, storage
|
|
@@ -182,15 +184,15 @@ cmd
|
|
|
182
184
|
(0, logger_1["default"])(err.message, true);
|
|
183
185
|
process.exit(1);
|
|
184
186
|
})];
|
|
185
|
-
case
|
|
187
|
+
case 12:
|
|
186
188
|
_a.sent();
|
|
187
|
-
if (!generator.hasRequiredPackages()) return [3 /*break*/,
|
|
189
|
+
if (!generator.hasRequiredPackages()) return [3 /*break*/, 18];
|
|
188
190
|
(0, logger_1["default"])("".concat(figures_1["default"].warning, " This generator requires some packages to be installed alongside it:"));
|
|
189
191
|
Object.entries(generator.requiredPackages).forEach(function (_a) {
|
|
190
192
|
var pkg = _a[0], pkgInfo = _a[1];
|
|
191
193
|
(0, logger_1["default"])(" ".concat(figures_1["default"].pointerSmall, " ").concat(pkg, ": ").concat(pkgInfo.reason, " ").concat(pkgInfo.url));
|
|
192
194
|
});
|
|
193
|
-
if (!!options.yes) return [3 /*break*/,
|
|
195
|
+
if (!!options.yes) return [3 /*break*/, 14];
|
|
194
196
|
return [4 /*yield*/, (0, prompt_1["default"])({
|
|
195
197
|
type: 'confirm',
|
|
196
198
|
name: 'value',
|
|
@@ -204,27 +206,27 @@ cmd
|
|
|
204
206
|
process.exit(1);
|
|
205
207
|
}
|
|
206
208
|
})];
|
|
207
|
-
case 12:
|
|
208
|
-
_a.sent();
|
|
209
|
-
_a.label = 13;
|
|
210
209
|
case 13:
|
|
211
|
-
|
|
210
|
+
_a.sent();
|
|
212
211
|
_a.label = 14;
|
|
213
212
|
case 14:
|
|
214
|
-
|
|
215
|
-
|
|
213
|
+
spinner = (0, ora_1["default"])('Installing required packages').start();
|
|
214
|
+
_a.label = 15;
|
|
216
215
|
case 15:
|
|
216
|
+
_a.trys.push([15, 17, , 18]);
|
|
217
|
+
return [4 /*yield*/, generator.installer(storage)];
|
|
218
|
+
case 16:
|
|
217
219
|
_a.sent();
|
|
218
220
|
spinner.succeed(spinner.text);
|
|
219
|
-
return [3 /*break*/,
|
|
220
|
-
case
|
|
221
|
+
return [3 /*break*/, 18];
|
|
222
|
+
case 17:
|
|
221
223
|
err_1 = _a.sent();
|
|
222
224
|
// @todo cleanup installed files
|
|
223
225
|
spinner.fail(spinner.text);
|
|
224
226
|
(0, logger_1["default"])(err_1.message, true);
|
|
225
227
|
process.exit(1);
|
|
226
|
-
return [3 /*break*/,
|
|
227
|
-
case
|
|
228
|
+
return [3 /*break*/, 18];
|
|
229
|
+
case 18:
|
|
228
230
|
(0, logger_1["default"])('🚀 All done!');
|
|
229
231
|
return [2 /*return*/];
|
|
230
232
|
}
|
package/dist/cli/storage.d.ts
CHANGED
|
@@ -23,6 +23,7 @@ export default class Storage {
|
|
|
23
23
|
static getDefaultLockfile(): Lockfile;
|
|
24
24
|
static generateIntegrityHash(definition: OASDocument): string;
|
|
25
25
|
static getLockfile(): Lockfile;
|
|
26
|
+
static isIdentifierValid(identifier: string, prefixWithAPINamespace?: boolean): boolean;
|
|
26
27
|
static isInLockFile(search: {
|
|
27
28
|
identifier?: string;
|
|
28
29
|
source?: string;
|
|
@@ -61,17 +62,16 @@ export default class Storage {
|
|
|
61
62
|
* ├── openapi.json
|
|
62
63
|
* └── package.json
|
|
63
64
|
*
|
|
64
|
-
* @param spec
|
|
65
65
|
*/
|
|
66
66
|
save(spec: OASDocument): OASDocument;
|
|
67
67
|
}
|
|
68
68
|
export interface Lockfile {
|
|
69
|
+
apis: LockfileAPI[];
|
|
69
70
|
/**
|
|
70
71
|
* The `api.json` schema version. This will only ever change if we introduce breaking changes to
|
|
71
72
|
* this store.
|
|
72
73
|
*/
|
|
73
74
|
version: '1.0';
|
|
74
|
-
apis: LockfileAPI[];
|
|
75
75
|
}
|
|
76
76
|
export interface LockfileAPI {
|
|
77
77
|
/**
|
|
@@ -82,13 +82,11 @@ export interface LockfileAPI {
|
|
|
82
82
|
*/
|
|
83
83
|
identifier: string;
|
|
84
84
|
/**
|
|
85
|
-
* The
|
|
85
|
+
* The version of `api` that was used to install this SDK.
|
|
86
86
|
*
|
|
87
|
-
* @example
|
|
88
|
-
* @example ./petstore.json
|
|
89
|
-
* @example @developers/v2.0#nysezql0wwo236
|
|
87
|
+
* @example 5.0.0
|
|
90
88
|
*/
|
|
91
|
-
|
|
89
|
+
installerVersion: string;
|
|
92
90
|
/**
|
|
93
91
|
* An integrity hash that will be used to determine on `npx api update` calls if the API has
|
|
94
92
|
* changed since the SDK was last generated.
|
|
@@ -97,9 +95,11 @@ export interface LockfileAPI {
|
|
|
97
95
|
*/
|
|
98
96
|
integrity: string;
|
|
99
97
|
/**
|
|
100
|
-
* The
|
|
98
|
+
* The original source that was used to generate the SDK with.
|
|
101
99
|
*
|
|
102
|
-
* @example
|
|
100
|
+
* @example https://raw.githubusercontent.com/readmeio/oas-examples/main/3.0/json/petstore-simple.json
|
|
101
|
+
* @example ./petstore.json
|
|
102
|
+
* @example @developers/v2.0#nysezql0wwo236
|
|
103
103
|
*/
|
|
104
|
-
|
|
104
|
+
source: string;
|
|
105
105
|
}
|
package/dist/cli/storage.js
CHANGED
|
@@ -43,6 +43,7 @@ var fs_1 = __importDefault(require("fs"));
|
|
|
43
43
|
var path_1 = __importDefault(require("path"));
|
|
44
44
|
var make_dir_1 = __importDefault(require("make-dir"));
|
|
45
45
|
var ssri_1 = __importDefault(require("ssri"));
|
|
46
|
+
var validate_npm_package_name_1 = __importDefault(require("validate-npm-package-name"));
|
|
46
47
|
var fetcher_1 = __importDefault(require("../fetcher"));
|
|
47
48
|
var packageInfo_1 = require("../packageInfo");
|
|
48
49
|
var Storage = /** @class */ (function () {
|
|
@@ -128,6 +129,19 @@ var Storage = /** @class */ (function () {
|
|
|
128
129
|
}
|
|
129
130
|
return Storage.lockfile;
|
|
130
131
|
};
|
|
132
|
+
Storage.isIdentifierValid = function (identifier, prefixWithAPINamespace) {
|
|
133
|
+
// Is this identifier already in storage?
|
|
134
|
+
if (Storage.isInLockFile({ identifier: identifier })) {
|
|
135
|
+
throw new Error("\"".concat(identifier, "\" is already taken in your `.api/` directory. Please try another identifier."));
|
|
136
|
+
}
|
|
137
|
+
var isValidForNPM = (0, validate_npm_package_name_1["default"])(prefixWithAPINamespace ? "@api/".concat(identifier) : identifier);
|
|
138
|
+
if (!isValidForNPM.validForNewPackages) {
|
|
139
|
+
// `prompts` doesn't support surfacing multiple errors in a `validate` call so we can only
|
|
140
|
+
// surface the first to the user.
|
|
141
|
+
throw new Error("Identifier cannot be used for an NPM package: ".concat(isValidForNPM.errors[0]));
|
|
142
|
+
}
|
|
143
|
+
return true;
|
|
144
|
+
};
|
|
131
145
|
Storage.isInLockFile = function (search) {
|
|
132
146
|
// Because this method may run before we initialize a new storage object we should make sure
|
|
133
147
|
// that we have a storage directory present.
|
|
@@ -221,7 +235,6 @@ var Storage = /** @class */ (function () {
|
|
|
221
235
|
* ├── openapi.json
|
|
222
236
|
* └── package.json
|
|
223
237
|
*
|
|
224
|
-
* @param spec
|
|
225
238
|
*/
|
|
226
239
|
Storage.prototype.save = function (spec) {
|
|
227
240
|
if (!this.identifier) {
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
declare class FetchError extends Error {
|
|
1
|
+
declare class FetchError<Status = number, Data = unknown> extends Error {
|
|
2
2
|
/** HTTP Status */
|
|
3
|
-
status:
|
|
3
|
+
status: Status;
|
|
4
4
|
/** The content of the response. */
|
|
5
|
-
data:
|
|
5
|
+
data: Data;
|
|
6
6
|
/** The Headers of the response. */
|
|
7
7
|
headers: Headers;
|
|
8
8
|
/** The raw `Response` object. */
|
|
9
9
|
res: Response;
|
|
10
|
-
constructor(status:
|
|
10
|
+
constructor(status: Status, data: Data, headers: Headers, res: Response);
|
|
11
11
|
}
|
|
12
12
|
export default FetchError;
|
|
@@ -8,7 +8,6 @@ import type { SchemaWrapper } from 'oas/dist/operation/get-parameters-as-json-sc
|
|
|
8
8
|
*
|
|
9
9
|
* @todo This is a good candidate to be moved into a core `oas` library method.
|
|
10
10
|
* @see {@link https://github.com/mdornseif/json-schema-default}
|
|
11
|
-
* @param jsonSchemas
|
|
12
11
|
*/
|
|
13
12
|
export default function getJSONSchemaDefaults(jsonSchemas: SchemaWrapper[]): {
|
|
14
13
|
[x: string]: Record<string, unknown>;
|
|
@@ -13,7 +13,6 @@ var json_schema_traverse_1 = __importDefault(require("json-schema-traverse"));
|
|
|
13
13
|
*
|
|
14
14
|
* @todo This is a good candidate to be moved into a core `oas` library method.
|
|
15
15
|
* @see {@link https://github.com/mdornseif/json-schema-default}
|
|
16
|
-
* @param jsonSchemas
|
|
17
16
|
*/
|
|
18
17
|
function getJSONSchemaDefaults(jsonSchemas) {
|
|
19
18
|
return jsonSchemas
|
package/dist/core/index.d.ts
CHANGED
|
@@ -16,9 +16,9 @@ export interface ConfigOptions {
|
|
|
16
16
|
}
|
|
17
17
|
export interface FetchResponse<status, data> {
|
|
18
18
|
data: data;
|
|
19
|
-
status: status;
|
|
20
19
|
headers: Headers;
|
|
21
20
|
res: Response;
|
|
21
|
+
status: status;
|
|
22
22
|
}
|
|
23
23
|
type Enumerate<N extends number, Acc extends number[] = []> = Acc['length'] extends N ? Acc[number] : Enumerate<N, [...Acc, Acc['length']]>;
|
|
24
24
|
export type HTTPMethodRange<F extends number, T extends number> = Exclude<Enumerate<T>, Enumerate<F>>;
|
|
@@ -3,9 +3,6 @@ import type Oas from 'oas';
|
|
|
3
3
|
* With an SDK server config and an instance of OAS we should extract and prepare the server and
|
|
4
4
|
* any server variables to be supplied to `@readme/oas-to-har`.
|
|
5
5
|
*
|
|
6
|
-
* @param spec
|
|
7
|
-
* @param url
|
|
8
|
-
* @param variables
|
|
9
6
|
*/
|
|
10
7
|
export default function prepareServer(spec: Oas, url: string, variables?: Record<string, string | number>): false | {
|
|
11
8
|
selected: number;
|
|
@@ -10,9 +10,6 @@ function stripTrailingSlash(url) {
|
|
|
10
10
|
* With an SDK server config and an instance of OAS we should extract and prepare the server and
|
|
11
11
|
* any server variables to be supplied to `@readme/oas-to-har`.
|
|
12
12
|
*
|
|
13
|
-
* @param spec
|
|
14
|
-
* @param url
|
|
15
|
-
* @param variables
|
|
16
13
|
*/
|
|
17
14
|
function prepareServer(spec, url, variables) {
|
|
18
15
|
if (variables === void 0) { variables = {}; }
|
package/dist/index.js
CHANGED
|
@@ -70,7 +70,6 @@ var Sdk = /** @class */ (function () {
|
|
|
70
70
|
* Create dynamic accessors for every operation with a defined operation ID. If an operation
|
|
71
71
|
* does not have an operation ID it can be accessed by its `.method('/path')` accessor instead.
|
|
72
72
|
*
|
|
73
|
-
* @param spec
|
|
74
73
|
*/
|
|
75
74
|
function loadOperations(spec) {
|
|
76
75
|
return Object.entries(spec.getPaths())
|
package/dist/packageInfo.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export declare const PACKAGE_NAME = "api";
|
|
2
|
-
export declare const PACKAGE_VERSION = "
|
|
2
|
+
export declare const PACKAGE_VERSION = "6.1.0";
|
package/dist/packageInfo.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "api",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "6.1.0",
|
|
4
4
|
"description": "Magical SDK generation from an OpenAPI definition 🪄",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
"debug:bin": "node -r ts-node/register src/bin.ts",
|
|
13
13
|
"prebuild": "rm -rf dist/; npm run version",
|
|
14
14
|
"prepack": "npm run build",
|
|
15
|
-
"test": "
|
|
16
|
-
"test:smoke": "npx
|
|
15
|
+
"test": "jest --coverage $(find test -name '*.test.ts' -not -path '*/smoketest.test.ts')",
|
|
16
|
+
"test:smoke": "npx jest test/cli/codegen/languages/typescript/smoketest.test.ts",
|
|
17
17
|
"version": "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"
|
|
18
18
|
},
|
|
19
19
|
"repository": {
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"author": "Jon Ursenbach <jon@readme.io>",
|
|
29
29
|
"license": "MIT",
|
|
30
30
|
"engines": {
|
|
31
|
-
"node": ">=
|
|
31
|
+
"node": ">=16"
|
|
32
32
|
},
|
|
33
33
|
"keywords": [
|
|
34
34
|
"api",
|
|
@@ -74,39 +74,28 @@
|
|
|
74
74
|
"devDependencies": {
|
|
75
75
|
"@readme/oas-examples": "^5.9.0",
|
|
76
76
|
"@types/caseless": "^0.12.2",
|
|
77
|
-
"@types/chai": "^4.3.4",
|
|
78
77
|
"@types/find-cache-dir": "^3.2.1",
|
|
78
|
+
"@types/jest": "^29.5.2",
|
|
79
79
|
"@types/js-yaml": "^4.0.5",
|
|
80
80
|
"@types/lodash.camelcase": "^4.3.7",
|
|
81
81
|
"@types/lodash.deburr": "^4.1.7",
|
|
82
82
|
"@types/lodash.merge": "^4.6.7",
|
|
83
83
|
"@types/lodash.setwith": "^4.3.7",
|
|
84
84
|
"@types/lodash.startcase": "^4.4.7",
|
|
85
|
-
"@types/mocha": "^10.0.1",
|
|
86
85
|
"@types/prettier": "^2.7.2",
|
|
87
86
|
"@types/prompts": "^2.4.2",
|
|
88
87
|
"@types/semver": "^7.3.13",
|
|
89
|
-
"@types/sinon-chai": "^3.2.9",
|
|
90
88
|
"@types/ssri": "^7.1.1",
|
|
91
89
|
"@types/validate-npm-package-name": "^4.0.0",
|
|
92
|
-
"chai": "^4.3.7",
|
|
93
90
|
"fetch-mock": "^9.11.0",
|
|
94
|
-
"
|
|
95
|
-
"
|
|
96
|
-
"nyc": "^15.1.0",
|
|
91
|
+
"jest": "^29.6.1",
|
|
92
|
+
"jest-extended": "^4.0.0",
|
|
97
93
|
"oas-normalize": "^8.3.2",
|
|
98
|
-
"
|
|
99
|
-
"sinon-chai": "^3.7.0",
|
|
94
|
+
"ts-jest": "^29.1.1",
|
|
100
95
|
"type-fest": "^3.5.4",
|
|
101
96
|
"typescript": "^4.9.5",
|
|
102
97
|
"unique-temp-dir": "^1.0.0"
|
|
103
98
|
},
|
|
104
99
|
"prettier": "@readme/eslint-config/prettier",
|
|
105
|
-
"
|
|
106
|
-
"exclude": [
|
|
107
|
-
"dist/",
|
|
108
|
-
"test/"
|
|
109
|
-
]
|
|
110
|
-
},
|
|
111
|
-
"gitHead": "bd15ecccb3aadea9ee821056c41d7897638bbe9d"
|
|
100
|
+
"gitHead": "244093f2ad00e5ea527420f49bdb541bc2095806"
|
|
112
101
|
}
|
package/src/cache.ts
CHANGED
|
@@ -16,8 +16,11 @@ type CacheStore = Record<
|
|
|
16
16
|
string,
|
|
17
17
|
{
|
|
18
18
|
hash: string;
|
|
19
|
-
path?: string; // Deprecated in v4.5.0 in favor of `hash`.
|
|
20
19
|
original: string | OASDocument;
|
|
20
|
+
/**
|
|
21
|
+
* @deprecated Deprecated in v4.5.0 in favor of `hash`.
|
|
22
|
+
*/
|
|
23
|
+
path?: string;
|
|
21
24
|
title?: string;
|
|
22
25
|
version?: string;
|
|
23
26
|
}
|
|
@@ -3,7 +3,13 @@ import type { InstallerOptions } from '../language';
|
|
|
3
3
|
import type Oas from 'oas';
|
|
4
4
|
import type { Operation } from 'oas';
|
|
5
5
|
import type { HttpMethods, SchemaObject } from 'oas/dist/rmoas.types';
|
|
6
|
-
import type {
|
|
6
|
+
import type {
|
|
7
|
+
ClassDeclaration,
|
|
8
|
+
JSDocStructure,
|
|
9
|
+
JSDocTagStructure,
|
|
10
|
+
OptionalKind,
|
|
11
|
+
ParameterDeclarationStructure,
|
|
12
|
+
} from 'ts-morph';
|
|
7
13
|
import type { PackageJson } from 'type-fest';
|
|
8
14
|
|
|
9
15
|
import fs from 'fs';
|
|
@@ -20,16 +26,22 @@ import CodeGeneratorLanguage from '../language';
|
|
|
20
26
|
import { docblockEscape, formatter, generateTypeName, wordWrap } from './typescript/util';
|
|
21
27
|
|
|
22
28
|
export interface TSGeneratorOptions {
|
|
23
|
-
outputJS?: boolean;
|
|
24
29
|
compilerTarget?: 'cjs' | 'esm';
|
|
30
|
+
outputJS?: boolean;
|
|
25
31
|
}
|
|
26
32
|
|
|
27
33
|
interface OperationTypeHousing {
|
|
34
|
+
operation: Operation;
|
|
28
35
|
types: {
|
|
29
36
|
params?: false | Record<'body' | 'formData' | 'metadata', string>;
|
|
30
|
-
responses?: Record<
|
|
37
|
+
responses?: Record<
|
|
38
|
+
string | number,
|
|
39
|
+
{
|
|
40
|
+
description?: string;
|
|
41
|
+
type: string;
|
|
42
|
+
}
|
|
43
|
+
>;
|
|
31
44
|
};
|
|
32
|
-
operation: Operation;
|
|
33
45
|
}
|
|
34
46
|
|
|
35
47
|
export default class TSGenerator extends CodeGeneratorLanguage {
|
|
@@ -60,7 +72,7 @@ export default class TSGenerator extends CodeGeneratorLanguage {
|
|
|
60
72
|
usesHTTPMethodRangeInterface = false;
|
|
61
73
|
|
|
62
74
|
constructor(spec: Oas, specPath: string, identifier: string, opts: TSGeneratorOptions = {}) {
|
|
63
|
-
const options: {
|
|
75
|
+
const options: { compilerTarget: 'cjs' | 'esm'; outputJS: boolean } = {
|
|
64
76
|
outputJS: false,
|
|
65
77
|
compilerTarget: 'cjs',
|
|
66
78
|
...opts,
|
|
@@ -153,12 +165,21 @@ export default class TSGenerator extends CodeGeneratorLanguage {
|
|
|
153
165
|
// This will install the installed SDK as a dependency within the current working directory,
|
|
154
166
|
// adding `@api/<sdk identifier>` as a dependency there so you can load it with
|
|
155
167
|
// `require('@api/<sdk identifier>)`.
|
|
156
|
-
return execa('npm', [...npmInstall, installDir].filter(Boolean))
|
|
157
|
-
|
|
158
|
-
(opts.
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
168
|
+
return execa('npm', [...npmInstall, installDir].filter(Boolean))
|
|
169
|
+
.then(res => {
|
|
170
|
+
if (opts.dryRun) {
|
|
171
|
+
(opts.logger ? opts.logger : logger)(res.command);
|
|
172
|
+
(opts.logger ? opts.logger : logger)(res.stdout);
|
|
173
|
+
}
|
|
174
|
+
})
|
|
175
|
+
.catch(err => {
|
|
176
|
+
if (opts.dryRun) {
|
|
177
|
+
(opts.logger ? opts.logger : logger)(err.message);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
throw err;
|
|
182
|
+
});
|
|
162
183
|
}
|
|
163
184
|
|
|
164
185
|
/**
|
|
@@ -493,6 +514,20 @@ sdk.server('https://eu.api.example.com/v14');`)
|
|
|
493
514
|
return sourceFile;
|
|
494
515
|
}
|
|
495
516
|
|
|
517
|
+
/**
|
|
518
|
+
* Add a new JSDoc `@tag` to an existing docblock.
|
|
519
|
+
*
|
|
520
|
+
*/
|
|
521
|
+
static addTagToDocblock(docblock: OptionalKind<JSDocStructure>, tag: OptionalKind<JSDocTagStructure>) {
|
|
522
|
+
const tags = docblock.tags ?? [];
|
|
523
|
+
tags.push(tag);
|
|
524
|
+
|
|
525
|
+
return {
|
|
526
|
+
...docblock,
|
|
527
|
+
tags,
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
|
|
496
531
|
/**
|
|
497
532
|
* Create operation accessors on the SDK.
|
|
498
533
|
*
|
|
@@ -503,7 +538,7 @@ sdk.server('https://eu.api.example.com/v14');`)
|
|
|
503
538
|
paramTypes?: OperationTypeHousing['types']['params'],
|
|
504
539
|
responseTypes?: OperationTypeHousing['types']['responses']
|
|
505
540
|
) {
|
|
506
|
-
|
|
541
|
+
let docblock: OptionalKind<JSDocStructure> = {};
|
|
507
542
|
const summary = operation.getSummary();
|
|
508
543
|
const description = operation.getDescription();
|
|
509
544
|
if (summary || description) {
|
|
@@ -522,7 +557,10 @@ sdk.server('https://eu.api.example.com/v14');`)
|
|
|
522
557
|
};
|
|
523
558
|
|
|
524
559
|
if (summary && description) {
|
|
525
|
-
docblock
|
|
560
|
+
docblock = TSGenerator.addTagToDocblock(docblock, {
|
|
561
|
+
tagName: 'summary',
|
|
562
|
+
text: docblockEscape(wordWrap(summary)),
|
|
563
|
+
});
|
|
526
564
|
}
|
|
527
565
|
}
|
|
528
566
|
|
|
@@ -559,8 +597,8 @@ sdk.server('https://eu.api.example.com/v14');`)
|
|
|
559
597
|
|
|
560
598
|
let returnType = 'Promise<FetchResponse<number, unknown>>';
|
|
561
599
|
if (responseTypes) {
|
|
562
|
-
|
|
563
|
-
.map(([status, responseType]) => {
|
|
600
|
+
const returnTypes = Object.entries(responseTypes)
|
|
601
|
+
.map(([status, { description: responseDescription, type: responseType }]) => {
|
|
564
602
|
if (status.toLowerCase() === 'default') {
|
|
565
603
|
return `FetchResponse<number, ${responseType}>`;
|
|
566
604
|
} else if (status.length === 3 && status.toUpperCase().endsWith('XX')) {
|
|
@@ -571,19 +609,54 @@ sdk.server('https://eu.api.example.com/v14');`)
|
|
|
571
609
|
return `FetchResponse<number, ${responseType}>`;
|
|
572
610
|
}
|
|
573
611
|
|
|
612
|
+
if (Number(statusPrefix) >= 4) {
|
|
613
|
+
docblock = TSGenerator.addTagToDocblock(docblock, {
|
|
614
|
+
tagName: 'throws',
|
|
615
|
+
text: `FetchError<${status}, ${responseType}>${
|
|
616
|
+
responseDescription ? docblockEscape(wordWrap(` ${responseDescription}`)) : ''
|
|
617
|
+
}`,
|
|
618
|
+
});
|
|
619
|
+
|
|
620
|
+
return false;
|
|
621
|
+
}
|
|
622
|
+
|
|
574
623
|
this.usesHTTPMethodRangeInterface = true;
|
|
575
624
|
return `FetchResponse<HTTPMethodRange<${statusPrefix}00, ${statusPrefix}99>, ${responseType}>`;
|
|
576
625
|
}
|
|
577
626
|
|
|
627
|
+
// 400 and 500 status code families are thrown as exceptions so adding them as a possible
|
|
628
|
+
// return type isn't valid.
|
|
629
|
+
if (Number(status) >= 400) {
|
|
630
|
+
docblock = TSGenerator.addTagToDocblock(docblock, {
|
|
631
|
+
tagName: 'throws',
|
|
632
|
+
text: `FetchError<${status}, ${responseType}>${
|
|
633
|
+
responseDescription ? docblockEscape(wordWrap(` ${responseDescription}`)) : ''
|
|
634
|
+
}`,
|
|
635
|
+
});
|
|
636
|
+
|
|
637
|
+
return false;
|
|
638
|
+
}
|
|
639
|
+
|
|
578
640
|
return `FetchResponse<${status}, ${responseType}>`;
|
|
579
641
|
})
|
|
580
|
-
.
|
|
642
|
+
.filter(Boolean)
|
|
643
|
+
.join(' | ');
|
|
644
|
+
|
|
645
|
+
// If all of our documented responses are for error status codes then all we can document for
|
|
646
|
+
// anything else that might happen is `unknown`.
|
|
647
|
+
returnType = `Promise<${returnTypes.length ? returnTypes : 'FetchResponse<number, unknown>'}>`;
|
|
581
648
|
}
|
|
582
649
|
|
|
650
|
+
const shouldAddAltTypedOverloads = Object.keys(parameters).length === 2 && hasOptionalBody && !hasOptionalMetadata;
|
|
583
651
|
const operationIdAccessor = this.sdk.addMethod({
|
|
584
652
|
name: operationId,
|
|
585
653
|
returnType,
|
|
586
|
-
|
|
654
|
+
|
|
655
|
+
// If we're going to be creating typed method overloads for optional body an metadata handling
|
|
656
|
+
// we should only add a docblock to the first overload we create because IDE Intellisense will
|
|
657
|
+
// always use that and adding a docblock to all three will bloat the SDK with unused and
|
|
658
|
+
// unsurfaced method documentation.
|
|
659
|
+
docs: shouldAddAltTypedOverloads ? null : Object.keys(docblock).length ? [docblock] : null,
|
|
587
660
|
statements: writer => {
|
|
588
661
|
/**
|
|
589
662
|
* @example return this.core.fetch('/pet/findByStatus', 'get', body, metadata);
|
|
@@ -617,10 +690,6 @@ sdk.server('https://eu.api.example.com/v14');`)
|
|
|
617
690
|
// If we have both body and metadata parameters but only body is optional we need to create
|
|
618
691
|
// a couple function overloads as Typescript doesn't let us have an optional method parameter
|
|
619
692
|
// come before one that's required.
|
|
620
|
-
//
|
|
621
|
-
// None of these accessor overloads will receive a docblock because the original will have
|
|
622
|
-
// that covered.
|
|
623
|
-
const shouldAddAltTypedOverloads = Object.keys(parameters).length === 2 && hasOptionalBody && !hasOptionalMetadata;
|
|
624
693
|
if (shouldAddAltTypedOverloads) {
|
|
625
694
|
// Create an overload that has both `body` and `metadata` parameters as required.
|
|
626
695
|
operationIdAccessor.addOverload({
|
|
@@ -636,7 +705,6 @@ sdk.server('https://eu.api.example.com/v14');`)
|
|
|
636
705
|
operationIdAccessor.addOverload({
|
|
637
706
|
parameters: [{ ...parameters.metadata }],
|
|
638
707
|
returnType,
|
|
639
|
-
docs: Object.keys(docblock).length ? [docblock] : null,
|
|
640
708
|
});
|
|
641
709
|
|
|
642
710
|
// Create an overload that has both `body` and `metadata` parameters as optional. Even though
|
|
@@ -798,7 +866,7 @@ sdk.server('https://eu.api.example.com/v14');`)
|
|
|
798
866
|
.reduce((prev, next) => Object.assign(prev, next));
|
|
799
867
|
|
|
800
868
|
const res = Object.entries(schemas)
|
|
801
|
-
.map(([status, { schema }]) => {
|
|
869
|
+
.map(([status, { description, schema }]) => {
|
|
802
870
|
let typeName;
|
|
803
871
|
|
|
804
872
|
if (typeof schema === 'string' && schema.startsWith('::convert::')) {
|
|
@@ -817,7 +885,10 @@ sdk.server('https://eu.api.example.com/v14');`)
|
|
|
817
885
|
return {
|
|
818
886
|
// Types are prefixed with `types.` because that's how we're importing them from
|
|
819
887
|
// `types.d.ts`.
|
|
820
|
-
[status]:
|
|
888
|
+
[status]: {
|
|
889
|
+
type: `types.${typeName}`,
|
|
890
|
+
description,
|
|
891
|
+
},
|
|
821
892
|
};
|
|
822
893
|
})
|
|
823
894
|
.reduce((prev, next) => Object.assign(prev, next), {});
|
|
@@ -4,7 +4,6 @@ import { Command, Option } from 'commander';
|
|
|
4
4
|
import figures from 'figures';
|
|
5
5
|
import Oas from 'oas';
|
|
6
6
|
import ora from 'ora';
|
|
7
|
-
import validateNPMPackageName from 'validate-npm-package-name';
|
|
8
7
|
|
|
9
8
|
import Fetcher from '../../fetcher';
|
|
10
9
|
import codegen from '../codegen';
|
|
@@ -18,6 +17,7 @@ cmd
|
|
|
18
17
|
.name('install')
|
|
19
18
|
.description('install an API SDK into your codebase')
|
|
20
19
|
.argument('<uri>', 'an API to install')
|
|
20
|
+
.option('-i, --identifier <identifier>', 'API identifier (eg. `@api/petstore`)')
|
|
21
21
|
.addOption(
|
|
22
22
|
new Option('-l, --lang <language>', 'SDK language').choices([
|
|
23
23
|
'js', // User generally wants JS, we'll prompt if they want CJS or ESM files.
|
|
@@ -27,7 +27,7 @@ cmd
|
|
|
27
27
|
])
|
|
28
28
|
)
|
|
29
29
|
.addOption(new Option('-y, --yes', 'Automatically answer "yes" to any prompts printed'))
|
|
30
|
-
.action(async (uri: string, options: { lang: string; yes?: boolean }) => {
|
|
30
|
+
.action(async (uri: string, options: { identifier?: string; lang: string; yes?: boolean }) => {
|
|
31
31
|
let language: SupportedLanguages;
|
|
32
32
|
if (options.lang) {
|
|
33
33
|
language = options.lang as SupportedLanguages;
|
|
@@ -69,7 +69,12 @@ cmd
|
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
let identifier;
|
|
72
|
-
if (
|
|
72
|
+
if (options.identifier) {
|
|
73
|
+
// `Storage.isIdentifierValid` will throw an exception if an identifier is invalid.
|
|
74
|
+
if (Storage.isIdentifierValid(options.identifier)) {
|
|
75
|
+
identifier = options.identifier;
|
|
76
|
+
}
|
|
77
|
+
} else if (Fetcher.isAPIRegistryUUID(uri)) {
|
|
73
78
|
identifier = Fetcher.getProjectPrefixFromRegistryUUID(uri);
|
|
74
79
|
} else {
|
|
75
80
|
({ value: identifier } = await promptTerminal({
|
|
@@ -82,19 +87,11 @@ cmd
|
|
|
82
87
|
return false;
|
|
83
88
|
}
|
|
84
89
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
const isValidForNPM = validateNPMPackageName(`@api/${value}`);
|
|
91
|
-
if (!isValidForNPM.validForNewPackages) {
|
|
92
|
-
// `prompts` doesn't support surfacing multiple errors in a `validate` call so we can
|
|
93
|
-
// only surface the first to the user.
|
|
94
|
-
return isValidForNPM.errors[0];
|
|
90
|
+
try {
|
|
91
|
+
return Storage.isIdentifierValid(value, true);
|
|
92
|
+
} catch (err) {
|
|
93
|
+
return err.message;
|
|
95
94
|
}
|
|
96
|
-
|
|
97
|
-
return true;
|
|
98
95
|
},
|
|
99
96
|
}));
|
|
100
97
|
}
|
package/src/cli/storage.ts
CHANGED
|
@@ -5,6 +5,7 @@ import path from 'path';
|
|
|
5
5
|
|
|
6
6
|
import makeDir from 'make-dir';
|
|
7
7
|
import ssri from 'ssri';
|
|
8
|
+
import validateNPMPackageName from 'validate-npm-package-name';
|
|
8
9
|
|
|
9
10
|
import Fetcher from '../fetcher';
|
|
10
11
|
import { PACKAGE_VERSION } from '../packageInfo';
|
|
@@ -105,6 +106,22 @@ export default class Storage {
|
|
|
105
106
|
return Storage.lockfile;
|
|
106
107
|
}
|
|
107
108
|
|
|
109
|
+
static isIdentifierValid(identifier: string, prefixWithAPINamespace?: boolean) {
|
|
110
|
+
// Is this identifier already in storage?
|
|
111
|
+
if (Storage.isInLockFile({ identifier })) {
|
|
112
|
+
throw new Error(`"${identifier}" is already taken in your \`.api/\` directory. Please try another identifier.`);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const isValidForNPM = validateNPMPackageName(prefixWithAPINamespace ? `@api/${identifier}` : identifier);
|
|
116
|
+
if (!isValidForNPM.validForNewPackages) {
|
|
117
|
+
// `prompts` doesn't support surfacing multiple errors in a `validate` call so we can only
|
|
118
|
+
// surface the first to the user.
|
|
119
|
+
throw new Error(`Identifier cannot be used for an NPM package: ${isValidForNPM.errors[0]}`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
|
|
108
125
|
static isInLockFile(search: { identifier?: string; source?: string }) {
|
|
109
126
|
// Because this method may run before we initialize a new storage object we should make sure
|
|
110
127
|
// that we have a storage directory present.
|
|
@@ -207,7 +224,6 @@ export default class Storage {
|
|
|
207
224
|
* ├── openapi.json
|
|
208
225
|
* └── package.json
|
|
209
226
|
*
|
|
210
|
-
* @param spec
|
|
211
227
|
*/
|
|
212
228
|
save(spec: OASDocument) {
|
|
213
229
|
if (!this.identifier) {
|
|
@@ -254,12 +270,13 @@ export default class Storage {
|
|
|
254
270
|
}
|
|
255
271
|
|
|
256
272
|
export interface Lockfile {
|
|
273
|
+
apis: LockfileAPI[];
|
|
274
|
+
|
|
257
275
|
/**
|
|
258
276
|
* The `api.json` schema version. This will only ever change if we introduce breaking changes to
|
|
259
277
|
* this store.
|
|
260
278
|
*/
|
|
261
279
|
version: '1.0';
|
|
262
|
-
apis: LockfileAPI[];
|
|
263
280
|
}
|
|
264
281
|
|
|
265
282
|
export interface LockfileAPI {
|
|
@@ -272,13 +289,11 @@ export interface LockfileAPI {
|
|
|
272
289
|
identifier: string;
|
|
273
290
|
|
|
274
291
|
/**
|
|
275
|
-
* The
|
|
292
|
+
* The version of `api` that was used to install this SDK.
|
|
276
293
|
*
|
|
277
|
-
* @example
|
|
278
|
-
* @example ./petstore.json
|
|
279
|
-
* @example @developers/v2.0#nysezql0wwo236
|
|
294
|
+
* @example 5.0.0
|
|
280
295
|
*/
|
|
281
|
-
|
|
296
|
+
installerVersion: string;
|
|
282
297
|
|
|
283
298
|
/**
|
|
284
299
|
* An integrity hash that will be used to determine on `npx api update` calls if the API has
|
|
@@ -289,9 +304,11 @@ export interface LockfileAPI {
|
|
|
289
304
|
integrity: string;
|
|
290
305
|
|
|
291
306
|
/**
|
|
292
|
-
* The
|
|
307
|
+
* The original source that was used to generate the SDK with.
|
|
293
308
|
*
|
|
294
|
-
* @example
|
|
309
|
+
* @example https://raw.githubusercontent.com/readmeio/oas-examples/main/3.0/json/petstore-simple.json
|
|
310
|
+
* @example ./petstore.json
|
|
311
|
+
* @example @developers/v2.0#nysezql0wwo236
|
|
295
312
|
*/
|
|
296
|
-
|
|
313
|
+
source: string;
|
|
297
314
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
class FetchError extends Error {
|
|
1
|
+
class FetchError<Status = number, Data = unknown> extends Error {
|
|
2
2
|
/** HTTP Status */
|
|
3
|
-
status:
|
|
3
|
+
status: Status;
|
|
4
4
|
|
|
5
5
|
/** The content of the response. */
|
|
6
|
-
data:
|
|
6
|
+
data: Data;
|
|
7
7
|
|
|
8
8
|
/** The Headers of the response. */
|
|
9
9
|
headers: Headers;
|
|
@@ -11,7 +11,7 @@ class FetchError extends Error {
|
|
|
11
11
|
/** The raw `Response` object. */
|
|
12
12
|
res: Response;
|
|
13
13
|
|
|
14
|
-
constructor(status:
|
|
14
|
+
constructor(status: Status, data: Data, headers: Headers, res: Response) {
|
|
15
15
|
super(res.statusText);
|
|
16
16
|
|
|
17
17
|
this.name = 'FetchError';
|
|
@@ -12,7 +12,6 @@ import traverse from 'json-schema-traverse';
|
|
|
12
12
|
*
|
|
13
13
|
* @todo This is a good candidate to be moved into a core `oas` library method.
|
|
14
14
|
* @see {@link https://github.com/mdornseif/json-schema-default}
|
|
15
|
-
* @param jsonSchemas
|
|
16
15
|
*/
|
|
17
16
|
export default function getJSONSchemaDefaults(jsonSchemas: SchemaWrapper[]) {
|
|
18
17
|
return jsonSchemas
|
package/src/core/index.ts
CHANGED
|
@@ -26,9 +26,9 @@ export interface ConfigOptions {
|
|
|
26
26
|
|
|
27
27
|
export interface FetchResponse<status, data> {
|
|
28
28
|
data: data;
|
|
29
|
-
status: status;
|
|
30
29
|
headers: Headers;
|
|
31
30
|
res: Response;
|
|
31
|
+
status: status;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
// https://stackoverflow.com/a/39495173
|
|
@@ -128,7 +128,12 @@ export default class APICore {
|
|
|
128
128
|
const parsed = await parseResponse(res);
|
|
129
129
|
|
|
130
130
|
if (res.status >= 400 && res.status <= 599) {
|
|
131
|
-
throw new FetchError
|
|
131
|
+
throw new FetchError<typeof parsed.status, typeof parsed.data>(
|
|
132
|
+
parsed.status,
|
|
133
|
+
parsed.data,
|
|
134
|
+
parsed.headers,
|
|
135
|
+
parsed.res
|
|
136
|
+
);
|
|
132
137
|
}
|
|
133
138
|
|
|
134
139
|
return parsed;
|
package/src/core/prepareAuth.ts
CHANGED
|
@@ -76,7 +76,7 @@ function merge(src: any, target: any) {
|
|
|
76
76
|
function processFile(
|
|
77
77
|
paramName: string,
|
|
78
78
|
file: string | ReadStream
|
|
79
|
-
): Promise<{
|
|
79
|
+
): Promise<{ base64: string; buffer: Buffer; filename: string; paramName: string }> {
|
|
80
80
|
if (typeof file === 'string') {
|
|
81
81
|
// In order to support relative pathed files, we need to attempt to resolve them.
|
|
82
82
|
const resolvedFile = path.resolve(file);
|
|
@@ -12,9 +12,6 @@ function stripTrailingSlash(url: string) {
|
|
|
12
12
|
* With an SDK server config and an instance of OAS we should extract and prepare the server and
|
|
13
13
|
* any server variables to be supplied to `@readme/oas-to-har`.
|
|
14
14
|
*
|
|
15
|
-
* @param spec
|
|
16
|
-
* @param url
|
|
17
|
-
* @param variables
|
|
18
15
|
*/
|
|
19
16
|
export default function prepareServer(spec: Oas, url: string, variables: Record<string, string | number> = {}) {
|
|
20
17
|
let serverIdx;
|
package/src/index.ts
CHANGED
|
@@ -41,7 +41,6 @@ class Sdk {
|
|
|
41
41
|
* Create dynamic accessors for every operation with a defined operation ID. If an operation
|
|
42
42
|
* does not have an operation ID it can be accessed by its `.method('/path')` accessor instead.
|
|
43
43
|
*
|
|
44
|
-
* @param spec
|
|
45
44
|
*/
|
|
46
45
|
function loadOperations(spec: Oas) {
|
|
47
46
|
return Object.entries(spec.getPaths())
|
package/src/packageInfo.ts
CHANGED