Compare commits
90 Commits
@blitzjs/g
...
pr-changes
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1ecc5ca714 | ||
|
|
80bbe8e849 | ||
|
|
985ce05eba | ||
|
|
58d7f053e6 | ||
|
|
154a287aa5 | ||
|
|
558d9132c5 | ||
|
|
cc13affe9c | ||
|
|
ad3e202ef0 | ||
|
|
1bea6b33b0 | ||
|
|
484853edc2 | ||
|
|
ae410d5720 | ||
|
|
6c5256b262 | ||
|
|
38d16177d5 | ||
|
|
e335061157 | ||
|
|
2cb2dd2910 | ||
|
|
2b6bd18629 | ||
|
|
f715d7fc6f | ||
|
|
ba1087c031 | ||
|
|
cb7a08aea4 | ||
|
|
384ca635de | ||
|
|
ae0488335a | ||
|
|
3b5bff1b56 | ||
|
|
959f975c3b | ||
|
|
b4b20d5223 | ||
|
|
74bcc213c2 | ||
|
|
48c3b74ae9 | ||
|
|
4b258b0f01 | ||
|
|
799b7431c4 | ||
|
|
d8d047db7e | ||
|
|
34a1ffea99 | ||
|
|
7dddab6afe | ||
|
|
d33acf0538 | ||
|
|
a2ecc9f20b | ||
|
|
fa0d10121d | ||
|
|
5c930dcdb3 | ||
|
|
4620d00640 | ||
|
|
41cf34fea7 | ||
|
|
b8ed7b9af9 | ||
|
|
9c78c5f3c7 | ||
|
|
fbd9b98d9f | ||
|
|
b721bda375 | ||
|
|
4918acdf59 | ||
|
|
842934cf15 | ||
|
|
01bf82a605 | ||
|
|
2e8d899f81 | ||
|
|
92a1b59118 | ||
|
|
fbd8585a3e | ||
|
|
646b92a439 | ||
|
|
a17ea51595 | ||
|
|
3ac434b265 | ||
|
|
b3782a21b4 | ||
|
|
c36cfb7d09 | ||
|
|
b502c45d74 | ||
|
|
f6f5468151 | ||
|
|
97b64699a3 | ||
|
|
bd9bd166ca | ||
|
|
f8fffc122f | ||
|
|
11c4ff3272 | ||
|
|
b3953c445f | ||
|
|
2e7cb3a062 | ||
|
|
d6b760bb06 | ||
|
|
aef3bb2efc | ||
|
|
bf4f2b9214 | ||
|
|
e49f138e1d | ||
|
|
caac3522ec | ||
|
|
56b8656826 | ||
|
|
2674f5dcfb | ||
|
|
e3524b8279 | ||
|
|
49b5339e4c | ||
|
|
93e4c29d8a | ||
|
|
af3dd373a8 | ||
|
|
08d0d1e0ca | ||
|
|
d7e88dece5 | ||
|
|
4671586454 | ||
|
|
0fb4439fc5 | ||
|
|
900c19159e | ||
|
|
6a07489043 | ||
|
|
4459fbfe4d | ||
|
|
8d5fa13cb2 | ||
|
|
13de89321c | ||
|
|
85ef684e31 | ||
|
|
948acafe6a | ||
|
|
27dcfd81e1 | ||
|
|
20d17b7561 | ||
|
|
4afa6b5b1b | ||
|
|
0effae154d | ||
|
|
aaa0b67861 | ||
|
|
ed54550c46 | ||
|
|
ff52627cbb | ||
|
|
fe9a423862 |
6
.changeset/silly-peas-work.md
Normal file
6
.changeset/silly-peas-work.md
Normal 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
47
.github/workflows/pr-release.yml
vendored
Normal 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 }}
|
||||
```
|
||||
|
||||
|
||||
|
||||
63
apps/toolkit-app/src/core/components/LabelSelectField.tsx
Normal file
63
apps/toolkit-app/src/core/components/LabelSelectField.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
)
|
||||
31
apps/toolkit-app/src/users/queries/getUsers.ts
Normal file
31
apps/toolkit-app/src/users/queries/getUsers.ts
Normal 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,
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -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),
|
||||
|
||||
@@ -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" &&
|
||||
|
||||
@@ -2,7 +2,7 @@ import {BuildConfig} from "unbuild"
|
||||
|
||||
const config: BuildConfig = {
|
||||
entries: ["./src/index"],
|
||||
externals: ["react"],
|
||||
externals: ["react", "zod"],
|
||||
declaration: true,
|
||||
rollup: {
|
||||
emitCJS: true,
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 ""
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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()))
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
139
packages/generator/src/generators/template-builders/builder.ts
Normal file
139
packages/generator/src/generators/template-builders/builder.ts
Normal 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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import {IBuilder} from "./builder"
|
||||
|
||||
export const NullBuilder: IBuilder<any, any> = {
|
||||
// eslint-disable-next-line require-await
|
||||
getTemplateValues: async () => {
|
||||
return {}
|
||||
},
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
155
packages/generator/src/utils/codemod-utils.ts
Normal file
155
packages/generator/src/utils/codemod-utils.ts
Normal 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
|
||||
}
|
||||
165
packages/generator/src/utils/get-codegen.ts
Normal file
165
packages/generator/src/utils/get-codegen.ts
Normal 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
|
||||
}
|
||||
}
|
||||
65
packages/generator/src/utils/get-prisma-schema.ts
Normal file
65
packages/generator/src/utils/get-prisma-schema.ts
Normal 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}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
14
packages/generator/src/utils/model-names.ts
Normal file
14
packages/generator/src/utils/model-names.ts
Normal 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)
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
)
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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__(),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -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__! })}>
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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"> {}
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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",
|
||||
},
|
||||
])
|
||||
})
|
||||
})
|
||||
50
packages/generator/test/generators/form-generator.test.ts
Normal file
50
packages/generator/test/generators/form-generator.test.ts
Normal 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__(),`,
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -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",
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -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})
|
||||
})
|
||||
|
||||
@@ -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
141
pnpm-lock.yaml
generated
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user