diff --git a/.changeset/brave-boats-nail.md b/.changeset/brave-boats-nail.md new file mode 100644 index 0000000..4179738 --- /dev/null +++ b/.changeset/brave-boats-nail.md @@ -0,0 +1,5 @@ +--- +"@ozhanefe/ts-codegenerator": minor +--- + +add eslint config diff --git a/.changeset/tame-worms-dress.md b/.changeset/tame-worms-dress.md new file mode 100644 index 0000000..d21eb5c --- /dev/null +++ b/.changeset/tame-worms-dress.md @@ -0,0 +1,5 @@ +--- +"@ozhanefe/ts-codegenerator": minor +--- + +nextjs support diff --git a/.changeset/warm-pots-repeat.md b/.changeset/warm-pots-repeat.md new file mode 100644 index 0000000..630263b --- /dev/null +++ b/.changeset/warm-pots-repeat.md @@ -0,0 +1,5 @@ +--- +"@ozhanefe/ts-codegenerator": minor +--- + +add nextjs parsing/generator features diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..0c09dcc --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,37 @@ +const { resolve } = require("node:path"); + +const project = resolve(process.cwd(), "tsconfig.json"); + +/** @type {import("eslint").Linter.Config} */ +module.exports = { + root: true, + extends: ["eslint:recommended", "prettier"], + plugins: ["only-warn", "@typescript-eslint"], + parser: "@typescript-eslint/parser", + parserOptions: { + project: "./tsconfig.json", + tsconfigRootDir: __dirname, + }, + env: { + node: true, + }, + settings: { + "import/resolver": { + typescript: { + project, + }, + }, + }, + ignorePatterns: [".*.js", "node_modules/", "dist/"], + overrides: [ + { + files: ["*.js?(x)", "*.ts?(x)"], + }, + { + files: ["**/*.test.ts", "**/*.test.tsx"], + env: { + jest: true, + }, + }, + ], +}; diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..472a4ae --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,30 @@ +name: CI + +on: + push: + branches: + - "**" + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: oven-sh/setup-bun@v1 + with: + bun-version: latest + - uses: actions/setup-node@v3 + with: + node-version: 18.x + + - name: Install dependencies + run: bun install + + - name: Lint + run: bun run lint + + - name: Build + run: bun run build + + - name: Test + run: bun run test diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml deleted file mode 100644 index 37120bc..0000000 --- a/.github/workflows/main.yml +++ /dev/null @@ -1,21 +0,0 @@ -name: CI -on: - push: - branches: - - "**" - -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: pnpm/action-setup@v2 - with: - version: 9 - - uses: actions/setup-node@v3 - with: - node-version: 18.x - cache: "pnpm" - - - run: pnpm install --frozen-lockfile - - run: pnpm run lint && pnpm run build diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml deleted file mode 100644 index 6df79b3..0000000 --- a/.github/workflows/publish.yml +++ /dev/null @@ -1,36 +0,0 @@ -name: Publish -on: - workflow_run: - workflows: [CI] - branches: [main] - types: [completed] - -concurrency: ${{ github.workflow }}-${{ github.ref }} - -permissions: - contents: write - pull-requests: write - -jobs: - publish: - if: ${{ github.event.workflow_run.conclusion == 'success' }} - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: pnpm/action-setup@v2 - with: - version: 9 - - uses: actions/setup-node@v3 - with: - node-version: 18.x - cache: "pnpm" - - - run: pnpm install --frozen-lockfile - - name: Create Release Pull Request or Publish - id: changesets - uses: changesets/action@v1 - with: - publish: pnpm run release - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/release-ts-codegenerator.yml b/.github/workflows/release-ts-codegenerator.yml new file mode 100644 index 0000000..84aad0e --- /dev/null +++ b/.github/workflows/release-ts-codegenerator.yml @@ -0,0 +1,36 @@ +name: Release ts-codegenerator + +on: + push: + branches: + - main + paths: + - "packages/ts-generator/**" + +jobs: + release: + name: Release + runs-on: ubuntu-latest + steps: + - name: Checkout Repo + uses: actions/checkout@v3 + with: + # This makes Actions fetch all Git history so that Changesets can generate changelogs with the correct commits + fetch-depth: 0 + + - name: Setup Bun + uses: oven-sh/setup-bun@v1 + + - name: Install Dependencies + run: bun install + + - name: Create Release Pull Request or Publish to npm + id: changesets + uses: changesets/action@v1 + with: + # This expects your changesets to be in the root packages/ts-generator/.changeset + cwd: ./packages/ts-generator + publish: bun run release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.turbo/turbo-build.log b/.turbo/turbo-build.log deleted file mode 100644 index 58f8ab4..0000000 --- a/.turbo/turbo-build.log +++ /dev/null @@ -1,17 +0,0 @@ -$ tsup src/index.ts --format cjs,esm --dts --minify --clean -CLI Building entry: src\index.ts -CLI Using tsconfig: tsconfig.json -CLI tsup v8.2.1 -CLI Using tsup config: C:\Users\ozhan\Desktop\oz\self\visual-ts\packages\ts-generator\package.json -CLI Target: es2022 -CLI Cleaning output folder -CJS Build start -ESM Build start -ESM dist\index.mjs 3.56 KB -ESM ⚡️ Build success in 41ms -CJS dist\index.js 4.25 KB -CJS ⚡️ Build success in 42ms -DTS Build start -DTS ⚡️ Build success in 1940ms -DTS dist\index.d.ts 2.79 KB -DTS dist\index.d.mts 2.79 KB diff --git a/CHANGELOG.md b/CHANGELOG.md index 97e153f..ac1a187 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,21 @@ # @ozhanefe/ts-codegenerator +## 1.7.0 + +### Minor Changes + +- add eslint config +- nextjs support +- add nextjs parsing/generator features + +## 1.6.0 + +### Minor Changes + +- add eslint config +- nextjs support +- add nextjs parsing/generator features + ## 1.5.1 ### Patch Changes diff --git a/bun.lockb b/bun.lockb new file mode 100644 index 0000000..a21e9dd Binary files /dev/null and b/bun.lockb differ diff --git a/package.json b/package.json index 09a2353..9b8067a 100644 --- a/package.json +++ b/package.json @@ -1,23 +1,28 @@ { "name": "@ozhanefe/ts-codegenerator", + "version": "1.7.0", "license": "MIT", - "version": "1.5.1", "main": "dist/index.js", "module": "dist/index.mjs", "types": "dist/index.d.ts", "scripts": { "build": "tsup src/index.ts --format cjs,esm --dts --minify --clean", - "release": "pnpm run build && changeset publish", - "lint": "tsc", - "test": "jest" + "lint": "eslint .", + "test": "jest", + "changeset": "changeset", + "version-packages": "changeset version", + "release": "bun run build && bun run lint && bun run test && changeset publish" }, "devDependencies": { "@changesets/cli": "^2.27.7", "@jest/types": "^29.6.3", "@types/jest": "^29.5.12", - "@types/node": "^20.14.11", + "@typescript-eslint/eslint-plugin": "^7.17.0", + "@typescript-eslint/parser": "^7.17.0", + "eslint": "^8.57.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-only-warn": "^1.1.0", "jest": "^29.7.0", - "jest-mock-fs": "^1.0.2", "ts-jest": "^29.2.3", "ts-node": "^10.9.2", "tsup": "^8.1.2", diff --git a/src/__tests__/codebase-scanner.test.ts b/src/__tests__/codebase-scanner.test.ts index bbab4f2..ac403d3 100644 --- a/src/__tests__/codebase-scanner.test.ts +++ b/src/__tests__/codebase-scanner.test.ts @@ -1,6 +1,6 @@ -import { scanCodebase } from "../codebase-scanner"; -import { CodebaseInfo, FunctionInfo, TypeInfo } from "../types"; import * as path from "path"; +import { scanCodebase } from "../codebase-scanner"; +import { CodebaseInfo } from "../types"; describe("Codebase Scanner", () => { it("should scan codebase", () => { diff --git a/src/codebase-scanner.ts b/src/codebase-scanner.ts index f69c744..2d8c7cc 100644 --- a/src/codebase-scanner.ts +++ b/src/codebase-scanner.ts @@ -1,5 +1,5 @@ -import { Project, Node, Type, Symbol as TsSymbol } from "ts-morph"; -import { FunctionInfo, TypeInfo, CodebaseInfo } from "./types"; +import { Node, Project } from "ts-morph"; +import { CodebaseInfo, FunctionInfo, TypeInfo } from "./types"; export function scanCodebase(projectPath: string): CodebaseInfo { const project = new Project(); diff --git a/src/index.ts b/src/index.ts index 4e184ed..f54251f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -17,3 +17,7 @@ export type { } from "./types"; export { scanCodebase } from "./codebase-scanner"; + +export type { NextCodebaseInfo, ServerActionInfo } from "./nextjs"; +export { scanNextjsCodebase, generateServerAction } from "./nextjs"; +export * as NextJS from "./nextjs"; diff --git a/src/module-parser/index.ts b/src/module-parser/index.ts index 50fb28a..15ae7e7 100644 --- a/src/module-parser/index.ts +++ b/src/module-parser/index.ts @@ -1,2 +1,2 @@ -export { getFunctionInfoFromNode } from "./parser"; +export { getFunctionInfoFromNode, parseFunctionsFromText } from "./parser"; export { getFunctionVariables } from "./utils"; diff --git a/src/nextjs/__tests__/nextjs.test.ts b/src/nextjs/__tests__/nextjs.test.ts new file mode 100644 index 0000000..ca04b63 --- /dev/null +++ b/src/nextjs/__tests__/nextjs.test.ts @@ -0,0 +1,96 @@ +import { Project } from "ts-morph"; +import { + analyzeNextjsSourceFiles, + generateServerAction, + ServerActionInfo, +} from "../index"; + +describe("Next.js functionality", () => { + let project: Project; + + beforeEach(() => { + project = new Project({ useInMemoryFileSystem: true }); + }); + + describe("analyzeNextjsSourceFiles", () => { + it("should correctly identify and parse server actions", () => { + const serverActionCode = ` + "use server"; + + export async function submitForm(data: FormData) { + // Server action logic + } + + export async function deleteItem(id: string) { + // Delete item logic + } + `; + + const sourceFile = project.createSourceFile( + "app/actions.ts", + serverActionCode + ); + + const result = analyzeNextjsSourceFiles([sourceFile]); + + expect(result.serverActions).toHaveLength(2); + expect(result.serverActions[0]?.name).toBe("submitForm"); + expect(result.serverActions[1]?.name).toBe("deleteItem"); + }); + + it("should not identify non-server actions", () => { + const regularCode = ` + export function regularFunction() { + // Regular function logic + } + `; + + const sourceFile = project.createSourceFile( + "app/regular.ts", + regularCode + ); + + const result = analyzeNextjsSourceFiles([sourceFile]); + + expect(result.serverActions).toHaveLength(0); + }); + }); + + describe("generateServerAction", () => { + it("should generate correct server action code", () => { + const serverActionInfo: ServerActionInfo = { + name: "testAction", + returnType: "Promise", // This could be 'string' or 'Promise', the function should handle both + parameters: [ + { name: "data", type: "FormData" }, + { name: "userId", type: "string" }, + ], + filePath: "app/actions.ts", + }; + + const generatedCode = generateServerAction(serverActionInfo); + + expect(generatedCode).toContain('"use server";'); + expect(generatedCode).toContain( + "export async function testAction(data: FormData, userId: string): Promise" + ); + expect(generatedCode).toContain("// TODO: Implement server action logic"); + expect(generatedCode).toContain('throw new Error("Not implemented");'); + }); + + it("should handle non-Promise return types", () => { + const serverActionInfo: ServerActionInfo = { + name: "testAction", + returnType: "void", + parameters: [], + filePath: "app/actions.ts", + }; + + const generatedCode = generateServerAction(serverActionInfo); + + expect(generatedCode).toContain( + "export async function testAction(): Promise" + ); + }); + }); +}); diff --git a/src/nextjs/generator.ts b/src/nextjs/generator.ts new file mode 100644 index 0000000..35cab2c --- /dev/null +++ b/src/nextjs/generator.ts @@ -0,0 +1,18 @@ +import { ServerActionInfo } from "./types"; + +export function generateServerAction(info: ServerActionInfo): string { + const parameters = (info.parameters ?? []) + .map((param) => `${param.name}: ${param.type}`) + .join(", "); + + let returnType = info.returnType.replace(/Promise<(.*)>/, "$1"); + + returnType = `Promise<${returnType}>`; + + return `"use server"; + +export async function ${info.name}(${parameters}): ${returnType} { + // TODO: Implement server action logic + throw new Error("Not implemented"); +}`; +} diff --git a/src/nextjs/index.ts b/src/nextjs/index.ts new file mode 100644 index 0000000..5dca3b3 --- /dev/null +++ b/src/nextjs/index.ts @@ -0,0 +1,3 @@ +export type { NextCodebaseInfo, ServerActionInfo } from "./types"; +export { scanNextjsCodebase, analyzeNextjsSourceFiles } from "./scanner"; +export { generateServerAction } from "./generator"; diff --git a/src/nextjs/scanner.ts b/src/nextjs/scanner.ts new file mode 100644 index 0000000..0dae59e --- /dev/null +++ b/src/nextjs/scanner.ts @@ -0,0 +1,48 @@ +import { Project, SourceFile } from "ts-morph"; +import { ServerActionInfo, NextCodebaseInfo } from "./types"; + +export function scanNextjsCodebase(projectPath: string): NextCodebaseInfo { + const project = new Project(); + project.addSourceFilesAtPaths(`${projectPath}/**/*.ts`); + return analyzeNextjsSourceFiles(project.getSourceFiles()); +} + +export function analyzeNextjsSourceFiles( + sourceFiles: SourceFile[] +): NextCodebaseInfo { + const serverActions: ServerActionInfo[] = []; + + sourceFiles.forEach((sourceFile) => { + if (isServerActionFile(sourceFile)) { + serverActions.push(...extractServerActions(sourceFile)); + } + }); + + return { serverActions }; +} + +function isServerActionFile(sourceFile: SourceFile): boolean { + return sourceFile.getFullText().trim().startsWith('"use server";'); +} + +function extractServerActions(sourceFile: SourceFile): ServerActionInfo[] { + const serverActions: ServerActionInfo[] = []; + + sourceFile.getFunctions().forEach((func) => { + if (func.isAsync()) { + const functionInfo: ServerActionInfo = { + name: func.getName() || "anonymous", + returnType: func.getReturnType().getText(), + parameters: func.getParameters().map((param) => ({ + name: param.getName(), + type: param.getType().getText(), + })), + filePath: sourceFile.getFilePath(), + }; + + serverActions.push(functionInfo); + } + }); + + return serverActions; +} diff --git a/src/nextjs/types.ts b/src/nextjs/types.ts new file mode 100644 index 0000000..9ddbcc3 --- /dev/null +++ b/src/nextjs/types.ts @@ -0,0 +1,9 @@ +import { FunctionInfo } from "../types"; + +export interface ServerActionInfo extends FunctionInfo { + filePath: string; +} + +export interface NextCodebaseInfo { + serverActions: ServerActionInfo[]; +} diff --git a/tsconfig.json b/tsconfig.json index 0772fd6..683fd1b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -17,8 +17,9 @@ "baseUrl": ".", "paths": { "*": ["node_modules/*", "src/*"] - } + }, + "types": ["node", "jest"] }, - "include": ["src/**/*"], - "exclude": ["node_modules", "**/*.spec.ts", "**/*.test.ts"] + "include": ["src/**/*", "jest.config.ts"], + "exclude": ["node_modules"] }