api 7.0.0-beta.0 → 7.0.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/dist/codegen/codegenerator.d.ts +9 -11
  2. package/dist/codegen/codegenerator.d.ts.map +1 -1
  3. package/dist/codegen/codegenerator.js +11 -0
  4. package/dist/codegen/codegenerator.js.map +1 -1
  5. package/dist/codegen/factory.d.ts +19 -3
  6. package/dist/codegen/factory.d.ts.map +1 -1
  7. package/dist/codegen/factory.js +12 -5
  8. package/dist/codegen/factory.js.map +1 -1
  9. package/dist/codegen/languages/typescript/index.d.ts +8 -2
  10. package/dist/codegen/languages/typescript/index.d.ts.map +1 -1
  11. package/dist/codegen/languages/typescript/index.js +63 -17
  12. package/dist/codegen/languages/typescript/index.js.map +1 -1
  13. package/dist/commands/index.d.ts +2 -0
  14. package/dist/commands/index.d.ts.map +1 -1
  15. package/dist/commands/index.js +4 -0
  16. package/dist/commands/index.js.map +1 -1
  17. package/dist/commands/install.d.ts.map +1 -1
  18. package/dist/commands/install.js +5 -5
  19. package/dist/commands/install.js.map +1 -1
  20. package/dist/commands/list.d.ts +4 -0
  21. package/dist/commands/list.d.ts.map +1 -0
  22. package/dist/commands/list.js +37 -0
  23. package/dist/commands/list.js.map +1 -0
  24. package/dist/commands/uninstall.d.ts +4 -0
  25. package/dist/commands/uninstall.d.ts.map +1 -0
  26. package/dist/commands/uninstall.js +72 -0
  27. package/dist/commands/uninstall.js.map +1 -0
  28. package/dist/lockfileSchema.d.ts +125 -0
  29. package/dist/lockfileSchema.d.ts.map +1 -0
  30. package/dist/lockfileSchema.js +78 -0
  31. package/dist/lockfileSchema.js.map +1 -0
  32. package/dist/packageInfo.d.ts +1 -1
  33. package/dist/packageInfo.js +1 -1
  34. package/dist/storage.d.ts +75 -60
  35. package/dist/storage.d.ts.map +1 -1
  36. package/dist/storage.js +85 -25
  37. package/dist/storage.js.map +1 -1
  38. package/package.json +14 -7
  39. package/schema.json +69 -0
  40. package/src/bin.ts +0 -21
  41. package/src/codegen/codegenerator.ts +0 -75
  42. package/src/codegen/factory.ts +0 -23
  43. package/src/codegen/languages/typescript/index.ts +0 -984
  44. package/src/codegen/languages/typescript/util.ts +0 -174
  45. package/src/commands/index.ts +0 -5
  46. package/src/commands/install.ts +0 -196
  47. package/src/fetcher.ts +0 -145
  48. package/src/lib/prompt.ts +0 -29
  49. package/src/logger.ts +0 -10
  50. package/src/packageInfo.ts +0 -3
  51. package/src/storage.ts +0 -333
  52. package/tsconfig.json +0 -10
@@ -1,174 +0,0 @@
1
- import camelCase from 'lodash.camelcase';
2
- import deburr from 'lodash.deburr';
3
- import startCase from 'lodash.startcase';
4
-
5
- /**
6
- * This is a mix of reserved JS words and keywords in TypeScript that might be reserved or
7
- * allowable but functionally confusing (like `let any = 'buster';`)
8
- *
9
- * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar}
10
- */
11
- const RESERVED_WORDS = [
12
- 'abstract',
13
- 'any',
14
- 'arguments',
15
- 'as',
16
- 'async',
17
- 'await',
18
- 'boolean',
19
- 'break',
20
- 'byte',
21
- 'case',
22
- 'catch',
23
- 'char',
24
- 'class',
25
- 'const',
26
- 'continue',
27
- 'constructor',
28
- 'debugger',
29
- 'default',
30
- 'delete',
31
- 'do',
32
- 'double',
33
- 'else',
34
- 'enum',
35
- 'eval',
36
- 'export',
37
- 'extends',
38
- 'false',
39
- 'final',
40
- 'finally',
41
- 'float',
42
- 'for',
43
- 'from',
44
- 'function',
45
- 'get',
46
- 'goto',
47
- 'if',
48
- 'implements',
49
- 'import',
50
- 'interface',
51
- 'in',
52
- 'instanceof',
53
- 'int',
54
- 'let',
55
- 'long',
56
- 'native',
57
- 'new',
58
- 'null',
59
- 'number',
60
- 'of',
61
- 'package',
62
- 'private',
63
- 'protected',
64
- 'public',
65
- 'module',
66
- 'namespace',
67
- 'return',
68
- 'set',
69
- 'short',
70
- 'static',
71
- 'string',
72
- 'super',
73
- 'switch',
74
- 'synchronized',
75
- 'this',
76
- 'throw',
77
- 'throws',
78
- 'transient',
79
- 'true',
80
- 'try',
81
- 'type',
82
- 'typeof',
83
- 'var',
84
- 'void',
85
- 'while',
86
- 'with',
87
- 'volatile',
88
- 'yield',
89
-
90
- // These aren't reserved keywords but because we maybe codegen'ing an SDK to be used in the
91
- // browser it'd be very bad if we overwrote these. This obviously doesn't account for browser APIs
92
- // you can access outside of the `Window` API (like `alert()`), but we can add checks for those
93
- // later if we need to.
94
- 'frames',
95
- 'global',
96
- 'globalThis',
97
- 'navigator',
98
- 'self',
99
- 'window',
100
- ];
101
-
102
- /**
103
- * @see {@link https://www.30secondsofcode.org/js/s/word-wrap}
104
- */
105
- export function wordWrap(str: string, max = 88) {
106
- return str.replace(new RegExp(`(?![^\\n]{1,${max}}$)([^\\n]{1,${max}})\\s`, 'g'), '$1\n');
107
- }
108
-
109
- /**
110
- * Safely escape some string characters that may break a docblock.
111
- *
112
- */
113
- export function docblockEscape(str: string) {
114
- return str.replace(/\/\*/g, '/\\*').replace(/\*\//g, '*\\/');
115
- }
116
-
117
- /**
118
- * Convert a string that might contain spaces or special characters to one that can safely be used
119
- * as a TypeScript interface or enum name.
120
- *
121
- * This function has been adapted and slighty modified from `json-schema-to-typescript`.
122
- *
123
- * @license MIT
124
- * @see {@link https://github.com/bcherny/json-schema-to-typescript}
125
- */
126
- function toSafeString(str: string) {
127
- // identifiers in javaScript/ts:
128
- // First character: a-zA-Z | _ | $
129
- // Rest: a-zA-Z | _ | $ | 0-9
130
-
131
- // remove accents, umlauts, ... by their basic latin letters
132
- return (
133
- deburr(str)
134
- // if the string starts with a number, prefix it with character that typescript can accept
135
- // https://github.com/bcherny/json-schema-to-typescript/issues/489
136
- .replace(/^(\d){1}/, '$$1')
137
- // replace chars which are not valid for typescript identifiers with whitespace
138
- .replace(/(^\s*[^a-zA-Z_$])|([^a-zA-Z_$\d])/g, ' ')
139
- // uppercase leading underscores followed by lowercase
140
- .replace(/^_[a-z]/g, (match: string) => match.toUpperCase())
141
- // remove non-leading underscores followed by lowercase (convert snake_case)
142
- .replace(/_[a-z]/g, (match: string) => match.substr(1, match.length).toUpperCase())
143
- // uppercase letters after digits, dollars
144
- .replace(/([\d$]+[a-zA-Z])/g, (match: string) => match.toUpperCase())
145
- // uppercase first letter after whitespace
146
- .replace(/\s+([a-zA-Z])/g, (match: string) => match.toUpperCase().trim())
147
- // remove remaining whitespace
148
- .replace(/\s/g, '')
149
- );
150
- }
151
-
152
- export function generateTypeName(...parts: string[]) {
153
- let str;
154
-
155
- // If the end of our string ends with something like `2XX`, the combination of `startCase` and
156
- // `camelCase` will transform it into `2Xx`.
157
- if (parts.length > 1) {
158
- const last = parts[parts.length - 1];
159
- if (last.match(/^(\d)XX$/)) {
160
- str = startCase(camelCase(parts.slice(0, -1).join(' ')));
161
- str += ` ${last}`;
162
- }
163
- }
164
-
165
- if (!str) {
166
- str = startCase(camelCase(parts.join(' ')));
167
- }
168
-
169
- if (RESERVED_WORDS.includes(str.toLowerCase())) {
170
- str = `$${str}`;
171
- }
172
-
173
- return toSafeString(str);
174
- }
@@ -1,5 +0,0 @@
1
- import installCommand from './install.js';
2
-
3
- export default {
4
- install: installCommand,
5
- };
@@ -1,196 +0,0 @@
1
- import { Command, Option } from 'commander';
2
- import figures from 'figures';
3
- import Oas from 'oas';
4
- import ora from 'ora';
5
- import uslug from 'uslug';
6
-
7
- import codegenFactory, { SupportedLanguages } from '../codegen/factory.js';
8
- import Fetcher from '../fetcher.js';
9
- import promptTerminal from '../lib/prompt.js';
10
- import logger from '../logger.js';
11
- import Storage from '../storage.js';
12
-
13
- // @todo log logs to `.api/.logs` and have `.logs` ignored
14
- const cmd = new Command();
15
- cmd
16
- .name('install')
17
- .description('install an API SDK into your codebase')
18
- .argument('<uri>', 'an API to install')
19
- .option('-i, --identifier <identifier>', 'API identifier (eg. `@api/petstore`)')
20
- .addOption(
21
- new Option('-l, --lang <language>', 'SDK language').default(SupportedLanguages.JS).choices([SupportedLanguages.JS]),
22
- )
23
- .addOption(new Option('-y, --yes', 'Automatically answer "yes" to any prompts printed'))
24
- .action(async (uri: string, options: { identifier?: string; lang: string; yes?: boolean }) => {
25
- let language: SupportedLanguages;
26
- if (options.lang) {
27
- language = options.lang as SupportedLanguages;
28
- } else {
29
- ({ value: language } = await promptTerminal({
30
- type: 'select',
31
- name: 'value',
32
- message: 'What language would you like to generate an SDK for?',
33
- choices: [{ title: 'JavaScript', value: SupportedLanguages.JS }],
34
- initial: 1,
35
- }));
36
- }
37
-
38
- // @todo let them know that we're going to be creating a `.api/ directory
39
- // @todo detect if they have a gitigore and .npmignore and if .api woudl be ignored by that
40
- // @todo don't support swagger files without upconverting them
41
-
42
- if (Storage.isInLockFile({ source: uri })) {
43
- // @todo
44
- // logger(`It looks like you already have this API installed. Would you like to update it?`);
45
- }
46
-
47
- let spinner = ora('Fetching your API definition').start();
48
- const storage = new Storage(uri);
49
-
50
- const oas = await storage
51
- .load(false)
52
- .then(res => {
53
- spinner.succeed(spinner.text);
54
- return res;
55
- })
56
- .then(Oas.init)
57
- .then(async spec => {
58
- await spec.dereference({ preserveRefAsJSONSchemaTitle: true });
59
- return spec;
60
- })
61
- .catch(err => {
62
- // @todo cleanup installed files
63
- spinner.fail(spinner.text);
64
- logger(err.message, true);
65
- process.exit(1);
66
- });
67
-
68
- let identifier;
69
- if (options.identifier) {
70
- // `Storage.isIdentifierValid` will throw an exception if an identifier is invalid.
71
- if (Storage.isIdentifierValid(options.identifier)) {
72
- identifier = options.identifier;
73
- }
74
- } else if (Fetcher.isAPIRegistryUUID(uri)) {
75
- identifier = Fetcher.getProjectPrefixFromRegistryUUID(uri);
76
- } else {
77
- ({ value: identifier } = await promptTerminal({
78
- type: 'text',
79
- name: 'value',
80
- initial: oas.api?.info?.title ? uslug(oas.api.info.title, { lower: true }) : undefined,
81
- message:
82
- 'What would you like to identify this API as? This will be how you use the SDK. (e.g. entering `petstore` would result in `@api/petstore`)',
83
- validate: value => {
84
- if (!value) {
85
- return false;
86
- }
87
-
88
- try {
89
- return Storage.isIdentifierValid(value, true);
90
- } catch (err) {
91
- return err.message;
92
- }
93
- },
94
- }));
95
- }
96
-
97
- if (!identifier) {
98
- logger('You must tell us what you would like to identify this API as in order to install it.', true);
99
- process.exit(1);
100
- }
101
-
102
- // Now that we've got an identifier we can save their spec and generate the directory structure
103
- // for their SDK.
104
- storage.setIdentifier(identifier);
105
- await storage.save(oas.api);
106
-
107
- // @todo look for a prettier config and if we find one ask them if we should use it
108
- spinner = ora('Generating your SDK').start();
109
- const generator = codegenFactory(language, oas, '../openapi.json', identifier);
110
- const sdkSource = await generator
111
- .generate()
112
- .then(res => {
113
- spinner.succeed(spinner.text);
114
- return res;
115
- })
116
- .catch(err => {
117
- // @todo cleanup installed files
118
- spinner.fail(spinner.text);
119
- logger(err.message, true);
120
- process.exit(1);
121
- });
122
-
123
- spinner = ora('Saving your SDK into your codebase').start();
124
- await storage
125
- .saveSourceFiles(sdkSource)
126
- .then(() => {
127
- spinner.succeed(spinner.text);
128
- })
129
- .catch(err => {
130
- // @todo cleanup installed files
131
- spinner.fail(spinner.text);
132
- logger(err.message, true);
133
- process.exit(1);
134
- });
135
-
136
- if (generator.hasRequiredPackages()) {
137
- logger(`${figures.warning} This generator requires some packages to be installed alongside it:`);
138
- Object.entries(generator.requiredPackages).forEach(([pkg, pkgInfo]) => {
139
- let msg = ` ${figures.pointerSmall} ${pkg}: ${pkgInfo.reason}`;
140
- if (pkgInfo.url) {
141
- msg += ` ${pkgInfo.url}`;
142
- }
143
-
144
- logger(msg);
145
- });
146
-
147
- if (!options.yes) {
148
- await promptTerminal({
149
- type: 'confirm',
150
- name: 'value',
151
- message: 'OK to proceed with package installation?',
152
- initial: true,
153
- }).then(({ value }) => {
154
- if (!value) {
155
- // @todo cleanup installed files
156
- logger('Installation cancelled.', true);
157
- process.exit(1);
158
- }
159
- });
160
- }
161
-
162
- spinner = ora('Installing required packages').start();
163
- try {
164
- await generator.install(storage);
165
- spinner.succeed(spinner.text);
166
- } catch (err) {
167
- // @todo cleanup installed files
168
- spinner.fail(spinner.text);
169
- logger(err.message, true);
170
- process.exit(1);
171
- }
172
- }
173
-
174
- spinner = ora('Compiling your SDK').start();
175
- try {
176
- await generator.compile(storage);
177
- spinner.succeed(spinner.text);
178
- } catch (err) {
179
- // @todo cleanup installed files
180
- spinner.fail(spinner.text);
181
- logger(err.message, true);
182
- process.exit(1);
183
- }
184
-
185
- logger('🚀 All done!');
186
- })
187
- .addHelpText(
188
- 'after',
189
- `
190
- Examples:
191
- $ api install @developers/v2.0#nysezql0wwo236
192
- $ api install https://raw.githubusercontent.com/readmeio/oas-examples/main/3.0/json/petstore-simple.json
193
- $ api install ./petstore.json`,
194
- );
195
-
196
- export default cmd;
package/src/fetcher.ts DELETED
@@ -1,145 +0,0 @@
1
- import type { OASDocument } from 'oas/rmoas.types';
2
-
3
- import fs from 'node:fs';
4
- import path from 'node:path';
5
-
6
- import OpenAPIParser from '@readme/openapi-parser';
7
- import yaml from 'js-yaml';
8
-
9
- export default class Fetcher {
10
- uri: string | OASDocument;
11
-
12
- /**
13
- * @note This regex also exists in `httpsnippet-client-api`.
14
- *
15
- * @example @petstore/v1.0#n6kvf10vakpemvplx
16
- * @example @petstore#n6kvf10vakpemvplx
17
- */
18
- static registryUUIDRegex = /^@(?<project>[a-zA-Z0-9-_]+)(\/?(?<version>.+))?#(?<uuid>[a-z0-9]+)$/;
19
-
20
- constructor(uri: string | OASDocument) {
21
- if (typeof uri === 'string') {
22
- if (Fetcher.isAPIRegistryUUID(uri)) {
23
- // Resolve OpenAPI definition shorthand accessors from within the ReadMe API Registry.
24
- this.uri = uri.replace(Fetcher.registryUUIDRegex, 'https://dash.readme.com/api/v1/api-registry/$4');
25
- } else if (Fetcher.isGitHubBlobURL(uri)) {
26
- /**
27
- * People may try to use a public repository URL to the source viewer on GitHub not knowing
28
- * that this page actually serves HTML. In this case we want to rewrite these to the "raw"
29
- * version of this page that'll allow us to access the API definition.
30
- *
31
- * @example https://github.com/readmeio/oas-examples/blob/main/3.1/json/petstore.json
32
- */
33
- this.uri = uri.replace(/\/\/github.com/, '//raw.githubusercontent.com').replace(/\/blob\//, '/');
34
- } else {
35
- this.uri = uri;
36
- }
37
- } else {
38
- this.uri = uri;
39
- }
40
- }
41
-
42
- static isAPIRegistryUUID(uri: string) {
43
- return Fetcher.registryUUIDRegex.test(uri);
44
- }
45
-
46
- static isGitHubBlobURL(uri: string) {
47
- return /\/\/github.com\/[-_a-zA-Z0-9]+\/[-_a-zA-Z0-9]+\/blob\/(.*).(yaml|json|yml)/.test(uri);
48
- }
49
-
50
- /**
51
- * @note This function also exists in `httpsnippet-client-api`.
52
- */
53
- static getProjectPrefixFromRegistryUUID(uri: string) {
54
- const matches = uri.match(Fetcher.registryUUIDRegex);
55
- if (!matches) {
56
- return undefined;
57
- }
58
-
59
- return matches.groups?.project;
60
- }
61
-
62
- async load() {
63
- if (typeof this.uri !== 'string') {
64
- throw new TypeError(
65
- "Something disastrous occurred and a non-string URI was supplied to the Fetcher library. This shouldn't have happened!",
66
- );
67
- }
68
-
69
- return Promise.resolve(this.uri)
70
- .then(uri => {
71
- let url;
72
- try {
73
- url = new URL(uri);
74
- } catch (err) {
75
- // If that try fails for whatever reason than the URI that we have isn't a real URL and
76
- // we can safely attempt to look for it on the filesystem.
77
- return Fetcher.getFile(uri);
78
- }
79
-
80
- return Fetcher.getURL(url.href);
81
- })
82
- .then(res => Fetcher.validate(res))
83
- .then(res => res as OASDocument);
84
- }
85
-
86
- static getURL(url: string) {
87
- // @todo maybe include our user-agent here to identify our request
88
- return fetch(url).then(res => {
89
- if (!res.ok) {
90
- throw new Error(`Unable to retrieve URL (${url}). Reason: ${res.statusText}`);
91
- }
92
-
93
- if (res.headers.get('content-type') === 'application/yaml' || /\.(yaml|yml)/.test(url)) {
94
- return res.text().then(text => {
95
- return yaml.load(text);
96
- });
97
- }
98
-
99
- return res.json();
100
- });
101
- }
102
-
103
- static getFile(uri: string) {
104
- // Support relative paths by resolving them against the cwd.
105
- const file = path.resolve(process.cwd(), uri);
106
-
107
- if (!fs.existsSync(file)) {
108
- throw new Error(
109
- `Sorry, we were unable to load an API definition from ${file}. Please either supply a URL or a path on your filesystem.`,
110
- );
111
- }
112
-
113
- return Promise.resolve(fs.readFileSync(file, 'utf8')).then((res: string) => {
114
- if (/\.(yaml|yml)/.test(file)) {
115
- return yaml.load(res);
116
- }
117
-
118
- return JSON.parse(res);
119
- });
120
- }
121
-
122
- static validate(json: OASDocument) {
123
- if (json.swagger) {
124
- throw new Error('Sorry, this module only supports OpenAPI definitions.');
125
- }
126
-
127
- // The `validate` method handles dereferencing for us.
128
- return OpenAPIParser.validate(json, {
129
- dereference: {
130
- /**
131
- * If circular `$refs` are ignored they'll remain in the API definition as `$ref: String`.
132
- * This allows us to not only do easy circular reference detection but also stringify and
133
- * save dereferenced API definitions back into the cache directory.
134
- */
135
- circular: 'ignore',
136
- },
137
- }).catch(err => {
138
- if (/is not a valid openapi definition/i.test(err.message)) {
139
- throw new Error("Sorry, that doesn't look like a valid OpenAPI definition.");
140
- }
141
-
142
- throw err;
143
- });
144
- }
145
- }
package/src/lib/prompt.ts DELETED
@@ -1,29 +0,0 @@
1
- import prompts from 'prompts';
2
-
3
- /**
4
- * The `prompts` library doesn't always interpret CTRL+C and release the terminal back to the user
5
- * so we need handle this ourselves. This function is just a simple overload of the main `prompts`
6
- * import that we use.
7
- *
8
- * @see {@link https://github.com/terkelg/prompts/issues/252}
9
- */
10
- export default async function promptTerminal<T extends string = string>(
11
- question: prompts.PromptObject<T>,
12
- options?: prompts.Options,
13
- ) {
14
- const enableTerminalCursor = () => {
15
- process.stdout.write('\x1B[?25h');
16
- };
17
-
18
- const onState = (state: { aborted: boolean }) => {
19
- if (state.aborted) {
20
- // If we don't re-enable the terminal cursor before exiting the program, the cursor will
21
- // remain hidden.
22
- enableTerminalCursor();
23
- process.stdout.write('\n');
24
- process.exit(1);
25
- }
26
- };
27
-
28
- return prompts({ ...question, onState }, options);
29
- }
package/src/logger.ts DELETED
@@ -1,10 +0,0 @@
1
- /* eslint-disable no-console */
2
- import chalk from 'chalk';
3
-
4
- export default function logger(log: string, error?: boolean) {
5
- if (error) {
6
- console.error(chalk.red(log));
7
- } else {
8
- console.log(log);
9
- }
10
- }
@@ -1,3 +0,0 @@
1
- // This file is automatically updated by the build script.
2
- export const PACKAGE_NAME = 'api';
3
- export const PACKAGE_VERSION = '7.0.0-beta.0';