Skip to content

Commit

Permalink
Merge pull request #133 from seamapi/vendor-code-generators
Browse files Browse the repository at this point in the history
Adds support for parsing front-matter when generating code from Zod's .describe()
  • Loading branch information
phpnode authored Jan 5, 2024
2 parents a1c7a33 + 3491f1d commit 48a65e0
Show file tree
Hide file tree
Showing 12 changed files with 1,379 additions and 228 deletions.
2 changes: 2 additions & 0 deletions apps/example-todo-app/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,5 @@ yarn-error.log*

.nsm
dist

openapi.json
2 changes: 1 addition & 1 deletion apps/example-todo-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"scripts": {
"dev": "next dev",
"build": "rimraf dist && nsm build && tsup ./index.ts --dts --sourcemap && cpy .next dist/.next",
"build:openapi": "nextlove generate-openapi --packageDir . --apiName 'Example TODO API'",
"build:openapi": "nextlove generate-openapi --packageDir . --apiName 'Example TODO API' --outputFile ./openapi.json",
"build:type": "nextlove generate-route-types --packageDir .",
"start": "next start",
"lint": "next lint",
Expand Down
15 changes: 14 additions & 1 deletion apps/example-todo-app/pages/api/todo/add.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,21 @@ import { v4 as uuidv4 } from "uuid"

export const jsonBody = z.object({
id: z.string().uuid().optional().default(uuidv4()),
title: z.string(),
title: z.string().describe(`
---
title: Todo Title
---
# The Title
This is the title of the todo item.
`),
completed: z.boolean().optional().default(false),
unused: z.string().optional().describe(`
---
title: Unused
deprecated: yes, because it's deprecated.
---
This is an unused, deprecated field.
`),
})

export const route_spec = checkRouteSpec({
Expand Down
27 changes: 11 additions & 16 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/eslint-plugin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"eslint-plugin-eslint-plugin": "^3.2.0",
"eslint-plugin-node": "^11.1.0",
"requireindex": "^1.1.0",
"typescript": "^5.0.2"
"typescript": "^5.3.3"
},
"devDependencies": {
"jest": "^29.5.0"
Expand Down
7 changes: 5 additions & 2 deletions packages/nextlove/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,16 @@
"prettier": "^2.0.0 || ^3.0.0",
"react": ">=18",
"react-dom": ">=18",
"typescript": "^5.0.2",
"zod": "^3.0.0"
},
"dependencies": {
"@types/js-yaml": "^4.0.9",
"dedent": "^1.5.1",
"lodash": "^4.17.21",
"openapi3-ts": "^4.1.2",
"zod-to-ts": "^1.1.4"
"js-yaml": "^4.1.0",
"ts-deepmerge": "^6.0.3"
},
"devDependencies": {
"@anatine/zod-openapi": "^2.0.1",
Expand Down Expand Up @@ -84,7 +88,6 @@
"tsup": "^5.6.3",
"turbo": "^1.3.1",
"type-fest": "^3.1.0",
"typescript": "^5.0.2",
"zod": "^3.21.4"
}
}
6 changes: 3 additions & 3 deletions packages/nextlove/src/generators/generate-openapi/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import fs from "node:fs/promises"
import { generateSchema } from "@anatine/zod-openapi"
import {
OpenApiBuilder,
OperationObject,
Expand All @@ -8,6 +7,7 @@ import {
import { SetupParams } from "../../types"
import { z } from "zod"
import { parseRoutesInPackage } from "../lib/parse-routes-in-package"
import { generateSchema } from "../lib/zod-openapi"
import { embedSchemaReferences } from "./embed-schema-references"
import { mapMethodsToFernSdkMetadata } from "./fern-sdk-utils"

Expand Down Expand Up @@ -305,8 +305,8 @@ export async function generateOpenAPI(opts: GenerateOpenAPIOpts) {
}

if (outputFile) {
await fs.writeFile(outputFile, builder.getSpecAsJson())
await fs.writeFile(outputFile, builder.getSpecAsJson(undefined, 2))
}

return builder.getSpecAsJson()
return builder.getSpecAsJson(undefined, 2)
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as fs from "node:fs/promises"
import { parseRoutesInPackage } from "../lib/parse-routes-in-package"
import { zodToTs, printNode } from "zod-to-ts"
import { zodToTs, printNode } from "../lib/zod-to-ts"
import prettier from "prettier"
import { z, ZodEffects, ZodOptional } from "zod"

Expand Down
113 changes: 113 additions & 0 deletions packages/nextlove/src/generators/lib/front-matter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/**
* Front-Matter
*
* Vendored from https://github.com/jxson/front-matter
*
* MIT License
*
* Copyright (c) Jason Campbell ("Author")
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

import { load } from "js-yaml"
const optionalByteOrderMark = "\\ufeff?"
const platform = typeof process !== "undefined" ? process.platform : ""
const pattern =
"^(" +
optionalByteOrderMark +
"(= yaml =|---)" +
"$([\\s\\S]*?)" +
"^(?:\\2|\\.\\.\\.)\\s*" +
"$" +
(platform === "win32" ? "\\r?" : "") +
"(?:\\n)?)"
// NOTE: If this pattern uses the 'g' flag the `regex` variable definition will
// need to be moved down into the functions that use it.
const regex = new RegExp(pattern, "m")

export function parseFrontMatter(
string: string,
options = {}
): { attributes: Record<string, unknown>; body: string; bodyBegin: number } {
string = string || ""
var defaultOptions = { allowUnsafe: false }
options =
options instanceof Object
? { ...defaultOptions, ...options }
: defaultOptions
var lines = string.split(/(\r?\n)/)
if (lines[0] && /= yaml =|---/.test(lines[0])) {
return parse(string)
} else {
return {
attributes: {},
body: string,
bodyBegin: 1,
}
}
}

function computeLocation(match: RegExpExecArray, body: string) {
var line = 1
var pos = body.indexOf("\n")
var offset = match.index + match[0].length

while (pos !== -1) {
if (pos >= offset) {
return line
}
line++
pos = body.indexOf("\n", pos + 1)
}

return line
}

function parse(string: string): {
attributes: Record<string, unknown>
body: string
bodyBegin: number
} {
var match = regex.exec(string)
if (!match) {
return {
attributes: {},
body: string,
bodyBegin: 1,
}
}

var yaml = match[match.length - 1].replace(/^\s+|\s+$/g, "")
var attributes = (load(yaml) || {}) as Record<string, unknown>
var body = string.replace(match[0], "")
var line = computeLocation(match, string)

return {
attributes: attributes,
body: body,
bodyBegin: line,
}
}

export function testFrontMatter(string: string) {
string = string || ""

return regex.test(string)
}
Loading

0 comments on commit 48a65e0

Please sign in to comment.