api 7.0.0-alpha.0 → 7.0.0-alpha.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.
@@ -0,0 +1,226 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const node_fs_1 = __importDefault(require("node:fs"));
7
+ const node_path_1 = __importDefault(require("node:path"));
8
+ const make_dir_1 = __importDefault(require("make-dir"));
9
+ const ssri_1 = __importDefault(require("ssri"));
10
+ const validate_npm_package_name_1 = __importDefault(require("validate-npm-package-name"));
11
+ const fetcher_1 = __importDefault(require("./fetcher"));
12
+ const packageInfo_1 = require("./packageInfo");
13
+ class Storage {
14
+ static dir;
15
+ static lockfile;
16
+ /**
17
+ * This is the original source that the file came from (relative/absolute file path, URL, ReadMe
18
+ * registry UUID, etc.).
19
+ */
20
+ source;
21
+ identifier;
22
+ fetcher;
23
+ constructor(source, identifier) {
24
+ Storage.setStorageDir();
25
+ this.fetcher = new fetcher_1.default(source);
26
+ this.source = source;
27
+ if (identifier) {
28
+ this.identifier = identifier;
29
+ }
30
+ // This should default to false so we have awareness if we've looked at the lockfile yet.
31
+ Storage.lockfile = false;
32
+ }
33
+ static getLockfilePath() {
34
+ return node_path_1.default.join(Storage.dir, 'api.json');
35
+ }
36
+ static getAPIsDir() {
37
+ return node_path_1.default.join(Storage.dir, 'apis');
38
+ }
39
+ static setStorageDir(dir) {
40
+ if (dir) {
41
+ Storage.dir = dir;
42
+ return;
43
+ }
44
+ else if (Storage.dir) {
45
+ // If we already have a storage dir set and aren't explicitly it to something new then we
46
+ // shouldn't overwrite what we've already got.
47
+ return;
48
+ }
49
+ Storage.dir = make_dir_1.default.sync(node_path_1.default.join(process.cwd(), '.api'));
50
+ make_dir_1.default.sync(Storage.getAPIsDir());
51
+ }
52
+ /**
53
+ * Reset the state of the entire storage system.
54
+ *
55
+ * This will completely destroy the contents of the `.api/` directory!
56
+ */
57
+ static async reset() {
58
+ if (Storage.getLockfilePath()) {
59
+ await node_fs_1.default.promises.writeFile(Storage.getLockfilePath(), JSON.stringify(Storage.getDefaultLockfile(), null, 2));
60
+ }
61
+ if (Storage.getAPIsDir()) {
62
+ await node_fs_1.default.promises.rm(Storage.getAPIsDir(), { recursive: true });
63
+ await node_fs_1.default.promises.mkdir(Storage.getAPIsDir(), { recursive: true });
64
+ }
65
+ }
66
+ static getDefaultLockfile() {
67
+ return {
68
+ version: '1.0',
69
+ apis: [],
70
+ };
71
+ }
72
+ static generateIntegrityHash(definition) {
73
+ return ssri_1.default
74
+ .fromData(JSON.stringify(definition), {
75
+ algorithms: ['sha512'],
76
+ })
77
+ .toString();
78
+ }
79
+ static getLockfile() {
80
+ if (typeof Storage.lockfile === 'object') {
81
+ return Storage.lockfile;
82
+ }
83
+ if (node_fs_1.default.existsSync(Storage.getLockfilePath())) {
84
+ const file = node_fs_1.default.readFileSync(Storage.getLockfilePath(), 'utf8');
85
+ Storage.lockfile = JSON.parse(file);
86
+ }
87
+ else {
88
+ Storage.lockfile = Storage.getDefaultLockfile();
89
+ }
90
+ return Storage.lockfile;
91
+ }
92
+ static isIdentifierValid(identifier, prefixWithAPINamespace) {
93
+ // Is this identifier already in storage?
94
+ if (Storage.isInLockFile({ identifier })) {
95
+ throw new Error(`"${identifier}" is already taken in your \`.api/\` directory. Please try another identifier.`);
96
+ }
97
+ const isValidForNPM = (0, validate_npm_package_name_1.default)(prefixWithAPINamespace ? `@api/${identifier}` : identifier);
98
+ if (!isValidForNPM.validForNewPackages) {
99
+ // `prompts` doesn't support surfacing multiple errors in a `validate` call so we can only
100
+ // surface the first to the user.
101
+ throw new Error(`Identifier cannot be used for an NPM package: ${isValidForNPM?.errors?.[0] || '[error unavailable]'}`);
102
+ }
103
+ return true;
104
+ }
105
+ static isInLockFile(search) {
106
+ // Because this method may run before we initialize a new storage object we should make sure
107
+ // that we have a storage directory present.
108
+ Storage.setStorageDir();
109
+ if (!search.identifier && !search.source) {
110
+ throw new TypeError('An `identifier` or `source` must be supplied to this method to search in the lockfile.');
111
+ }
112
+ const lockfile = Storage.getLockfile();
113
+ if (typeof lockfile !== 'object' || lockfile === null || !lockfile.apis) {
114
+ return false;
115
+ }
116
+ const res = lockfile.apis.find(a => {
117
+ if (search.identifier) {
118
+ return a.identifier === search.identifier;
119
+ }
120
+ return a.source === search.source;
121
+ });
122
+ return res === undefined ? false : res;
123
+ }
124
+ setIdentifier(identifier) {
125
+ this.identifier = identifier;
126
+ }
127
+ /**
128
+ * Determine if the current spec + identifier we're working with is already in the lockfile.
129
+ */
130
+ isInLockfile() {
131
+ return Boolean(this.getFromLockfile());
132
+ }
133
+ /**
134
+ * Retrieve the lockfile record for the current spec + identifier if it exists in the lockfile.
135
+ */
136
+ getFromLockfile() {
137
+ const lockfile = Storage.getLockfile();
138
+ return lockfile.apis.find(a => a.identifier === this.identifier);
139
+ }
140
+ getIdentifierStorageDir() {
141
+ if (!this.isInLockfile()) {
142
+ throw new Error(`${this.source} has not been saved to storage yet and must do so before being retrieved.`);
143
+ }
144
+ return node_path_1.default.join(Storage.getAPIsDir(), this.identifier);
145
+ }
146
+ getAPIDefinition() {
147
+ const file = node_fs_1.default.readFileSync(node_path_1.default.join(this.getIdentifierStorageDir(), 'openapi.json'), 'utf8');
148
+ return JSON.parse(file);
149
+ }
150
+ saveSourceFiles(files) {
151
+ if (!this.isInLockfile()) {
152
+ throw new Error(`${this.source} has not been saved to storage yet and must do so before being retrieved.`);
153
+ }
154
+ return new Promise(resolve => {
155
+ const savedSource = [];
156
+ Object.entries(files).forEach(([fileName, contents]) => {
157
+ const sourceFilePath = node_path_1.default.join(this.getIdentifierStorageDir(), fileName);
158
+ node_fs_1.default.writeFileSync(sourceFilePath, contents);
159
+ savedSource.push(sourceFilePath);
160
+ });
161
+ resolve(savedSource);
162
+ });
163
+ }
164
+ async load() {
165
+ return this.fetcher.load().then(async (spec) => this.save(spec));
166
+ }
167
+ /**
168
+ * @example <caption>Storage directory structure</caption>
169
+ * .api/
170
+ * ├── api.json // The `package-lock.json` equivalent that records everything that's
171
+ * | // installed, when it was installed, what the original source was,
172
+ * | // and what version of `api` was used.
173
+ * └── apis/
174
+ * ├── readme/
175
+ * | ├── node_modules/
176
+ * │ ├── index.js // We may offer the option to export a raw TS file for folks who want
177
+ * | | // that, but for now it'll be a compiled JS file.
178
+ * │ ├── index.d.ts // All types for their SDK, ready to use in an IDE.
179
+ * │ |── openapi.json
180
+ * │ └── package.json
181
+ * └── petstore/
182
+ * ├── node_modules/
183
+ * ├── index.js
184
+ * ├── index.d.ts
185
+ * ├── openapi.json
186
+ * └── package.json
187
+ *
188
+ */
189
+ save(spec) {
190
+ if (!this.identifier) {
191
+ throw new TypeError('An identifier must be set before saving the API definition into storage.');
192
+ }
193
+ // Create our main `.api/` directory.
194
+ if (!node_fs_1.default.existsSync(Storage.dir)) {
195
+ node_fs_1.default.mkdirSync(Storage.dir, { recursive: true });
196
+ }
197
+ // Create the `.api/apis/` diretory where we'll be storing API definitions.
198
+ if (!node_fs_1.default.existsSync(Storage.getAPIsDir())) {
199
+ node_fs_1.default.mkdirSync(Storage.getAPIsDir(), { recursive: true });
200
+ }
201
+ if (!this.isInLockfile()) {
202
+ // This API doesn't exist within our storage system yet so we need to record it in the
203
+ // lockfile.
204
+ const identifierStorageDir = node_path_1.default.join(Storage.getAPIsDir(), this.identifier);
205
+ const saved = JSON.stringify(spec, null, 2);
206
+ // Create the `.api/apis/<identifier>` directory where we'll be storing this API definition
207
+ // and eventually its codegen'd SDK.
208
+ if (!node_fs_1.default.existsSync(identifierStorageDir)) {
209
+ node_fs_1.default.mkdirSync(identifierStorageDir, { recursive: true });
210
+ }
211
+ Storage.lockfile.apis.push({
212
+ identifier: this.identifier,
213
+ source: this.source,
214
+ integrity: Storage.generateIntegrityHash(spec),
215
+ installerVersion: packageInfo_1.PACKAGE_VERSION,
216
+ });
217
+ node_fs_1.default.writeFileSync(node_path_1.default.join(identifierStorageDir, 'openapi.json'), saved);
218
+ node_fs_1.default.writeFileSync(Storage.getLockfilePath(), JSON.stringify(Storage.lockfile, null, 2));
219
+ }
220
+ else {
221
+ // Is this the same spec that we already have? Should we update it? // @todo
222
+ }
223
+ return spec;
224
+ }
225
+ }
226
+ exports.default = Storage;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "api",
3
- "version": "7.0.0-alpha.0",
3
+ "version": "7.0.0-alpha.2",
4
4
  "description": "Magical SDK generation from an OpenAPI definition 🪄",
5
5
  "bin": {
6
6
  "api": "./bin/api"
@@ -47,8 +47,8 @@
47
47
  "lodash.deburr": "^4.1.0",
48
48
  "lodash.setwith": "^4.3.2",
49
49
  "lodash.startcase": "^4.4.0",
50
- "make-dir": "^3.1.0",
51
- "oas": "^20.4.0",
50
+ "make-dir": "^4.0.0",
51
+ "oas": "^22.0.0",
52
52
  "ora": "^5.4.1",
53
53
  "prompts": "^2.4.2",
54
54
  "semver": "^7.3.8",
@@ -57,7 +57,8 @@
57
57
  "validate-npm-package-name": "^5.0.0"
58
58
  },
59
59
  "devDependencies": {
60
- "@api/test-utils": "file:../test-utils",
60
+ "@api/test-utils": "^7.0.0-alpha.2",
61
+ "@readme/api-core": "^7.0.0-alpha.2",
61
62
  "@readme/oas-examples": "^5.12.0",
62
63
  "@types/js-yaml": "^4.0.5",
63
64
  "@types/lodash.camelcase": "^4.3.7",
@@ -69,13 +70,13 @@
69
70
  "@types/ssri": "^7.1.1",
70
71
  "@types/validate-npm-package-name": "^4.0.0",
71
72
  "@vitest/coverage-v8": "^0.34.4",
72
- "api.core": "file:../core",
73
73
  "fetch-mock": "^9.11.0",
74
- "oas-normalize": "^8.3.2",
74
+ "oas-normalize": "^11.0.0",
75
75
  "type-fest": "^4.3.1",
76
76
  "typescript": "^5.2.2",
77
77
  "unique-temp-dir": "^1.0.0",
78
- "vitest": "^0.34.1"
78
+ "vitest": "^0.34.5"
79
79
  },
80
- "prettier": "@readme/eslint-config/prettier"
80
+ "prettier": "@readme/eslint-config/prettier",
81
+ "gitHead": "fae2070b68d3f73bad20c92e112d41b4d5875ede"
81
82
  }
@@ -1,8 +1,8 @@
1
1
  import type Storage from '../../storage';
2
2
  import type { InstallerOptions } from '../language';
3
3
  import type Oas from 'oas';
4
- import type { Operation } from 'oas';
5
- import type { HttpMethods, SchemaObject } from 'oas/dist/rmoas.types';
4
+ import type Operation from 'oas/operation';
5
+ import type { HttpMethods, SchemaObject } from 'oas/rmoas.types';
6
6
  import type { SemVer } from 'semver';
7
7
  import type {
8
8
  ClassDeclaration,
@@ -86,7 +86,7 @@ export default class TSGenerator extends CodeGeneratorLanguage {
86
86
 
87
87
  this.requiredPackages = {
88
88
  api: {
89
- reason: "Required for the `api.core` library that the codegen'd SDK uses for making requests.",
89
+ reason: "Required for the `@readme/api-core` library that the codegen'd SDK uses for making requests.",
90
90
  url: 'https://npm.im/api',
91
91
  },
92
92
  'json-schema-to-ts': {
@@ -94,7 +94,7 @@ export default class TSGenerator extends CodeGeneratorLanguage {
94
94
  url: 'https://npm.im/json-schema-to-ts',
95
95
  },
96
96
  oas: {
97
- reason: 'Used within `api.core` and is also loaded for TypeScript types.',
97
+ reason: 'Used within `@readme/api-core` and is also loaded for TypeScript types.',
98
98
  url: 'https://npm.im/oas',
99
99
  },
100
100
  };
@@ -229,7 +229,7 @@ export default class TSGenerator extends CodeGeneratorLanguage {
229
229
  sdkSource
230
230
  .getImportDeclarations()
231
231
  .find(id => id.getText().includes('HTTPMethodRange'))
232
- ?.replaceWithText("import type { ConfigOptions, FetchResponse } from 'api.core';");
232
+ ?.replaceWithText("import type { ConfigOptions, FetchResponse } from '@readme/api-core';");
233
233
  }
234
234
 
235
235
  if (this.outputJS) {
@@ -300,10 +300,10 @@ export default class TSGenerator extends CodeGeneratorLanguage {
300
300
  {
301
301
  // `HTTPMethodRange` will be conditionally removed later if it ends up not being used.
302
302
  defaultImport: 'type { ConfigOptions, FetchResponse, HTTPMethodRange }',
303
- moduleSpecifier: 'api.core',
303
+ moduleSpecifier: '@readme/api-core',
304
304
  },
305
305
  { defaultImport: 'Oas', moduleSpecifier: 'oas' },
306
- { defaultImport: 'APICore', moduleSpecifier: 'api.core' },
306
+ { defaultImport: 'APICore', moduleSpecifier: '@readme/api-core' },
307
307
  { defaultImport: 'definition', moduleSpecifier: this.specPath },
308
308
  ]);
309
309
 
@@ -710,7 +710,7 @@ sdk.server('https://eu.api.example.com/v14');`),
710
710
  // our `metadata` parameter is actually required for this operation this is the only way we're
711
711
  // able to have an optional `body` parameter be present before `metadata`.
712
712
  //
713
- // Thankfully our core fetch work in `api.core` is able to do the proper determination to
713
+ // Thankfully our core fetch work in `@readme/api-core` is able to do the proper determination to
714
714
  // see if what the user is supplying is `metadata` or `body` content when they supply one or
715
715
  // both.
716
716
  operationIdAccessor.addParameters([
package/src/fetcher.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { OASDocument } from 'oas/dist/rmoas.types';
1
+ import type { OASDocument } from 'oas/rmoas.types';
2
2
 
3
3
  import fs from 'node:fs';
4
4
  import path from 'node:path';
@@ -1,3 +1,3 @@
1
1
  // This file is automatically updated by the build script.
2
2
  export const PACKAGE_NAME = 'api';
3
- export const PACKAGE_VERSION = '7.0.0-alpha.0';
3
+ export const PACKAGE_VERSION = '7.0.0-alpha.2';
package/src/storage.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { OASDocument } from 'oas/dist/rmoas.types';
1
+ import type { OASDocument } from 'oas/rmoas.types';
2
2
 
3
3
  import fs from 'node:fs';
4
4
  import path from 'node:path';
package/tsconfig.json CHANGED
@@ -5,8 +5,10 @@
5
5
  "declaration": true,
6
6
  "esModuleInterop": true,
7
7
  "lib": ["DOM", "DOM.Iterable", "ES2020"],
8
+ "module": "NodeNext",
8
9
  "noImplicitAny": true,
9
10
  "outDir": "dist/",
11
+ "skipLibCheck": true,
10
12
  "strict": true,
11
13
  "useUnknownInCatchVariables": false
12
14
  },