api 6.0.0 → 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 +49 -13
- 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 +8 -19
- package/src/cache.ts +4 -1
- package/src/cli/codegen/languages/typescript.ts +80 -18
- 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.
|
|
@@ -471,6 +471,16 @@ var TSGenerator = /** @class */ (function (_super) {
|
|
|
471
471
|
});
|
|
472
472
|
return sourceFile;
|
|
473
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
|
+
};
|
|
474
484
|
/**
|
|
475
485
|
* Create operation accessors on the SDK.
|
|
476
486
|
*
|
|
@@ -495,7 +505,10 @@ var TSGenerator = /** @class */ (function (_super) {
|
|
|
495
505
|
return writer;
|
|
496
506
|
};
|
|
497
507
|
if (summary && description) {
|
|
498
|
-
docblock
|
|
508
|
+
docblock = TSGenerator.addTagToDocblock(docblock, {
|
|
509
|
+
tagName: 'summary',
|
|
510
|
+
text: (0, util_1.docblockEscape)((0, util_1.wordWrap)(summary))
|
|
511
|
+
});
|
|
499
512
|
}
|
|
500
513
|
}
|
|
501
514
|
var hasOptionalBody = false;
|
|
@@ -523,9 +536,9 @@ var TSGenerator = /** @class */ (function (_super) {
|
|
|
523
536
|
}
|
|
524
537
|
var returnType = 'Promise<FetchResponse<number, unknown>>';
|
|
525
538
|
if (responseTypes) {
|
|
526
|
-
|
|
539
|
+
var returnTypes = Object.entries(responseTypes)
|
|
527
540
|
.map(function (_a) {
|
|
528
|
-
var status = _a[0],
|
|
541
|
+
var status = _a[0], _b = _a[1], responseDescription = _b.description, responseType = _b.type;
|
|
529
542
|
if (status.toLowerCase() === 'default') {
|
|
530
543
|
return "FetchResponse<number, ".concat(responseType, ">");
|
|
531
544
|
}
|
|
@@ -536,17 +549,42 @@ var TSGenerator = /** @class */ (function (_super) {
|
|
|
536
549
|
// it and should instead fall back to treating it as an unknown number.
|
|
537
550
|
return "FetchResponse<number, ".concat(responseType, ">");
|
|
538
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
|
+
}
|
|
539
559
|
_this.usesHTTPMethodRangeInterface = true;
|
|
540
560
|
return "FetchResponse<HTTPMethodRange<".concat(statusPrefix, "00, ").concat(statusPrefix, "99>, ").concat(responseType, ">");
|
|
541
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
|
+
}
|
|
542
571
|
return "FetchResponse<".concat(status, ", ").concat(responseType, ">");
|
|
543
572
|
})
|
|
544
|
-
.
|
|
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>', ">");
|
|
545
578
|
}
|
|
579
|
+
var shouldAddAltTypedOverloads = Object.keys(parameters).length === 2 && hasOptionalBody && !hasOptionalMetadata;
|
|
546
580
|
var operationIdAccessor = this.sdk.addMethod({
|
|
547
581
|
name: operationId,
|
|
548
582
|
returnType: returnType,
|
|
549
|
-
|
|
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,
|
|
550
588
|
statements: function (writer) {
|
|
551
589
|
/**
|
|
552
590
|
* @example return this.core.fetch('/pet/findByStatus', 'get', body, metadata);
|
|
@@ -576,10 +614,6 @@ var TSGenerator = /** @class */ (function (_super) {
|
|
|
576
614
|
// If we have both body and metadata parameters but only body is optional we need to create
|
|
577
615
|
// a couple function overloads as Typescript doesn't let us have an optional method parameter
|
|
578
616
|
// come before one that's required.
|
|
579
|
-
//
|
|
580
|
-
// None of these accessor overloads will receive a docblock because the original will have
|
|
581
|
-
// that covered.
|
|
582
|
-
var shouldAddAltTypedOverloads = Object.keys(parameters).length === 2 && hasOptionalBody && !hasOptionalMetadata;
|
|
583
617
|
if (shouldAddAltTypedOverloads) {
|
|
584
618
|
// Create an overload that has both `body` and `metadata` parameters as required.
|
|
585
619
|
operationIdAccessor.addOverload({
|
|
@@ -593,8 +627,7 @@ var TSGenerator = /** @class */ (function (_super) {
|
|
|
593
627
|
// Create an overload that just has a single `metadata` parameter.
|
|
594
628
|
operationIdAccessor.addOverload({
|
|
595
629
|
parameters: [__assign({}, parameters.metadata)],
|
|
596
|
-
returnType: returnType
|
|
597
|
-
docs: Object.keys(docblock).length ? [docblock] : null
|
|
630
|
+
returnType: returnType
|
|
598
631
|
});
|
|
599
632
|
// Create an overload that has both `body` and `metadata` parameters as optional. Even though
|
|
600
633
|
// our `metadata` parameter is actually required for this operation this is the only way we're
|
|
@@ -746,7 +779,7 @@ var TSGenerator = /** @class */ (function (_super) {
|
|
|
746
779
|
var res = Object.entries(schemas)
|
|
747
780
|
.map(function (_a) {
|
|
748
781
|
var _b;
|
|
749
|
-
var status = _a[0],
|
|
782
|
+
var status = _a[0], _c = _a[1], description = _c.description, schema = _c.schema;
|
|
750
783
|
var typeName;
|
|
751
784
|
if (typeof schema === 'string' && schema.startsWith('::convert::')) {
|
|
752
785
|
// If this schema is a string and has our conversion prefix then we've already created
|
|
@@ -763,7 +796,10 @@ var TSGenerator = /** @class */ (function (_super) {
|
|
|
763
796
|
return _b = {},
|
|
764
797
|
// Types are prefixed with `types.` because that's how we're importing them from
|
|
765
798
|
// `types.d.ts`.
|
|
766
|
-
_b[status] =
|
|
799
|
+
_b[status] = {
|
|
800
|
+
type: "types.".concat(typeName),
|
|
801
|
+
description: description
|
|
802
|
+
},
|
|
767
803
|
_b;
|
|
768
804
|
})
|
|
769
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 = "6.
|
|
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": "6.
|
|
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": {
|
|
@@ -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": "9759db4c067baf3928ad3a9d76b4965658955d78"
|
|
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,
|
|
@@ -502,6 +514,20 @@ sdk.server('https://eu.api.example.com/v14');`)
|
|
|
502
514
|
return sourceFile;
|
|
503
515
|
}
|
|
504
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
|
+
|
|
505
531
|
/**
|
|
506
532
|
* Create operation accessors on the SDK.
|
|
507
533
|
*
|
|
@@ -512,7 +538,7 @@ sdk.server('https://eu.api.example.com/v14');`)
|
|
|
512
538
|
paramTypes?: OperationTypeHousing['types']['params'],
|
|
513
539
|
responseTypes?: OperationTypeHousing['types']['responses']
|
|
514
540
|
) {
|
|
515
|
-
|
|
541
|
+
let docblock: OptionalKind<JSDocStructure> = {};
|
|
516
542
|
const summary = operation.getSummary();
|
|
517
543
|
const description = operation.getDescription();
|
|
518
544
|
if (summary || description) {
|
|
@@ -531,7 +557,10 @@ sdk.server('https://eu.api.example.com/v14');`)
|
|
|
531
557
|
};
|
|
532
558
|
|
|
533
559
|
if (summary && description) {
|
|
534
|
-
docblock
|
|
560
|
+
docblock = TSGenerator.addTagToDocblock(docblock, {
|
|
561
|
+
tagName: 'summary',
|
|
562
|
+
text: docblockEscape(wordWrap(summary)),
|
|
563
|
+
});
|
|
535
564
|
}
|
|
536
565
|
}
|
|
537
566
|
|
|
@@ -568,8 +597,8 @@ sdk.server('https://eu.api.example.com/v14');`)
|
|
|
568
597
|
|
|
569
598
|
let returnType = 'Promise<FetchResponse<number, unknown>>';
|
|
570
599
|
if (responseTypes) {
|
|
571
|
-
|
|
572
|
-
.map(([status, responseType]) => {
|
|
600
|
+
const returnTypes = Object.entries(responseTypes)
|
|
601
|
+
.map(([status, { description: responseDescription, type: responseType }]) => {
|
|
573
602
|
if (status.toLowerCase() === 'default') {
|
|
574
603
|
return `FetchResponse<number, ${responseType}>`;
|
|
575
604
|
} else if (status.length === 3 && status.toUpperCase().endsWith('XX')) {
|
|
@@ -580,19 +609,54 @@ sdk.server('https://eu.api.example.com/v14');`)
|
|
|
580
609
|
return `FetchResponse<number, ${responseType}>`;
|
|
581
610
|
}
|
|
582
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
|
+
|
|
583
623
|
this.usesHTTPMethodRangeInterface = true;
|
|
584
624
|
return `FetchResponse<HTTPMethodRange<${statusPrefix}00, ${statusPrefix}99>, ${responseType}>`;
|
|
585
625
|
}
|
|
586
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
|
+
|
|
587
640
|
return `FetchResponse<${status}, ${responseType}>`;
|
|
588
641
|
})
|
|
589
|
-
.
|
|
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>'}>`;
|
|
590
648
|
}
|
|
591
649
|
|
|
650
|
+
const shouldAddAltTypedOverloads = Object.keys(parameters).length === 2 && hasOptionalBody && !hasOptionalMetadata;
|
|
592
651
|
const operationIdAccessor = this.sdk.addMethod({
|
|
593
652
|
name: operationId,
|
|
594
653
|
returnType,
|
|
595
|
-
|
|
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,
|
|
596
660
|
statements: writer => {
|
|
597
661
|
/**
|
|
598
662
|
* @example return this.core.fetch('/pet/findByStatus', 'get', body, metadata);
|
|
@@ -626,10 +690,6 @@ sdk.server('https://eu.api.example.com/v14');`)
|
|
|
626
690
|
// If we have both body and metadata parameters but only body is optional we need to create
|
|
627
691
|
// a couple function overloads as Typescript doesn't let us have an optional method parameter
|
|
628
692
|
// come before one that's required.
|
|
629
|
-
//
|
|
630
|
-
// None of these accessor overloads will receive a docblock because the original will have
|
|
631
|
-
// that covered.
|
|
632
|
-
const shouldAddAltTypedOverloads = Object.keys(parameters).length === 2 && hasOptionalBody && !hasOptionalMetadata;
|
|
633
693
|
if (shouldAddAltTypedOverloads) {
|
|
634
694
|
// Create an overload that has both `body` and `metadata` parameters as required.
|
|
635
695
|
operationIdAccessor.addOverload({
|
|
@@ -645,7 +705,6 @@ sdk.server('https://eu.api.example.com/v14');`)
|
|
|
645
705
|
operationIdAccessor.addOverload({
|
|
646
706
|
parameters: [{ ...parameters.metadata }],
|
|
647
707
|
returnType,
|
|
648
|
-
docs: Object.keys(docblock).length ? [docblock] : null,
|
|
649
708
|
});
|
|
650
709
|
|
|
651
710
|
// Create an overload that has both `body` and `metadata` parameters as optional. Even though
|
|
@@ -807,7 +866,7 @@ sdk.server('https://eu.api.example.com/v14');`)
|
|
|
807
866
|
.reduce((prev, next) => Object.assign(prev, next));
|
|
808
867
|
|
|
809
868
|
const res = Object.entries(schemas)
|
|
810
|
-
.map(([status, { schema }]) => {
|
|
869
|
+
.map(([status, { description, schema }]) => {
|
|
811
870
|
let typeName;
|
|
812
871
|
|
|
813
872
|
if (typeof schema === 'string' && schema.startsWith('::convert::')) {
|
|
@@ -826,7 +885,10 @@ sdk.server('https://eu.api.example.com/v14');`)
|
|
|
826
885
|
return {
|
|
827
886
|
// Types are prefixed with `types.` because that's how we're importing them from
|
|
828
887
|
// `types.d.ts`.
|
|
829
|
-
[status]:
|
|
888
|
+
[status]: {
|
|
889
|
+
type: `types.${typeName}`,
|
|
890
|
+
description,
|
|
891
|
+
},
|
|
830
892
|
};
|
|
831
893
|
})
|
|
832
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