fe-scaffold

📁 ingpdw/pdw-fe-dev-tool 📅 6 days ago
1
总安装量
1
周安装量
#43469
全站排名
安装命令
npx skills add https://github.com/ingpdw/pdw-fe-dev-tool --skill fe-scaffold

Agent 安装分布

mcpjam 1
claude-code 1
replit 1
junie 1
windsurf 1
zencoder 1

Skill 文档

FE Scaffolding

$ARGUMENTS를 파싱하여 해당하는 타입의 보일러플레이트를 생성한다.

지원 타입

타입 설명 예시
component React 컴포넌트 + 테스트 /fe-scaffold component UserProfile
page Next.js App Router 페이지 /fe-scaffold page dashboard
api Route Handler (API) /fe-scaffold api users
hook 커스텀 훅 + 테스트 /fe-scaffold hook useDebounce
store Zustand 스토어 /fe-scaffold store auth
feature 기능 모듈 (컴포넌트 + 훅 + 타입 + 테스트) /fe-scaffold feature checkout
form React Hook Form + Zod 스키마 폼 /fe-scaffold form LoginForm

인자가 없으면 사용자에게 어떤 타입을 생성할지 질문한다.

템플릿 규칙

component

파일 생성 위치: src/components/[이름]/[이름].tsx + [이름].test.tsx

// src/components/[Name]/[Name].tsx
import { cn } from "@/lib/utils";

interface [Name]Props {
  className?: string;
  children?: React.ReactNode;
}

function [Name]({ className, children }: [Name]Props) {
  return (
    <div className={cn("", className)}>
      {children}
    </div>
  );
}

export { [Name] };
export type { [Name]Props };
// src/components/[Name]/[Name].test.tsx
import { render, screen } from "@testing-library/react";
import { describe, expect, it } from "vitest";
import { [Name] } from "./[Name]";

describe("[Name]", () => {
  it("renders children", () => {
    render(<[Name]>test</[Name]>);
    expect(screen.getByText("test")).toBeInTheDocument();
  });
});

page

파일 생성 위치: src/app/[이름]/page.tsx + layout.tsx (필요 시) + loading.tsx

// src/app/[name]/page.tsx
import type { Metadata } from "next";

export const metadata: Metadata = {
  title: "[Name]",
  description: "[Name] page",
};

export default function [Name]Page() {
  return (
    <main>
      <h1>[Name]</h1>
    </main>
  );
}
// src/app/[name]/loading.tsx
import { Skeleton } from "@/components/ui/skeleton";

export default function [Name]Loading() {
  return <Skeleton className="h-screen w-full" />;
}

api

파일 생성 위치: src/app/api/[이름]/route.ts

// src/app/api/[name]/route.ts
import { NextRequest, NextResponse } from "next/server";

export async function GET(request: NextRequest) {
  try {
    // TODO: implement
    return NextResponse.json({ data: [] });
  } catch (error) {
    return NextResponse.json(
      { error: "Internal Server Error" },
      { status: 500 }
    );
  }
}

export async function POST(request: NextRequest) {
  try {
    const body = await request.json();
    // TODO: implement
    return NextResponse.json({ data: body }, { status: 201 });
  } catch (error) {
    return NextResponse.json(
      { error: "Internal Server Error" },
      { status: 500 }
    );
  }
}

hook

파일 생성 위치: src/hooks/[이름].ts + [이름].test.ts

// src/hooks/[useName].ts
import { useCallback, useState } from "react";

function [useName]() {
  // TODO: implement
  return {};
}

export { [useName] };
// src/hooks/[useName].test.ts
import { renderHook, act } from "@testing-library/react";
import { describe, expect, it } from "vitest";
import { [useName] } from "./[useName]";

describe("[useName]", () => {
  it("should work", () => {
    const { result } = renderHook(() => [useName]());
    expect(result.current).toBeDefined();
  });
});

store

파일 생성 위치: src/stores/[이름]Store.ts

// src/stores/[name]Store.ts
import { create } from "zustand";
import { devtools } from "zustand/middleware";

interface [Name]State {
  // TODO: define state
}

interface [Name]Actions {
  // TODO: define actions
  reset: () => void;
}

const initial[Name]State: [Name]State = {
  // TODO: initial values
};

const use[Name]Store = create<[Name]State & [Name]Actions>()(
  devtools(
    (set) => ({
      ...initial[Name]State,
      reset: () => set(initial[Name]State),
    }),
    { name: "[name]-store" }
  )
);

export { use[Name]Store };
export type { [Name]State, [Name]Actions };

feature

기능 모듈 전체를 생성한다. 위치: src/components/[feature]/

src/components/[feature]/
├── [Feature].tsx           # 메인 컴포넌트
├── [Feature].test.tsx      # 테스트
├── use[Feature].ts         # 기능 전용 훅
├── [feature].types.ts      # 타입 정의
└── index.ts                # barrel export

form

React Hook Form + Zod 기반 폼 컴포넌트를 생성한다.

// src/components/[Name]/[Name].tsx
"use client";

import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import { z } from "zod";
import { Button } from "@/components/ui/button";
import {
  Form,
  FormControl,
  FormField,
  FormItem,
  FormLabel,
  FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";

const [name]Schema = z.object({
  // TODO: define schema
  email: z.string().email(),
});

type [Name]Values = z.infer<typeof [name]Schema>;

interface [Name]Props {
  onSubmit: (values: [Name]Values) => void;
}

function [Name]({ onSubmit }: [Name]Props) {
  const form = useForm<[Name]Values>({
    resolver: zodResolver([name]Schema),
    defaultValues: {
      email: "",
    },
  });

  return (
    <Form {...form}>
      <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
        <FormField
          control={form.control}
          name="email"
          render={({ field }) => (
            <FormItem>
              <FormLabel>Email</FormLabel>
              <FormControl>
                <Input placeholder="email@example.com" {...field} />
              </FormControl>
              <FormMessage />
            </FormItem>
          )}
        />
        <Button type="submit">Submit</Button>
      </form>
    </Form>
  );
}

export { [Name] };
export type { [Name]Values };

실행 규칙

  1. $ARGUMENTS에서 타입과 이름을 파싱
  2. 프로젝트의 기존 구조를 Glob/Read로 확인하여 실제 경로 패턴에 맞춤
  3. 이미 존재하는 파일이 있으면 덮어쓰지 않고 사용자에게 확인
  4. 파일 생성 후 생성된 파일 목록을 출력
  5. shadcn/ui 컴포넌트가 필요한데 없으면 설치 명령어 안내