@specbook/codegen 0.1.0 → 0.3.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@specbook/codegen",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "Turns an OpenAPI or Swagger document into types, examples, schemas and metadata, in Node or in a browser.",
5
5
  "keywords": [
6
6
  "specbook",
@@ -40,7 +40,7 @@
40
40
  "swagger2openapi": "7.0.8"
41
41
  },
42
42
  "devDependencies": {
43
- "@specbook/core": "0.1.0",
43
+ "@specbook/core": "0.3.0",
44
44
  "@types/node": "24.19.2",
45
45
  "@types/swagger2openapi": "7.0.4",
46
46
  "typescript": "7.0.2"
package/src/build.ts CHANGED
@@ -547,7 +547,7 @@ export function generateFromDoc(
547
547
  content: `import { defineOperation } from '${core}'
548
548
  import type * as T from '../types'
549
549
 
550
- ${jsdoc({ description: [...new Set([op.summary, op.description].filter(Boolean))].join('\n\n'), deprecated: op.deprecated })}export const ${id} = defineOperation<{
550
+ ${jsdoc({ description: [...new Set([op.summary, op.description].filter(Boolean))].join('\n\n'), deprecated: op.deprecated })}export const ${id} = /* @__PURE__ */ defineOperation<{
551
551
  params: { ${paramsT} }
552
552
  body: ${bodyT}
553
553
  responses: { ${responseTypes.join('; ')} }
@@ -596,6 +596,8 @@ ${jsdoc({ description: [...new Set([op.summary, op.description].filter(Boolean))
596
596
  content: `${exportsFor(ids).replace(/'\.\/operations/g, "'../operations")}\n`,
597
597
  })
598
598
  }
599
+ // what a run needs of each operation (method, path, encoding…): the committed operations file is built from it
600
+ if (ts) files.push({ path: 'opmeta.json', content: `${JSON.stringify(metas, null, 2)}\n` })
599
601
  files.push({
600
602
  path: 'manifest.json',
601
603
  content: `${JSON.stringify(
package/src/generate.ts CHANGED
@@ -1,3 +1,7 @@
1
+ import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'
2
+ import { tmpdir } from 'node:os'
3
+ import { basename, join } from 'node:path'
4
+ import { gunzipSync } from 'node:zlib'
1
5
  import SwaggerParser from '@apidevtools/swagger-parser'
2
6
  import swagger2openapi from 'swagger2openapi'
3
7
  import {
@@ -5,12 +9,29 @@ import {
5
9
  type GenerateOptions,
6
10
  generateFromDoc,
7
11
  type ManifestOperation,
12
+ type OperationMeta,
8
13
  } from './build'
9
14
 
10
15
  export * from './build'
11
16
 
17
+ /** Runs `use` with a spec the parser can read: a `.gz` one is unpacked to a temporary file of the same kind. */
18
+ export async function withPlainSpec<T>(
19
+ spec: string | object,
20
+ use: (spec: string | object) => Promise<T>,
21
+ ): Promise<T> {
22
+ if (typeof spec !== 'string' || !spec.endsWith('.gz')) return use(spec)
23
+ const dir = await mkdtemp(join(tmpdir(), 'specbook-spec-'))
24
+ try {
25
+ const file = join(dir, basename(spec).replace(/\.gz$/, ''))
26
+ await writeFile(file, gunzipSync(await readFile(spec)))
27
+ return await use(file)
28
+ } finally {
29
+ await rm(dir, { recursive: true, force: true })
30
+ }
31
+ }
32
+
12
33
  async function loadOpenApi3(spec: string | object): Promise<any> {
13
- const bundled: any = await SwaggerParser.bundle(spec as any)
34
+ const bundled: any = await withPlainSpec(spec, (plain) => SwaggerParser.bundle(plain as any))
14
35
  if (bundled.swagger?.startsWith('2')) {
15
36
  const res = await swagger2openapi.convertObj(bundled, { patch: true, warnOnly: true } as any)
16
37
  return res.openapi
@@ -21,7 +42,9 @@ async function loadOpenApi3(spec: string | object): Promise<any> {
21
42
  export async function generate(opts: GenerateOptions): Promise<{
22
43
  files: GeneratedFile[]
23
44
  operations: ManifestOperation[]
45
+ /** what a request needs of each operation at run time, by operation id */
46
+ metas: Record<string, OperationMeta>
24
47
  }> {
25
- const { files, operations } = generateFromDoc(await loadOpenApi3(opts.spec), opts)
26
- return { files, operations }
48
+ const { files, operations, metas } = generateFromDoc(await loadOpenApi3(opts.spec), opts)
49
+ return { files, operations, metas }
27
50
  }
package/src/index.ts CHANGED
@@ -5,6 +5,6 @@ export type {
5
5
  ManifestOperation,
6
6
  OperationMeta,
7
7
  } from './build'
8
- export { generate, generateFromDoc } from './generate'
8
+ export { generate, generateFromDoc, withPlainSpec } from './generate'
9
9
  export { synthesize, tsType } from './schema'
10
- export { generateToDisk } from './write'
10
+ export { generateToDisk, writeAtomically } from './write'
package/src/write.ts CHANGED
@@ -17,7 +17,7 @@ let counter = 0
17
17
  * over the target (atomic on one file system). A file that already holds this content is left alone,
18
18
  * which keeps its timestamp and spares a watching dev server a reload for nothing.
19
19
  */
20
- async function writeAtomically(full: string, content: string): Promise<void> {
20
+ export async function writeAtomically(full: string, content: string): Promise<void> {
21
21
  const current = await readFile(full, 'utf8').catch(() => undefined)
22
22
  if (current === content) return
23
23
  const tmp = `${full}.${process.pid}.${counter++}.tmp`