1
0
mirror of synced 2026-02-04 03:01:17 -05:00

Compare commits

...

90 Commits

Author SHA1 Message Date
Siddharth Suresh
1ecc5ca714 add changeset pre exit to fix error 2023-01-11 23:53:04 +05:30
Siddharth Suresh
80bbe8e849 Create pr-release.yml 2023-01-11 23:36:04 +05:30
Siddharth Suresh
985ce05eba update unit tests 2023-01-11 17:10:47 +05:30
Siddharth Suresh
58d7f053e6 update zod to 3.20.0 in generator 2023-01-11 13:45:18 +05:30
Siddharth Suresh
154a287aa5 pnpm lock 2023-01-11 13:32:04 +05:30
Siddharth Suresh
558d9132c5 Merge branch 'port-subtemplate' of https://github.com/blitz-js/blitz into port-subtemplate 2023-01-11 13:29:30 +05:30
Siddharth Suresh
cc13affe9c add new zod datetime
Co-authored-by: Tobias <tobias@fixmycity.de>
2023-01-11 13:26:54 +05:30
Siddharth Suresh
ad3e202ef0 Merge branch 'main' into port-subtemplate 2023-01-11 13:26:27 +05:30
Siddharth Suresh
1bea6b33b0 cleanup 2023-01-10 20:42:16 +05:30
Siddharth Suresh
484853edc2 fix version 2023-01-10 20:32:46 +05:30
Siddharth Suresh
ae410d5720 with deps 2023-01-10 20:26:14 +05:30
Siddharth Suresh
6c5256b262 try another way 2023-01-10 20:20:11 +05:30
Siddharth Suresh
38d16177d5 fix version 2023-01-10 20:13:51 +05:30
Siddharth Suresh
e335061157 fix path 2023-01-10 20:07:16 +05:30
Siddharth Suresh
2cb2dd2910 fix env location 2023-01-10 20:01:48 +05:30
Siddharth Suresh
2b6bd18629 try agin 2023-01-10 19:55:49 +05:30
Siddharth Suresh
f715d7fc6f Merge branch 'port-subtemplate' of https://github.com/blitz-js/blitz into port-subtemplate 2023-01-10 19:52:48 +05:30
Siddharth Suresh
ba1087c031 Update main.yml 2023-01-10 19:52:01 +05:30
Siddharth Suresh
cb7a08aea4 fix playwright issue 2023-01-10 18:42:21 +05:30
Siddharth Suresh
384ca635de Merge branch 'main' into port-subtemplate 2023-01-10 18:25:48 +05:30
Siddharth Suresh
ae0488335a Merge branch 'main' into port-subtemplate 2023-01-05 17:47:57 +05:30
Siddharth Suresh
3b5bff1b56 Update main.yml 2022-12-26 21:06:22 +05:30
Siddharth Suresh
959f975c3b minor fix 2022-12-25 18:50:56 +05:30
Siddharth Suresh
b4b20d5223 minor fix 2022-12-22 22:08:02 +05:30
Siddharth Suresh
74bcc213c2 initial revert changes to log and add better types 2022-12-22 22:04:49 +05:30
Siddharth Suresh
48c3b74ae9 Merge branch 'main' into port-subtemplate 2022-12-22 18:13:03 +05:30
Siddharth Suresh
4b258b0f01 Update packages/generator/src/generators/template-builders/field-values-builder.ts
Co-authored-by: John Vandivier <vandivier_john@yahoo.com>
2022-12-22 18:12:39 +05:30
Siddharth Suresh
799b7431c4 fix pnpm lock 2022-12-19 23:05:14 +05:30
Siddharth Suresh
d8d047db7e Merge branch 'main' into port-subtemplate 2022-12-19 22:53:35 +05:30
Siddharth Suresh
34a1ffea99 Merge branch 'main' into port-subtemplate 2022-12-14 18:48:30 +05:30
Siddharth Suresh
7dddab6afe preWriteFile and postWriteFile hooks 2022-12-12 13:11:24 +05:30
Siddharth Suresh
d33acf0538 Merge branch 'main' into port-subtemplate 2022-12-10 22:16:09 +05:30
Siddharth Suresh
a2ecc9f20b fix zod version 2022-12-08 17:00:27 +05:30
Siddharth Suresh
fa0d10121d move zod to devDeps 2022-12-08 16:42:11 +05:30
Siddharth Suresh
5c930dcdb3 works without prisma 2022-12-08 16:26:38 +05:30
Siddharth Suresh
4620d00640 remove error log if no prisma schema 2022-12-08 13:42:06 +05:30
Siddharth Suresh
41cf34fea7 add required test and remove error if prisma does not exist 2022-12-08 13:40:54 +05:30
Siddharth Suresh
b8ed7b9af9 fix tests and remove custom log 2022-12-08 13:28:23 +05:30
Siddharth Suresh
9c78c5f3c7 implement suggessions 2022-12-08 13:13:46 +05:30
Siddharth Suresh
fbd9b98d9f Merge branch 'main' into port-subtemplate 2022-12-08 12:13:38 +05:30
Siddharth Suresh
b721bda375 Merge branch 'main' into port-subtemplate 2022-11-26 17:33:49 +05:30
Siddharth Suresh
4918acdf59 update to latest changes 2022-11-23 23:15:48 +05:30
Siddharth Suresh
842934cf15 Merge branch 'main' into port-subtemplate 2022-11-23 16:23:40 +05:30
Siddharth Suresh
01bf82a605 Merge branch 'main' into port-subtemplate 2022-11-09 13:09:55 +05:30
Siddharth Suresh
2e8d899f81 Update apps/toolkit-app/app/blitz-server.ts 2022-11-09 13:08:41 +05:30
Siddharth Suresh
92a1b59118 Merge branch 'main' into port-subtemplate 2022-10-31 10:48:22 +05:30
Siddharth Suresh
fbd8585a3e Merge branch 'main' into port-subtemplate 2022-10-28 10:45:20 +05:30
Siddharth Suresh
646b92a439 Merge branch 'main' into port-subtemplate 2022-10-20 15:14:21 +05:30
Siddharth Suresh
a17ea51595 Merge branch 'main' into port-subtemplate 2022-10-18 15:28:20 +05:30
Siddharth Suresh
3ac434b265 Merge branch 'main' into port-subtemplate 2022-10-17 13:14:38 +05:30
Siddharth Suresh
b3782a21b4 fix default bug for string values in #2863 2022-10-16 12:55:20 +05:30
Siddharth Suresh
c36cfb7d09 fix lock file and add react-final-form select component 2022-10-14 23:22:06 +05:30
Siddharth Suresh
b502c45d74 Merge branch 'main' into port-subtemplate 2022-10-13 11:35:28 +05:30
Siddharth Suresh
f6f5468151 Update blitz-server.ts 2022-10-12 23:36:51 +05:30
Siddharth Suresh
97b64699a3 update tests 2022-10-12 23:30:01 +05:30
Siddharth Suresh
bd9bd166ca update prop type to options for --parent or belongsTo option and add LabelSelectField 2022-10-12 23:19:29 +05:30
Siddharth Suresh
f8fffc122f fix duplicate 2022-10-12 22:08:06 +05:30
Siddharth Suresh
11c4ff3272 Merge branch 'main' into port-subtemplate 2022-10-12 22:01:50 +05:30
Siddharth Suresh
b3953c445f Update getUsers.ts 2022-10-06 19:08:04 +05:30
Siddharth Suresh
2e7cb3a062 fix type 2022-10-06 11:10:05 +05:30
Siddharth Suresh
d6b760bb06 fix --parent flag 2022-10-05 23:13:26 +05:30
Siddharth Suresh
aef3bb2efc cleanup 2022-10-05 22:53:55 +05:30
Siddharth Suresh
bf4f2b9214 initial commit adding dropdown option when using belongTo and parent flag 2022-10-05 22:45:34 +05:30
Siddharth Suresh
e49f138e1d Merge branch 'main' into port-subtemplate 2022-10-04 16:03:00 +05:30
Siddharth Suresh
caac3522ec Merge branch 'main' into port-subtemplate 2022-10-04 14:15:28 +05:30
Siddharth Suresh
56b8656826 generic prisma to work on subsequent runs 2022-10-04 00:48:39 +05:30
Siddharth Suresh
2674f5dcfb update tests 2022-10-04 00:29:07 +05:30
Siddharth Suresh
e3524b8279 make prisma import generic 2022-10-04 00:12:25 +05:30
Siddharth Suresh
49b5339e4c Merge branch 'main' into port-subtemplate 2022-10-03 23:40:34 +05:30
Siddharth Suresh
93e4c29d8a Merge branch 'main' into port-subtemplate 2022-10-03 22:27:01 +05:30
Siddharth Suresh
af3dd373a8 cleanup 2022-10-03 20:43:19 +05:30
Siddharth Suresh
08d0d1e0ca show error when structure of codegen is incorrect using zod 2022-10-03 20:39:36 +05:30
Siddharth Suresh
d7e88dece5 use latest blitz logging setup in generator 2022-10-03 19:13:50 +05:30
Siddharth Suresh
4671586454 fix pnpm-lock 2022-10-03 19:01:51 +05:30
Siddharth Suresh
0fb4439fc5 Merge branch 'main' into port-subtemplate 2022-10-03 18:56:30 +05:30
Siddharth Suresh
900c19159e fix belongsTo not working 2022-10-03 18:55:29 +05:30
Siddharth Suresh
6a07489043 add types for codegen 2022-10-03 18:42:06 +05:30
Siddharth Suresh
4459fbfe4d Update .changeset/silly-peas-work.md 2022-10-03 18:13:31 +05:30
Siddharth Suresh
8d5fa13cb2 Create silly-peas-work.md 2022-10-03 18:09:20 +05:30
Siddharth Suresh
13de89321c Merge branch 'main' into port-subtemplate 2022-10-03 18:07:49 +05:30
Siddharth Suresh
85ef684e31 cleanup 2022-10-03 18:06:32 +05:30
Siddharth Suresh
948acafe6a fix parent generation in createMutation and components, and move existing pages when --parent is called 2022-10-03 18:03:20 +05:30
Siddharth Suresh
27dcfd81e1 cleanup 2022-10-02 15:49:56 +05:30
Siddharth Suresh
20d17b7561 fix blitz genrate not updating values 2022-10-02 11:33:00 +05:30
Siddharth Suresh
4afa6b5b1b make db import generic 2022-10-01 13:54:58 +05:30
Siddharth Suresh
0effae154d remove comments 2022-10-01 00:09:37 +05:30
Siddharth Suresh
aaa0b67861 add unit tests 2022-09-30 23:22:10 +05:30
Siddharth Suresh
ed54550c46 fix unit tests 2022-09-30 23:04:05 +05:30
Siddharth Suresh
ff52627cbb read custom template values from blitz-server 2022-09-30 22:38:39 +05:30
Siddharth Suresh
fe9a423862 initial port 2022-09-30 20:34:01 +05:30
46 changed files with 1814 additions and 376 deletions

View File

@@ -0,0 +1,6 @@
---
"blitz": patch
"@blitzjs/generator": patch
---
Multiple fields forms using templates during generation - TODO

47
.github/workflows/pr-release.yml vendored Normal file
View File

@@ -0,0 +1,47 @@
name: PR-Release
on:
pull_request:
types: [issue_comment]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
changeset:
if: ${{ contains(github.event.pull_request.labels.*.name, 'release/next') && github.event.pull_request.merged == false && contains(github.event.comment.body,'/publish') && github.event.comment.author_association == 'MEMBER' }}
runs-on: ubuntu-latest
steps:
- name: Checkout Repo
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Setup node
uses: actions/setup-node@v2
with:
node-version: 16
cache: "pnpm"
- run: pnpm install --frozen-lockfile
- name: Build
run: pnpm build
- name: Version
run: |
pnpm changeset pre exit
pnpm changeset version --snapshot next-${{ github.event.pull_request.number }}
- name: Publish
run: pnpm changeset publish --tag next@${{ github.event.pull_request.number }}
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Comment on PR
uses: peter-evans/create-or-update-comment@v2
with:
issue-number: ${{ github.event.pull_request.number }}
body: |
The new version of this PR is available as next in npm:
```
npm install <package>@next${{ github.event.pull_request.number }}
```

View File

@@ -0,0 +1,63 @@
import { ComponentPropsWithoutRef, forwardRef, PropsWithoutRef } from "react"
import { useFormContext } from "react-hook-form"
import { ErrorMessage } from "@hookform/error-message"
export interface LabeledSelectFieldProps extends PropsWithoutRef<JSX.IntrinsicElements["select"]> {
/** Field name. */
name: string
/** Field label. */
label: string
/** Field type. Doesn't include radio buttons and checkboxes */
options: any[]
outerProps?: PropsWithoutRef<JSX.IntrinsicElements["div"]>
labelProps?: ComponentPropsWithoutRef<"label">
}
export const LabeledSelectField = forwardRef<HTMLSelectElement, LabeledSelectFieldProps>(
({ label, outerProps, labelProps, name, options, ...props }, ref) => {
const {
register,
formState: { isSubmitting, errors },
} = useFormContext()
return (
<div {...outerProps}>
<label {...labelProps}>
{label}
<select {...register(name)} disabled={isSubmitting} {...props}>
{options &&
options.map((value) => (
<option value={value.id} key={value.id}>
{value[name]}
</option>
))}
</select>
</label>
<ErrorMessage
render={({ message }) => (
<div role="alert" style={{ color: "red" }}>
{message}
</div>
)}
errors={errors}
name={name}
/>
<style jsx>{`
label {
display: flex;
flex-direction: column;
align-items: start;
font-size: 1rem;
}
select {
font-size: 1rem;
padding: 0.25rem 0.5rem;
border-radius: 3px;
border: 1px solid purple;
appearance: none;
margin-top: 0.5rem;
}
`}</style>
</div>
)
}
)

View File

@@ -0,0 +1,31 @@
import { paginate } from "blitz"
import { resolver } from "@blitzjs/rpc"
import db, { Prisma } from "db"
interface GetProjectsInput
extends Pick<Prisma.UserFindManyArgs, "where" | "orderBy" | "skip" | "take"> {}
export default resolver.pipe(
resolver.authorize(),
async ({ where, orderBy, skip = 0, take = 100 }: GetProjectsInput) => {
// TODO: in multi-tenant app, you must add validation to ensure correct tenant
const {
items: users,
hasMore,
nextPage,
count,
} = await paginate({
skip,
take,
count: () => db.user.count({ where }),
query: (paginateArgs) => db.user.findMany({ ...paginateArgs, where, orderBy }),
})
return {
users,
nextPage,
hasMore,
count,
}
}
)

View File

@@ -15,6 +15,10 @@ import {
MutationsGenerator,
ModelGenerator,
QueryGenerator,
ModelName,
modelName,
ModelNames,
modelNames,
customTemplatesBlitzConfig,
} from "@blitzjs/generator"
import {log} from "../../logging"
@@ -35,19 +39,6 @@ enum ResourceType {
CustomTemplates = "custom-templates",
}
function modelName(input: string = "") {
return singleCamel(input)
}
function modelNames(input: string = "") {
return pluralCamel(input)
}
function ModelName(input: string = "") {
return singlePascal(input)
}
function ModelNames(input: string = "") {
return pluralPascal(input)
}
const createCustomTemplates = async () => {
const continuePrompt = await prompts({
type: "confirm",
@@ -66,7 +57,6 @@ const createCustomTemplates = async () => {
const templatesPathValue: string = templatesPath.value
const isTypeScript = await getIsTypeScript()
await customTemplatesBlitzConfig(isTypeScript, templatesPathValue, true) // to run the codemod
log.success(`🚀 Custom templates path added/updated in src/blitz-server file`)
const customTemplatesPath = require("path").join(process.cwd(), templatesPathValue)
const fsExtra = await import("fs-extra")
const blitzGeneratorPath = require.resolve("@blitzjs/generator")
@@ -286,6 +276,7 @@ const generate: CliCommand = async () => {
modelNames: modelNames(singularRootContext),
ModelName: ModelName(singularRootContext),
ModelNames: ModelNames(singularRootContext),
rawParentModelName: args["--parent"],
parentModel: modelName(selectedParent),
parentModels: modelNames(selectedParent),
ParentModel: ModelName(selectedParent),

View File

@@ -2,6 +2,16 @@ import {UrlObject} from "url"
// Context for plugins to declaration merge stuff into
export interface Ctx {}
export interface AuthenticatedMiddlewareCtx {}
export type CodegenField = {
component: string
inputType: string
zodType: string
prismaType: string
default?: string
}
export interface RouteUrlObject extends Pick<UrlObject, "pathname" | "query" | "href"> {
pathname: string
href: string
@@ -32,9 +42,33 @@ export type ResolverConfig = {
}
export type BlitzCliConfig = {
codegen?: {
fieldTypeMap?: Record<
| "string"
| "boolean"
| "int"
| "number"
| "bigint"
| "float"
| "decimal"
| "datetime"
| "uuid"
| "json",
CodegenField
>
}
customTemplates?: string
}
export type CodegenConfig = {
templateDir?: string
fieldTypeMap?: Record<string, CodegenField>
}
export interface RouteUrlObject extends Pick<UrlObject, "pathname" | "query"> {
pathname: string
}
export const isRouteUrlObject = (x: any): x is RouteUrlObject => {
return (
typeof x === "object" &&

View File

@@ -2,7 +2,7 @@ import {BuildConfig} from "unbuild"
const config: BuildConfig = {
entries: ["./src/index"],
externals: ["react"],
externals: ["react", "zod"],
declaration: true,
rollup: {
emitCJS: true,

View File

@@ -30,6 +30,7 @@
"cross-spawn": "7.0.3",
"diff": "5.0.0",
"enquirer": "2.3.6",
"fast-glob": "3.2.12",
"fs-extra": "10.0.1",
"globby": "13.1.2",
"got": "^11.8.1",
@@ -68,7 +69,8 @@
"react": "18.2.0",
"typescript": "^4.8.4",
"unbuild": "0.6.9",
"watch": "1.0.2"
"watch": "1.0.2",
"zod": "3.20.2"
},
"publishConfig": {
"access": "public"

View File

@@ -1,10 +1,13 @@
import * as babel from "@babel/core"
// @ts-ignore TS wants types for this module but none exist
import babelTransformTypescript from "@babel/plugin-transform-typescript"
import {escapePath} from "fast-glob"
import Enquirer from "enquirer"
import {EventEmitter} from "events"
import * as fs from "fs-extra"
import j from "jscodeshift"
import {CommonTemplateValues, IBuilder} from "./generators/template-builders/builder"
import {NullBuilder} from "./generators/template-builders/null-builder"
import {create as createStore, Store} from "mem-fs"
import {create as createEditor, Editor} from "mem-fs-editor"
import * as path from "path"
@@ -14,6 +17,7 @@ import {ConflictChecker} from "./conflict-checker"
import {pipe} from "./utils/pipe"
import {readdirRecursive} from "./utils/readdir-recursive"
import prettier from "prettier"
import {log} from "./utils/log"
const debug = require("debug")("blitz:generator")
export function getProjectRootSync() {
@@ -40,7 +44,7 @@ export const customTemplatesBlitzConfig = async (
if (blitzServer.length > 1) {
throw new Error("Found more than one blitz-server.js or blitz-server.ts in app or src folder")
}
const blitzServerPath = require("path").join(process.cwd(), blitzServer.at(0))
const blitzServerPath = require("path").join(process.cwd(), blitzServer[0])
const userConfigModuleSource = fs.readFileSync(blitzServerPath, {encoding: "utf-8"})
const userConfigModule = j(userConfigModuleSource, {parser: customTsParser})
const program = userConfigModule.get()
@@ -267,7 +271,16 @@ export abstract class Generator<
if (!this.options.destinationRoot) this.options.destinationRoot = process.cwd()
}
abstract getTemplateValues(): Promise<any>
public templateValuesBuilder: IBuilder<T, any> = NullBuilder
async getTemplateValues(): Promise<any> {
const values = await this.templateValuesBuilder.getTemplateValues(this.options)
return values
}
public fieldTemplateRegExp: RegExp = new RegExp(
/({?\/\*\s*template: (.*) \*\/}?|\s*\/\/\s*template: (.*))/g,
)
abstract getTargetDirectory(): string
@@ -295,6 +308,12 @@ export abstract class Generator<
replaceTemplateValues(input: string, templateValues: any) {
let result = input
for (let templateKey in templateValues) {
const token = `__${templateKey}__`
if (result.includes(token)) {
result = result.replace(new RegExp(token, "g"), templateValues[templateKey] as string)
}
}
for (let templateKey in templateValues.fieldTemplateValues) {
const token = `__${templateKey}__`
if (result.includes(token)) {
result = result.replace(new RegExp(token, "g"), templateValues[templateKey])
@@ -319,6 +338,21 @@ export abstract class Generator<
if (codeFileExtensions.test(pathEnding)) {
templatedFile = this.replaceConditionals(inputStr, templateValues, prettierOptions || {})
}
const fieldTemplateString = templatedFile
?.match(this.fieldTemplateRegExp)
?.at(0)
?.replace(this.fieldTemplateRegExp, "$2$3")
if (fieldTemplateString) {
const fieldTemplatePosition = templatedFile.search(this.fieldTemplateRegExp)
templatedFile = [
templatedFile.slice(0, fieldTemplatePosition),
...(templateValues.fieldTemplateValues?.map((values: any) =>
this.replaceTemplateValues(fieldTemplateString, values),
) || []),
templatedFile.slice(fieldTemplatePosition),
].join("")
}
templatedFile = this.replaceTemplateValues(templatedFile, templateValues)
if (!this.useTs && tsExtension.test(pathEnding)) {
templatedFile =
@@ -343,12 +377,21 @@ export abstract class Generator<
try {
templatedFile = this.prettier.format(templatedFile, options)
} catch (error) {
console.warn(`Failed trying to run prettier: ` + error)
console.error(`Failed trying to run prettier: ` + error)
}
}
return templatedFile
}
async preFileWrite(filePath: string): Promise<CommonTemplateValues> {
// allow subclasses to do something before writing a file
return this.getTemplateValues()
}
async postFileWrite(filePath: string, templateValues: CommonTemplateValues): Promise<void> {
// allow subclasses to do something after writing a file
}
async write(): Promise<void> {
debug("Generator.write...")
const sourcePath = this.sourcePath()
@@ -356,33 +399,45 @@ export abstract class Generator<
const additionalFilesToIgnore = this.filesToIgnore()
return ![...alwaysIgnoreFiles, ...additionalFilesToIgnore].includes(name)
})
const prettierOptions = await this.prettier?.resolveConfig(sourcePath)
for (let filePath of paths) {
try {
let pathSuffix = filePath
pathSuffix = path.join(this.getTargetDirectory(), pathSuffix)
const templateValues = await this.getTemplateValues()
let templateValues = await this.getTemplateValues()
const pathSuffix = path.join(this.getTargetDirectory(), filePath)
const sourcePath = this.sourcePath(filePath)
const destinationPath = this.destinationPath(pathSuffix)
let templatedPathSuffix = this.replaceTemplateValues(pathSuffix, templateValues)
const templatedDestinationPath = this.destinationPath(templatedPathSuffix)
const destinationExists = fs.existsSync(templatedDestinationPath)
const newContent = this.process(
this.fs.read(this.sourcePath(filePath), {raw: true}) as any,
pathSuffix,
templateValues,
prettierOptions ?? undefined,
)
this.fs.write(this.destinationPath(pathSuffix), newContent)
templateValues = await this.preFileWrite(templatedPathSuffix)
if (!this.useTs && tsExtension.test(this.destinationPath(pathSuffix))) {
templatedPathSuffix = templatedPathSuffix.replace(tsExtension, ".js")
}
if (templatedPathSuffix !== pathSuffix) {
this.fs.move(this.destinationPath(pathSuffix), this.destinationPath(templatedPathSuffix))
if (destinationExists) {
const newContent = this.process(
this.fs.read(templatedDestinationPath, {raw: true}) as any,
pathSuffix,
templateValues,
prettierOptions ?? undefined,
)
this.fs.write(templatedDestinationPath, newContent)
} else {
this.fs.copy(escapePath(sourcePath), escapePath(destinationPath), {
process: (input) =>
this.process(input, pathSuffix, templateValues, prettierOptions ?? undefined),
})
if (templatedPathSuffix !== pathSuffix) {
this.fs.move(destinationPath, templatedDestinationPath)
}
}
await this.postFileWrite(templatedPathSuffix, templateValues)
} catch (error) {
console.error(`Error generating ${filePath}`)
log.error(`Error generating ${filePath}`)
throw error
}
}

View File

@@ -2,7 +2,7 @@ import spawn from "cross-spawn"
import chalk from "chalk"
import {readJSONSync, writeJson} from "fs-extra"
import {join} from "path"
import username from "username"
import {AppValuesBuilder} from "./template-builders/app-values-builder"
import {Generator, GeneratorOptions, SourceRootType} from "../generator"
import {baseLogger, log} from "../utils/log"
import {fetchLatestVersionsFor} from "../utils/fetch-latest-version-for"
@@ -12,6 +12,12 @@ function assert(condition: any, message: string): asserts condition {
if (!condition) throw new Error(message)
}
export interface AppTemplateValues {
name: string
safeNameSlug: string
username: string | undefined
}
type TemplateConfig = {
path: string
skipForms?: boolean
@@ -46,13 +52,7 @@ export class AppGenerator extends Generator<AppGeneratorOptions> {
return ["jsconfig.json", "package.js.json", "vitest.config.js"]
}
async getTemplateValues() {
return {
name: this.options.appName,
safeNameSlug: this.options.appName.replace(/[^a-zA-Z0-9-_]/g, "-"),
username: await username(),
}
}
templateValuesBuilder = new AppValuesBuilder(this.fs)
getTargetDirectory() {
return ""

View File

@@ -1,17 +1,21 @@
import {Generator, GeneratorOptions, SourceRootType} from "../generator"
import {getTemplateRoot} from "../utils/get-template-root"
import {camelCaseToKebabCase} from "../utils/inflector"
import {
CommonTemplateValues,
createFieldTemplateValues,
FieldValuesBuilder,
ResourceGeneratorOptions,
} from ".."
import {customTsParser, Generator, SourceRootType} from "../generator"
import j from "jscodeshift"
import {
insertImportPagnatedQuery,
insertImportQuery,
insertLabeledSelectField,
updateFormWithParent,
} from "../../src/utils/codemod-utils"
export interface FormGeneratorOptions extends GeneratorOptions {
ModelName: string
ModelNames: string
modelName: string
modelNames: string
parentModel?: string
parentModels?: string
ParentModel?: string
ParentModels?: string
}
export interface FormGeneratorOptions extends ResourceGeneratorOptions {}
export class FormGenerator extends Generator<FormGeneratorOptions> {
sourceRoot: SourceRootType
@@ -22,31 +26,35 @@ export class FormGenerator extends Generator<FormGeneratorOptions> {
static subdirectory = "queries"
private getId(input: string = "") {
if (!input) return input
return `${input}Id`
templateValuesBuilder = new FieldValuesBuilder()
async preFileWrite(): Promise<CommonTemplateValues> {
let templateValues = await this.getTemplateValues()
if (templateValues.parentModel) {
const newFieldTemplateValues = await createFieldTemplateValues(
templateValues.parentModelId,
templateValues.parentModelIdZodType,
true,
)
if (templateValues.fieldTemplateValues) {
templateValues.fieldTemplateValues.push(newFieldTemplateValues)
} else {
templateValues.fieldTemplateValues = [newFieldTemplateValues]
}
}
return templateValues
}
private getParam(input: string = "") {
if (!input) return input
return `[${input}]`
}
// eslint-disable-next-line require-await
async getTemplateValues() {
return {
parentModelId: this.getId(this.options.parentModel),
parentModelParam: this.getParam(this.getId(this.options.parentModel)),
parentModel: this.options.parentModel,
parentModels: this.options.parentModels,
ParentModel: this.options.ParentModel,
ParentModels: this.options.ParentModels,
modelId: this.getId(this.options.modelName),
modelIdParam: this.getParam(this.getId(this.options.modelName)),
modelName: this.options.modelName,
modelNames: this.options.modelNames,
ModelName: this.options.ModelName,
ModelNames: this.options.ModelNames,
async postFileWrite(filePath: string, templateValues: CommonTemplateValues): Promise<void> {
if (templateValues.parentModel && filePath.match(/components/g)) {
let program = j(this.fs.read(filePath), {
parser: customTsParser,
})
program = insertLabeledSelectField(program)
program = insertImportQuery(program, templateValues)
program = insertImportPagnatedQuery(program)
program = updateFormWithParent(program, templateValues)
this.fs.write(filePath, program.toSource())
}
}

View File

@@ -1,12 +1,12 @@
import * as ast from "@mrleebo/prisma-ast"
import {spawn} from "cross-spawn"
import which from "npm-which"
import path from "path"
import {log} from "../utils/log"
import {Generator, GeneratorOptions, SourceRootType} from "../generator"
import {Field} from "../prisma/field"
import {Model} from "../prisma/model"
import {getTemplateRoot} from "../utils/get-template-root"
import {getPrismaSchema} from "../utils/get-prisma-schema"
export interface ModelGeneratorOptions extends GeneratorOptions {
modelName: string
@@ -22,8 +22,7 @@ export class ModelGenerator extends Generator<ModelGeneratorOptions> {
// default subdirectory is /app/[name], we need to back out of there to generate the model
static subdirectory = "../.."
unsafe_disableConflictChecker = true
async getTemplateValues() {}
prisma = true
getTargetDirectory() {
return ""
@@ -41,73 +40,68 @@ export class ModelGenerator extends Generator<ModelGeneratorOptions> {
// eslint-disable-next-line require-await
async write() {
const pkgJson = this.fs.readJSON("package.json", {}) as {[key: string]: any}
const rawSchemaPath = pkgJson?.prisma?.schema || "db/schema.prisma"
const obj = getPrismaSchema(this.fs)
if (typeof obj !== "boolean") {
const {schema, schemaPath} = obj
const {modelName, extraArgs, dryRun} = this.options
let updatedOrCreated = "created"
const schemaPath = path.resolve(rawSchemaPath)
if (!this.fs.exists(schemaPath)) {
throw new Error("Prisma schema file was not found")
}
let fieldPromises = (
extraArgs.length === 1 && extraArgs[0]?.includes(" ") ? extraArgs[0]?.split(" ") : extraArgs
).map((input) => Field.parse(input, schema))
let fields = (await Promise.all(fieldPromises)).flatMap((fieldArray) => fieldArray)
const modelDefinition = new Model(modelName, fields)
let schema: ast.Schema
try {
schema = ast.getSchema(this.fs.read(schemaPath))
} catch (err) {
console.error(`Failed to parse ${rawSchemaPath} file`)
throw err
}
const {modelName, extraArgs, dryRun} = this.options
let updatedOrCreated = "created"
let fields = (
extraArgs.length === 1 && extraArgs[0]?.includes(" ") ? extraArgs[0]?.split(" ") : extraArgs
).flatMap((input) => Field.parse(input, schema))
const modelDefinition = new Model(modelName, fields)
let model: ast.Model | undefined
if (!dryRun) {
model = schema.list.find(function (component): component is ast.Model {
return component.type === "model" && component.name === modelDefinition.name
})
try {
if (model) {
for (const field of fields) field.appendTo(model)
this.fs.write(schemaPath, ast.printSchema(schema))
updatedOrCreated = "updated"
} else {
model = modelDefinition.appendTo(schema)
this.fs.write(schemaPath, ast.printSchema(schema))
let model: ast.Model | undefined
if (!dryRun) {
model = schema.list.find(function (component): component is ast.Model {
return component.type === "model" && component.name === modelDefinition.name
})
try {
if (model) {
for (const field of fields) field.appendTo(model)
this.fs.write(schemaPath, ast.printSchema(schema))
updatedOrCreated = "updated"
} else {
model = modelDefinition.appendTo(schema)
this.fs.write(schemaPath, ast.printSchema(schema))
}
} catch (err) {
console.error(`Failed to apply changes to model '${modelDefinition.name}'`)
throw err
}
} catch (err) {
console.error(`Failed to apply changes to model '${modelDefinition.name}'`)
throw err
}
}
if (model) {
console.log("\n")
console.log(
`Model '${modelDefinition.name}'${
dryRun ? "" : ` ${updatedOrCreated} in schema.prisma`
}:\n`,
)
ast
.printSchema({type: "schema", list: [model]})
.split("\n")
.map(log.progress)
console.log("\n")
if (model) {
console.log("\n")
console.log(
`Model '${modelDefinition.name}'${
dryRun ? "" : ` ${updatedOrCreated} in schema.prisma`
}:\n`,
)
ast
.printSchema({type: "schema", list: [model]})
.split("\n")
.map(log.progress)
console.log("\n")
}
} else {
this.prisma = false
}
}
async postWrite() {
const shouldMigrate = await this.prismaMigratePrompt()
if (shouldMigrate) {
await new Promise<void>((res, rej) => {
const prismaBin = which(process.cwd()).sync("prisma")
const child = spawn(prismaBin, ["migrate", "dev"], {stdio: "inherit"})
child.on("exit", (code) => (code === 0 ? res() : rej()))
})
if (this.prisma) {
const prismaBin = which(process.cwd()).sync("prisma")
//@ts-ignore
spawn.sync(prismaBin, ["format"], {stdio: "inherit"})
const shouldMigrate = await this.prismaMigratePrompt()
if (shouldMigrate) {
await new Promise<void>((res, rej) => {
const child = spawn(prismaBin, ["migrate", "dev"], {stdio: "inherit"})
child.on("exit", (code) => (code === 0 ? res() : rej()))
})
}
}
}
}

View File

@@ -1,17 +1,16 @@
import {Generator, GeneratorOptions, SourceRootType} from "../generator"
import {getTemplateRoot} from "../utils/get-template-root"
import {camelCaseToKebabCase} from "../utils/inflector"
import j from "jscodeshift"
import {
CommonTemplateValues,
createFieldTemplateValues,
FieldValuesBuilder,
ResourceGeneratorOptions,
} from ".."
import {customTsParser, Generator, SourceRootType} from "../generator"
import {replaceImportDbWithPrismaFolder} from "../../src/utils/codemod-utils"
export interface MutationsGeneratorOptions extends GeneratorOptions {
ModelName: string
ModelNames: string
modelName: string
modelNames: string
parentModel?: string
parentModels?: string
ParentModel?: string
ParentModels?: string
}
export interface MutationsGeneratorOptions extends ResourceGeneratorOptions {}
export class MutationsGenerator extends Generator<MutationsGeneratorOptions> {
sourceRoot: SourceRootType
@@ -21,32 +20,31 @@ export class MutationsGenerator extends Generator<MutationsGeneratorOptions> {
}
static subdirectory = "mutations"
private getId(input: string = "") {
if (!input) return input
return `${input}Id`
}
templateValuesBuilder = new FieldValuesBuilder(this.fs)
private getParam(input: string = "") {
if (!input) return input
return `[${input}]`
}
// eslint-disable-next-line require-await
async getTemplateValues() {
return {
parentModelId: this.getId(this.options.parentModel),
parentModelParam: this.getParam(this.getId(this.options.parentModel)),
parentModel: this.options.parentModel,
parentModels: this.options.parentModels,
ParentModel: this.options.ParentModel,
ParentModels: this.options.ParentModels,
modelId: this.getId(this.options.modelName),
modelIdParam: this.getParam(this.getId(this.options.modelName)),
modelName: this.options.modelName,
modelNames: this.options.modelNames,
ModelName: this.options.ModelName,
ModelNames: this.options.ModelNames,
async preFileWrite(filePath: string): Promise<CommonTemplateValues> {
let templateValues = await this.getTemplateValues()
if (templateValues.parentModel && filePath.match(/.*mutations.*create.*/g)) {
const newFieldTemplateValues = await createFieldTemplateValues(
templateValues.parentModelId,
templateValues.parentModelIdZodType,
true,
)
if (templateValues.fieldTemplateValues) {
templateValues.fieldTemplateValues.push(newFieldTemplateValues)
} else {
templateValues.fieldTemplateValues = [newFieldTemplateValues]
}
}
if (this.fs.exists(filePath)) {
let program = j(this.fs.read(filePath) as any, {
parser: customTsParser,
})
program = replaceImportDbWithPrismaFolder(program)
this.fs.write(filePath, program.toSource())
}
return templateValues
}
getTargetDirectory() {

View File

@@ -1,19 +1,12 @@
import {Generator, GeneratorOptions, SourceRootType} from "../generator"
import {CommonTemplateValues, FieldValuesBuilder, ResourceGeneratorOptions} from ".."
import {Generator, SourceRootType} from "../generator"
import {getTemplateRoot} from "../utils/get-template-root"
import {camelCaseToKebabCase} from "../utils/inflector"
import {spawn} from "cross-spawn"
import which from "npm-which"
import * as fs from "fs-extra"
export interface PageGeneratorOptions extends GeneratorOptions {
ModelName: string
ModelNames: string
modelName: string
modelNames: string
parentModel?: string
parentModels?: string
ParentModel?: string
ParentModels?: string
}
export interface PageGeneratorOptions extends ResourceGeneratorOptions {}
export class PageGenerator extends Generator<PageGeneratorOptions> {
sourceRoot: SourceRootType
@@ -23,34 +16,7 @@ export class PageGenerator extends Generator<PageGeneratorOptions> {
}
static subdirectory = "../../.."
private getId(input: string = "") {
if (!input) return input
return `${input}Id`
}
private getParam(input: string = "") {
if (!input) return input
return `[${input}]`
}
// eslint-disable-next-line require-await
async getTemplateValues() {
return {
parentModelId: this.getId(this.options.parentModel),
parentModelParam: this.getParam(this.getId(this.options.parentModel)),
parentModel: this.options.parentModel,
parentModels: this.options.parentModels,
ParentModel: this.options.ParentModel,
ParentModels: this.options.ParentModels,
modelId: this.getId(this.options.modelName),
modelIdParam: this.getParam(this.getId(this.options.modelName)),
modelName: this.options.modelName,
modelNames: this.options.modelNames,
ModelName: this.options.ModelName,
ModelNames: this.options.ModelNames,
modelNamesPath: this.getModelNamesPath(),
}
}
templateValuesBuilder = new FieldValuesBuilder(this.fs)
getModelNamesPath() {
const kebabCaseContext = this.options.context
@@ -68,6 +34,28 @@ export class PageGenerator extends Generator<PageGeneratorOptions> {
return `src/pages/${parent}${kebabCaseModelName}`
}
async preFileWrite(): Promise<CommonTemplateValues> {
const templateValues = await this.getTemplateValues()
const targetDirectory = this.getTargetDirectory().replace(
"__parentModelParam__",
templateValues.parentModelParam,
)
if (templateValues.parentModel) {
const modelPages = fs.existsSync(
`src/pages/${camelCaseToKebabCase(templateValues.modelNames)}`,
)
if (modelPages) {
if (!fs.existsSync(targetDirectory)) {
fs.moveSync(
`src/pages/${camelCaseToKebabCase(templateValues.modelNames)}`,
targetDirectory,
)
}
}
}
return templateValues
}
async postWrite() {
await new Promise<void>((res, rej) => {
const blitzBin = which(process.cwd()).sync("blitz")

View File

@@ -1,17 +1,11 @@
import {Generator, GeneratorOptions, SourceRootType} from "../generator"
import {CommonTemplateValues, FieldValuesBuilder, ResourceGeneratorOptions} from ".."
import {customTsParser, Generator, SourceRootType} from "../generator"
import {getTemplateRoot} from "../utils/get-template-root"
import {camelCaseToKebabCase} from "../utils/inflector"
import j from "jscodeshift"
import {replaceImportDbWithPrismaFolder} from "../../src/utils/codemod-utils"
export interface QueriesGeneratorOptions extends GeneratorOptions {
ModelName: string
ModelNames: string
modelName: string
modelNames: string
parentModel?: string
parentModels?: string
ParentModel?: string
ParentModels?: string
}
export interface QueriesGeneratorOptions extends ResourceGeneratorOptions {}
export class QueriesGenerator extends Generator<QueriesGeneratorOptions> {
sourceRoot: SourceRootType
@@ -21,32 +15,19 @@ export class QueriesGenerator extends Generator<QueriesGeneratorOptions> {
}
static subdirectory = "queries"
private getId(input: string = "") {
if (!input) return input
return `${input}Id`
}
templateValuesBuilder = new FieldValuesBuilder(this.fs)
private getParam(input: string = "") {
if (!input) return input
return `[${input}]`
}
// eslint-disable-next-line require-await
async getTemplateValues() {
return {
parentModelId: this.getId(this.options.parentModel),
parentModelParam: this.getParam(this.getId(this.options.parentModel)),
parentModel: this.options.parentModel,
parentModels: this.options.parentModels,
ParentModel: this.options.ParentModel,
ParentModels: this.options.ParentModels,
modelId: this.getId(this.options.modelName),
modelIdParam: this.getParam(this.getId(this.options.modelName)),
modelName: this.options.modelName,
modelNames: this.options.modelNames,
ModelName: this.options.ModelName,
ModelNames: this.options.ModelNames,
async preFileWrite(filePath: string): Promise<CommonTemplateValues> {
let templateValues = await this.getTemplateValues()
if (this.fs.exists(filePath)) {
let program = j(this.fs.read(filePath) as any, {
parser: customTsParser,
})
program = replaceImportDbWithPrismaFolder(program)
this.fs.write(filePath, program.toSource())
}
return templateValues
}
getTargetDirectory() {

View File

@@ -0,0 +1,14 @@
import username from "username"
import {AppGeneratorOptions, AppTemplateValues} from "../.."
import {Builder} from "./builder"
export class AppValuesBuilder extends Builder<AppGeneratorOptions, AppTemplateValues> {
public async getTemplateValues(options: AppGeneratorOptions): Promise<AppTemplateValues> {
const values = {
name: options.appName,
safeNameSlug: options.appName.replace(/[^a-zA-Z0-9-_]/g, "-"),
username: await username(),
}
return values
}
}

View File

@@ -0,0 +1,139 @@
import {Editor} from "mem-fs-editor"
import {GeneratorOptions} from "../../generator"
import {getCodegen, getResourceValueFromCodegen} from "../../utils/get-codegen"
import {CodegenField} from "../../utils/get-codegen"
import {
addSpaceBeforeCapitals,
camelCaseToKebabCase,
singleCamel,
singlePascal,
} from "../../utils/inflector"
export interface IBuilder<T, U> {
getTemplateValues(Options: T): Promise<U>
}
const defaultFieldConfig: CodegenField = {
component: "LabeledTextField",
inputType: "text",
zodType: "string",
prismaType: "String",
}
export async function createFieldTemplateValues(
valueName: string | undefined,
typeName: string | undefined,
parent = false,
): Promise<{[x: string]: any}> {
{
let values: {[x: string]: any} = {
attributeName: singleCamel(valueName),
fieldName: singleCamel(valueName),
FieldName: singlePascal(valueName),
field_name: addSpaceBeforeCapitals(`${valueName}`).toLocaleLowerCase(), // field name
Field_name: singlePascal(addSpaceBeforeCapitals(`${valueName}`).toLocaleLowerCase()), // Field name
Field_Name: singlePascal(addSpaceBeforeCapitals(`${valueName}`)), // Field Name
}
const codegen = await getCodegen()
// iterate over resources defined for this field type
const fieldConfig =
codegen.fieldTypeMap?.[typeName as keyof typeof codegen.fieldTypeMap] || defaultFieldConfig
values = {...values, ...fieldConfig}
if (parent) {
values.inputType = singleCamel(valueName).replace("Id", "s")
values.component = "LabeledSelectField"
values.fieldName = "id"
return values
}
values.inputType = fieldConfig.inputType
return values
}
}
export interface ResourceGeneratorOptions extends GeneratorOptions {
ModelName: string
ModelNames: string
modelName: string
modelNames: string
rawParentModelName?: string
parentModel?: string
parentModels?: string
ParentModel?: string
ParentModels?: string
extraArgs?: string[]
}
export interface CommonTemplateValues {
prismaFolder?: string
parentModelId: string
parentModelParam: string
parentModel?: string
parentModels?: string
ParentModel?: string
ParentModels?: string
parentModelIdZodType?: string
modelId: string
modelIdZodType?: string
modelIdParam: string
modelName: string
modelNames: string
ModelName: string
ModelNames: string
modelNamesPath: string
fieldTemplateValues?: {[x: string]: any}
}
export abstract class Builder<T, U> implements IBuilder<T, U> {
public constructor(fs?: Editor) {
this.fs = fs
}
abstract getTemplateValues(Options: T): Promise<U>
public fs: Editor | undefined
public defaultFieldConfig = defaultFieldConfig
public getId(input: string = "") {
if (!input) return input
return `${input}Id`
}
public getParam(input: string = "") {
if (!input) return input
return `[${input}]`
}
public getModelNamesPath(context: string | undefined, modelNames: string) {
const kebabCaseContext = context ? `${camelCaseToKebabCase(context)}/` : ""
const kebabCaseModelNames = camelCaseToKebabCase(modelNames)
return kebabCaseContext + kebabCaseModelNames
}
// eslint-disable-next-line require-await
public async getZodType(type: string = "") {
return getResourceValueFromCodegen(type, "zodType")
}
// eslint-disable-next-line require-await
public async getComponentForType(type: string = "") {
return getResourceValueFromCodegen(type, "component")
}
// eslint-disable-next-line require-await
public async getInputType(type: string = "") {
return getResourceValueFromCodegen(type, "inputType")
}
// eslint-disable-next-line require-await
public async getFieldTemplateValues(args: string[]) {
const argsPromises = args.map(async (arg: string) => {
let [valueName, typeName] = arg.split(":")
if (typeName?.includes("?")) {
typeName = typeName.replace("?", "")
}
const values = await createFieldTemplateValues(valueName, typeName)
return values
})
return Promise.all(argsPromises)
}
}

View File

@@ -0,0 +1,120 @@
import * as ast from "@mrleebo/prisma-ast"
import {create as createStore} from "mem-fs"
import {create as createEditor, Editor} from "mem-fs-editor"
import {getResourceValueFromCodegen} from "../../utils/get-codegen"
import {getPrismaSchema} from "../../utils/get-prisma-schema"
import {ModelName, modelName, ModelNames, modelNames} from "../../utils/model-names"
import {Builder, CommonTemplateValues, ResourceGeneratorOptions} from "./builder"
export class FieldValuesBuilder extends Builder<ResourceGeneratorOptions, CommonTemplateValues> {
private getEditor = (): Editor => {
if (this.fs !== undefined) {
return this.fs
}
const store = createStore()
this.fs = createEditor(store)
return this.fs
}
// eslint-disable-next-line require-await
public async getTemplateValues(options: ResourceGeneratorOptions): Promise<CommonTemplateValues> {
const prismaFolder = getPrismaSchema(this.getEditor())
let values: CommonTemplateValues = {
prismaFolder: typeof prismaFolder !== "boolean" ? prismaFolder.dbFolder : "db",
parentModelId: this.getId(options.parentModel),
parentModelIdZodType: undefined,
parentModelParam: this.getParam(this.getId(options.parentModel)),
parentModel: options.parentModel,
parentModels: options.parentModels,
ParentModel: options.ParentModel,
ParentModels: options.ParentModels,
modelId: this.getId(options.modelName),
modelIdZodType: "number",
modelIdParam: this.getParam(this.getId(options.modelName)),
modelName: options.modelName,
modelNames: options.modelNames,
ModelName: options.ModelName,
ModelNames: options.ModelNames,
modelNamesPath: this.getModelNamesPath(options.context, options.modelNames),
}
if (options.extraArgs) {
// specialArgs - these are arguments like 'id' or 'belongsTo', which are not meant to
// be processed as fields but have their own special logic
const specialArgs: {[key in string]: string} = {}
const processSpecialArgs: Promise<void>[] = options.extraArgs.map(async (arg) => {
const [valueName, typeName] = arg.split(":")
if (valueName === "id") {
values.modelIdZodType = await this.getZodType(typeName)
specialArgs[arg] = "present"
}
if (valueName === "belongsTo") {
specialArgs[arg] = "present"
process.env.parentModel = typeName
options.rawParentModelName = typeName
options.parentModel = modelName(typeName)
options.parentModels = modelNames(typeName)
options.ParentModel = ModelName(typeName)
options.ParentModels = ModelNames(typeName)
values.parentModelId = this.getId(modelName(typeName))
}
})
await Promise.all(processSpecialArgs)
// Filter out special args by making sure the argument isn't present in the list
const nonSpecialArgs = options.extraArgs.filter((arg) => specialArgs[arg] !== "present")
// Get the parent model it type if options.parentModel exists
if (options.parentModel !== undefined && options.parentModel.length > 0) {
const obj = getPrismaSchema(this.getEditor())
if (typeof obj !== "boolean") {
const schema = obj.schema
// O(N) - N is total ast Blocks
const model = schema.list.find(function (component): component is ast.Model {
return component.type === "model" && component.name === options.rawParentModelName
})
if (model !== undefined) {
// O(N) - N is number of properties in parent model
const idField = model.properties.find(function (property): property is ast.Field {
return (
property.type === "field" &&
property.attributes?.findIndex((attr) => attr.name === "id") !== -1
)
})
// TODO: Do we want a map between prisma types and "user types", we can then use that map instead of these conditionals
// We have a map from "user types" (which are what users type into the blitz generate command)
// to primsa type and other types, but we dont have a reverse map 1:1. This is because we lose
// some information for certain maps. E.g.: fieldname:uuid will be converted into a Prisma field with
// the String type, and the uuid portion is added to a decorator at the end of the field.
// This means it is more complicated to extract the original "user specified type" than creating a reverse map
if (idField?.fieldType === "Int") {
// TODO: Check if ints have decorators that make them a different type, like Bigint, etc.
// And see if that has to map to a different user specified type
values.parentModelIdZodType = await getResourceValueFromCodegen("int", "zodType")
} else if (idField?.fieldType === "String") {
if (
idField.attributes?.find(
(attr) =>
attr.name === "default" &&
attr.args?.findIndex((arg) => arg.value === "uuid") !== -1,
)
) {
values.parentModelIdZodType = await getResourceValueFromCodegen("uuid", "zodType")
} else {
values.parentModelIdZodType = await getResourceValueFromCodegen("string", "zodType")
}
}
}
} else {
// TODO: handle scenario where parent wasnt found in existing schema. Should we throw an error, or a warning asking the user to verify that the parent model exists?
}
}
if (nonSpecialArgs.length > 0) {
const ftv = await this.getFieldTemplateValues(nonSpecialArgs)
return {...values, fieldTemplateValues: ftv}
}
}
return values
}
}

View File

@@ -0,0 +1,8 @@
import {IBuilder} from "./builder"
export const NullBuilder: IBuilder<any, any> = {
// eslint-disable-next-line require-await
getTemplateValues: async () => {
return {}
},
}

View File

@@ -7,6 +7,11 @@ export * from "./generators/queries-generator"
export * from "./generators/query-generator"
export * from "./generators/form-generator"
export * from "./generator"
export * from "./generators/template-builders/builder"
export * from "./generators/template-builders/null-builder"
export * from "./generators/template-builders/app-values-builder"
export * from "./generators/template-builders/field-values-builder"
export * from "./utils/model-names"
export * from "./conflict-checker"
export {getLatestVersion} from "./utils/get-latest-version"
export * from "./utils/npm-fetch"
@@ -18,4 +23,5 @@ export {
pluralPascal,
capitalize,
uncapitalize,
addSpaceBeforeCapitals,
} from "./utils/inflector"

View File

@@ -1,5 +1,7 @@
import * as ast from "@mrleebo/prisma-ast"
import {getResourceConfigFromCodegen} from "../utils/get-codegen"
import {capitalize, singlePascal, uncapitalize} from "../utils/inflector"
const debug = require("debug")("blitz:field")
export enum FieldType {
Boolean = "Boolean",
@@ -54,7 +56,8 @@ export class Field {
relationToFields?: string[]
// 'name:type?[]:attribute' => Field
static parse(input: string, schema?: ast.Schema): Field[] {
static async parse(input: string, schema?: ast.Schema): Promise<Field[]> {
debug(`parsing "Field" for input ${input}`)
const [_fieldName, _fieldType = "String", _attribute] = input.split(":")
let attribute = _attribute as string
let fieldName = uncapitalize(_fieldName as string)
@@ -73,6 +76,16 @@ export class Field {
fieldType = fieldType.replace("?", "")
isRequired = false
}
const {prismaType, default: defaultConfigValue} =
(await Field.getConfigForPrismaType(fieldType)) ?? {}
if (prismaType) {
fieldType = prismaType
}
if (defaultConfigValue) {
attribute = `default=${defaultConfigValue}`
}
if (fieldType.includes("[]")) {
fieldType = fieldType.replace("[]", "")
fieldName = uncapitalize(fieldName)
@@ -86,7 +99,7 @@ export class Field {
// has modified fieldName
if (typeof _fieldName === "string" && isRelation(_fieldName)) {
// this field is an object type, not a scalar type
const relationType = Relation[_fieldName]
const relationType = Relation[_fieldName as keyof typeof Relation]
// translate the type into the name since they should stay in sync
fieldName = uncapitalize(fieldType)
fieldType = singlePascal(fieldType)
@@ -159,6 +172,10 @@ export class Field {
}
}
public static getConfigForPrismaType = async (fieldType: string) => {
return await getResourceConfigFromCodegen(fieldType.toLowerCase())
}
constructor(name: string, options: FieldArgs) {
if (!name) throw new MissingFieldNameError("[PrismaField]: A field name is required")
if (!options.type) {
@@ -214,7 +231,12 @@ export class Field {
args: [
{
type: "attributeArgument",
value: typeof this.default === "object" ? `${this.default.name}()` : String(this.default),
value:
typeof this.default === "object"
? `${this.default.name}()`
: this.type === FieldType.String
? `"${this.default}"`
: this.default,
},
],
}

View File

@@ -0,0 +1,155 @@
import j, {Collection} from "jscodeshift"
import {capitalize, singleCamel} from "./inflector"
export function replaceImportDbWithPrismaFolder(program: Collection<any>) {
const importDb = program.find(j.ImportDeclaration).filter((path) => {
return path.value.source.value === "db"
})
importDb.replaceWith((path) => {
path.value.source.value = "__prismaFolder__"
return path.value
})
return program
}
export function insertLabeledSelectField(program: Collection<any>) {
const importExists = program
.find(j.ImportDeclaration)
.filter((path) => path.node.source.value === "src/core/components/LabelSelectField")
.size()
if (!importExists) {
program
.find(j.ImportDeclaration)
.at(-1)
.insertAfter(
j.importDeclaration(
[j.importSpecifier(j.identifier("LabeledSelectField"))],
j.literal("src/core/components/LabelSelectField"),
),
)
}
return program
}
export function insertImportQuery(program: Collection<any>, templateValues: any) {
program
.find(j.ImportDeclaration)
.at(-1)
.insertAfter(
j.importDeclaration(
[
j.importDefaultSpecifier(
j.identifier(
singleCamel("get" + capitalize(templateValues.parentModelId)).replace("Id", "s"),
),
),
],
j.literal(
`src/${templateValues.parentModelId.replace("Id", "s")}/queries/${
"get" + capitalize(templateValues.parentModelId).replace("Id", "s")
}`,
),
),
)
return program
}
export function insertImportPagnatedQuery(program: Collection<any>) {
const importPaginatedQuery = program
.find(j.ImportDeclaration)
.filter((path) => {
if (path.node.specifiers) {
return (
path.node.source.value === "@blitzjs/rpc" &&
path.node.specifiers.filter((specifier) => {
if (specifier.type === "ImportSpecifier") {
return specifier.imported.name === "usePaginatedQuery"
} else {
return false
}
}).length > 0
)
} else {
return false
}
})
.size()
if (!importPaginatedQuery) {
program
.find(j.ImportDeclaration)
.at(-1)
.insertAfter(
j.importDeclaration(
[j.importSpecifier(j.identifier("usePaginatedQuery"))],
j.literal("@blitzjs/rpc"),
),
)
}
return program
}
export function updateFormWithParent(program: Collection<any>, templateValues: any) {
const modalFormSuspense = program.find(j.FunctionDeclaration).filter((path) => {
if (path.node.id?.name) {
return path.node.id.name.includes("Form")
}
return false
})
if (modalFormSuspense.size()) {
modalFormSuspense
.find(j.ReturnStatement)
.at(-1)
.insertBefore(
j.variableDeclaration("const", [
j.variableDeclarator(
j.arrayPattern([
j.objectPattern([
j.objectProperty(
j.identifier(`${templateValues.parentModelId.replace("Id", "s")}`),
j.identifier(`${templateValues.parentModelId.replace("Id", "s")}`),
),
]),
]),
j.callExpression(j.identifier("usePaginatedQuery"), [
j.identifier("get" + capitalize(templateValues.parentModelId).replace("Id", "s")),
j.objectExpression([
j.objectProperty(
j.identifier("orderBy"),
j.objectExpression([
j.objectProperty(j.identifier("id"), j.stringLiteral("asc")),
]),
),
]),
]),
),
]),
)
modalFormSuspense
.find(j.JSXElement)
.filter((path) => {
return (
path.node.openingElement.name.type === "JSXIdentifier" &&
path.node.openingElement.name.name === "LabeledSelectField"
)
})
.forEach((path) => {
let value = ""
path.node.openingElement.attributes = path.node.openingElement.attributes?.filter(
(attribute) => {
if (attribute.type === "JSXAttribute" && attribute.name.name === "type") {
if (attribute.value?.type === "StringLiteral") {
value = attribute.value.value
}
return false
}
return true
},
)
path.node.openingElement.attributes?.push(
j.jsxAttribute(j.jsxIdentifier("options"), j.jsxExpressionContainer(j.identifier(value))),
)
})
}
return program
}

View File

@@ -0,0 +1,165 @@
import {log} from "../utils/log"
import * as z from "zod"
export type CodegenField = {
component: string
inputType: string
zodType: string
prismaType: string
default?: string
}
export type CodegenConfig = {
fieldTypeMap?: Record<
| "string"
| "boolean"
| "int"
| "number"
| "bigint"
| "float"
| "decimal"
| "datetime"
| "uuid"
| "json",
CodegenField
>
}
const CodegenSchema = z.object({
fieldTypeMap: z.record(
z.union([
z.literal("string"),
z.literal("boolean"),
z.literal("int"),
z.literal("number"),
z.literal("bigint"),
z.literal("float"),
z.literal("decimal"),
z.literal("datetime"),
z.literal("uuid"),
z.literal("json"),
]),
z.object({
component: z.string(),
inputType: z.string(),
zodType: z.string(),
prismaType: z.string(),
default: z.string().optional(),
}),
),
})
export const defaultCodegenConfig: CodegenConfig = {
fieldTypeMap: {
string: {
component: "LabeledTextField",
inputType: "text",
zodType: "string",
prismaType: "String",
},
boolean: {
component: "LabeledTextField",
inputType: "text",
zodType: "boolean",
prismaType: "Boolean",
},
int: {
component: "LabeledTextField",
inputType: "number",
zodType: "number",
prismaType: "Int",
},
number: {
component: "LabeledTextField",
inputType: "number",
zodType: "number",
prismaType: "Int",
},
bigint: {
component: "LabeledTextField",
inputType: "number",
zodType: "number",
prismaType: "BigInt",
},
float: {
component: "LabeledTextField",
inputType: "number",
zodType: "number",
prismaType: "Float",
},
decimal: {
component: "LabeledTextField",
inputType: "number",
zodType: "number",
prismaType: "Decimal",
},
datetime: {
component: "LabeledTextField",
inputType: "text",
zodType: "string().datetime()",
prismaType: "DateTime",
},
uuid: {
component: "LabeledTextField",
inputType: "text",
zodType: "string().uuid",
prismaType: "String",
default: "uuid",
},
json: {
component: "LabeledTextField",
inputType: "text",
zodType: "any",
prismaType: "Json",
},
},
}
export const getResourceValueFromCodegen = async (
fieldType: string,
resource: keyof CodegenField,
): Promise<string | undefined> => {
const codegen = await getCodegen()
const templateValue = codegen.fieldTypeMap?.[fieldType]?.[resource]
return templateValue
}
export const getResourceConfigFromCodegen = async (
fieldType: string,
): Promise<CodegenField | undefined> => {
const codegen = await getCodegen()
const config = codegen.fieldTypeMap?.[fieldType]
return config
}
const getIsTypeScript = async () =>
require("fs").existsSync(require("path").join(process.cwd(), "tsconfig.json"))
export const getCodegen = async () => {
try {
const isTypeScript = await getIsTypeScript()
const blitzServerPath = isTypeScript ? "app/blitz-server.ts" : "app/blitz-server.js"
const blitzServer = require("path").join(process.cwd(), blitzServerPath)
const {register} = require("esbuild-register/dist/node")
const {unregister} = register({
target: "es6",
})
const blitzConfig = require(blitzServer)
const config = blitzConfig
const {cliConfig} = config
unregister()
if (cliConfig.codegen !== undefined) {
const result = CodegenSchema.safeParse(cliConfig.codegen)
if (!result.success) {
log.error("Failed parsing codegen config. Check if it is well formed. Using default config")
return defaultCodegenConfig
}
return cliConfig.codegen
}
return defaultCodegenConfig
} catch (ex) {
log.debug("Failed loading config" + ex)
return defaultCodegenConfig
}
}

View File

@@ -0,0 +1,65 @@
import * as ast from "@mrleebo/prisma-ast"
import {Editor} from "mem-fs-editor"
import {log} from "../utils/log"
import fs from "fs"
import path from "path"
//create a custom error class when prisma does not exist
export class PrismaSchemaNotFoundError extends Error {
constructor() {
super("Prisma schema not found.")
this.name = "PrismaSchemaNotFoundError"
}
}
function getDbFolder() {
try {
const packageJsonPath = path.join(process.cwd(), "package.json")
if (fs.existsSync(packageJsonPath)) {
const packageJson = fs.readFileSync(packageJsonPath, "utf8")
const packageJsonObj = JSON.parse(packageJson)
if (!packageJsonObj.prisma || !packageJsonObj.prisma.schema) {
throw new PrismaSchemaNotFoundError()
}
const prismaSchemaPath = path.join(process.cwd(), packageJsonObj.prisma.schema)
if (!fs.existsSync(prismaSchemaPath)) {
throw new PrismaSchemaNotFoundError()
}
const folder = packageJsonObj.prisma.schema.split("/")[0] as string
return folder
} else {
throw new PrismaSchemaNotFoundError()
}
} catch (e) {
if (fs.existsSync(path.join(process.cwd(), "db/schema.prisma"))) {
return "db"
}
throw e
}
}
export const getPrismaSchema = (
memFsEditor: Editor,
): {schema: ast.Schema; schemaPath: string; dbFolder: string} | boolean => {
let dbFolder: string, schemaPath: string
try {
dbFolder = getDbFolder()
schemaPath = path.join(process.cwd(), dbFolder, "schema.prisma")
} catch (e) {
if (e instanceof PrismaSchemaNotFoundError) {
return false
}
throw e
}
if (!fs.existsSync(schemaPath)) {
return {schema: {type: "schema", list: []}, schemaPath, dbFolder}
}
let schema: ast.Schema
try {
schema = ast.getSchema(memFsEditor.read(schemaPath))
} catch (err) {
log.debug("Failed to parse schema.prisma file")
throw err
}
return {schema, schemaPath, dbFolder}
}

View File

@@ -5,6 +5,10 @@ export function plural(input: string): string {
return pluralize.isPlural(input) ? input : pluralize.plural(input)
}
export function addSpaceBeforeCapitals(input: string): string {
return singleCamel(input).replace(/(?!^)([A-Z])/g, " $1")
}
export function singular(input: string): string {
return pluralize.isSingular(input) ? input : pluralize.singular(input)
}

View File

@@ -167,6 +167,19 @@ const withProgress = (str: string) => {
return withCaret(str)
}
const greenText = (str: string) => {
return `${c.green(str)}`
}
/**
* Logs a red error message to stdout.
*
* @param {string} msg
*/
const error = (msg: string) => {
console.log(`${c.red(msg)}`)
}
/**
* Logs a branded purple message to stdout.
*
@@ -248,6 +261,8 @@ export const log = {
progress,
spinner,
success,
greenText,
error,
variable,
debug,
Table,

View File

@@ -0,0 +1,14 @@
import {pluralCamel, pluralPascal, singleCamel, singlePascal} from "../index"
export function modelName(input: string = "") {
return singleCamel(input)
}
export function modelNames(input: string = "") {
return pluralCamel(input)
}
export function ModelName(input: string = "") {
return singlePascal(input)
}
export function ModelNames(input: string = "") {
return pluralPascal(input)
}

View File

@@ -0,0 +1,60 @@
import { forwardRef, PropsWithoutRef } from "react"
import { useField } from "react-final-form"
export interface LabeledSelectFieldProps extends PropsWithoutRef<JSX.IntrinsicElements["select"]> {
/** Field name. */
name: string
/** Field label. */
label: string
type?: "number" | "string"
options: any
outerProps?: PropsWithoutRef<JSX.IntrinsicElements["div"]>
}
export const LabeledSelectField = forwardRef<HTMLSelectElement, LabeledSelectFieldProps>(
({ name, label, outerProps, options, type="number", ...props }, ref) => {
const {
input,
meta: { touched, error, submitError, submitting },
} = useField(name, {
parse: type === "number" ? Number : undefined,
})
const normalizedError = Array.isArray(error) ? error.join(", ") : error || submitError
return (
<div {...outerProps}>
<label>
{label}
<select {...input} disabled={submitting} {...props} ref={ref}>
{options && options.map((value) => <option value={value.id}>{value[name]}</option>)}
</select>
</label>
{touched && normalizedError && (
<div role="alert" style={{ color: "red" }}>
{normalizedError}
</div>
)}
<style jsx>{`
label {
display: flex;
flex-direction: column;
align-items: start;
font-size: 1rem;
}
select {
font-size: 1rem;
padding: 0.25rem 0.5rem;
border-radius: 3px;
appearance: none;
margin-top: 0.5rem;
}
`}</style>
</div>
)
}
)
export default LabeledSelectField

View File

@@ -0,0 +1,68 @@
import { forwardRef, PropsWithoutRef } from "react";
import { useFormikContext, ErrorMessage, Field } from "formik";
export interface LabeledSelectFieldProps
extends PropsWithoutRef<JSX.IntrinsicElements["select"]> {
/** Field name. */
name: string;
/** Field label. */
label: string;
/** Field options. */
options: any;
outerProps?: PropsWithoutRef<JSX.IntrinsicElements["div"]>;
}
export const LabeledSelectField = forwardRef<
HTMLSelectElement,
LabeledSelectFieldProps
>(({ name, label, outerProps, options, ...props }, ref) => {
const { isSubmitting } = useFormikContext();
return (
<div {...outerProps}>
<label
style={{
display: "flex",
flexDirection: "column",
alignItems: "start",
fontSize: "1rem",
}}
>
{label}
<Field
{...props}
disabled={isSubmitting}
ref={ref}
name={name}
as="select"
style={{
fontSize: "1rem",
padding: " 0.25rem 0.4rem",
borderRadius: "3px",
border: "1px solid purple",
marginTop: "0.5rem",
backgroundColor: "white",
}}
>
<option value="" selected disabled hidden>
Select {label}
</option>
{options.map((option, index) => (
<option key={index} value={option.id}>
{option[name]}
</option>
))}
</Field>
</label>
<ErrorMessage name={name}>
{(msg) => (
<div role="alert" style={{ color: "red" }}>
{msg}
</div>
)}
</ErrorMessage>
</div>
);
});
export default LabeledSelectField;

View File

@@ -0,0 +1,58 @@
import { ComponentPropsWithoutRef, forwardRef, PropsWithoutRef } from "react"
import { useFormContext } from "react-hook-form"
import { ErrorMessage } from "@hookform/error-message"
export interface LabeledSelectFieldProps extends PropsWithoutRef<JSX.IntrinsicElements["select"]> {
/** Field name. */
name: string
/** Field label. */
label: string
/** Field type. Doesn't include radio buttons and checkboxes */
options: any[]
outerProps?: PropsWithoutRef<JSX.IntrinsicElements["div"]>
labelProps?: ComponentPropsWithoutRef<"label">
}
export const LabeledSelectField = forwardRef<HTMLSelectElement, LabeledSelectFieldProps>(
({ label, outerProps, labelProps, name, options, ...props }, ref) => {
const {
register,
formState: { isSubmitting, errors },
} = useFormContext()
return (
<div {...outerProps}>
<label {...labelProps}>
{label}
<select {...register(name)} disabled={isSubmitting} {...props}>
{options && options.map((value) => <option value={value.id}>{value[name]}</option>)}
</select>
</label>
<ErrorMessage
render={({ message }) => (
<div role="alert" style={{ color: "red" }}>
{message}
</div>
)}
errors={errors}
name={name}
/>
<style jsx>{`
label {
display: flex;
flex-direction: column;
align-items: start;
font-size: 1rem;
}
select {
font-size: 1rem;
padding: 0.25rem 0.5rem;
border-radius: 3px;
border: 1px solid purple;
appearance: none;
margin-top: 0.5rem;
}
`}</style>
</div>
)
}
)

View File

@@ -1,13 +1,14 @@
import React, {Suspense} from "react"
import {Form, FormProps} from "src/core/components/Form"
import {LabeledTextField} from "src/core/components/LabeledTextField"
import {z} from "zod"
export {FORM_ERROR} from "src/core/components/Form"
export function __ModelName__Form<S extends z.ZodType<any, any>>(props: FormProps<S>) {
return (
<Form<S> {...props}>
<LabeledTextField name="name" label="Name" placeholder="Name" />
{/* template: <__component__ name="__fieldName__" label="__Field_Name__" placeholder="__Field_Name__" type="__inputType__" /> */}
</Form>
)
}

View File

@@ -1,5 +1,5 @@
import { resolver } from "@blitzjs/rpc"
import db from "db"
import db from "__prismaFolder__"
import {z} from "zod"
const __Name__ = z.object({

View File

@@ -1,15 +1,15 @@
import { resolver } from "@blitzjs/rpc"
import db from "db"
import db from "__prismaFolder__"
import {z} from "zod"
if (process.env.parentModel) {
const Create__ModelName__ = z.object({
name: z.string(),
__parentModelId__: z.number()
__parentModelId__: z.__parentModelIdZodType__(),
// template: __fieldName__: z.__zodType__(),
})
} else {
const Create__ModelName__ = z.object({
name: z.string(),
// template: __fieldName__: z.__zodType__(),
})
}

View File

@@ -1,9 +1,9 @@
import { resolver } from "@blitzjs/rpc"
import db from "db"
import db from "__prismaFolder__"
import {z} from "zod"
const Delete__ModelName__ = z.object({
id: z.number(),
id: z.__modelIdZodType__(),
})
export default resolver.pipe(

View File

@@ -1,10 +1,10 @@
import { resolver } from "@blitzjs/rpc"
import db from "db"
import db from "__prismaFolder__"
import {z} from "zod"
const Update__ModelName__ = z.object({
id: z.number(),
name: z.string(),
id: z.__modelIdZodType__(),
// template: __fieldName__: z.__zodType__(),
})
export default resolver.pipe(

View File

@@ -36,7 +36,7 @@ export const Edit__ModelName__ = () => {
<div>
<h1>Edit __ModelName__ {__modelName__.id}</h1>
<pre>{JSON.stringify(__modelName__, null, 2)}</pre>
<Suspense fallback={<div>Loading...</div>}>
<__ModelName__Form
submitText="Update __ModelName__"
// TODO use a zod schema for form validation
@@ -64,6 +64,7 @@ export const Edit__ModelName__ = () => {
}
}}
/>
</Suspense>
</div>
</>
)

View File

@@ -12,6 +12,7 @@ if (process.env.parentModel) {
import Layout from "src/core/layouts/Layout"
import create__ModelName__ from "src/__modelNamesPath__/mutations/create__ModelName__"
import {__ModelName__Form, FORM_ERROR} from "src/__modelNamesPath__/components/__ModelName__Form"
import { Suspense } from 'react'
const New__ModelName__Page = () => {
const router = useRouter()
@@ -23,7 +24,7 @@ const New__ModelName__Page = () => {
return (
<Layout title={"Create New __ModelName__"}>
<h1>Create New __ModelName__</h1>
<Suspense fallback={<div>Loading...</div>}>
<__ModelName__Form
submitText="Create __ModelName__"
// TODO use a zod schema for form validation
@@ -51,7 +52,7 @@ const New__ModelName__Page = () => {
}
}}
/>
</Suspense>
<p>
<if condition="parentModel">
<Link href={Routes.__ModelNames__Page({ __parentModelId__: __parentModelId__! })}>

View File

@@ -1,6 +1,6 @@
import {NotFoundError} from "blitz"
import { resolver } from "@blitzjs/rpc"
import db from "db"
import db from "__prismaFolder__"
import {z} from "zod"
const Get__ModelName__ = z.object({

View File

@@ -1,6 +1,6 @@
import {paginate} from "blitz"
import { resolver } from "@blitzjs/rpc"
import db, {Prisma} from "db"
import db, {Prisma} from "__prismaFolder__"
interface Get__ModelNames__Input
extends Pick<Prisma.__ModelName__FindManyArgs, "where" | "orderBy" | "skip" | "take"> {}

View File

@@ -1,5 +1,5 @@
import { resolver } from "@blitzjs/rpc"
import db from "db"
import db from "__prismaFolder__"
import {z} from "zod"
const __Name__ = z.object({

View File

@@ -0,0 +1,254 @@
import {FieldValuesBuilder} from "../../../src/generators/template-builders/field-values-builder"
import {describe, it, expect} from "vitest"
describe("Form Generator", () => {
process.env.BLITZ_APP_DIR = process.cwd()
const generator = new FieldValuesBuilder()
it("Should work with two word field names", async () => {
expect(
await generator.getFieldTemplateValues(["orgName:string", "orgId:integer"]),
).toStrictEqual([
{
component: "LabeledTextField",
FieldName: "OrgName",
Field_Name: "Org Name",
Field_name: "Org name",
attributeName: "orgName",
fieldName: "orgName",
field_name: "org name",
zodType: "string",
prismaType: "String",
inputType: "text",
},
{
component: "LabeledTextField",
FieldName: "OrgId",
Field_Name: "Org Id",
Field_name: "Org id",
attributeName: "orgId",
fieldName: "orgId",
field_name: "org id",
zodType: "string",
prismaType: "String",
inputType: "text",
},
])
})
it("Should work with simple types", async () => {
expect(
await generator.getFieldTemplateValues(["field1:string", "field2:string"]),
).toStrictEqual([
{
component: "LabeledTextField",
FieldName: "Field1",
Field_Name: "Field1",
Field_name: "Field1",
attributeName: "field1",
fieldName: "field1",
field_name: "field1",
zodType: "string",
prismaType: "String",
inputType: "text",
},
{
component: "LabeledTextField",
FieldName: "Field2",
Field_Name: "Field2",
Field_name: "Field2",
attributeName: "field2",
fieldName: "field2",
field_name: "field2",
zodType: "string",
prismaType: "String",
inputType: "text",
},
])
})
it("Should work with optional types", async () => {
expect(
await generator.getFieldTemplateValues(["field1:string?", "field2:number"]),
).toStrictEqual([
{
component: "LabeledTextField",
FieldName: "Field1",
Field_Name: "Field1",
Field_name: "Field1",
attributeName: "field1",
fieldName: "field1",
field_name: "field1",
zodType: "string",
prismaType: "String",
inputType: "text",
},
{
component: "LabeledTextField",
FieldName: "Field2",
Field_Name: "Field2",
Field_name: "Field2",
attributeName: "field2",
fieldName: "field2",
field_name: "field2",
zodType: "number",
prismaType: "Int",
inputType: "number",
},
])
})
it("Should work with default values", async () => {
expect(await generator.getFieldTemplateValues(["field1:string:default=test"])).toStrictEqual([
{
component: "LabeledTextField",
FieldName: "Field1",
Field_Name: "Field1",
Field_name: "Field1",
attributeName: "field1",
fieldName: "field1",
field_name: "field1",
zodType: "string",
prismaType: "String",
inputType: "text",
},
])
})
it("Should work with different input types", async () => {
const fields = [
"field1:string?",
"field2:boolean",
"field3:int",
"field4:number",
"field5:bigint?",
"field6:float",
"field7:decimal",
"field8:datetime",
"field9:uuid",
"field10:json?",
]
expect(await generator.getFieldTemplateValues(fields)).toStrictEqual([
{
component: "LabeledTextField",
FieldName: "Field1",
Field_Name: "Field1",
Field_name: "Field1",
attributeName: "field1",
fieldName: "field1",
field_name: "field1",
zodType: "string",
prismaType: "String",
inputType: "text",
},
{
component: "LabeledTextField",
FieldName: "Field2",
Field_Name: "Field2",
Field_name: "Field2",
attributeName: "field2",
fieldName: "field2",
field_name: "field2",
zodType: "boolean",
prismaType: "Boolean",
inputType: "text",
},
{
component: "LabeledTextField",
FieldName: "Field3",
Field_Name: "Field3",
Field_name: "Field3",
attributeName: "field3",
fieldName: "field3",
field_name: "field3",
zodType: "number",
prismaType: "Int",
inputType: "number",
},
{
component: "LabeledTextField",
FieldName: "Field4",
Field_Name: "Field4",
Field_name: "Field4",
attributeName: "field4",
fieldName: "field4",
field_name: "field4",
zodType: "number",
prismaType: "Int",
inputType: "number",
},
{
component: "LabeledTextField",
FieldName: "Field5",
Field_Name: "Field5",
Field_name: "Field5",
attributeName: "field5",
fieldName: "field5",
field_name: "field5",
zodType: "number",
prismaType: "BigInt",
inputType: "number",
},
{
component: "LabeledTextField",
FieldName: "Field6",
Field_Name: "Field6",
Field_name: "Field6",
attributeName: "field6",
fieldName: "field6",
field_name: "field6",
zodType: "number",
prismaType: "Float",
inputType: "number",
},
{
component: "LabeledTextField",
FieldName: "Field7",
Field_Name: "Field7",
Field_name: "Field7",
attributeName: "field7",
fieldName: "field7",
field_name: "field7",
zodType: "number",
prismaType: "Decimal",
inputType: "number",
},
{
component: "LabeledTextField",
FieldName: "Field8",
Field_Name: "Field8",
Field_name: "Field8",
attributeName: "field8",
fieldName: "field8",
field_name: "field8",
zodType: "string().datetime()",
prismaType: "DateTime",
inputType: "text",
},
{
component: "LabeledTextField",
FieldName: "Field9",
Field_Name: "Field9",
Field_name: "Field9",
attributeName: "field9",
fieldName: "field9",
field_name: "field9",
zodType: "string().uuid",
prismaType: "String",
inputType: "text",
default: "uuid",
},
{
component: "LabeledTextField",
FieldName: "Field10",
Field_Name: "Field10",
Field_name: "Field10",
attributeName: "field10",
fieldName: "field10",
field_name: "field10",
zodType: "any",
prismaType: "Json",
inputType: "text",
},
])
})
})

View File

@@ -0,0 +1,50 @@
import {FormGenerator} from "../../src/generators/form-generator"
import {describe, it, expect} from "vitest"
describe("Form Generator", () => {
process.env.BLITZ_APP_DIR = process.cwd()
const generator = new FormGenerator({
ModelName: "project",
ModelNames: "projects",
modelName: "project",
modelNames: "projects",
extraArgs: ["myProjectName:string"],
})
it("Correctly generates field names", async () => {
const templateValues = await generator.getTemplateValues()
expect(templateValues.fieldTemplateValues[0].fieldName).toEqual("myProjectName")
expect(templateValues.fieldTemplateValues[0].FieldName).toEqual("MyProjectName")
expect(templateValues.fieldTemplateValues[0].field_name).toEqual("my project name")
expect(templateValues.fieldTemplateValues[0].Field_name).toEqual("My project name")
expect(templateValues.fieldTemplateValues[0].Field_Name).toEqual("My Project Name")
})
it("matches template comments correctly", () => {
const regex = generator.fieldTemplateRegExp
const curlyBraceComment1 = `{/* template: <__component__ name="__fieldName__" label="__Field_Name__" placeholder="__Field_Name__" /> */}`
expect(curlyBraceComment1.match(regex)?.[0]?.replace(regex, "$2$3")).toBe(
`<__component__ name="__fieldName__" label="__Field_Name__" placeholder="__Field_Name__" />`,
)
expect(curlyBraceComment1.match(regex)?.[0]?.replace(regex, "$2$3")).not.toBe(
`something Random`,
)
const normalComment1 = `// template: __fieldName__: z.__zodType__(),`
expect(normalComment1.match(regex)?.[0]?.replace(regex, "$2$3")).toBe(
`__fieldName__: z.__zodType__(),`,
)
expect(normalComment1.match(regex)?.[0]?.replace(regex, "$2$3")).not.toBe(`something Random`)
const commentWithSpacing = `// template: __fieldName__: z.__zodType__(),`
const commentWithNoSpacing = `//template: __fieldName__: z.__zodType__(),`
expect(commentWithSpacing.match(regex)?.[0]?.replace(regex, "$2$3")).toBe(
`__fieldName__: z.__zodType__(),`,
)
expect(commentWithNoSpacing.match(regex)?.[0]?.replace(regex, "$2$3")).toBe(
`__fieldName__: z.__zodType__(),`,
)
})
})

View File

@@ -39,6 +39,7 @@ describe("PageGenerator", () => {
ParentModels: undefined,
modelNamesPath: "projects",
modelId: "projectId",
modelIdZodType: "number",
modelIdParam: "[projectId]",
modelName: "project",
modelNames: "projects",
@@ -46,6 +47,7 @@ describe("PageGenerator", () => {
parentModelId: "",
parentModelParam: "",
parentModels: undefined,
prismaFolder: "db",
})
})

View File

@@ -3,75 +3,75 @@ import {Schema} from "@mrleebo/prisma-ast"
import {Field} from "../../src/prisma/field"
describe("Field", () => {
it("parses optional types", () => {
const [field] = Field.parse("name:string?")
it("parses optional types", async () => {
const [field] = await Field.parse("name:string?")
expect(field?.isRequired).toBe(false)
})
it("appends unique attribute", () => {
const [field] = Field.parse("email:string?:unique")
it("appends unique attribute", async () => {
const [field] = await Field.parse("email:string?:unique")
expect(field?.isUnique).toBe(true)
})
it("appends updatedAt attribute", () => {
const [field] = Field.parse("updatedAt:DateTime:updatedAt")
it("appends updatedAt attribute", async () => {
const [field] = await Field.parse("updatedAt:DateTime:updatedAt")
expect(field?.isUpdatedAt).toBe(true)
})
it("handles default simple attribute", () => {
const [field] = Field.parse("isActive:boolean:default=true")
it("handles default simple attribute", async () => {
const [field] = await Field.parse("isActive:boolean:default=true")
expect(field?.default).toBe("true")
})
it("handles default uuid attribute", () => {
const [field] = Field.parse("id:string:default=uuid")
it("handles default uuid attribute", async () => {
const [field] = await Field.parse("id:string:default=uuid")
expect(field?.default).toMatchObject({name: "uuid"})
})
it("handles uuid convenience syntax", () => {
const [field] = Field.parse("someSpecialToken:uuid")
it("handles uuid convenience syntax", async () => {
const [field] = await Field.parse("someSpecialToken:uuid")
expect(field?.type).toBe("String")
expect(field?.default).toMatchObject({name: "uuid"})
})
it("handles default autoincrement attribute", () => {
const [field] = Field.parse("id:int:default=autoincrement")
it("handles default autoincrement attribute", async () => {
const [field] = await Field.parse("id:int:default=autoincrement")
expect(field?.default).toMatchObject({name: "autoincrement"})
})
it("has default field type", () => {
const [field] = Field.parse("name")
it("has default field type", async () => {
const [field] = await Field.parse("name")
expect(field?.type).toBe("String")
})
it("allow number characters in model name", () => {
const [field] = Field.parse("name2")
it("allow number characters in model name", async () => {
const [field] = await Field.parse("name2")
expect(field?.name).toBe("name2")
})
it("allow underscore characters in model name", () => {
const [field] = Field.parse("first_name")
it("allow underscore characters in model name", async () => {
const [field] = await Field.parse("first_name")
expect(field?.name).toBe("first_name")
})
it("disallows number as a first character in model name", () => {
expect(() => Field.parse("2first")).toThrow()
it("disallows number as a first character in model name", async () => {
await expect(async () => await Field.parse("2first")).rejects.toThrow()
})
it("disallows underscore as a first character in model name", () => {
expect(() => Field.parse("_first")).toThrow()
it("disallows underscore as a first character in model name", async () => {
await expect(async () => await Field.parse("_first")).rejects.toThrow()
})
it("disallows special characters in model name", () => {
expect(() => Field.parse("app-user:int")).toThrow()
it("disallows special characters in model name", async () => {
await expect(async () => await Field.parse("app-user:int")).rejects.toThrow()
})
it("disallows optional list fields", () => {
expect(() => Field.parse("users:int?[]")).toThrow()
it("disallows optional list fields", async () => {
await expect(async () => await Field.parse("users:int?[]")).rejects.toThrow()
})
it("requires a name", () => {
expect(() => Field.parse(":int")).toThrow()
it("requires a name", async () => {
await expect(async () => await Field.parse(":int")).rejects.toThrow()
})
describe("belongsTo", () => {
@@ -103,8 +103,8 @@ describe("Field", () => {
],
}
it("simple relation", () => {
const [relation, foreignKey] = Field.parse("belongsTo:task")
it("simple relation", async () => {
const [relation, foreignKey] = await Field.parse("belongsTo:task")
expect(relation).toMatchObject({
name: "task",
type: "Task",
@@ -114,8 +114,8 @@ describe("Field", () => {
expect(foreignKey).toMatchObject({name: "taskId", type: "Int"})
})
it("relation with schema", () => {
const [relation, foreignKey] = Field.parse("belongsTo:project?", schema)
it("relation with schema", async () => {
const [relation, foreignKey] = await Field.parse("belongsTo:project?", schema)
expect(relation).toMatchObject({
name: "project",
type: "Project",
@@ -126,8 +126,8 @@ describe("Field", () => {
expect(foreignKey).toMatchObject({name: "projectId", type: "String", isRequired: false})
})
it("relation with list directive", () => {
const [relation, foreignKey] = Field.parse("belongsTo:tasks[]", schema)
it("relation with list directive", async () => {
const [relation, foreignKey] = await Field.parse("belongsTo:tasks[]", schema)
expect(relation).toMatchObject({name: "tasks", type: "Task", isList: false})
expect(foreignKey).toMatchObject({name: "tasksId", type: "Int", isList: false})
})

View File

@@ -3,18 +3,13 @@ import {Field} from "../../src/prisma/field"
import {Model} from "../../src/prisma/model"
describe("Prisma Model", () => {
it("generates a proper model", () => {
expect(
new Model(
"user",
[
Field.parse("email:string:unique"),
Field.parse("updated:dateTime:updatedAt"),
Field.parse("recentLogins:dateTime[]"),
Field.parse("twoFactorEnabled:boolean"),
Field.parse("twoFactorMethod:string?"),
].flat(),
).toString(),
).toMatchSnapshot()
it("generates a proper model", async () => {
const email = await Field.parse("email:string:unique")
const updated = await Field.parse("updated:dateTime:updatedAt")
const recentLogins = await Field.parse("recentLogins:dateTime[]")
const twoFactorEnabled = await Field.parse("twoFactorEnabled:boolean")
const twoFactorMethod = await Field.parse("twoFactorMethod:string?")
const fields = [email, updated, recentLogins, twoFactorEnabled, twoFactorMethod].flat()
expect(new Model("user", fields).toString()).toMatchSnapshot()
})
})

141
pnpm-lock.yaml generated
View File

@@ -1194,6 +1194,7 @@ importers:
diff: 5.0.0
enquirer: 2.3.6
eslint: 8.27.0
fast-glob: 3.2.12
fs-extra: 10.0.1
globby: 13.1.2
got: ^11.8.1
@@ -1213,6 +1214,7 @@ importers:
username: 5.1.0
vinyl: 2.2.1
watch: 1.0.2
zod: 3.20.2
dependencies:
"@babel/core": 7.12.10_supports-color@8.1.1
"@babel/plugin-transform-typescript": 7.12.1_ps3yxa7qdojvlda5ukda3zlwie
@@ -1224,6 +1226,7 @@ importers:
cross-spawn: 7.0.3
diff: 5.0.0
enquirer: 2.3.6
fast-glob: 3.2.12
fs-extra: 10.0.1
globby: 13.1.2
got: 11.8.1
@@ -1262,6 +1265,7 @@ importers:
typescript: 4.8.4
unbuild: 0.6.9_supports-color@8.1.1
watch: 1.0.2
zod: 3.20.2
packages/pkg-template:
specifiers:
@@ -1620,6 +1624,7 @@ packages:
source-map: 0.5.7
transitivePeerDependencies:
- supports-color
dev: false
/@babel/core/7.18.2:
resolution:
@@ -1757,7 +1762,7 @@ packages:
"@babel/core": ^7.0.0
dependencies:
"@babel/compat-data": 7.17.10
"@babel/core": 7.12.10_supports-color@8.1.1
"@babel/core": 7.12.10
"@babel/helper-validator-option": 7.16.7
browserslist: 4.20.3
semver: 6.3.0
@@ -1772,7 +1777,7 @@ packages:
"@babel/core": ^7.0.0
dependencies:
"@babel/compat-data": 7.17.10
"@babel/core": 7.18.2_supports-color@8.1.1
"@babel/core": 7.18.2
"@babel/helper-validator-option": 7.16.7
browserslist: 4.20.3
semver: 6.3.0
@@ -1925,7 +1930,7 @@ packages:
peerDependencies:
"@babel/core": ^7.0.0
dependencies:
"@babel/core": 7.12.10_supports-color@8.1.1
"@babel/core": 7.12.10
"@babel/helper-annotate-as-pure": 7.16.7
regexpu-core: 5.0.1
@@ -2449,7 +2454,7 @@ packages:
peerDependencies:
"@babel/core": ^7.0.0-0
dependencies:
"@babel/core": 7.12.10_supports-color@8.1.1
"@babel/core": 7.12.10
"@babel/helper-plugin-utils": 7.17.12
"@babel/plugin-syntax-dynamic-import": 7.8.3_@babel+core@7.12.10
@@ -2462,7 +2467,7 @@ packages:
peerDependencies:
"@babel/core": ^7.0.0-0
dependencies:
"@babel/core": 7.12.10_supports-color@8.1.1
"@babel/core": 7.12.10
"@babel/helper-plugin-utils": 7.17.12
"@babel/plugin-syntax-export-namespace-from": 7.8.3_@babel+core@7.12.10
@@ -2475,7 +2480,7 @@ packages:
peerDependencies:
"@babel/core": ^7.0.0-0
dependencies:
"@babel/core": 7.12.10_supports-color@8.1.1
"@babel/core": 7.12.10
"@babel/helper-plugin-utils": 7.17.12
"@babel/plugin-syntax-json-strings": 7.8.3_@babel+core@7.12.10
@@ -2488,7 +2493,7 @@ packages:
peerDependencies:
"@babel/core": ^7.0.0-0
dependencies:
"@babel/core": 7.12.10_supports-color@8.1.1
"@babel/core": 7.12.10
"@babel/helper-plugin-utils": 7.17.12
"@babel/plugin-syntax-logical-assignment-operators": 7.10.4_@babel+core@7.12.10
@@ -2501,7 +2506,7 @@ packages:
peerDependencies:
"@babel/core": ^7.0.0-0
dependencies:
"@babel/core": 7.12.10_supports-color@8.1.1
"@babel/core": 7.12.10
"@babel/helper-plugin-utils": 7.17.12
"@babel/plugin-syntax-nullish-coalescing-operator": 7.8.3_@babel+core@7.12.10
@@ -2528,7 +2533,7 @@ packages:
peerDependencies:
"@babel/core": ^7.0.0-0
dependencies:
"@babel/core": 7.12.10_supports-color@8.1.1
"@babel/core": 7.12.10
"@babel/helper-plugin-utils": 7.17.12
"@babel/plugin-syntax-numeric-separator": 7.10.4_@babel+core@7.12.10
@@ -2542,7 +2547,7 @@ packages:
"@babel/core": ^7.0.0-0
dependencies:
"@babel/compat-data": 7.17.10
"@babel/core": 7.12.10_supports-color@8.1.1
"@babel/core": 7.12.10
"@babel/helper-compilation-targets": 7.18.2_@babel+core@7.12.10
"@babel/helper-plugin-utils": 7.17.12
"@babel/plugin-syntax-object-rest-spread": 7.8.3_@babel+core@7.12.10
@@ -2557,7 +2562,7 @@ packages:
peerDependencies:
"@babel/core": ^7.0.0-0
dependencies:
"@babel/core": 7.12.10_supports-color@8.1.1
"@babel/core": 7.12.10
"@babel/helper-plugin-utils": 7.17.12
"@babel/plugin-syntax-optional-catch-binding": 7.8.3_@babel+core@7.12.10
@@ -2570,7 +2575,7 @@ packages:
peerDependencies:
"@babel/core": ^7.0.0-0
dependencies:
"@babel/core": 7.12.10_supports-color@8.1.1
"@babel/core": 7.12.10
"@babel/helper-plugin-utils": 7.17.12
"@babel/helper-skip-transparent-expression-wrappers": 7.16.0
"@babel/plugin-syntax-optional-chaining": 7.8.3_@babel+core@7.12.10
@@ -2630,7 +2635,7 @@ packages:
peerDependencies:
"@babel/core": ^7.0.0-0
dependencies:
"@babel/core": 7.12.10_supports-color@8.1.1
"@babel/core": 7.12.10
"@babel/helper-create-regexp-features-plugin": 7.17.12_@babel+core@7.12.10
"@babel/helper-plugin-utils": 7.17.12
@@ -2642,7 +2647,7 @@ packages:
peerDependencies:
"@babel/core": ^7.0.0-0
dependencies:
"@babel/core": 7.12.10_supports-color@8.1.1
"@babel/core": 7.12.10
"@babel/helper-plugin-utils": 7.17.12
/@babel/plugin-syntax-async-generators/7.8.4_@babel+core@7.20.2:
@@ -2675,7 +2680,7 @@ packages:
peerDependencies:
"@babel/core": ^7.0.0-0
dependencies:
"@babel/core": 7.12.10_supports-color@8.1.1
"@babel/core": 7.12.10
"@babel/helper-plugin-utils": 7.17.12
/@babel/plugin-syntax-class-properties/7.12.13_@babel+core@7.20.2:
@@ -2697,7 +2702,7 @@ packages:
peerDependencies:
"@babel/core": ^7.0.0-0
dependencies:
"@babel/core": 7.12.10_supports-color@8.1.1
"@babel/core": 7.12.10
"@babel/helper-plugin-utils": 7.17.12
/@babel/plugin-syntax-export-namespace-from/7.8.3_@babel+core@7.12.10:
@@ -2708,7 +2713,7 @@ packages:
peerDependencies:
"@babel/core": ^7.0.0-0
dependencies:
"@babel/core": 7.12.10_supports-color@8.1.1
"@babel/core": 7.12.10
"@babel/helper-plugin-utils": 7.17.12
/@babel/plugin-syntax-flow/7.17.12_@babel+core@7.18.2:
@@ -2743,7 +2748,7 @@ packages:
peerDependencies:
"@babel/core": ^7.0.0-0
dependencies:
"@babel/core": 7.12.10_supports-color@8.1.1
"@babel/core": 7.12.10
"@babel/helper-plugin-utils": 7.17.12
/@babel/plugin-syntax-json-strings/7.8.3_@babel+core@7.20.2:
@@ -2816,7 +2821,7 @@ packages:
peerDependencies:
"@babel/core": ^7.0.0-0
dependencies:
"@babel/core": 7.12.10_supports-color@8.1.1
"@babel/core": 7.12.10
"@babel/helper-plugin-utils": 7.17.12
/@babel/plugin-syntax-logical-assignment-operators/7.10.4_@babel+core@7.20.2:
@@ -2838,7 +2843,7 @@ packages:
peerDependencies:
"@babel/core": ^7.0.0-0
dependencies:
"@babel/core": 7.12.10_supports-color@8.1.1
"@babel/core": 7.12.10
"@babel/helper-plugin-utils": 7.17.12
/@babel/plugin-syntax-nullish-coalescing-operator/7.8.3_@babel+core@7.18.2:
@@ -2872,7 +2877,7 @@ packages:
peerDependencies:
"@babel/core": ^7.0.0-0
dependencies:
"@babel/core": 7.12.10_supports-color@8.1.1
"@babel/core": 7.12.10
"@babel/helper-plugin-utils": 7.17.12
/@babel/plugin-syntax-numeric-separator/7.10.4_@babel+core@7.20.2:
@@ -2894,7 +2899,7 @@ packages:
peerDependencies:
"@babel/core": ^7.0.0-0
dependencies:
"@babel/core": 7.12.10_supports-color@8.1.1
"@babel/core": 7.12.10
"@babel/helper-plugin-utils": 7.17.12
/@babel/plugin-syntax-object-rest-spread/7.8.3_@babel+core@7.20.2:
@@ -2916,7 +2921,7 @@ packages:
peerDependencies:
"@babel/core": ^7.0.0-0
dependencies:
"@babel/core": 7.12.10_supports-color@8.1.1
"@babel/core": 7.12.10
"@babel/helper-plugin-utils": 7.17.12
/@babel/plugin-syntax-optional-catch-binding/7.8.3_@babel+core@7.20.2:
@@ -2938,7 +2943,7 @@ packages:
peerDependencies:
"@babel/core": ^7.0.0-0
dependencies:
"@babel/core": 7.12.10_supports-color@8.1.1
"@babel/core": 7.12.10
"@babel/helper-plugin-utils": 7.17.12
/@babel/plugin-syntax-optional-chaining/7.8.3_@babel+core@7.18.2:
@@ -2973,7 +2978,7 @@ packages:
peerDependencies:
"@babel/core": ^7.0.0-0
dependencies:
"@babel/core": 7.12.10_supports-color@8.1.1
"@babel/core": 7.12.10
"@babel/helper-plugin-utils": 7.17.12
/@babel/plugin-syntax-top-level-await/7.14.5_@babel+core@7.20.2:
@@ -3035,7 +3040,7 @@ packages:
peerDependencies:
"@babel/core": ^7.0.0-0
dependencies:
"@babel/core": 7.12.10_supports-color@8.1.1
"@babel/core": 7.12.10
"@babel/helper-plugin-utils": 7.17.12
/@babel/plugin-transform-async-to-generator/7.17.12_@babel+core@7.12.10:
@@ -3080,7 +3085,7 @@ packages:
peerDependencies:
"@babel/core": ^7.0.0-0
dependencies:
"@babel/core": 7.12.10_supports-color@8.1.1
"@babel/core": 7.12.10
"@babel/helper-plugin-utils": 7.17.12
/@babel/plugin-transform-block-scoping/7.18.4_@babel+core@7.12.10:
@@ -3092,7 +3097,7 @@ packages:
peerDependencies:
"@babel/core": ^7.0.0-0
dependencies:
"@babel/core": 7.12.10_supports-color@8.1.1
"@babel/core": 7.12.10
"@babel/helper-plugin-utils": 7.17.12
/@babel/plugin-transform-classes/7.18.4_@babel+core@7.12.10:
@@ -3147,7 +3152,7 @@ packages:
peerDependencies:
"@babel/core": ^7.0.0-0
dependencies:
"@babel/core": 7.12.10_supports-color@8.1.1
"@babel/core": 7.12.10
"@babel/helper-plugin-utils": 7.17.12
/@babel/plugin-transform-destructuring/7.18.0_@babel+core@7.12.10:
@@ -3159,7 +3164,7 @@ packages:
peerDependencies:
"@babel/core": ^7.0.0-0
dependencies:
"@babel/core": 7.12.10_supports-color@8.1.1
"@babel/core": 7.12.10
"@babel/helper-plugin-utils": 7.17.12
/@babel/plugin-transform-dotall-regex/7.16.7_@babel+core@7.12.10:
@@ -3171,7 +3176,7 @@ packages:
peerDependencies:
"@babel/core": ^7.0.0-0
dependencies:
"@babel/core": 7.12.10_supports-color@8.1.1
"@babel/core": 7.12.10
"@babel/helper-create-regexp-features-plugin": 7.17.12_@babel+core@7.12.10
"@babel/helper-plugin-utils": 7.17.12
@@ -3184,7 +3189,7 @@ packages:
peerDependencies:
"@babel/core": ^7.0.0-0
dependencies:
"@babel/core": 7.12.10_supports-color@8.1.1
"@babel/core": 7.12.10
"@babel/helper-plugin-utils": 7.17.12
/@babel/plugin-transform-exponentiation-operator/7.16.7_@babel+core@7.12.10:
@@ -3196,7 +3201,7 @@ packages:
peerDependencies:
"@babel/core": ^7.0.0-0
dependencies:
"@babel/core": 7.12.10_supports-color@8.1.1
"@babel/core": 7.12.10
"@babel/helper-builder-binary-assignment-operator-visitor": 7.16.7
"@babel/helper-plugin-utils": 7.17.12
@@ -3223,7 +3228,7 @@ packages:
peerDependencies:
"@babel/core": ^7.0.0-0
dependencies:
"@babel/core": 7.12.10_supports-color@8.1.1
"@babel/core": 7.12.10
"@babel/helper-plugin-utils": 7.17.12
/@babel/plugin-transform-function-name/7.16.7_@babel+core@7.12.10:
@@ -3235,7 +3240,7 @@ packages:
peerDependencies:
"@babel/core": ^7.0.0-0
dependencies:
"@babel/core": 7.12.10_supports-color@8.1.1
"@babel/core": 7.12.10
"@babel/helper-compilation-targets": 7.18.2_@babel+core@7.12.10
"@babel/helper-function-name": 7.17.9
"@babel/helper-plugin-utils": 7.17.12
@@ -3249,7 +3254,7 @@ packages:
peerDependencies:
"@babel/core": ^7.0.0-0
dependencies:
"@babel/core": 7.12.10_supports-color@8.1.1
"@babel/core": 7.12.10
"@babel/helper-plugin-utils": 7.17.12
/@babel/plugin-transform-member-expression-literals/7.16.7_@babel+core@7.12.10:
@@ -3261,7 +3266,7 @@ packages:
peerDependencies:
"@babel/core": ^7.0.0-0
dependencies:
"@babel/core": 7.12.10_supports-color@8.1.1
"@babel/core": 7.12.10
"@babel/helper-plugin-utils": 7.17.12
/@babel/plugin-transform-modules-amd/7.18.0_@babel+core@7.12.10:
@@ -3445,7 +3450,7 @@ packages:
peerDependencies:
"@babel/core": ^7.0.0
dependencies:
"@babel/core": 7.12.10_supports-color@8.1.1
"@babel/core": 7.12.10
"@babel/helper-create-regexp-features-plugin": 7.17.12_@babel+core@7.12.10
"@babel/helper-plugin-utils": 7.17.12
@@ -3458,7 +3463,7 @@ packages:
peerDependencies:
"@babel/core": ^7.0.0-0
dependencies:
"@babel/core": 7.12.10_supports-color@8.1.1
"@babel/core": 7.12.10
"@babel/helper-plugin-utils": 7.17.12
/@babel/plugin-transform-object-super/7.16.7_@babel+core@7.12.10:
@@ -3501,7 +3506,7 @@ packages:
peerDependencies:
"@babel/core": ^7.0.0-0
dependencies:
"@babel/core": 7.12.10_supports-color@8.1.1
"@babel/core": 7.12.10
"@babel/helper-plugin-utils": 7.17.12
/@babel/plugin-transform-property-literals/7.16.7_@babel+core@7.12.10:
@@ -3513,7 +3518,7 @@ packages:
peerDependencies:
"@babel/core": ^7.0.0-0
dependencies:
"@babel/core": 7.12.10_supports-color@8.1.1
"@babel/core": 7.12.10
"@babel/helper-plugin-utils": 7.17.12
/@babel/plugin-transform-react-jsx-development/7.16.7_@babel+core@7.18.2:
@@ -3637,7 +3642,7 @@ packages:
peerDependencies:
"@babel/core": ^7.0.0-0
dependencies:
"@babel/core": 7.12.10_supports-color@8.1.1
"@babel/core": 7.12.10
"@babel/helper-plugin-utils": 7.17.12
regenerator-transform: 0.15.0
@@ -3650,7 +3655,7 @@ packages:
peerDependencies:
"@babel/core": ^7.0.0-0
dependencies:
"@babel/core": 7.12.10_supports-color@8.1.1
"@babel/core": 7.12.10
"@babel/helper-plugin-utils": 7.17.12
/@babel/plugin-transform-shorthand-properties/7.16.7_@babel+core@7.12.10:
@@ -3662,7 +3667,7 @@ packages:
peerDependencies:
"@babel/core": ^7.0.0-0
dependencies:
"@babel/core": 7.12.10_supports-color@8.1.1
"@babel/core": 7.12.10
"@babel/helper-plugin-utils": 7.17.12
/@babel/plugin-transform-spread/7.17.12_@babel+core@7.12.10:
@@ -3674,7 +3679,7 @@ packages:
peerDependencies:
"@babel/core": ^7.0.0-0
dependencies:
"@babel/core": 7.12.10_supports-color@8.1.1
"@babel/core": 7.12.10
"@babel/helper-plugin-utils": 7.17.12
"@babel/helper-skip-transparent-expression-wrappers": 7.16.0
@@ -3687,7 +3692,7 @@ packages:
peerDependencies:
"@babel/core": ^7.0.0-0
dependencies:
"@babel/core": 7.12.10_supports-color@8.1.1
"@babel/core": 7.12.10
"@babel/helper-plugin-utils": 7.17.12
/@babel/plugin-transform-template-literals/7.18.2_@babel+core@7.12.10:
@@ -3699,7 +3704,7 @@ packages:
peerDependencies:
"@babel/core": ^7.0.0-0
dependencies:
"@babel/core": 7.12.10_supports-color@8.1.1
"@babel/core": 7.12.10
"@babel/helper-plugin-utils": 7.17.12
/@babel/plugin-transform-typeof-symbol/7.17.12_@babel+core@7.12.10:
@@ -3711,7 +3716,7 @@ packages:
peerDependencies:
"@babel/core": ^7.0.0-0
dependencies:
"@babel/core": 7.12.10_supports-color@8.1.1
"@babel/core": 7.12.10
"@babel/helper-plugin-utils": 7.17.12
/@babel/plugin-transform-typescript/7.12.1_ps3yxa7qdojvlda5ukda3zlwie:
@@ -3773,7 +3778,7 @@ packages:
peerDependencies:
"@babel/core": ^7.0.0-0
dependencies:
"@babel/core": 7.12.10_supports-color@8.1.1
"@babel/core": 7.12.10
"@babel/helper-plugin-utils": 7.17.12
/@babel/plugin-transform-unicode-regex/7.16.7_@babel+core@7.12.10:
@@ -3785,7 +3790,7 @@ packages:
peerDependencies:
"@babel/core": ^7.0.0-0
dependencies:
"@babel/core": 7.12.10_supports-color@8.1.1
"@babel/core": 7.12.10
"@babel/helper-create-regexp-features-plugin": 7.17.12_@babel+core@7.12.10
"@babel/helper-plugin-utils": 7.17.12
@@ -3944,6 +3949,7 @@ packages:
semver: 5.7.1
transitivePeerDependencies:
- supports-color
dev: false
/@babel/preset-flow/7.17.12_@babel+core@7.18.2:
resolution:
@@ -3968,7 +3974,7 @@ packages:
peerDependencies:
"@babel/core": ^7.0.0-0
dependencies:
"@babel/core": 7.12.10_supports-color@8.1.1
"@babel/core": 7.12.10
"@babel/helper-plugin-utils": 7.17.12
"@babel/plugin-proposal-unicode-property-regex": 7.17.12_@babel+core@7.12.10
"@babel/plugin-transform-dotall-regex": 7.16.7_@babel+core@7.12.10
@@ -6825,6 +6831,7 @@ packages:
typescript: 4.8.4
transitivePeerDependencies:
- supports-color
dev: false
/@typescript-eslint/experimental-utils/5.28.0_rmayb2veg2btbq6mbmnyivgasy:
resolution:
@@ -6863,7 +6870,6 @@ packages:
typescript: 4.8.4
transitivePeerDependencies:
- supports-color
dev: true
/@typescript-eslint/parser/5.43.0_typescript@4.8.4:
resolution:
@@ -6885,6 +6891,7 @@ packages:
typescript: 4.8.4
transitivePeerDependencies:
- supports-color
dev: false
/@typescript-eslint/parser/5.9.1_nw6v2wse7au2evadw7vu3hneg4:
resolution:
@@ -10947,6 +10954,7 @@ packages:
transitivePeerDependencies:
- eslint-import-resolver-webpack
- supports-color
dev: false
/eslint-config-next/13.1.1_rmayb2veg2btbq6mbmnyivgasy:
resolution:
@@ -10984,6 +10992,7 @@ packages:
hasBin: true
peerDependencies:
eslint: ">=7.0.0"
dev: false
/eslint-config-prettier/8.5.0_eslint@8.27.0:
resolution:
@@ -11027,7 +11036,6 @@ packages:
tsconfig-paths: 3.14.1
transitivePeerDependencies:
- supports-color
dev: true
/eslint-import-resolver-typescript/2.7.1_fkfqfehjtk7sk2efaqbgxsuasa:
resolution:
@@ -11047,6 +11055,7 @@ packages:
tsconfig-paths: 3.14.1
transitivePeerDependencies:
- supports-color
dev: false
/eslint-import-resolver-typescript/3.5.2_dcpv4nbdr5ks2h5677xdltrk6e:
resolution:
@@ -11122,10 +11131,10 @@ packages:
eslint-import-resolver-webpack:
optional: true
dependencies:
"@typescript-eslint/parser": 5.43.0_typescript@4.8.4
"@typescript-eslint/parser": 5.43.0_rmayb2veg2btbq6mbmnyivgasy
debug: 3.2.7
eslint-import-resolver-node: 0.3.6
eslint-import-resolver-typescript: 2.7.1_fkfqfehjtk7sk2efaqbgxsuasa
eslint-import-resolver-typescript: 2.7.1_dcpv4nbdr5ks2h5677xdltrk6e
find-up: 2.1.0
transitivePeerDependencies:
- supports-color
@@ -11195,6 +11204,7 @@ packages:
- eslint-import-resolver-typescript
- eslint-import-resolver-webpack
- supports-color
dev: false
/eslint-plugin-import/2.26.0_ttnp75sbivpcvanbhjbkcsh3ly:
resolution:
@@ -11228,7 +11238,6 @@ packages:
- eslint-import-resolver-typescript
- eslint-import-resolver-webpack
- supports-color
dev: true
/eslint-plugin-jsx-a11y/6.5.1:
resolution:
@@ -11966,6 +11975,19 @@ packages:
merge2: 1.4.1
micromatch: 4.0.5
/fast-glob/3.2.12:
resolution:
{
integrity: sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==,
}
engines: {node: ">=8.6.0"}
dependencies:
"@nodelib/fs.stat": 2.0.5
"@nodelib/fs.walk": 1.2.8
glob-parent: 5.1.2
merge2: 1.4.1
micromatch: 4.0.5
/fast-json-stable-stringify/2.1.0:
resolution:
{
@@ -12646,7 +12668,7 @@ packages:
dependencies:
array-union: 2.1.0
dir-glob: 3.0.1
fast-glob: 3.2.11
fast-glob: 3.2.12
ignore: 5.2.0
merge2: 1.4.1
slash: 3.0.0
@@ -13971,7 +13993,7 @@ packages:
pretty-format: 29.2.1
slash: 3.0.0
strip-json-comments: 3.1.1
ts-node: 10.9.1_typescript@4.8.4
ts-node: 10.9.1_cbe7ovvae6zqfnmtgctpgpys54
transitivePeerDependencies:
- supports-color
@@ -19749,7 +19771,6 @@ packages:
typescript: 4.8.4
v8-compile-cache-lib: 3.0.1
yn: 3.1.1
dev: false
/ts-node/10.9.1_ieummqxttktzud32hpyrer46t4:
resolution:
@@ -19816,6 +19837,7 @@ packages:
typescript: 4.8.4
v8-compile-cache-lib: 3.0.1
yn: 3.1.1
dev: false
/tsconfig-paths/3.14.1:
resolution:
@@ -20716,6 +20738,7 @@ packages:
- sugarss
- supports-color
- terser
dev: false
/vitest/0.25.3_jsdom@20.0.3:
resolution: