api 7.0.0-alpha.3 → 7.0.0-alpha.5

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 (64) hide show
  1. package/bin/api.js +2 -0
  2. package/dist/bin.d.ts +1 -0
  3. package/dist/bin.d.ts.map +1 -0
  4. package/dist/bin.js +6 -33
  5. package/dist/bin.js.map +1 -0
  6. package/dist/codegen/{language.d.ts → codegenerator.d.ts} +7 -4
  7. package/dist/codegen/codegenerator.d.ts.map +1 -0
  8. package/dist/codegen/{language.js → codegenerator.js} +4 -6
  9. package/dist/codegen/codegenerator.js.map +1 -0
  10. package/dist/codegen/factory.d.ts +7 -0
  11. package/dist/codegen/factory.d.ts.map +1 -0
  12. package/dist/codegen/factory.js +14 -0
  13. package/dist/codegen/factory.js.map +1 -0
  14. package/dist/codegen/languages/typescript/index.d.ts +90 -0
  15. package/dist/codegen/languages/typescript/index.d.ts.map +1 -0
  16. package/dist/codegen/languages/{typescript.js → typescript/index.js} +260 -206
  17. package/dist/codegen/languages/typescript/index.js.map +1 -0
  18. package/dist/codegen/languages/typescript/util.d.ts +1 -0
  19. package/dist/codegen/languages/typescript/util.d.ts.map +1 -0
  20. package/dist/codegen/languages/typescript/util.js +10 -18
  21. package/dist/codegen/languages/typescript/util.js.map +1 -0
  22. package/dist/commands/index.d.ts +1 -0
  23. package/dist/commands/index.d.ts.map +1 -0
  24. package/dist/commands/index.js +4 -8
  25. package/dist/commands/index.js.map +1 -0
  26. package/dist/commands/install.d.ts +1 -0
  27. package/dist/commands/install.d.ts.map +1 -0
  28. package/dist/commands/install.js +52 -67
  29. package/dist/commands/install.js.map +1 -0
  30. package/dist/fetcher.d.ts +1 -0
  31. package/dist/fetcher.d.ts.map +1 -0
  32. package/dist/fetcher.js +12 -17
  33. package/dist/fetcher.js.map +1 -0
  34. package/dist/lib/prompt.d.ts +1 -0
  35. package/dist/lib/prompt.d.ts.map +1 -0
  36. package/dist/lib/prompt.js +4 -9
  37. package/dist/lib/prompt.js.map +1 -0
  38. package/dist/logger.d.ts +1 -0
  39. package/dist/logger.d.ts.map +1 -0
  40. package/dist/logger.js +4 -9
  41. package/dist/logger.js.map +1 -0
  42. package/dist/packageInfo.d.ts +2 -1
  43. package/dist/packageInfo.d.ts.map +1 -0
  44. package/dist/packageInfo.js +3 -5
  45. package/dist/packageInfo.js.map +1 -0
  46. package/dist/storage.d.ts +2 -1
  47. package/dist/storage.d.ts.map +1 -0
  48. package/dist/storage.js +43 -40
  49. package/dist/storage.js.map +1 -0
  50. package/package.json +12 -10
  51. package/src/bin.ts +2 -2
  52. package/src/codegen/{language.ts → codegenerator.ts} +15 -6
  53. package/src/codegen/factory.ts +23 -0
  54. package/src/codegen/languages/{typescript.ts → typescript/index.ts} +269 -203
  55. package/src/commands/index.ts +1 -1
  56. package/src/commands/install.ts +21 -35
  57. package/src/packageInfo.ts +1 -1
  58. package/src/storage.ts +14 -5
  59. package/tsconfig.json +3 -10
  60. package/bin/api +0 -2
  61. package/dist/codegen/index.d.ts +0 -4
  62. package/dist/codegen/index.js +0 -23
  63. package/dist/codegen/languages/typescript.d.ts +0 -111
  64. package/src/codegen/index.ts +0 -31
@@ -1,151 +1,138 @@
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 execa_1 = __importDefault(require("execa"));
9
- const lodash_setwith_1 = __importDefault(require("lodash.setwith"));
10
- const semver_1 = __importDefault(require("semver"));
11
- const ts_morph_1 = require("ts-morph");
12
- const logger_1 = __importDefault(require("../../logger"));
13
- const language_1 = __importDefault(require("../language"));
14
- const util_1 = require("./typescript/util");
15
- class TSGenerator extends language_1.default {
1
+ import path from 'node:path';
2
+ import execa from 'execa';
3
+ import setWith from 'lodash.setwith';
4
+ import semver from 'semver';
5
+ import { IndentationText, Project, QuoteKind, ScriptTarget, VariableDeclarationKind } from 'ts-morph';
6
+ import logger from '../../../logger.js';
7
+ import CodeGenerator from '../../codegenerator.js';
8
+ import { docblockEscape, generateTypeName, wordWrap } from './util.js';
9
+ /**
10
+ * This is the conversion prefix that we add to all `$ref` pointers we find in generated JSON
11
+ * Schema.
12
+ *
13
+ * Because the pointer name is a string we want to have it reference the schema constant we're
14
+ * adding into the codegen'd schema file. As there's no way, not even using `eval()` in this case,
15
+ * to convert a string to a constant we're prefixing them with this so we can later remove it and
16
+ * rewrite the value to a literal. eg. `'Pet'` becomes `Pet`.
17
+ *
18
+ * And because our TypeScript type name generator properly ignores `:`, this is safe to prepend to
19
+ * all generated type names.
20
+ */
21
+ const REF_PLACEHOLDER = '::convert::';
22
+ const REF_PLACEHOLDER_REGEX = /"::convert::([a-zA-Z_$\\d]*)"/g;
23
+ export default class TSGenerator extends CodeGenerator {
16
24
  project;
17
- outputJS;
18
- compilerTarget;
19
25
  types;
20
26
  sdk;
21
27
  schemas;
22
28
  usesHTTPMethodRangeInterface = false;
23
- constructor(spec, specPath, identifier, opts = {}) {
24
- const options = {
25
- outputJS: false,
26
- compilerTarget: 'cjs',
27
- ...opts,
28
- };
29
- if (!options.outputJS) {
30
- // TypeScript compilation will always target towards ESM-like imports and exports.
31
- options.compilerTarget = 'esm';
32
- }
29
+ constructor(spec, specPath, identifier) {
33
30
  super(spec, specPath, identifier);
34
31
  this.requiredPackages = {
35
- api: {
36
- reason: "Required for the `@readme/api-core` library that the codegen'd SDK uses for making requests.",
32
+ '@readme/api-core': {
33
+ reason: "The core magic of your codegen'd SDK and is what is used for making requests.",
37
34
  url: 'https://npm.im/api',
35
+ version:
36
+ // When running unit tests we're installing `@readme/api-core` but because that package
37
+ // source lives in this repository NPM will throw a gnarly "Cannot set properties of null
38
+ // (setting 'dev')" workspace error message because we're creating a funky circular
39
+ // dependency.
40
+ process.env.NODE_ENV === 'test'
41
+ ? `file:${path.relative(__dirname, path.dirname(require.resolve('@readme/api-core/package.json')))}`
42
+ : '^7.0.0',
38
43
  },
39
44
  'json-schema-to-ts': {
40
45
  reason: 'Required for TypeScript type handling.',
41
46
  url: 'https://npm.im/json-schema-to-ts',
47
+ version: '^2.9.2',
42
48
  },
43
49
  oas: {
44
50
  reason: 'Used within `@readme/api-core` and is also loaded for TypeScript types.',
45
51
  url: 'https://npm.im/oas',
52
+ version: '^23.0.0',
46
53
  },
47
54
  };
48
- this.project = new ts_morph_1.Project({
49
- manipulationSettings: {
50
- indentationText: ts_morph_1.IndentationText.TwoSpaces,
51
- quoteKind: ts_morph_1.QuoteKind.Single,
52
- },
55
+ this.project = new Project({
53
56
  compilerOptions: {
54
- // If we're exporting a TypeScript SDK then we don't need to pollute the codegen directory
55
- // with unnecessary declaration `.d.ts` files.
56
- declaration: options.outputJS,
57
57
  outDir: 'dist',
58
58
  resolveJsonModule: true,
59
- target: options.compilerTarget === 'cjs' ? ts_morph_1.ScriptTarget.ES5 : ts_morph_1.ScriptTarget.ES2020,
60
- // If we're compiling to a CJS target then we need to include this compiler option
61
- // otherwise TS will attempt to load our `openapi.json` import with a `.default` property
62
- // which doesn't exist. `esModuleInterop` wraps imports in a small `__importDefault`
63
- // function that does some determination to see if the module has a default export or not.
64
- //
65
- // Basically without this option CJS code will fail.
66
- ...(options.compilerTarget === 'cjs' ? { esModuleInterop: true } : {}),
59
+ target: ScriptTarget.ES2022,
67
60
  },
61
+ manipulationSettings: {
62
+ indentationText: IndentationText.TwoSpaces,
63
+ quoteKind: QuoteKind.Single,
64
+ },
65
+ useInMemoryFileSystem: true,
68
66
  });
69
- this.compilerTarget = options.compilerTarget;
70
- this.outputJS = options.outputJS;
71
67
  this.types = new Map();
72
68
  this.schemas = {};
73
69
  }
74
- async installer(storage, opts = {}) {
70
+ // eslint-disable-next-line class-methods-use-this
71
+ async install(storage, opts = {}) {
75
72
  const installDir = storage.getIdentifierStorageDir();
76
- const info = this.spec.getDefinition().info;
77
- let pkgVersion = semver_1.default.coerce(info.version);
78
- if (!pkgVersion) {
79
- // If the version that's in `info.version` isn't compatible with semver NPM won't be able to
80
- // handle it properly so we need to fallback to something it can.
81
- pkgVersion = semver_1.default.coerce('0.0.0');
82
- }
83
- const pkg = {
84
- name: `@api/${storage.identifier}`,
85
- version: pkgVersion.version,
86
- main: `./index.${this.outputJS ? 'js' : 'ts'}`,
87
- types: './index.d.ts', // Types are always present regardless if you're getting compiled JS.
88
- };
89
- node_fs_1.default.writeFileSync(node_path_1.default.join(installDir, 'package.json'), JSON.stringify(pkg, null, 2));
90
73
  const npmInstall = ['install', '--save', opts.dryRun ? '--dry-run' : ''].filter(Boolean);
91
- // This will install packages required for the SDK within its installed directory in `.apis/`.
92
- await (0, execa_1.default)('npm', [...npmInstall, ...Object.keys(this.requiredPackages)].filter(Boolean), {
93
- cwd: installDir,
94
- }).then(res => {
95
- if (opts.dryRun) {
96
- (opts.logger ? opts.logger : logger_1.default)(res.command);
97
- (opts.logger ? opts.logger : logger_1.default)(res.stdout);
98
- }
99
- });
100
74
  // This will install the installed SDK as a dependency within the current working directory,
101
75
  // adding `@api/<sdk identifier>` as a dependency there so you can load it with
102
76
  // `require('@api/<sdk identifier>)`.
103
- return (0, execa_1.default)('npm', [...npmInstall, installDir].filter(Boolean))
77
+ await execa('npm', [...npmInstall, installDir].filter(Boolean))
104
78
  .then(res => {
105
79
  if (opts.dryRun) {
106
- (opts.logger ? opts.logger : logger_1.default)(res.command);
107
- (opts.logger ? opts.logger : logger_1.default)(res.stdout);
80
+ (opts.logger ? opts.logger : logger)(res.command);
81
+ (opts.logger ? opts.logger : logger)(res.stdout);
108
82
  }
109
83
  })
110
84
  .catch(err => {
85
+ // If `npm install` throws this error it always happens **after** our dependencies have been
86
+ // installed and is an annoying quirk that sometimes occurs when installing a package within
87
+ // our workspace as we're creating a circular dependency on `@readme/api-core`.
88
+ if (process.env.NODE_ENV === 'test' &&
89
+ err.message.includes("npm ERR! Cannot set properties of null (setting 'dev')")) {
90
+ (opts.logger ? opts.logger : logger)("npm threw an error but we're ignoring it");
91
+ return;
92
+ }
111
93
  if (opts.dryRun) {
112
- (opts.logger ? opts.logger : logger_1.default)(err.message);
94
+ (opts.logger ? opts.logger : logger)(err.message);
113
95
  return;
114
96
  }
115
97
  throw err;
116
98
  });
117
99
  }
118
100
  /**
119
- * Compile the current OpenAPI definition into a TypeScript library.
101
+ * Compile the TS code we generated into JS for use in CJS and ESM environments.
120
102
  *
121
103
  */
122
- async generator() {
123
- const sdkSource = this.createSourceFile();
124
- if (Object.keys(this.schemas).length) {
125
- this.createSchemasFile();
126
- this.createTypesFile();
127
- /**
128
- * Export all of our available types so they can be used in SDK implementations. Types are
129
- * exported individually because TS has no way right now of allowing us to do
130
- * `export type * from './types'` on a non-named entry.
131
- *
132
- * Types in the main entry point are only being exported for TS outputs as JS users won't be
133
- * able to use them and it clashes with the default SDK export present.
134
- *
135
- * @see {@link https://github.com/microsoft/TypeScript/issues/37238}
136
- * @see {@link https://github.com/readmeio/api/issues/588}
137
- */
138
- if (!this.outputJS) {
139
- const types = Array.from(this.types.keys());
140
- types.sort();
141
- sdkSource.addExportDeclarations([
142
- {
143
- isTypeOnly: true,
144
- namedExports: types,
145
- moduleSpecifier: './types',
146
- },
147
- ]);
104
+ // eslint-disable-next-line class-methods-use-this
105
+ async compile(storage, opts = {}) {
106
+ const installDir = storage.getIdentifierStorageDir();
107
+ await execa('npx', ['tsup'], {
108
+ cwd: installDir,
109
+ })
110
+ .then(res => {
111
+ if (opts.dryRun) {
112
+ (opts.logger ? opts.logger : logger)(res.command);
113
+ (opts.logger ? opts.logger : logger)(res.stdout);
114
+ }
115
+ })
116
+ .catch(err => {
117
+ if (opts.dryRun) {
118
+ (opts.logger ? opts.logger : logger)(err.message);
119
+ return;
148
120
  }
121
+ throw err;
122
+ });
123
+ }
124
+ /**
125
+ * Generate the current OpenAPI definition into a TypeScript library.
126
+ *
127
+ */
128
+ async generate() {
129
+ const srcDirectory = this.project.createDirectory('src');
130
+ const sdkSource = this.createSDKSource(srcDirectory);
131
+ this.createPackageJSON();
132
+ this.createTSConfig();
133
+ if (Object.keys(this.schemas).length) {
134
+ this.createSchemasFile(srcDirectory);
135
+ this.createTypesFile(srcDirectory);
149
136
  }
150
137
  else {
151
138
  // If we don't have any schemas then we shouldn't import a `types` file that doesn't exist.
@@ -162,43 +149,18 @@ class TSGenerator extends language_1.default {
162
149
  .find(id => id.getText().includes('HTTPMethodRange'))
163
150
  ?.replaceWithText("import type { ConfigOptions, FetchResponse } from '@readme/api-core';");
164
151
  }
165
- if (this.outputJS) {
166
- return this.project
167
- .emitToMemory()
168
- .getFiles()
169
- .map(sourceFile => {
170
- const file = node_path_1.default.basename(sourceFile.filePath);
171
- if (file === 'schemas.js' || file === 'types.js') {
172
- // If we're generating a JS SDK then we don't need to generate these two files as the
173
- // user will have `.d.ts` files for them instead.
174
- return {};
175
- }
176
- let code = sourceFile.text;
177
- if (file === 'index.js' && this.compilerTarget === 'cjs') {
178
- /**
179
- * There's an annoying quirk with `ts-morph` where if we're exporting a default export
180
- * to a CJS environment, it'll export it as `exports.default`. Because we don't want
181
- * folks in these environments to have to load their SDKs with
182
- * `require('@api/sdk').default` we're overriding that here to change it to being the
183
- * module exports.
184
- *
185
- * `ts-morph` unfortunately doesn't give us any options for programatically doing this
186
- * so we need to resort to modifying the emitted JS code.
187
- */
188
- code = code
189
- .replace(/Object\.defineProperty\(exports, '__esModule', { value: true }\);\n/, '')
190
- .replace('exports.default = createSDK;', 'module.exports = createSDK;');
191
- }
152
+ return [
153
+ ...this.project.getSourceFiles().map(sourceFile => {
154
+ // `getFilePath` will always return a string that contains a preceeding directory separator
155
+ // however when we're creating these codegen'd files that may cause us to create that file
156
+ // in the root directory (because it's preceeded by a `/`). We don't want that to happen so
157
+ // we're slicing off that first character.
158
+ let filePath = sourceFile.getFilePath().toString();
159
+ filePath = filePath.substring(1);
192
160
  return {
193
- [file]: code,
161
+ [filePath]: sourceFile.getFullText(),
194
162
  };
195
- })
196
- .reduce((prev, next) => Object.assign(prev, next));
197
- }
198
- return [
199
- ...this.project.getSourceFiles().map(sourceFile => ({
200
- [sourceFile.getBaseName()]: sourceFile.getFullText(),
201
- })),
163
+ }),
202
164
  // Because we're returning the raw source files for TS generation we also need to separately
203
165
  // emit out our declaration files so we can put those into a separate file in the installed
204
166
  // SDK directory.
@@ -206,7 +168,7 @@ class TSGenerator extends language_1.default {
206
168
  .emitToMemory({ emitOnlyDtsFiles: true })
207
169
  .getFiles()
208
170
  .map(sourceFile => ({
209
- [node_path_1.default.basename(sourceFile.filePath)]: sourceFile.text,
171
+ [path.basename(sourceFile.filePath)]: sourceFile.text,
210
172
  })),
211
173
  ].reduce((prev, next) => Object.assign(prev, next));
212
174
  }
@@ -214,9 +176,9 @@ class TSGenerator extends language_1.default {
214
176
  * Create our main SDK source file.
215
177
  *
216
178
  */
217
- createSourceFile() {
179
+ createSDKSource(sourceDirectory) {
218
180
  const { operations } = this.loadOperationsAndMethods();
219
- const sourceFile = this.project.createSourceFile('index.ts', '');
181
+ const sourceFile = sourceDirectory.createSourceFile('index.ts', '');
220
182
  sourceFile.addImportDeclarations([
221
183
  // This import will be automatically removed later if the SDK ends up not having any types.
222
184
  { defaultImport: 'type * as types', moduleSpecifier: './types' },
@@ -252,12 +214,12 @@ class TSGenerator extends language_1.default {
252
214
  statements: writer => writer.writeLine('this.core.setConfig(config);'),
253
215
  docs: [
254
216
  {
255
- description: writer => writer.writeLine((0, util_1.wordWrap)('Optionally configure various options that the SDK allows.')),
217
+ description: writer => writer.writeLine(wordWrap('Optionally configure various options that the SDK allows.')),
256
218
  tags: [
257
219
  { tagName: 'param', text: 'config Object of supported SDK options and toggles.' },
258
220
  {
259
221
  tagName: 'param',
260
- text: (0, util_1.wordWrap)('config.timeout Override the default `fetch` request timeout of 30 seconds. This number should be represented in milliseconds.'),
222
+ text: wordWrap('config.timeout Override the default `fetch` request timeout of 30 seconds. This number should be represented in milliseconds.'),
261
223
  },
262
224
  ],
263
225
  },
@@ -273,7 +235,7 @@ class TSGenerator extends language_1.default {
273
235
  },
274
236
  docs: [
275
237
  {
276
- description: writer => writer.writeLine((0, util_1.wordWrap)(`If the API you're using requires authentication you can supply the required credentials through this method and the library will magically determine how they should be used within your API request.
238
+ description: writer => writer.writeLine(wordWrap(`If the API you're using requires authentication you can supply the required credentials through this method and the library will magically determine how they should be used within your API request.
277
239
 
278
240
  With the exception of OpenID and MutualTLS, it supports all forms of authentication supported by the OpenAPI specification.
279
241
 
@@ -305,7 +267,7 @@ sdk.auth('myApiKey');`)),
305
267
  statements: writer => writer.writeLine('this.core.setServer(url, variables);'),
306
268
  docs: [
307
269
  {
308
- description: writer => writer.writeLine((0, util_1.wordWrap)(`If the API you're using offers alternate server URLs, and server variables, you can tell the SDK which one to use with this method. To use it you can supply either one of the server URLs that are contained within the OpenAPI definition (along with any server variables), or you can pass it a fully qualified URL to use (that may or may not exist within the OpenAPI definition).
270
+ description: writer => writer.writeLine(wordWrap(`If the API you're using offers alternate server URLs, and server variables, you can tell the SDK which one to use with this method. To use it you can supply either one of the server URLs that are contained within the OpenAPI definition (along with any server variables), or you can pass it a fully qualified URL to use (that may or may not exist within the OpenAPI definition).
309
271
 
310
272
  @example <caption>Server URL with server variables</caption>
311
273
  sdk.server('https://{region}.api.example.com/{basePath}', {
@@ -329,12 +291,12 @@ sdk.server('https://eu.api.example.com/v14');`)),
329
291
  });
330
292
  // Export our SDK into the source file.
331
293
  sourceFile.addVariableStatement({
332
- declarationKind: ts_morph_1.VariableDeclarationKind.Const,
294
+ declarationKind: VariableDeclarationKind.Const,
333
295
  declarations: [
334
296
  {
335
297
  name: 'createSDK',
336
298
  initializer: writer => {
337
- // `ts-morph` doesn't have any way to cleanly create an IFEE.
299
+ // `ts-morph` doesn't have any way to cleanly create an IIFE.
338
300
  writer.writeLine('(() => { return new SDK(); })()');
339
301
  return writer;
340
302
  },
@@ -342,52 +304,144 @@ sdk.server('https://eu.api.example.com/v14');`)),
342
304
  ],
343
305
  });
344
306
  sourceFile.addExportAssignment({
345
- // Because CJS targets have `createSDK` exported with `module.exports`, but the TS type side
346
- // of things to work right we need to set this as `export =`. Thankfully `ts-morph` will
347
- // handle this accordingly and still create our JS file with `module.exports` and not
348
- // `export =` -- only TS types will have this export style.
349
- isExportEquals: this.compilerTarget === 'cjs' && this.outputJS,
307
+ // Because we're exporting `createSDK` as an IIFE constant we need to have it exported as
308
+ // `export default createSDK`. `addExportAssignment` by default wants it exported as
309
+ // `export = createSDK`, which will throw TS errors because we may also be exporting types in
310
+ // the `./types.ts` file.
311
+ isExportEquals: false,
350
312
  expression: 'createSDK',
351
313
  });
352
314
  return sourceFile;
353
315
  }
316
+ /**
317
+ * Create the `tsconfig.json` file that will allow this SDK to be compiled for use.
318
+ *
319
+ */
320
+ createTSConfig() {
321
+ const sourceFile = this.project.createSourceFile('tsconfig.json', '');
322
+ const config = {
323
+ compilerOptions: {
324
+ checkJs: true,
325
+ esModuleInterop: true,
326
+ module: 'NodeNext',
327
+ resolveJsonModule: true,
328
+ },
329
+ include: ['./src/**/*'],
330
+ exclude: ['dist'],
331
+ };
332
+ sourceFile.addStatements(JSON.stringify(config, null, 2));
333
+ return sourceFile;
334
+ }
335
+ /**
336
+ * Create the `package.json` file that will ultimately make this SDK available to use.
337
+ *
338
+ */
339
+ createPackageJSON() {
340
+ const sourceFile = this.project.createSourceFile('package.json', '');
341
+ const hasTypes = !!Object.keys(this.schemas).length;
342
+ const info = this.spec.getDefinition().info;
343
+ let pkgVersion = semver.coerce(info.version);
344
+ if (!pkgVersion) {
345
+ // If the version that's in `info.version` isn't compatible with semver NPM won't be able to
346
+ // handle it properly so we need to fallback to something it can.
347
+ pkgVersion = semver.coerce('0.0.0');
348
+ }
349
+ const tsupOptions = {
350
+ cjsInterop: true,
351
+ clean: true,
352
+ dts: true,
353
+ entry: [
354
+ './src/index.ts',
355
+ // If this SDK has schemas and generated types then we should also export those too so
356
+ // they're available to use.
357
+ hasTypes ? './src/types.ts' : '',
358
+ ].filter(Boolean),
359
+ // TODO: figure this out
360
+ // external: ['@readme/api-core'],
361
+ format: ['esm', 'cjs'],
362
+ minify: false,
363
+ shims: true,
364
+ sourcemap: true,
365
+ splitting: true,
366
+ };
367
+ const dependencies = Object.entries(this.requiredPackages)
368
+ .map(([dep, { version }]) => ({ [dep]: version }))
369
+ .reduce((prev, next) => Object.assign(prev, next));
370
+ const pkg = {
371
+ name: `@api/${this.identifier}`,
372
+ version: pkgVersion.version,
373
+ main: './dist/index.js',
374
+ types: './dist/index.d.ts',
375
+ module: './dist/index.mts',
376
+ exports: {
377
+ '.': {
378
+ import: './dist/index.mjs',
379
+ require: './dist/index.js',
380
+ },
381
+ ...(hasTypes
382
+ ? {
383
+ './types': {
384
+ import: './dist/types.d.mts',
385
+ require: './dist/types.d.ts',
386
+ },
387
+ }
388
+ : {}),
389
+ },
390
+ dependencies,
391
+ tsup: tsupOptions,
392
+ };
393
+ sourceFile.addStatements(JSON.stringify(pkg, null, 2));
394
+ return sourceFile;
395
+ }
354
396
  /**
355
397
  * Create our main schemas file. This is where all of the JSON Schema that our TypeScript typing
356
398
  * infrastructure sources its data from. Without this there are no types.
357
399
  *
358
400
  */
359
- createSchemasFile() {
360
- const sourceFile = this.project.createSourceFile('schemas.ts', '');
401
+ createSchemasFile(sourceDirectory) {
402
+ const sourceFile = sourceDirectory.createSourceFile('schemas.ts', '');
403
+ const schemasDir = sourceDirectory.createDirectory('schemas');
361
404
  const sortedSchemas = new Map(Array.from(Object.entries(this.schemas)).sort());
362
405
  Array.from(sortedSchemas).forEach(([schemaName, schema]) => {
363
- sourceFile.addVariableStatement({
364
- declarationKind: ts_morph_1.VariableDeclarationKind.Const,
406
+ const schemaFile = schemasDir.createSourceFile(`${schemaName}.ts`);
407
+ // Because we're chunking our schemas into a `schemas/` directory we need to add imports
408
+ // for these schemas into our main `schemas.ts` file.`
409
+ sourceFile.addImportDeclaration({
410
+ defaultImport: schemaName,
411
+ moduleSpecifier: `./schemas/${schemaName}`,
412
+ });
413
+ let str = JSON.stringify(schema);
414
+ const referencedSchemas = str.match(REF_PLACEHOLDER_REGEX)?.map(s => s.replace(REF_PLACEHOLDER_REGEX, '$1'));
415
+ if (referencedSchemas) {
416
+ referencedSchemas.sort();
417
+ referencedSchemas.forEach(ref => {
418
+ // Because this schema is referenced from another file we need to create an `import`
419
+ // declaration for it.
420
+ schemaFile.addImportDeclaration({
421
+ defaultImport: ref,
422
+ moduleSpecifier: `./${ref}`,
423
+ });
424
+ });
425
+ }
426
+ // Load the schema into the schema file within the `schemas/` directory.
427
+ schemaFile.addVariableStatement({
428
+ declarationKind: VariableDeclarationKind.Const,
365
429
  declarations: [
366
430
  {
367
431
  name: schemaName,
368
432
  initializer: writer => {
369
- /**
370
- * This is the conversion prefix that we add to all `$ref` pointers we find in
371
- * generated JSON Schema.
372
- *
373
- * Because the pointer name is a string we want to have it reference the schema
374
- * constant we're adding into the codegen'd schema file. As there's no way, not even
375
- * using `eval()` in this case, to convert a string to a constant we're prefixing
376
- * them with this so we can later remove it and rewrite the value to a literal.
377
- * eg. `'Pet'` becomes `Pet`.
378
- *
379
- * And because our TypeScript type name generator properly ignores `:`, this is safe
380
- * to prepend to all generated type names.
381
- */
382
- let str = JSON.stringify(schema);
383
- str = str.replace(/"::convert::([a-zA-Z_$\\d]*)"/g, '$1');
433
+ // We can't have `::convert::<schemaName>` variables within these schema files so we
434
+ // need to clean them up.
435
+ str = str.replace(REF_PLACEHOLDER_REGEX, '$1');
384
436
  writer.writeLine(`${str} as const`);
385
437
  return writer;
386
438
  },
387
439
  },
388
440
  ],
389
441
  });
442
+ schemaFile.addStatements(`export default ${schemaName}`);
390
443
  });
444
+ // Export all of our schemas from inside the main `schemas.ts` file.
391
445
  sourceFile.addStatements(`export { ${Array.from(sortedSchemas.keys()).join(', ')} }`);
392
446
  return sourceFile;
393
447
  }
@@ -398,8 +452,8 @@ sdk.server('https://eu.api.example.com/v14');`)),
398
452
  *
399
453
  * @see {@link https://npm.im/json-schema-to-ts}
400
454
  */
401
- createTypesFile() {
402
- const sourceFile = this.project.createSourceFile('types.ts', '');
455
+ createTypesFile(sourceDirectory) {
456
+ const sourceFile = sourceDirectory.createSourceFile('types.ts', '');
403
457
  sourceFile.addImportDeclarations([
404
458
  { defaultImport: 'type { FromSchema }', moduleSpecifier: 'json-schema-to-ts' },
405
459
  { defaultImport: '* as schemas', moduleSpecifier: './schemas' },
@@ -409,18 +463,6 @@ sdk.server('https://eu.api.example.com/v14');`)),
409
463
  });
410
464
  return sourceFile;
411
465
  }
412
- /**
413
- * Add a new JSDoc `@tag` to an existing docblock.
414
- *
415
- */
416
- static addTagToDocblock(docblock, tag) {
417
- const tags = docblock.tags ?? [];
418
- tags.push(tag);
419
- return {
420
- ...docblock,
421
- tags,
422
- };
423
- }
424
466
  /**
425
467
  * Create operation accessors on the SDK.
426
468
  *
@@ -435,18 +477,18 @@ sdk.server('https://eu.api.example.com/v14');`)),
435
477
  // what we surface the main docblock description.
436
478
  docblock.description = writer => {
437
479
  if (description) {
438
- writer.writeLine((0, util_1.docblockEscape)((0, util_1.wordWrap)(description)));
480
+ writer.writeLine(docblockEscape(wordWrap(description)));
439
481
  }
440
482
  else if (summary) {
441
- writer.writeLine((0, util_1.docblockEscape)((0, util_1.wordWrap)(summary)));
483
+ writer.writeLine(docblockEscape(wordWrap(summary)));
442
484
  }
443
485
  writer.newLineIfLastNot();
444
486
  return writer;
445
487
  };
446
488
  if (summary && description) {
447
- docblock = TSGenerator.addTagToDocblock(docblock, {
489
+ docblock = TSGenerator.#addTagToDocblock(docblock, {
448
490
  tagName: 'summary',
449
- text: (0, util_1.docblockEscape)((0, util_1.wordWrap)(summary)),
491
+ text: docblockEscape(wordWrap(summary)),
450
492
  });
451
493
  }
452
494
  }
@@ -488,9 +530,9 @@ sdk.server('https://eu.api.example.com/v14');`)),
488
530
  return `FetchResponse<number, ${responseType}>`;
489
531
  }
490
532
  if (Number(statusPrefix) >= 4) {
491
- docblock = TSGenerator.addTagToDocblock(docblock, {
533
+ docblock = TSGenerator.#addTagToDocblock(docblock, {
492
534
  tagName: 'throws',
493
- text: `FetchError<${status}, ${responseType}>${responseDescription ? (0, util_1.docblockEscape)((0, util_1.wordWrap)(` ${responseDescription}`)) : ''}`,
535
+ text: `FetchError<${status}, ${responseType}>${responseDescription ? docblockEscape(wordWrap(` ${responseDescription}`)) : ''}`,
494
536
  });
495
537
  return false;
496
538
  }
@@ -500,9 +542,9 @@ sdk.server('https://eu.api.example.com/v14');`)),
500
542
  // 400 and 500 status code families are thrown as exceptions so adding them as a possible
501
543
  // return type isn't valid.
502
544
  if (Number(status) >= 400) {
503
- docblock = TSGenerator.addTagToDocblock(docblock, {
545
+ docblock = TSGenerator.#addTagToDocblock(docblock, {
504
546
  tagName: 'throws',
505
- text: `FetchError<${status}, ${responseType}>${responseDescription ? (0, util_1.docblockEscape)((0, util_1.wordWrap)(` ${responseDescription}`)) : ''}`,
547
+ text: `FetchError<${status}, ${responseType}>${responseDescription ? docblockEscape(wordWrap(` ${responseDescription}`)) : ''}`,
506
548
  });
507
549
  return false;
508
550
  }
@@ -641,9 +683,9 @@ sdk.server('https://eu.api.example.com/v14');`)),
641
683
  // As our schemas are dereferenced in the `oas` library we don't want to pollute our
642
684
  // codegen'd schemas file with duplicate schemas.
643
685
  if ('x-readme-ref-name' in s && typeof s['x-readme-ref-name'] !== 'undefined') {
644
- const typeName = (0, util_1.generateTypeName)(s['x-readme-ref-name']);
686
+ const typeName = generateTypeName(s['x-readme-ref-name']);
645
687
  this.addSchemaToExport(s, typeName, typeName);
646
- return `::convert::${typeName}`;
688
+ return `${REF_PLACEHOLDER}${typeName}`;
647
689
  }
648
690
  return s;
649
691
  },
@@ -657,14 +699,14 @@ sdk.server('https://eu.api.example.com/v14');`)),
657
699
  return Object.entries(res)
658
700
  .map(([paramType, schema]) => {
659
701
  let typeName;
660
- if (typeof schema === 'string' && schema.startsWith('::convert::')) {
702
+ if (typeof schema === 'string' && schema.startsWith(REF_PLACEHOLDER)) {
661
703
  // If this schema is a string and has our conversion prefix then we've already created
662
704
  // a type for it.
663
- typeName = schema.replace('::convert::', '');
705
+ typeName = schema.replace(REF_PLACEHOLDER, '');
664
706
  }
665
707
  else {
666
- typeName = (0, util_1.generateTypeName)(operationId, paramType, 'param');
667
- this.addSchemaToExport(schema, typeName, `${(0, util_1.generateTypeName)(operationId)}.${paramType}`);
708
+ typeName = generateTypeName(operationId, paramType, 'param');
709
+ this.addSchemaToExport(schema, typeName, `${generateTypeName(operationId)}.${paramType}`);
668
710
  }
669
711
  return {
670
712
  // Types are prefixed with `types.` because that's how we're importing them from
@@ -691,9 +733,9 @@ sdk.server('https://eu.api.example.com/v14');`)),
691
733
  // As our schemas are dereferenced in the `oas` library we don't want to pollute our
692
734
  // codegen'd schemas file with duplicate schemas.
693
735
  if ('x-readme-ref-name' in s && typeof s['x-readme-ref-name'] !== 'undefined') {
694
- const typeName = (0, util_1.generateTypeName)(s['x-readme-ref-name']);
736
+ const typeName = generateTypeName(s['x-readme-ref-name']);
695
737
  this.addSchemaToExport(s, typeName, `${typeName}`);
696
- return `::convert::${typeName}`;
738
+ return `${REF_PLACEHOLDER}${typeName}`;
697
739
  }
698
740
  return s;
699
741
  },
@@ -709,17 +751,17 @@ sdk.server('https://eu.api.example.com/v14');`)),
709
751
  const res = Object.entries(schemas)
710
752
  .map(([status, { description, schema }]) => {
711
753
  let typeName;
712
- if (typeof schema === 'string' && schema.startsWith('::convert::')) {
754
+ if (typeof schema === 'string' && schema.startsWith(REF_PLACEHOLDER)) {
713
755
  // If this schema is a string and has our conversion prefix then we've already created
714
756
  // a type for it.
715
- typeName = schema.replace('::convert::', '');
757
+ typeName = schema.replace(REF_PLACEHOLDER, '');
716
758
  }
717
759
  else {
718
- typeName = (0, util_1.generateTypeName)(operationId, 'response', status);
760
+ typeName = generateTypeName(operationId, 'response', status);
719
761
  // Because `status` will usually be a number here we need to set the pointer for it
720
762
  // within an `[]` as if we do `FromSchema<typeof schemas.operation.response.200>`,
721
763
  // TypeScript will throw a compilation error.
722
- this.addSchemaToExport(schema, typeName, `${(0, util_1.generateTypeName)(operationId)}.response['${status}']`);
764
+ this.addSchemaToExport(schema, typeName, `${generateTypeName(operationId)}.response['${status}']`);
723
765
  }
724
766
  return {
725
767
  // Types are prefixed with `types.` because that's how we're importing them from
@@ -741,8 +783,20 @@ sdk.server('https://eu.api.example.com/v14');`)),
741
783
  if (this.types.has(typeName)) {
742
784
  return;
743
785
  }
744
- (0, lodash_setwith_1.default)(this.schemas, pointer, schema, Object);
786
+ setWith(this.schemas, pointer, schema, Object);
745
787
  this.types.set(typeName, `FromSchema<typeof schemas.${pointer}>`);
746
788
  }
789
+ /**
790
+ * Add a new JSDoc `@tag` to an existing docblock.
791
+ *
792
+ */
793
+ static #addTagToDocblock(docblock, tag) {
794
+ const tags = docblock.tags ?? [];
795
+ tags.push(tag);
796
+ return {
797
+ ...docblock,
798
+ tags,
799
+ };
800
+ }
747
801
  }
748
- exports.default = TSGenerator;
802
+ //# sourceMappingURL=index.js.map