diff --git a/.changeset/big-boats-lay.md b/.changeset/big-boats-lay.md new file mode 100644 index 0000000000..233d05975f --- /dev/null +++ b/.changeset/big-boats-lay.md @@ -0,0 +1,5 @@ +--- +"@blitzjs/generator": patch +--- + +Guard `blitz g` input via an allow-list of characters; throw if unwanted characters are found. Prevents to break the blitz command by accident (https://github.com/blitz-js/blitz/issues/4021). diff --git a/packages/generator/src/generators/model-generator.ts b/packages/generator/src/generators/model-generator.ts index 2d10b25e31..c9163eb96a 100644 --- a/packages/generator/src/generators/model-generator.ts +++ b/packages/generator/src/generators/model-generator.ts @@ -2,11 +2,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 {checkInputsOrRaise} from "../utils/checkInputOrRaise" import {getTemplateRoot} from "../utils/get-template-root" +import {log} from "../utils/log" export interface ModelGeneratorOptions extends GeneratorOptions { modelName: string @@ -59,9 +60,13 @@ export class ModelGenerator extends Generator { 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 splitInputCommans = (input: typeof extraArgs) => { + const inputs = input.length === 1 && input[0]?.includes(" ") ? input[0]?.split(" ") : input + checkInputsOrRaise(inputs) + return inputs + } + + let fields = splitInputCommans(extraArgs).flatMap((input) => Field.parse(input, schema)) const modelDefinition = new Model(modelName, fields) diff --git a/packages/generator/src/prisma/field.ts b/packages/generator/src/prisma/field.ts index c40056d3d4..e7260126ab 100644 --- a/packages/generator/src/prisma/field.ts +++ b/packages/generator/src/prisma/field.ts @@ -1,4 +1,5 @@ import * as ast from "@mrleebo/prisma-ast" +import {checkInputsOrRaise} from "../utils/checkInputOrRaise" import {capitalize, singlePascal, uncapitalize} from "../utils/inflector" export enum FieldType { @@ -55,6 +56,8 @@ export class Field { // 'name:type?[]:attribute' => Field static parse(input: string, schema?: ast.Schema): Field[] { + checkInputsOrRaise(input) + const [_fieldName, _fieldType = "String", _attribute] = input.split(":") let attribute = _attribute as string let fieldName = uncapitalize(_fieldName as string) diff --git a/packages/generator/src/utils/checkInputOrRaise.ts b/packages/generator/src/utils/checkInputOrRaise.ts new file mode 100644 index 0000000000..cddae0b92a --- /dev/null +++ b/packages/generator/src/utils/checkInputOrRaise.ts @@ -0,0 +1,28 @@ +import chalk from "chalk" +import {log} from "./log" + +export const checkInputsOrRaise = (inputs: string[] | string) => { + if (typeof inputs === "string") { + checkInputOrRaise(inputs) + } else { + inputs.forEach((input) => checkInputOrRaise(input)) + } +} + +const checkInputOrRaise = (input: string) => { + const regex = /^[a-zA-Z0-9-_:=\?[\]\s]+$/ + if (!regex.test(input)) { + const firstInvalidCharacter = input.match(/[^a-zA-Z0-9-_:=\?[\]\s]/) + if (firstInvalidCharacter) { + log.branded( + "Blitz Generator Parser Error: " + + chalk.red( + `Input contains invalid character: "${firstInvalidCharacter[0]}" in ${firstInvalidCharacter.input} at position ${firstInvalidCharacter.index}`, + ), + ) + } + throw new Error( + "Input should only contain alphanumeric characters, spaces, and the following characters: - _ : = ? [ ]", + ) + } +} diff --git a/packages/generator/test/generators/utils/checkInputOrRaise.test.ts b/packages/generator/test/generators/utils/checkInputOrRaise.test.ts new file mode 100644 index 0000000000..ea103980c5 --- /dev/null +++ b/packages/generator/test/generators/utils/checkInputOrRaise.test.ts @@ -0,0 +1,26 @@ +import {checkInputsOrRaise} from "../../../../generator/src/utils/checkInputOrRaise" +import {describe, expect, it} from "vitest" + +describe("checkInputsOrRaise()", () => { + describe("with input string", () => { + it("does not raise when input string OK", () => { + expect(() => checkInputsOrRaise("Foo:foo=bar[]?:-_ ")).not.toThrow() + }) + it("raisees when input has unwanted characters", () => { + expect(() => checkInputsOrRaise("()!")).toThrowError( + "Input should only contain alphanumeric characters, spaces, and the following characters: - _ : = ? [ ]", + ) + }) + }) + + describe("with input string[]", () => { + it("does not raise when input strings OK", () => { + expect(() => checkInputsOrRaise(["ok:ok=ok", "also ok:ok"])).not.toThrow() + }) + it("raisees when one input has unwanted characters", () => { + expect(() => checkInputsOrRaise(["ok:ok=ok", "not ok!"])).toThrowError( + "Input should only contain alphanumeric characters, spaces, and the following characters: - _ : = ? [ ]", + ) + }) + }) +}) diff --git a/packages/generator/test/prisma/field.test.ts b/packages/generator/test/prisma/field.test.ts index 75f6f1e8b0..febfe0a77a 100644 --- a/packages/generator/test/prisma/field.test.ts +++ b/packages/generator/test/prisma/field.test.ts @@ -28,6 +28,14 @@ describe("Field", () => { expect(field?.default).toMatchObject({name: "uuid"}) }) + it("disallow brackes `()` as default attribute", () => { + expect(() => Field.parse("id:string:default=now()")).toThrow() + }) + + it("disallow not allowed characters like `!`", () => { + expect(() => Field.parse("id:string!")).toThrow() + }) + it("handles uuid convenience syntax", () => { const [field] = Field.parse("someSpecialToken:uuid") expect(field?.type).toBe("String")