mirror of
https://github.com/FranP-code/create-better-t-stack.git
synced 2025-10-12 23:52:15 +00:00
use zod for project name validation
This commit is contained in:
5
.changeset/easy-olives-accept.md
Normal file
5
.changeset/easy-olives-accept.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"create-better-t-stack": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
use zod for project name validation
|
||||||
@@ -27,6 +27,7 @@ import {
|
|||||||
FrontendSchema,
|
FrontendSchema,
|
||||||
ORMSchema,
|
ORMSchema,
|
||||||
PackageManagerSchema,
|
PackageManagerSchema,
|
||||||
|
ProjectNameSchema,
|
||||||
RuntimeSchema,
|
RuntimeSchema,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
import { trackProjectCreation } from "./utils/analytics";
|
import { trackProjectCreation } from "./utils/analytics";
|
||||||
@@ -36,10 +37,6 @@ import { getLatestCLIVersion } from "./utils/get-latest-cli-version";
|
|||||||
import { renderTitle } from "./utils/render-title";
|
import { renderTitle } from "./utils/render-title";
|
||||||
import { getProvidedFlags, processAndValidateFlags } from "./validation";
|
import { getProvidedFlags, processAndValidateFlags } from "./validation";
|
||||||
|
|
||||||
const exit = () => process.exit(0);
|
|
||||||
process.on("SIGINT", exit);
|
|
||||||
process.on("SIGTERM", exit);
|
|
||||||
|
|
||||||
const t = trpcServer.initTRPC.create();
|
const t = trpcServer.initTRPC.create();
|
||||||
|
|
||||||
async function handleDirectoryConflict(currentPathInput: string): Promise<{
|
async function handleDirectoryConflict(currentPathInput: string): Promise<{
|
||||||
@@ -268,32 +265,23 @@ const router = t.router({
|
|||||||
})
|
})
|
||||||
.input(
|
.input(
|
||||||
z.tuple([
|
z.tuple([
|
||||||
z.string().optional().describe("project-name"),
|
ProjectNameSchema.optional(),
|
||||||
z
|
z
|
||||||
.object({
|
.object({
|
||||||
yes: z
|
yes: z
|
||||||
.boolean()
|
.boolean()
|
||||||
.optional()
|
.optional()
|
||||||
.default(false)
|
.default(false)
|
||||||
.describe("Use default configuration and skip prompts"),
|
.describe("Use default configuration"),
|
||||||
database: DatabaseSchema.optional(),
|
database: DatabaseSchema.optional(),
|
||||||
orm: ORMSchema.optional(),
|
orm: ORMSchema.optional(),
|
||||||
auth: z.boolean().optional().describe("Include authentication"),
|
auth: z.boolean().optional(),
|
||||||
frontend: z
|
frontend: z.array(FrontendSchema).optional(),
|
||||||
.array(FrontendSchema)
|
addons: z.array(AddonsSchema).optional(),
|
||||||
.optional()
|
examples: z.array(ExamplesSchema).optional(),
|
||||||
.describe("Frontend frameworks"),
|
git: z.boolean().optional(),
|
||||||
addons: z
|
|
||||||
.array(AddonsSchema)
|
|
||||||
.optional()
|
|
||||||
.describe("Additional addons"),
|
|
||||||
examples: z
|
|
||||||
.array(ExamplesSchema)
|
|
||||||
.optional()
|
|
||||||
.describe("Examples to include"),
|
|
||||||
git: z.boolean().optional().describe("Initialize git repository"),
|
|
||||||
packageManager: PackageManagerSchema.optional(),
|
packageManager: PackageManagerSchema.optional(),
|
||||||
install: z.boolean().optional().describe("Install dependencies"),
|
install: z.boolean().optional(),
|
||||||
dbSetup: DatabaseSetupSchema.optional(),
|
dbSetup: DatabaseSetupSchema.optional(),
|
||||||
backend: BackendSchema.optional(),
|
backend: BackendSchema.optional(),
|
||||||
runtime: RuntimeSchema.optional(),
|
runtime: RuntimeSchema.optional(),
|
||||||
|
|||||||
@@ -3,25 +3,14 @@ import { cancel, isCancel, text } from "@clack/prompts";
|
|||||||
import fs from "fs-extra";
|
import fs from "fs-extra";
|
||||||
import pc from "picocolors";
|
import pc from "picocolors";
|
||||||
import { DEFAULT_CONFIG } from "../constants";
|
import { DEFAULT_CONFIG } from "../constants";
|
||||||
|
import { ProjectNameSchema } from "../types";
|
||||||
const INVALID_CHARS = ["<", ">", ":", '"', "|", "?", "*"];
|
|
||||||
const MAX_LENGTH = 255;
|
|
||||||
|
|
||||||
function validateDirectoryName(name: string): string | undefined {
|
function validateDirectoryName(name: string): string | undefined {
|
||||||
if (name === ".") return undefined;
|
if (name === ".") return undefined;
|
||||||
|
|
||||||
if (!name) return "Project name cannot be empty";
|
const result = ProjectNameSchema.safeParse(name);
|
||||||
if (name.length > MAX_LENGTH) {
|
if (!result.success) {
|
||||||
return `Project name must be less than ${MAX_LENGTH} characters`;
|
return result.error.issues[0]?.message || "Invalid project name";
|
||||||
}
|
|
||||||
if (INVALID_CHARS.some((char) => name.includes(char))) {
|
|
||||||
return "Project name contains invalid characters";
|
|
||||||
}
|
|
||||||
if (name.startsWith(".") || name.startsWith("-")) {
|
|
||||||
return "Project name cannot start with a dot or dash";
|
|
||||||
}
|
|
||||||
if (name.toLowerCase() === "node_modules") {
|
|
||||||
return "Project name is reserved";
|
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,6 +66,29 @@ export type DatabaseSetup = z.infer<typeof DatabaseSetupSchema>;
|
|||||||
export const APISchema = z.enum(["trpc", "orpc", "none"]).describe("API type");
|
export const APISchema = z.enum(["trpc", "orpc", "none"]).describe("API type");
|
||||||
export type API = z.infer<typeof APISchema>;
|
export type API = z.infer<typeof APISchema>;
|
||||||
|
|
||||||
|
export const ProjectNameSchema = z
|
||||||
|
.string()
|
||||||
|
.min(1, "Project name cannot be empty")
|
||||||
|
.max(255, "Project name must be less than 255 characters")
|
||||||
|
.refine(
|
||||||
|
(name) => name === "." || !name.startsWith("."),
|
||||||
|
"Project name cannot start with a dot (except for '.')",
|
||||||
|
)
|
||||||
|
.refine(
|
||||||
|
(name) => name === "." || !name.startsWith("-"),
|
||||||
|
"Project name cannot start with a dash",
|
||||||
|
)
|
||||||
|
.refine((name) => {
|
||||||
|
const invalidChars = ["<", ">", ":", '"', "|", "?", "*"];
|
||||||
|
return !invalidChars.some((char) => name.includes(char));
|
||||||
|
}, "Project name contains invalid characters")
|
||||||
|
.refine(
|
||||||
|
(name) => name.toLowerCase() !== "node_modules",
|
||||||
|
"Project name is reserved",
|
||||||
|
)
|
||||||
|
.describe("Project name or path");
|
||||||
|
export type ProjectName = z.infer<typeof ProjectNameSchema>;
|
||||||
|
|
||||||
export type CreateInput = {
|
export type CreateInput = {
|
||||||
projectName?: string;
|
projectName?: string;
|
||||||
yes?: boolean;
|
yes?: boolean;
|
||||||
|
|||||||
@@ -1,18 +1,19 @@
|
|||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { consola } from "consola";
|
import { consola } from "consola";
|
||||||
import type {
|
import {
|
||||||
API,
|
type API,
|
||||||
Addons,
|
type Addons,
|
||||||
Backend,
|
type Backend,
|
||||||
CLIInput,
|
type CLIInput,
|
||||||
Database,
|
type Database,
|
||||||
DatabaseSetup,
|
type DatabaseSetup,
|
||||||
Examples,
|
type Examples,
|
||||||
Frontend,
|
type Frontend,
|
||||||
ORM,
|
type ORM,
|
||||||
PackageManager,
|
type PackageManager,
|
||||||
ProjectConfig,
|
type ProjectConfig,
|
||||||
Runtime,
|
ProjectNameSchema,
|
||||||
|
type Runtime,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
|
|
||||||
export function processAndValidateFlags(
|
export function processAndValidateFlags(
|
||||||
@@ -82,11 +83,30 @@ export function processAndValidateFlags(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (projectName) {
|
if (projectName) {
|
||||||
|
const result = ProjectNameSchema.safeParse(path.basename(projectName));
|
||||||
|
if (!result.success) {
|
||||||
|
consola.fatal(
|
||||||
|
`Invalid project name: ${
|
||||||
|
result.error.issues[0]?.message || "Invalid project name"
|
||||||
|
}`,
|
||||||
|
);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
config.projectName = projectName;
|
config.projectName = projectName;
|
||||||
} else if (options.projectDirectory) {
|
} else if (options.projectDirectory) {
|
||||||
config.projectName = path.basename(
|
const baseName = path.basename(
|
||||||
path.resolve(process.cwd(), options.projectDirectory),
|
path.resolve(process.cwd(), options.projectDirectory),
|
||||||
);
|
);
|
||||||
|
const result = ProjectNameSchema.safeParse(baseName);
|
||||||
|
if (!result.success) {
|
||||||
|
consola.fatal(
|
||||||
|
`Invalid project name: ${
|
||||||
|
result.error.issues[0]?.message || "Invalid project name"
|
||||||
|
}`,
|
||||||
|
);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
config.projectName = baseName;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.frontend && options.frontend.length > 0) {
|
if (options.frontend && options.frontend.length > 0) {
|
||||||
|
|||||||
@@ -21,10 +21,10 @@ export const auth = betterAuth({
|
|||||||
enabled: true,
|
enabled: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
{{~#if (includes frontend "native")}}
|
{{#if (includes frontend "native")}}
|
||||||
,
|
,
|
||||||
plugins: [expo()]
|
plugins: [expo()]
|
||||||
{{/if~}}
|
{{/if}}
|
||||||
});
|
});
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
@@ -52,10 +52,10 @@ export const auth = betterAuth({
|
|||||||
enabled: true,
|
enabled: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
{{~#if (includes frontend "native")}}
|
{{#if (includes frontend "native")}}
|
||||||
,
|
,
|
||||||
plugins: [expo()]
|
plugins: [expo()]
|
||||||
{{/if~}}
|
{{/if}}
|
||||||
});
|
});
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
@@ -68,19 +68,19 @@ import { expo } from "@better-auth/expo";
|
|||||||
import { client } from "../db";
|
import { client } from "../db";
|
||||||
|
|
||||||
export const auth = betterAuth({
|
export const auth = betterAuth({
|
||||||
database: mongodbAdapter(client),
|
database: mongodbAdapter(client),
|
||||||
trustedOrigins: [
|
trustedOrigins: [
|
||||||
process.env.CORS_ORIGIN || "",{{#if (includes frontend "native")}}
|
process.env.CORS_ORIGIN || "",{{#if (includes frontend "native")}}
|
||||||
"my-better-t-app://",{{/if}}
|
"my-better-t-app://",{{/if}}
|
||||||
],
|
],
|
||||||
emailAndPassword: {
|
emailAndPassword: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
{{~#if (includes frontend "native")}}
|
{{#if (includes frontend "native")}}
|
||||||
,
|
,
|
||||||
plugins: [expo()]
|
plugins: [expo()]
|
||||||
{{/if~}}
|
{{/if}}
|
||||||
});
|
});
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
@@ -100,9 +100,9 @@ export const auth = betterAuth({
|
|||||||
enabled: true,
|
enabled: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
{{~#if (includes frontend "native")}}
|
{{#if (includes frontend "native")}}
|
||||||
,
|
,
|
||||||
plugins: [expo()]
|
plugins: [expo()]
|
||||||
{{/if~}}
|
{{/if}}
|
||||||
});
|
});
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|||||||
Reference in New Issue
Block a user