├── .github
├── ISSUE_TEMPLATE
│ ├── bug-report.md
│ ├── config.yml
│ └── feature-request.md
├── dependabot.yml
└── workflows
│ ├── auto-merge
│ ├── publish.yml
│ └── update-blogs.yml
├── .gitignore
├── .husky
├── commit-msg
└── pre-commit
├── .prettierrc
├── ARCHITECTURE.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── PRISMA_SDK_REFERENCE.md
├── README.md
├── SECURITY.md
├── TESTING.md
├── commitlint.config.js
├── dev.to
├── README.md
├── blogs
│ └── create-prisma-generator
│ │ ├── assets
│ │ ├── .gitkeep
│ │ ├── add-secrets-to-github.png
│ │ ├── commit-change-log.png
│ │ ├── create-npm-token.png
│ │ ├── generated-successfully.png
│ │ ├── github-actions.png
│ │ ├── github-release.png
│ │ ├── husky-with-commitlint.png
│ │ ├── my-questions-answers.png
│ │ ├── npm-dropdown.png
│ │ └── passing-tests.png
│ │ ├── code
│ │ └── .gitkeep
│ │ └── content.md
├── dev-to-git.json
└── package.json
├── images
├── cool-banner.png
└── npx-create-prisma-generator.png
├── package.json
├── packages
├── cli-usage
│ └── package.json
├── cpg-github-actions
│ ├── .npmignore
│ ├── bin.js
│ ├── index.js
│ ├── package.json
│ └── template
│ │ └── .github
│ │ ├── dependabot.yml
│ │ └── workflows
│ │ └── CI.yml
├── cpg-root-configs
│ ├── .npmignore
│ ├── bin.js
│ ├── index.js
│ ├── package.json
│ └── template
│ │ ├── .prettierrc
│ │ ├── README.md
│ │ └── gitIgnoreConf.txt
├── cpg-semantic-releases
│ ├── .npmignore
│ ├── bin.js
│ ├── index.js
│ ├── package.json
│ └── template
│ │ ├── commitlint.config.js
│ │ └── package.json
├── cpg-template-gen-usage
│ ├── .npmignore
│ ├── bin.js
│ ├── index.js
│ ├── package.json
│ └── template
│ │ └── usage
│ │ ├── package.json
│ │ └── prisma
│ │ └── schema.prisma
├── cpg-template-typescript
│ ├── .npmignore
│ ├── bin.js
│ ├── index.js
│ ├── package.json
│ └── template
│ │ ├── .npmignore
│ │ ├── jest.config.js
│ │ ├── package.json
│ │ ├── src
│ │ ├── __tests__
│ │ │ ├── __fixtures__
│ │ │ │ ├── getSampleDMMF.ts
│ │ │ │ └── sample.prisma
│ │ │ ├── __snapshots__
│ │ │ │ └── genEnum.test.ts.snap
│ │ │ └── genEnum.test.ts
│ │ ├── bin.ts
│ │ ├── constants.ts
│ │ ├── generator.ts
│ │ ├── helpers
│ │ │ └── genEnum.ts
│ │ └── utils
│ │ │ ├── formatFile.ts
│ │ │ └── writeFileSafely.ts
│ │ └── tsconfig.json
├── cpg-template
│ ├── .npmignore
│ ├── bin.js
│ ├── index.js
│ ├── package.json
│ └── template
│ │ ├── .babelrc
│ │ ├── .npmignore
│ │ ├── jest.config.js
│ │ ├── package.json
│ │ └── src
│ │ ├── __tests__
│ │ ├── __fixtures__
│ │ │ ├── getSampleDMMF.js
│ │ │ └── sample.prisma
│ │ ├── __snapshots__
│ │ │ └── genEnum.test.js.snap
│ │ └── genEnum.test.js
│ │ ├── bin.js
│ │ ├── constants.js
│ │ ├── generator.js
│ │ ├── helpers
│ │ └── genEnum.js
│ │ └── utils
│ │ ├── formatFile.js
│ │ └── writeFileSafely.js
└── create-prisma-generator
│ ├── .npmignore
│ ├── jest.config.js
│ ├── package.json
│ ├── src
│ ├── __tests__
│ │ ├── __helpers__
│ │ │ ├── answer.ts
│ │ │ ├── clearInput.ts
│ │ │ ├── delay.ts
│ │ │ ├── keyCodes.ts
│ │ │ ├── skipQuestions.ts
│ │ │ ├── snapshotSerializers.ts
│ │ │ └── spyConsole.ts
│ │ ├── __in-memory-fs-snapshots__
│ │ │ ├── output-from-sample1.json
│ │ │ ├── output-from-sample2.json
│ │ │ ├── output-from-sample3.json
│ │ │ ├── output-from-sample4.json
│ │ │ └── output-from-sample5.json
│ │ ├── __snapshots__
│ │ │ ├── check-tiny-clis-commands.test.ts.snap
│ │ │ └── get-proper-answers-object.test.ts.snap
│ │ ├── check-ouput-structure.test.ts
│ │ ├── check-tiny-clis-commands.test.ts
│ │ ├── constants
│ │ │ └── valid-prisma-gen-name.ts
│ │ ├── e2e
│ │ │ └── setting-up-new-project.test.ts
│ │ ├── get-proper-answers-object.test.ts
│ │ ├── if-dir-exists.test.ts
│ │ ├── invalid-generator-names.test.ts
│ │ ├── prisma-generator-data-graph
│ │ │ └── why.txt
│ │ ├── skip-prisma-naming-convention.test.ts
│ │ ├── types
│ │ │ └── MockedFS.ts
│ │ └── valid-generator-names.test.ts
│ ├── bin.ts
│ ├── config
│ │ ├── husky-commit-msg-hook.ts
│ │ ├── pnpm-workspace.ts
│ │ └── yarn-workspace.ts
│ ├── index.ts
│ ├── tinyClis.ts
│ ├── types
│ │ └── answers.ts
│ └── utils
│ │ ├── getInstallCommands.ts
│ │ ├── getPkgManagerLockFile.ts
│ │ ├── getRunBlockingCommand.ts
│ │ ├── inquirer
│ │ ├── filters
│ │ │ └── value.ts
│ │ ├── flags.ts
│ │ ├── promptQuestions.ts
│ │ └── validations
│ │ │ └── generatorName.ts
│ │ ├── replacePlaceholders.ts
│ │ ├── runWithFrozenLockCMD.ts
│ │ └── transformScopedName.ts
│ └── tsconfig.json
├── pnpm-lock.yaml
├── pnpm-workspace.yaml
├── scripts
├── addNewBlog.ts
├── addNewTemplate.ts
├── ci
│ ├── publish.ts
│ └── utils
│ │ ├── authGithub.ts
│ │ ├── genReleaseNotes.ts
│ │ ├── getNextVersion.ts
│ │ ├── gitRelease.ts
│ │ ├── githubRelease.ts
│ │ ├── hasPkgChanged.ts
│ │ ├── npmPublish.ts
│ │ └── updatePackageVersion.ts
├── generateTags.ts
├── guideDependabot.ts
├── removeTags.bash
├── simulateDist.js
├── templates
│ ├── addNewBlog
│ │ ├── assets
│ │ │ └── .gitkeep
│ │ ├── code
│ │ │ └── .gitkeep
│ │ └── content.md
│ └── addNewTemplate
│ │ ├── .npmignore
│ │ ├── package.json
│ │ └── sample.template.js
└── utils
│ ├── kebabize.ts
│ └── logger.ts
├── tsconfig.json
└── ◭.⚙
/.github/ISSUE_TEMPLATE/bug-report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: "\U0001F41B Bug Report"
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: bug
6 | assignees: YassinEldeeb
7 | ---
8 |
9 | ## Bug description
10 |
11 | A clear and concise description of what the bug is.
12 |
13 | ## To Reproduce
14 |
15 | ### Screenshot for prompt questions answers:
16 |
17 | If applicable, add screenshot for your terminal showing the answers for prompt questions.
18 |
19 | ### Steps to reproduce the behavior:
20 |
21 | 1. Go to '...'
22 | 2. Click on '....'
23 | 3. Scroll down to '....'
24 | 4. See error
25 |
26 | ## Expected behavior
27 |
28 | A clear and concise description of what you expected to happen.
29 |
30 | ## Screenshots
31 |
32 | If applicable, add screenshots to help better explain your problem.
33 |
34 | ## Environment & setup
35 |
36 | - OS:
37 | - Node.js version:
38 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: true
2 | contact_links:
3 | - name: Ask a question
4 | url: https://github.com/YassinEldeeb/create-prisma-generator/discussions/new
5 | about: Ask questions and discuss with other community members
6 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature-request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: "\U0001F64B♂️ Feature request"
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: enhancement
6 | assignees: YassinEldeeb
7 | ---
8 |
9 | ## Is your feature request related to a problem? Please describe.
10 |
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | ## Describe the solution you'd like
14 |
15 | A clear and concise description of what you want to happen.
16 |
17 | ## Describe alternatives you've considered
18 |
19 | A clear and concise description of any alternative solutions or features you've considered.
20 |
21 | ## Additional context
22 |
23 | Add any other context or screenshots about the feature request here.
24 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # To get started with Dependabot version updates, you'll need to specify which
2 | # package ecosystems to update and where the package manifests are located.
3 | # Please see the documentation for all configuration options:
4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
5 |
6 | version: 2
7 | updates:
8 | - package-ecosystem: 'npm'
9 | directory: '/packages/create-prisma-generator'
10 | schedule:
11 | interval: 'daily'
12 |
13 | # Checking for dependencies in templates *weekly*
14 | # ADDED PROGRAMATICALLY
15 | - package-ecosystem: 'npm'
16 | directory: '/packages/cpg-semantic-releases/template'
17 | schedule:
18 | interval: 'weekly'
19 |
20 | - package-ecosystem: 'npm'
21 | directory: '/packages/cpg-template/template'
22 | schedule:
23 | interval: 'weekly'
24 |
25 | - package-ecosystem: 'npm'
26 | directory: '/packages/cpg-template-typescript/template'
27 | schedule:
28 | interval: 'weekly'
29 |
--------------------------------------------------------------------------------
/.github/workflows/auto-merge:
--------------------------------------------------------------------------------
1 | name: auto-merge
2 |
3 | on:
4 | pull_request:
5 |
6 | jobs:
7 | auto-merge:
8 | runs-on: ubuntu-latest
9 | steps:
10 | - uses: actions/checkout@v2
11 | - uses: ahmadnassri/action-dependabot-auto-merge@v2
12 | with:
13 | target: patch
14 | github-token: ${{ secrets.GH_TOKEN }}
15 |
--------------------------------------------------------------------------------
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------
1 | name: Test & Publish Packages
2 |
3 | on:
4 | push:
5 | branches: [main]
6 | pull_request:
7 | branches: ['*']
8 |
9 | jobs:
10 | unit-tests:
11 | runs-on: ${{ matrix.os }}
12 | strategy:
13 | matrix:
14 | node-version: [14.x, 15.x, 16.x, 17.x]
15 | os: [ubuntu-latest, windows-latest]
16 |
17 | steps:
18 | - uses: actions/checkout@v2
19 | with:
20 | fetch-depth: 100
21 | - name: Use Node.js ${{matrix.node-version}}
22 | uses: actions/setup-node@v2
23 | with:
24 | node-version: ${{ matrix.node-version }}
25 |
26 | - uses: YassinEldeeb/has-changed-path@v2.1
27 | id: changed-packages
28 | with:
29 | paths: packages
30 |
31 | - name: Setup PNPM
32 | if: steps.changed-packages.outputs.changed == 'true'
33 | uses: pnpm/action-setup@v2.0.1
34 | with:
35 | version: 6.23.6
36 |
37 | - name: Simulate CLI build
38 | if: steps.changed-packages.outputs.changed == 'true'
39 | run: pnpm simulate-dist
40 |
41 | - name: Install dependencies
42 | if: steps.changed-packages.outputs.changed == 'true'
43 | run: pnpm i --frozen-lockfile
44 |
45 | - name: Run Tests 🧪
46 | if: steps.changed-packages.outputs.changed == 'true'
47 | working-directory: ./packages/create-prisma-generator
48 | run: pnpm test -- --testPathIgnorePatterns e2e
49 |
50 | e2e-tests:
51 | runs-on: ${{ matrix.os }}
52 | needs: [unit-tests]
53 | strategy:
54 | matrix:
55 | node-version: [16.x]
56 | os: [ubuntu-latest, windows-latest, macos-latest]
57 |
58 | steps:
59 | - uses: actions/checkout@v2
60 | with:
61 | fetch-depth: 100
62 | - name: Use Node.js ${{matrix.node-version}}
63 | uses: actions/setup-node@v2
64 | with:
65 | node-version: ${{ matrix.node-version }}
66 |
67 | - uses: YassinEldeeb/has-changed-path@v2.1
68 | id: changed-packages
69 | with:
70 | paths: packages
71 |
72 | - name: Tell git who am I
73 | if: steps.changed-packages.outputs.changed == 'true'
74 | run: |
75 | git config --global user.name "me"
76 | git config --global user.email "me@example.com"
77 |
78 | - name: Setup PNPM
79 | if: steps.changed-packages.outputs.changed == 'true'
80 | uses: pnpm/action-setup@v2.0.1
81 | with:
82 | version: 6.23.6
83 |
84 | - name: Simulate CLI build
85 | if: steps.changed-packages.outputs.changed == 'true'
86 | run: pnpm simulate-dist
87 |
88 | - name: Install dependencies
89 | if: steps.changed-packages.outputs.changed == 'true'
90 | run: pnpm i --frozen-lockfile
91 |
92 | - name: Run E2E Tests 🧪
93 | if: steps.changed-packages.outputs.changed == 'true'
94 | working-directory: ./packages/create-prisma-generator
95 | run: pnpm test -- --testPathPattern e2e
96 |
97 | Publish:
98 | runs-on: ubuntu-latest
99 | if: ${{ github.ref == 'refs/heads/main' }}
100 | needs: [unit-tests, e2e-tests]
101 |
102 | steps:
103 | - uses: actions/checkout@v2
104 | with:
105 | fetch-depth: 100
106 | - name: Use Node.js
107 | uses: actions/setup-node@v2
108 | with:
109 | node-version: 16.x
110 | registry-url: https://registry.npmjs.org
111 |
112 | - uses: YassinEldeeb/has-changed-path@v2.1
113 | id: changed-packages
114 | with:
115 | paths: packages
116 |
117 | - name: Setup PNPM
118 | uses: pnpm/action-setup@v2.0.1
119 | if: steps.changed-packages.outputs.changed == 'true'
120 | with:
121 | version: 6.23.6
122 |
123 | - uses: actions/cache@v2
124 | if: steps.changed-packages.outputs.changed == 'true'
125 | with:
126 | path: '**/node_modules'
127 | key: ${{ runner.os }}-modules-${{ hashFiles('**/pnpm-lock.yaml') }}
128 |
129 | - name: Simulate CLI build
130 | if: steps.changed-packages.outputs.changed == 'true'
131 | run: pnpm simulate-dist
132 |
133 | - name: Install deps
134 | if: steps.changed-packages.outputs.changed == 'true'
135 | run: pnpm i --frozen-lockfile
136 |
137 | - name: Publish 🚀
138 | if: steps.changed-packages.outputs.changed == 'true'
139 | run: |
140 | GITHUB_TOKEN="${{secrets.GITHUB_TOKEN}}" GIT_COMMITTER_EMAIL="${{secrets.GIT_COMMITTER_EMAIL}}" GIT_COMMITTER_NAME="${{secrets.GIT_COMMITTER_NAME}}" npx ts-node ./scripts/ci/publish.ts
141 | env:
142 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
143 |
--------------------------------------------------------------------------------
/.github/workflows/update-blogs.yml:
--------------------------------------------------------------------------------
1 | name: Update Blogs
2 |
3 | on:
4 | push:
5 | branches: [main]
6 |
7 | jobs:
8 | UpdateBlogs:
9 | runs-on: ubuntu-latest
10 | steps:
11 | - uses: actions/checkout@v2
12 | with:
13 | fetch-depth: 100
14 | - name: Use Node.js
15 | uses: actions/setup-node@v2
16 | with:
17 | node-version: 16.x
18 |
19 | - uses: YassinEldeeb/has-changed-path@v2.1
20 | id: changed-blogs
21 | with:
22 | paths: dev.to
23 |
24 | - name: Setup PNPM
25 | if: steps.changed-blogs.outputs.changed == 'true'
26 | uses: pnpm/action-setup@v2.0.1
27 | with:
28 | version: 6.23.6
29 |
30 | - uses: actions/cache@v2
31 | if: steps.changed-blogs.outputs.changed == 'true'
32 | with:
33 | path: '**/node_modules'
34 | key: ${{ runner.os }}-modules-${{ hashFiles('**/pnpm-lock.yaml') }}
35 |
36 | - name: Install deps
37 | if: steps.changed-blogs.outputs.changed == 'true'
38 | run: pnpm i --filter "dev.to" --frozen-lockfile
39 |
40 | - name: Run Prettier
41 | if: steps.changed-blogs.outputs.changed == 'true'
42 | working-directory: dev.to
43 | run: pnpm prettier:check
44 |
45 | - name: Run Embedme
46 | if: steps.changed-blogs.outputs.changed == 'true'
47 | working-directory: dev.to
48 | run: pnpm embedme:check
49 |
50 | - name: Deploy to dev.to
51 | if: steps.changed-blogs.outputs.changed == 'true'
52 | working-directory: dev.to
53 | run: DEV_TO_GIT_TOKEN=${{ secrets.DEV_TOKEN }} pnpm dev-to-git
54 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | dist
2 | node_modules
3 | yarn-error.log
4 | build
5 | .pnpm-debug.log
6 | .env*
7 | .yalc
8 | my-gen
9 | .turbo
10 | .pnpm-debug.log
11 | temp
--------------------------------------------------------------------------------
/.husky/commit-msg:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | . "$(dirname "$0")/_/husky.sh"
3 |
4 | npx --no-install commitlint --edit "$1"
5 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | . "$(dirname "$0")/_/husky.sh"
3 |
4 | pnpm guide-dependabot
5 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true,
3 | "trailingComma": "all",
4 | "semi": false,
5 | "tabWidth": 2
6 | }
7 |
--------------------------------------------------------------------------------
/ARCHITECTURE.md:
--------------------------------------------------------------------------------
1 | # Architecture
2 |
3 | | CLIs | Short description |
4 | | --- | --- |
5 | | [create-prisma-generator](https://github.com/YassinEldeeb/create-prisma-generator/tree/main/packages/create-prisma-generator) | The main CLI that's responsible for prompting questions & executing other CLIs. |
6 | | [@cpg-cli/template-typescript](https://github.com/YassinEldeeb/create-prisma-generator/tree/main/packages/cpg-template-typescript) | CLI that stores the typescript template and copies it to the desired location. |
7 | | [@cpg-cli/template](https://github.com/YassinEldeeb/create-prisma-generator/tree/main/packages/cpg-template) | CLI that stores the javascript template and copies it to the desired location. |
8 | | [@cpg-cli/root-configs](https://github.com/YassinEldeeb/create-prisma-generator/tree/main/packages/cpg-root-configs) | CLI that stores the shared root configs and copies it to the desired location. |
9 | | [@cpg-cli/github-actions](https://github.com/YassinEldeeb/create-prisma-generator/tree/main/packages/cpg-github-actions) | CLI that stores the github actions template and copies it to the desired location. |
10 | | [@cpg-cli/semantic-releases](https://github.com/YassinEldeeb/create-prisma-generator/tree/main/packages/cpg-semantic-releases) | CLI that configs the current project to support [`semantic-release`](https://github.com/semantic-release/github) and add commit-msg safety. |
11 | | [@cpg-cli/template-gen-usage](https://github.com/YassinEldeeb/create-prisma-generator/tree/main/packages/cpg-template-gen-usage) | CLI that stores the generator usage template and copies it to the desired location. |
12 |
13 |
14 | ## create-prisma-generator
15 | This package is the `main CLI` that prompt questions to developers to know how they want their development environment to be like.
16 |
17 | 
18 |
19 | And based on the answers It'll execute the other Tiny CLIs to setup & configure different things.
20 | ```ts
21 | export const CLIs = {
22 | typescriptTemplate(path: string) {
23 | return `npx @cpg-cli/template-typescript@latest ${path}`
24 | },
25 | rootConfigs(path: string) {
26 | return `npx @cpg-cli/root-configs@latest ${path}`
27 | },
28 | usageTemplate(path: string) {
29 | return `npx @cpg-cli/template-gen-usage@latest ${path}`
30 | },
31 | javascriptTemplate(path: string) {
32 | return `npx @cpg-cli/template@latest ${path}`
33 | },
34 | githubActionsTemplate(path: string) {
35 | return `npx @cpg-cli/github-actions@latest ${path}`
36 | },
37 | setupSemanticRelease(path: string, workspaceFlag: string) {
38 | return `npx @cpg-cli/semantic-releases@latest ${path} ${workspaceFlag}`
39 | },
40 | }
41 | ```
42 |
43 | ## What are the folders that starts with cpg?
44 |
45 | > **cpg** is an acronym that stands for **Create Prisma Generator**
46 |
47 | Those folders contain scoped npm packages under [**@cpg-cli** organization](https://www.npmjs.com/org/cpg-cli) and those packages are basically **Tiny CLIs** that are responsible for configuring or copying templates(files/folders) to a desired location and are executed by the main CLI `create-prisma-generator` as shell commands.
48 |
49 | All of those Tiny CLIs packages are bootstrapped by [this script](https://github.com/YassinEldeeb/create-prisma-generator/blob/main/scripts/addNewTemplate.ts) which follow the same structure
50 |
51 | the template folder contains files/folders that are copied to the desired location as provided in the first argument when running the CLI.
52 |
53 | ```
54 | packages
55 | └── cpg-new-template
56 | ├── index.js
57 | ├── bin.js
58 | ├── package.json
59 | └── template
60 | ```
61 |
62 | ## But hey why typescript/javascript templates use hardcoded versions for their dependencies?
63 |
64 | At first you might see this approach super wrong and think of why not installing the latest versions of the dependencies but this is the same approuch used in [create-react-app](https://github.com/facebook/create-react-app/blob/main/packages/cra-template-typescript/template.json) but why?
65 |
66 | This approuch called Locking dependencies which means to set hardcoded versions for external/untrusted packages that can break unexpectedly with any release.
67 |
68 | ### but then, How those dependencies can be updated? manually?
69 | Dependabot is [configured](https://github.com/YassinEldeeb/create-prisma-generator/blob/main/.github/dependabot.yml) to watch dependencies in every `package.json` that's located under a template folder which run before committing using [Husky](https://github.com/typicode/husky).
70 |
71 | Dependabot is configured programmatically using [this script](https://github.com/YassinEldeeb/create-prisma-generator/blob/main/scripts/guideDependabot.ts), We're not doing anything manually here 😄.
72 |
73 | Dependabot is gonna PR me with the latest versions of the dependencies **weekly** so I can review and merge them which will update the templates' package.json(s) and publish them automatically using [this github action workflow](https://github.com/YassinEldeeb/create-prisma-generator/blob/main/.github/workflows/publish.yml)
74 |
75 | ## Why splitting templates/configs into different packages?
76 |
77 | 1. This ensures that developers only download what they asked for.
78 | 2. Shrinks the main CLI size.
79 | 3. Splitting them actually eliminates the need for updating the CLI to get the latest templates/configs cause the main CLI uses the latest versions of the Tiny CLIs to ensure that developers always get the latest templates/configs with the same `create-prisma-generator` version.
80 | 4. Control over managable tiny pieces.
81 | 5. If a developer needed a specific config after setting up his project, He can use one of the tiny CLIs to setup it in his existing project.
82 |
83 | ## What's the `dev.to` directory in the root?
84 |
85 | This stores the blogs published to [dev.to/YassinEldeeb](https://dev.to/YassinEldeeb) where I explain more about the generated boilerplate and prisma generators.
86 |
87 | Those blogs are updated automatically via [this github action workflow](https://github.com/YassinEldeeb/create-prisma-generator/blob/main/.github/workflows/update-blogs.yml).
88 |
89 | This enable a waterfall of features that couldn't be possible before:
90 | 1. History of changes, compare when editing.
91 | 2. Using [prettier](https://github.com/prettier/prettier) to format the markdown and all the code snippets.
92 | 3. Letting people contribute to the blogs by creating a PR against it.
93 | 4. Managing code examples and update them easier, Thanks to [Embedme](https://github.com/zakhenry/embedme)
94 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | We as members, contributors, and leaders pledge to make participation in our
6 | community a harassment-free experience for everyone, regardless of age, body
7 | size, visible or invisible disability, ethnicity, sex characteristics, gender
8 | identity and expression, level of experience, education, socio-economic status,
9 | nationality, personal appearance, race, religion, or sexual identity
10 | and orientation.
11 |
12 | We pledge to act and interact in ways that contribute to an open, welcoming,
13 | diverse, inclusive, and healthy community.
14 |
15 | ## Our Standards
16 |
17 | Examples of behavior that contributes to a positive environment for our
18 | community include:
19 |
20 | * Demonstrating empathy and kindness toward other people
21 | * Being respectful of differing opinions, viewpoints, and experiences
22 | * Giving and gracefully accepting constructive feedback
23 | * Accepting responsibility and apologizing to those affected by our mistakes,
24 | and learning from the experience
25 | * Focusing on what is best not just for us as individuals, but for the
26 | overall community
27 |
28 | Examples of unacceptable behavior include:
29 |
30 | * The use of sexualized language or imagery, and sexual attention or
31 | advances of any kind
32 | * Trolling, insulting or derogatory comments, and personal or political attacks
33 | * Public or private harassment
34 | * Publishing others' private information, such as a physical or email
35 | address, without their explicit permission
36 | * Other conduct which could reasonably be considered inappropriate in a
37 | professional setting
38 |
39 | ## Enforcement Responsibilities
40 |
41 | Community leaders are responsible for clarifying and enforcing our standards of
42 | acceptable behavior and will take appropriate and fair corrective action in
43 | response to any behavior that they deem inappropriate, threatening, offensive,
44 | or harmful.
45 |
46 | Community leaders have the right and responsibility to remove, edit, or reject
47 | comments, commits, code, wiki edits, issues, and other contributions that are
48 | not aligned to this Code of Conduct, and will communicate reasons for moderation
49 | decisions when appropriate.
50 |
51 | ## Scope
52 |
53 | This Code of Conduct applies within all community spaces, and also applies when
54 | an individual is officially representing the community in public spaces.
55 | Examples of representing our community include using an official e-mail address,
56 | posting via an official social media account, or acting as an appointed
57 | representative at an online or offline event.
58 |
59 | ## Enforcement
60 |
61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
62 | reported to the community leaders responsible for enforcement at
63 | .
64 | All complaints will be reviewed and investigated promptly and fairly.
65 |
66 | All community leaders are obligated to respect the privacy and security of the
67 | reporter of any incident.
68 |
69 | ## Enforcement Guidelines
70 |
71 | Community leaders will follow these Community Impact Guidelines in determining
72 | the consequences for any action they deem in violation of this Code of Conduct:
73 |
74 | ### 1. Correction
75 |
76 | **Community Impact**: Use of inappropriate language or other behavior deemed
77 | unprofessional or unwelcome in the community.
78 |
79 | **Consequence**: A private, written warning from community leaders, providing
80 | clarity around the nature of the violation and an explanation of why the
81 | behavior was inappropriate. A public apology may be requested.
82 |
83 | ### 2. Warning
84 |
85 | **Community Impact**: A violation through a single incident or series
86 | of actions.
87 |
88 | **Consequence**: A warning with consequences for continued behavior. No
89 | interaction with the people involved, including unsolicited interaction with
90 | those enforcing the Code of Conduct, for a specified period of time. This
91 | includes avoiding interactions in community spaces as well as external channels
92 | like social media. Violating these terms may lead to a temporary or
93 | permanent ban.
94 |
95 | ### 3. Temporary Ban
96 |
97 | **Community Impact**: A serious violation of community standards, including
98 | sustained inappropriate behavior.
99 |
100 | **Consequence**: A temporary ban from any sort of interaction or public
101 | communication with the community for a specified period of time. No public or
102 | private interaction with the people involved, including unsolicited interaction
103 | with those enforcing the Code of Conduct, is allowed during this period.
104 | Violating these terms may lead to a permanent ban.
105 |
106 | ### 4. Permanent Ban
107 |
108 | **Community Impact**: Demonstrating a pattern of violation of community
109 | standards, including sustained inappropriate behavior, harassment of an
110 | individual, or aggression toward or disparagement of classes of individuals.
111 |
112 | **Consequence**: A permanent ban from any sort of public interaction within
113 | the community.
114 |
115 | ## Attribution
116 |
117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage],
118 | version 2.0, available at
119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
120 |
121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct
122 | enforcement ladder](https://github.com/mozilla/diversity).
123 |
124 | [homepage]: https://www.contributor-covenant.org
125 |
126 | For answers to common questions about this code of conduct, see the FAQ at
127 | https://www.contributor-covenant.org/faq. Translations are available at
128 | https://www.contributor-covenant.org/translations.
129 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # How do I Contribute 💪
2 |
3 | we're excited to have you on board!
4 |
5 | - Take a look at the existing [Issues](https://github.com/YassinEldeeb/create-prisma-generator/issues) or [create a new issue](https://github.com/YassinEldeeb/create-prisma-generator/issues/new)!
6 | - Fork the Repo. Then, create a branch for any issue that you are working on. Finally, commit your work.
7 | - Create a **[Pull Request](https://github.com/YassinEldeeb/create-prisma-generator/compare)**, which will be promptly reviewed and given suggestions for improvements.
8 |
9 | ## Architecture
10 |
11 | Look at the [`ARCHITECTURE.md`](https://github.com/YassinEldeeb/create-prisma-generator/blob/main/ARCHITECTURE.md) to understand how everything is working.
12 |
13 | ## Prerequisites
14 |
15 | 1. Node.js version installed, [latest LTS is recommended](https://nodejs.org/en/about/releases/)
16 | 2. Install [pnpm](https://pnpm.io) (for installing npm dependencies, using pnpm workspaces)
17 |
18 | ## How to start developing?
19 |
20 | Setup and install the needed dependencies for all the packages by following these steps:
21 |
22 | ```sh
23 | git clone https://github.com/YassinEldeeb/create-prisma-generator.git
24 | cd create-prisma-generator
25 | pnpm i
26 | ```
27 |
28 | ## Add a new template
29 |
30 | To make a new template, You need to setup a new package simply by running the following command:
31 |
32 | ```sh
33 | pnpm new-template ${template-name}
34 | ```
35 |
36 | This script will make a new package at `packages/${template-name}` with all of the boilerplate for you to start adding your template
37 |
38 | ```diff
39 | packages
40 | +└── cpg-new-template
41 | + ├── index.js
42 | + ├── bin.js
43 | + ├── package.json
44 | + └── template
45 | ```
46 |
47 | The generated package contains `index.js` and that acts like a tiny CLI that takes a path as the first argument to identify where to copy the template to.
48 |
49 | Note that you can do whatever you want with this CLI like configuring existing files but this is just the default boilerplate that's most commonly used.
50 |
51 | > ⚠ `index.js` has to be named exactly like this and export a default function cause tests depends on it when mocking `child_process` executed CLIs.
52 | > If you're interested you can see the [whole shebang](https://github.com/YassinEldeeb/create-prisma-generator/blob/main/packages/create-prisma-generator/src/__tests__/check-ouput-structure.test.ts#L97-L118)
53 | ```js
54 | const path = require('path')
55 | const fs = require('fs')
56 |
57 | const setup = () => {
58 | function copySync(from, to) {
59 | fs.mkdirSync(to, { recursive: true })
60 | fs.readdirSync(from).forEach((element) => {
61 | if (fs.lstatSync(path.join(from, element)).isFile()) {
62 | fs.copyFileSync(path.join(from, element), path.join(to, element))
63 | } else {
64 | copySync(path.join(from, element), path.join(to, element))
65 | }
66 | })
67 | }
68 |
69 | copySync(
70 | path.join(path.join(__dirname, `./template`)),
71 | path.join(process.cwd(), process.argv[2]),
72 | )
73 | }
74 |
75 | module.exports = setup
76 | ```
77 |
78 | now you can place whatever configs, files, ..etc in the template directory and then PR me so I can review it and gratefully accept
79 | it where It can be published to npm as a scoped package under [`@cpg-cli`](https://www.npmjs.com/org/cpg-cli)
80 | organization by a [`Github Actions`](https://github.com/features/actions) workflow.
81 |
82 | **Yeah It's now published to npm but isn't it now just acts like a host to my files, like... how to transfer them to the actual CLI generated boilerplate?**
83 |
84 | That's a really good question, I'm glad you've asked it
85 |
86 | ## Use Templates in `create-prisma-generate`
87 |
88 | In `packages/create-prisma-generator` is where everything takes place, It's the CLI that's responsible for:
89 |
90 | 1. prompting the questions
91 | 2. validating answers
92 | 3. configuring the boilerplate
93 | 4. executing shell commands to run certain **Tiny CLIs** to do setup things based on the project's information
94 |
95 | ### So first:
96 |
97 | open `packages/create-prisma-generator/src/utils/promptQuestions.ts` this file contains all of the questions that are prompt to the developers to setup their customized
98 | project.
99 |
100 | This is using [Inquirer.js](https://github.com/SBoudrias/Inquirer.js), here you can edit the questions or ask more questions (which
101 | I wouldn't recommend that much cause It would be too annoying to answer all of these questions to get a project setup) to satisfy your needs.
102 |
103 | After that you can open `packages/create-prisma-generator/src/index.ts`, here you can find the `promptQuestions()` function we've just discussed that'll return us all
104 | of the developer's answers about the project setup.
105 |
106 | And so looking at the written examples you'll see syntax like this frequently, so the `runBlockingCommand(templateName, command, ?type)` function is a synchronous function
107 | that'll call `execSync` from node's child_process to execute a shell command that'll use our tiny CLIs to setup the different pieces of our boilerplate.
108 |
109 | ```ts
110 | const templateName = 'root default configs'
111 | runBlockingCommand(templateName, CLIs.rootConfigs(pkgName))
112 | ```
113 |
114 | And `CLIs` is just an object that has a bunch of methods to execute the Tiny CLIs which you'll have to add your own here as well
115 |
116 | ```ts
117 | // packages/create-prisma-generator/src/tinyClis.ts
118 | export const CLIs = {
119 | typescriptTemplate(path: string) {
120 | return `npx @cpg-cli/template-typescript ${path}`
121 | },
122 | rootConfigs(path: string) {
123 | return `npx @cpg-cli/root-configs ${path}`
124 | },
125 | usageTemplate(path: string) {
126 | return `npx @cpg-cli/template-gen-usage ${path}`
127 | },
128 | javascriptTemplate(path: string) {
129 | return `npx @cpg-cli/template ${path}`
130 | },
131 | githubActionsTemplate(path: string) {
132 | return `npx @cpg-cli/github-actions ${path}`
133 | },
134 | setupSemanticRelease(path: string, workspaceFlag: string) {
135 | return `npx @cpg-cli/semantic-releases ${path} ${workspaceFlag}`
136 | },
137 | }
138 | ```
139 |
140 | so now It just depends on what you're setting up, you're now equiped with all of the tools/utilities to support other things like other CIs as an example cause currently
141 | `Github Actions` is the only supported CI.
142 |
143 | # How to run create-prisma-generator in development?
144 |
145 | The setup I would recommend is running `pnpm dev` in `packages/create-prisma-generator` in a terminal and open another one and `cd packages/cli-usage` where you can find an empty package that has a single purpose of testing your changes to all of the other `packages`.
146 |
147 | So if you opened `packages/cli-usage/package.json` you'll see all of the packages linked locally from the workspace **which you'll find your own there as well cause the script you ran at first added it automatically**
148 |
149 | ```json
150 | "devDependencies": {
151 | "create-prisma-generator": "workspace:*",
152 | "@cpg-cli/semantic-releases": "workspace:*",
153 | "@cpg-cli/github-actions": "workspace:*",
154 | "@cpg-cli/template": "workspace:*",
155 | "@cpg-cli/template-gen-usage": "workspace:*",
156 | "@cpg-cli/template-typescript": "workspace:*",
157 | "@cpg-cli/root-configs": "workspace:*"
158 | }
159 | ```
160 |
161 | so after you make changes to any of the listed packages you just run `pnpm cli` to test the main `create-prisma-generator` cli that would execute all of the other tiny CLIs.
162 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Yassin Eldeeb
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/PRISMA_SDK_REFERENCE.md:
--------------------------------------------------------------------------------
1 | # Prisma SDK Reference (v3.6.0)
2 |
3 | > ⚠ WARNING: `@prisma/sdk` is an internal API that's not supposed to be used outside of prisma's fence as [mentioned in this issue](https://github.com/prisma/prisma/issues/10725)
4 | >
5 | > It's not recommended to fully document `@prisma/sdk` as it might change unexpectedly or be deleted entirely so if you wanna contribute, please keep it simple and don't dig deep.
6 | >
7 | > These are some of the most important Utilities when developing prisma generators that can save you some time.
8 | >
9 | > This was documented by someone who doesn't work at prisma but a big fan so I think I'm qualified to document some parts of this internal API 🤷♂️
10 |
11 |
12 | ## `logger`
13 |
14 | a simple wrapper around [`chalk`](https://github.com/chalk/chalk) to print colorized messages to the terminal formatted like that:
15 |
16 | `prisma:type` Message
17 |
18 | ### Usage
19 |
20 | ```ts
21 | const GENERATOR_NAME = 'prisma-generator-seeder'
22 |
23 | logger.info(`${GENERATOR_NAME}:Registered`)
24 | ```
25 |
26 | ## `getDMMF`
27 |
28 | this is a very great utility function that comes in handy especially when writting tests when you've a `sample.prisma` and want to get DMMF from it without running `prisma generate` and go through this cycle 👎:
29 |
30 | ```sh
31 | @prisma/cli -> @prisma/sdk -> Spawns Generators -> Send DMMF through RPCs
32 | ```
33 |
34 | a better approuch is to cut this cycle and just get the utility function in `@prisma/sdk` that's responsible for generating the DMMF from a prisma definitions string.
35 |
36 | This function calls a rust binary that introspects the prisma definations in the string and gives back a nice AST([Abstract Syntax Tree](https://en.wikipedia.org/wiki/Abstract_syntax_tree)) for the defined definations in prisma modelling language.
37 |
38 | > ⚠️ Note: The DMMF is a Prisma ORM internal API with no guarantees for stability to outside users. They might - and do - change the DMMF in potentially breaking ways between minor versions.
39 |
40 | ### Usage
41 |
42 | ```ts
43 | const samplePrismaSchema = fs.readFileSync(
44 | path.join(__dirname, './sample.prisma'),
45 | 'utf-8',
46 | )
47 |
48 | const sampleDMMF = getDMMF({
49 | datamodel: samplePrismaSchema,
50 | })
51 | ```
52 |
53 | ## `getSchema` & `getSchemaSync`
54 | shortcut for `fs.readFileSync(path,'utf-8')`, It takes a path for the prisma definitions file and returns a string of it's contents.
55 |
56 | ### Usage
57 |
58 | ```ts
59 | // Sync
60 | const schema = getSchemaSync(path.join(__dirname, './sample.prisma'))
61 |
62 | // Async
63 | const schema2 = await getSchema(path.join(__dirname, './sample2.prisma'))
64 | ```
65 |
66 | ## `getPlatform`
67 | a better formatted version of `process.platform` that's responsible for getting the current operating system of the running process.
68 |
69 | ### Usage
70 |
71 | ```ts
72 | const platform = await getPlatform()
73 | ```
74 |
75 | ## `isCi`
76 | a simple wrapper around [`ci-info`](https://github.com/watson/ci-info) to get a boolean if wether or not the generator is running in a CI environment.
77 |
78 | ### Usage
79 |
80 | ```ts
81 | if(isCi() || process.env.NODE_ENV === 'production') {
82 | return
83 | }
84 | ```
85 |
86 | ## `drawBox`
87 | a great utility for drawing boxes that can be useful for showing messages if there're any breaking changes or uncompatibility issues.
88 |
89 | **Notes:**
90 |
91 | anyVerticalValue = number of lines
92 |
93 | anyHorizontalValue = number of space chars
94 |
95 | ### Usage
96 |
97 | ```ts
98 | const incompatibilityErrorMessage = drawBox({
99 | title: 'Unsupported Node.js version',
100 | str: `prisma-generator-seeder only supports Node.js >= 14.3`,
101 | horizontalPadding: 3,
102 | height: 1,
103 | width: 74,
104 | })
105 |
106 | console.log(incompatibilityErrorMessage)
107 | ```
108 |
109 | ## `highlightSql`, `highlightDatamodel` & `highlightTS`
110 |
111 | Those are some utility functions that are used to highlight code snippets that can be then outputted to the terminal
112 |
113 | ### Usage
114 |
115 | ```ts
116 | const requiredDataModel = `generator client {
117 | provider = "prisma-client-js"
118 | }`
119 |
120 | console.log(highlightDatamodel(requiredDataModel))
121 | ```
122 |
123 | # Credits
124 |
125 | This API reference probably wouldn't exist without the help of [`Github Code Search`](https://www.youtube.com/watch?v=UOIPBfPXkus) which I got my preview license to try it the day I wrote this documentation and It helped me a lot in searching the different parts in `@prisma/sdk` in Prisma's code base to see how they're used and in which cases.
126 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 |
18 |
19 | ## ⚠️ Note
20 |
21 | `create-prisma-generator` doesn't support Prisma 4 yet, it works with Prisma 3, but that doesn't mean that you can't upgrade on your own quite smoothly.
22 |
23 | Any contributions to support Prisma 4 in the `create-prisma-generator` are highly appreciated and will be part of the authors/contributors section of this tool.
24 | ## Prisma
25 |
26 | > [Prisma](https://www.prisma.io/) is Database ORM Library for Node.js, Typescript.
27 |
28 | Prisma has a concept called "Generator". A generator is an executable program, which takes the parsed Prisma schema as an input and has full freedom to output anything.
29 |
30 | The most prominent generator is called [`prisma-client-js`](https://github.com/prisma/prisma/tree/main/packages/client). It's the ORM Client powering the main TypeScript and JavaScript usage of Prisma from Node.js.
31 |
32 | Generators will always be called when you run `prisma generate`. However, only the generators mentioned in the schema.prisma file are being run.
33 |
34 | [Strongly recommend reading the full article, It's pretty damn good](https://prismaio.notion.site/Prisma-Generators-a2cdf262207a4e9dbcd0e362dfac8dc0)
35 |
36 | # Motivation
37 | As a community, developing prisma generators is really hard cause that's a very new concept to us so It's like knowing JS but being exposed to do ML with it for the first time and there is nothing documented about **@prisma/sdk** ([this is done intentionally](https://github.com/prisma/prisma/discussions/10721#discussioncomment-1822836)) which has a very great utilities when developing or testing prisma generators and the only way you can get started is by looking at [other generators](https://www.prisma.io/docs/concepts/components/prisma-schema/generators#community-generators) code which might be useful to get you started.
38 |
39 | I'm really obsessed with this architecture that Prisma Client is built on and I can see a bright future for Prisma Generators from the community to integrate Prisma nicely with different frameworks or make tools that can beneift from Prisma models.
40 |
41 | But unfortunately I didn't have a smooth experience developing [my prisma generator](https://github.com/YassinEldeeb/prisma-tgql-types-gen).
42 |
43 | So I created this CLI to encourage developers to make their own prisma generators to have a smooth experience with all of the annoying repetitive things carried away like: getting started boilerplate, publishing, testing the gen locally by running `prisma generate`, ..etc
44 |
45 | ### Also Created a blog on dev.to where we're gonna be discussing the hello world prisma generator together that this CLI has setup for you and the different concepts you'll come across when developing prisma generators, [Check It out here](https://dev.to/yassineldeeb/create-prisma-generator-2mdg)
46 |
47 | # Usage
48 |
49 | Answer the prompt questions to setup your project, The project setup will be based on your answers.
50 |
51 | ```sh
52 | npx create-prisma-generator
53 | ```
54 |
55 | # What’s Included?
56 |
57 | Your environment will have everything you need to build your prisma generator like an elite open-source maintainer:
58 | - Hello World Prisma Generator.
59 | - Typescript Support.
60 | - JavaScript setup with babel to enable the usage of the latest JS features.
61 | - Automatic publishing workflow with Github Actions.
62 | - Workspace setup for testing the generator locally using `prisma generate`.
63 | - Scripts for development, building, packaging and testing.
64 | - Support for most package-managers `yarn`, `pnpm` and `npm`.
65 | - Automatic semantic release with safety in mind using [commitlint](https://github.com/conventional-changelog/commitlint) & [husky](https://github.com/typicode/husky) to validate your [commit messages](https://github.com/angular/angular/blob/master/CONTRIBUTING.md#-commit-message-format).
66 | - Test environment using [Jest](https://github.com/facebook/jest) with an example & fixtures to help you get started.
67 | - Dependabot for keeping dependencies up to date.
68 |
69 | # Architecture
70 | Read [Architecture.md](https://github.com/YassinEldeeb/create-prisma-generator/blob/main/ARCHITECTURE.md) to understand how everything is working.
71 |
72 | # Prisma SDK reference
73 | Read [Prisma_SDK_Reference.md](https://github.com/YassinEldeeb/create-prisma-generator/blob/main/PRISMA_SDK_REFERENCE.md)
74 |
75 | # Community
76 | The Create Prisma Generator community can be found on [GitHub Discussions](https://github.com/YassinEldeeb/create-prisma-generator/discussions), where you can ask questions, suggest ideas, and share your projects.
77 |
78 | # Contributing
79 | We'll be very thankful for all your contributions, whether it's for helping us find issues in our code, highlighting features that're missing, writing tests for uncovered cases, or contributing to the codebase.
80 |
81 | Read the [Contributing guide](https://github.com/YassinEldeeb/create-prisma-generator/blob/main/CONTRIBUTING.md) to get started.
82 |
83 | ### 💚 All Thanks to Prisma's brilliant developers for making such an awesome Node.js ORM that can be easily built on top of it.
84 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Security Policy
2 |
3 | If you have a security issue to report, please contact me at [yassineldeeb94@gmail.com](mailto:yassineldeeb94@gmail.com).
4 |
--------------------------------------------------------------------------------
/TESTING.md:
--------------------------------------------------------------------------------
1 | ## Testing
2 |
3 | coming soon...
4 |
--------------------------------------------------------------------------------
/commitlint.config.js:
--------------------------------------------------------------------------------
1 | // Config commitlint rules:
2 | // https://github.com/conventional-changelog/commitlint/blob/master/docs/reference-rules.md
3 | module.exports = {
4 | extends: ['@commitlint/config-conventional'],
5 | rules: {
6 | 'type-enum': [
7 | 2,
8 | 'always',
9 | [
10 | 'chore',
11 | 'feat',
12 | 'fix',
13 | 'blog',
14 | 'ci',
15 | 'refactor',
16 | 'perf',
17 | 'revert',
18 | 'style',
19 | 'test',
20 | 'docs',
21 | 'automate',
22 | 'script',
23 | ],
24 | ],
25 | },
26 | }
27 |
--------------------------------------------------------------------------------
/dev.to/README.md:
--------------------------------------------------------------------------------
1 | # Automatically Publish Blogs to Dev.to
2 |
--------------------------------------------------------------------------------
/dev.to/blogs/create-prisma-generator/assets/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YassinEldeeb/create-prisma-generator/f537239cdb37550ddda286336e6b4add4d268729/dev.to/blogs/create-prisma-generator/assets/.gitkeep
--------------------------------------------------------------------------------
/dev.to/blogs/create-prisma-generator/assets/add-secrets-to-github.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YassinEldeeb/create-prisma-generator/f537239cdb37550ddda286336e6b4add4d268729/dev.to/blogs/create-prisma-generator/assets/add-secrets-to-github.png
--------------------------------------------------------------------------------
/dev.to/blogs/create-prisma-generator/assets/commit-change-log.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YassinEldeeb/create-prisma-generator/f537239cdb37550ddda286336e6b4add4d268729/dev.to/blogs/create-prisma-generator/assets/commit-change-log.png
--------------------------------------------------------------------------------
/dev.to/blogs/create-prisma-generator/assets/create-npm-token.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YassinEldeeb/create-prisma-generator/f537239cdb37550ddda286336e6b4add4d268729/dev.to/blogs/create-prisma-generator/assets/create-npm-token.png
--------------------------------------------------------------------------------
/dev.to/blogs/create-prisma-generator/assets/generated-successfully.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YassinEldeeb/create-prisma-generator/f537239cdb37550ddda286336e6b4add4d268729/dev.to/blogs/create-prisma-generator/assets/generated-successfully.png
--------------------------------------------------------------------------------
/dev.to/blogs/create-prisma-generator/assets/github-actions.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YassinEldeeb/create-prisma-generator/f537239cdb37550ddda286336e6b4add4d268729/dev.to/blogs/create-prisma-generator/assets/github-actions.png
--------------------------------------------------------------------------------
/dev.to/blogs/create-prisma-generator/assets/github-release.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YassinEldeeb/create-prisma-generator/f537239cdb37550ddda286336e6b4add4d268729/dev.to/blogs/create-prisma-generator/assets/github-release.png
--------------------------------------------------------------------------------
/dev.to/blogs/create-prisma-generator/assets/husky-with-commitlint.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YassinEldeeb/create-prisma-generator/f537239cdb37550ddda286336e6b4add4d268729/dev.to/blogs/create-prisma-generator/assets/husky-with-commitlint.png
--------------------------------------------------------------------------------
/dev.to/blogs/create-prisma-generator/assets/my-questions-answers.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YassinEldeeb/create-prisma-generator/f537239cdb37550ddda286336e6b4add4d268729/dev.to/blogs/create-prisma-generator/assets/my-questions-answers.png
--------------------------------------------------------------------------------
/dev.to/blogs/create-prisma-generator/assets/npm-dropdown.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YassinEldeeb/create-prisma-generator/f537239cdb37550ddda286336e6b4add4d268729/dev.to/blogs/create-prisma-generator/assets/npm-dropdown.png
--------------------------------------------------------------------------------
/dev.to/blogs/create-prisma-generator/assets/passing-tests.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YassinEldeeb/create-prisma-generator/f537239cdb37550ddda286336e6b4add4d268729/dev.to/blogs/create-prisma-generator/assets/passing-tests.png
--------------------------------------------------------------------------------
/dev.to/blogs/create-prisma-generator/code/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YassinEldeeb/create-prisma-generator/f537239cdb37550ddda286336e6b4add4d268729/dev.to/blogs/create-prisma-generator/code/.gitkeep
--------------------------------------------------------------------------------
/dev.to/blogs/create-prisma-generator/content.md:
--------------------------------------------------------------------------------
1 | ---
2 | published: true
3 | title: 'Create Prisma Generator'
4 | cover_image: 'https://raw.githubusercontent.com/YassinEldeeb/create-prisma-generator/main/images/cool-banner.png'
5 | description: 'Introducing create-prisma-generator CLI and explaining the boilerplate'
6 | tags: typescript, javascript, prisma, generator
7 | ---
8 |
9 | This blog is hosted on [this github repo](https://github.com/YassinEldeeb/create-prisma-generator/tree/main/dev.to/blogs/create-prisma-generator) in `content.md` file so feel free to correct me when I miss up by making a PR there.
10 |
11 | ## What's a prisma generator? 🤔
12 |
13 | Prisma has a concept called "Generator". A generator is an executable program, which takes the parsed Prisma schema as an input and has full freedom to output anything.
14 |
15 | The most prominent generator is called [`prisma-client-js`](https://github.com/prisma/prisma/tree/main/packages/client). It's the ORM Client powering the main TypeScript and JavaScript usage of Prisma from Node.js.
16 |
17 | Generators will always be called when you run `prisma generate`. However, only the generators mentioned in the `schema.prisma` file are being run.
18 |
19 | [Strongly recommend reading the full article, It's pretty damn good](https://prismaio.notion.site/Prisma-Generators-a2cdf262207a4e9dbcd0e362dfac8dc0)
20 |
21 | From a community perspective when integrating prisma in different environments you'll often notice that there's a thing that you always go to change after modifying your prisma schema in your codebase, and that's when great developers realize that this thing should be automated to eliminate the problem of maintaining two or more different sources of the same definitions.
22 |
23 | ## Getting Started
24 |
25 | Now that you've a high level overview of what a prisma generator is, let's discuss the hello world prisma generator you'll get when using create-prisma-generator CLI 💪
26 |
27 | I made it so that it requires the least amount of effort to start developing your own prisma generator.
28 |
29 | Answer the prompt questions to setup your project, The project setup will be based on your answers.
30 |
31 | > Note: "? setup workspace for testing the generator" means to symlink the generator with another sample usage project so that when you run `prisma generate` from your terminal, It uses the local generator in the workspace which is very useful when developing, I strongly recommend it.
32 |
33 | ```sh
34 | $ npx create-prisma-generator
35 | ```
36 |
37 | I'll go and answer Yes for everything to go with the full capabilities of this CLI but you can follow along with your setup too.
38 |
39 | 
40 |
41 | And once you see the success message in your terminal saying that your project is now ready, open the project in your favourite IDE and let's have some fun 😉
42 |
43 | First let's open the `schema.prisma` which you can find it at `packages/usage/prisma/schema.prisma`.
44 |
45 | You'll notice your generator there symlinked with the generator code in the workspace
46 |
47 | > Note: the provider can differ from package manager to another, here I chose `pnpm`
48 |
49 | ```ts
50 | generator custom_generator {
51 | provider = "npx my-gen"
52 | output = "../types"
53 | }
54 | ```
55 |
56 | You'll also see some enums there, that's because the hello world generator that you get from running `create-prisma-generator` is for generating Typescript Enums from `schema.prisma`.
57 |
58 | Now let's run the `prisma generate` command which should run all of the generators listed in `schema.prisma`:
59 |
60 | ```sh
61 | $ cd packages/usage
62 | $ npx prisma generate
63 | ```
64 |
65 | Oh, WOW! the types directory wasn't there before, what the hell happened!
66 |
67 | You can see that the `types` directory was generated after running `prisma generate` which contains all of the different enums defined in `schema.prisma` organized by an enum per file.
68 |
69 | So if you opened any of the files in the `types` directory, you'll see an enum that matches exactly with the name and values as defined in `schema.prisma`
70 |
71 | ```ts
72 | enum Language {
73 | Typescript = 'Typescript',
74 | Javascript = 'Javascript',
75 | Rust = 'Rust',
76 | Go = 'Go',
77 | Python = 'Python',
78 | Cpp = 'Cpp',
79 | }
80 | ```
81 |
82 | Noticed something? the output option in the `custom_generator` block in `schema.prisma` tells the generator where to output the generated files with a path relative to the directory where `schema.prisma` is located, try to change this option to something different like `../src/types` and run `npx prisma generate` again.
83 |
84 | ```ts
85 | generator custom_generator {
86 | provider = "npx my-gen"
87 | output = "../src/types"
88 | }
89 | ```
90 |
91 | You'll see that it created all of the directories for the defined path and outputted the generated enums there.
92 |
93 | Now after we've played around with the Hello World generator, Let's take a look at the code for it.
94 |
95 | You can find the generator code located under `packages/generator` directory.
96 |
97 | Open `packages/generator/src/generator.(ts|js)` and let's slowly discuss what's in there.
98 |
99 | At the top you'll see we're importing some strange modules like `@prisma/generator-helper`, `@prisma/sdk`, what are those?
100 |
101 | ## @prisma/generator-helper
102 |
103 | The generator has to be an executable binary somewhere in the filesystem. This binary, for example `./my-gen` needs to implement a JSON RPC interface via stdio.
104 |
105 | > When `@prisma/sdk` spawns our generator, It uses [RPCs](https://en.wikipedia.org/wiki/JSON-RPC) to communicate with our generator to send it the parsed datamodel AST as an example.
106 |
107 | Luckily for us, prisma has wrote a helper library called `@prisma/generator-helper`. It takes all the work of implementing the interface and gives us simple callbacks where we can implement our business logic.
108 |
109 | And as you can see, It has a callback called `generatorHandler` which takes two methods:
110 |
111 | ### `onManifest:`
112 |
113 | When running the prisma cli with the following command `prisma generate` It gets our generator manifest that gets returned from the `onManifest` callback method which contains all of the information about our generator like It's name, version, default output, which binaries and which version the generator needs.
114 |
115 | ```ts
116 | generatorHandler({
117 | onManifest() {
118 | return {
119 | ...
120 | }
121 | },
122 | ...
123 | })
124 | ```
125 |
126 | ### `onGenerate:`
127 |
128 | This is a callback method that run when `@prisma/sdk` calls it with the correct arguments that contains the parsed datamodel AST, generator options and other useful information.
129 |
130 | ```ts
131 | generatorHandler({
132 | ...
133 | onGenerate: async (options: GeneratorOptions) => {
134 | ...
135 | },
136 | })
137 | ```
138 |
139 | ## @prisma/sdk
140 |
141 | This is an internal API that has some very cool utilities that are often used when developing prisma generators which I've documented some parts about it [here](https://github.com/YassinEldeeb/create-prisma-generator/blob/main/PRISMA_SDK_REFERENCE.md).
142 |
143 | ## Back to our Hello World generator
144 |
145 | After we've dicussed a bit about `@prisma/generator-helper` and `@prisma/sdk`, Let's get back to `generator.(ts|js)`
146 |
147 | You'll first see that we're importing the generator's package.json and grabbing the version out if it to pass it as a part of the generator manifest,
148 |
149 | then using the `GENERATOR_NAME` constant which is imported from `packages/generator/constants.ts` to log an info message to let us know when our generator is registred then returning an object expressing our generator manifest.
150 |
151 | `version` and `prettyName` are used by `@prisma/sdk` when It calls `getGeneratorSuccessMessage` to generate a success message from our generator manifest like shown below.
152 |
153 | 
154 |
155 | `defaultOutput` is a fallback for the `output` option if It wasn't provided in the generator block.
156 |
157 | ```ts
158 | const { version } = require('../package.json')
159 |
160 | generatorHandler({
161 | onManifest() {
162 | logger.info(`${GENERATOR_NAME}:Registered`)
163 | return {
164 | version,
165 | defaultOutput: '../generated',
166 | prettyName: GENERATOR_NAME,
167 | }
168 | },
169 | ...
170 | }
171 | ```
172 |
173 | Let's get to the `onGenerate` callback where you'll receive the generator options which you can find the latest type definitions [here](https://github.com/prisma/prisma/blob/main/packages/generator-helper/src/types.ts), this contains a lot of information for our generator to use like pure datamodel, dmmf, generator(config, name, output, provider), schemaPath, version and hell a lot more.
174 |
175 | > DMMF?? It's the Datamodel Meta Format. It is an AST (abstract syntax tree) of the datamodel in the form of JSON.
176 |
177 | You can see that we're specifically making use of `options.dmmf.datamodel.enums` which contains all of the parsed enums as AST that we can then have full freedom of outputting anything with this information.
178 |
179 | We're using a helper function that can be found in `packages/generator/src/helpers/genEnum.(ts|js)` that takes the enum information and gives us back a string containing a Typescript Enum.
180 |
181 | ```ts
182 | generatorHandler({
183 | ...
184 | onGenerate: async (options: GeneratorOptions) => {
185 | options.dmmf.datamodel.enums.forEach(async (enumInfo) => {
186 | const tsEnum = genEnum(enumInfo)
187 |
188 | const writeLocation = path.join(
189 | options.generator.output?.value!,
190 | `${enumInfo.name}.ts`,
191 | )
192 |
193 | await writeFileSafely(writeLocation, tsEnum)
194 | })
195 | },
196 | })
197 |
198 | ```
199 |
200 | Nothing crazy to make a Typescript Enum from the enum info, you can take a look at the file, It's really really simple.
201 |
202 | ```ts
203 | export const genEnum = ({ name, values }: DMMF.DatamodelEnum) => {
204 | const enumValues = values.map(({ name }) => `${name}="${name}"`).join(',\n')
205 |
206 | return `enum ${name} { \n${enumValues}\n }`
207 | }
208 | ```
209 |
210 | Another thing you'll see is a utility function called `writeFileSafely` which takes the write location for the file and the content for that file then It creates all of the directories recursivly following the write location path and uses another utility function called `formatFile` to format the content using prettier before writing the file to the specified path.
211 |
212 | ```ts
213 | export const writeFileSafely = async (writeLocation: string, content: any) => {
214 | fs.mkdirSync(path.dirname(writeLocation), {
215 | recursive: true,
216 | })
217 |
218 | fs.writeFileSync(writeLocation, await formatFile(content))
219 | }
220 | ```
221 |
222 | And that's it, that's our Hello World generator, hope It was a fun ride.
223 |
224 | ## How do I develop within this workspace?
225 |
226 | 1- Open a new terminal and cd into `packages/generator` and run
227 |
228 | ```sh
229 | # You can use whatever package manager to run the dev script
230 | $ pnpm dev
231 | ```
232 |
233 | This will watch your changes and compile on save into a dist folder.
234 |
235 | 2- Open another terminal and cd into `packages/usage` and here you'll have the latest build of your generator's code symlinked to this package so running:
236 |
237 | ```sh
238 | $ npx prisma generate
239 | ```
240 |
241 | ..will always use the latest code of your compiled generator.
242 |
243 | And as you iterate over your generator's code, you can run `npx prisma generate` to see the results.
244 |
245 | ## Testing 🧪
246 |
247 | Quality Software can't be shipped directly to the users and have to be well tested before It goes live.
248 |
249 | That's why I've included jest in any project that gets bootstrapped by `create-prisma-generator` CLI.
250 |
251 | > If you don't know what jest is? It's a JavaScript Testing Framework [learn more about it here](https://jestjs.io/docs/getting-started)
252 |
253 | There's a very simple test located under `packages/generator/__tests__/` called `genEnum.test.ts`, If you opened this file you'll see a test written that compares the generated output of the genEnum() helper function we've talked about previously with the already taken snapshot of a working version of this function.
254 |
255 | We can run that test by running the following command in `packages/generator` directory:
256 |
257 | ```sh
258 | # You can use whatever package manager to run the test script
259 | $ pnpm test
260 | ```
261 |
262 | You'll see all of the tests are passing, that means our software is ready to be shipped! 🥳
263 |
264 | 
265 |
266 | You can also see that we're not getting the DMMF from `@prisma/sdk`, mmm... that's strange but how are we getting the DMMF from a `schema.prisma` and where is even that `schema.prisma` file?
267 |
268 | Usually in production the DMMF gets sent through this cycle:
269 |
270 | ```sh
271 | @prisma/cli -> @prisma/sdk -> Spawns Generators -> Send DMMF through RPCs
272 | ```
273 |
274 | Which works perfectly fine but not the ideal when testing prisma generators, we can cut this cycle and just get the utility function in @prisma/sdk that's responsible for generating the DMMF from a prisma definitions string which called `getDMMF`.
275 |
276 | So as you can see we're calling `getSampleDMMF()` from the fixtures defined in the tests directory which then reads the `sample.prisma` located under `__tests__/__fixtures__/` and parse it to an AST exactly like the one we get normally in a production environment.
277 |
278 | And now It's up to you to write tests for your own generator.
279 |
280 | I'm curios to see your creative solutions for testing your prisma generator 🤗.
281 |
282 | ## Fancy Stuff ✨
283 |
284 | Now let's get fancy with the full capabilities of this CLI and manage this project like an elite open source programmer 💪.
285 |
286 | ### Auto Publishing 🚀
287 |
288 | Remember the "automate publishing the generator with Github Actions" I've said yes to it at first.
289 |
290 | That had setup a Github Actions workflow at `.github/workflows/CI.yml` which will run all of our generator tests then if they're all passing It will publish the package to npm using your Access Token.
291 |
292 | To get an access token, you must first be logged in with your npm account or [register here](https://www.npmjs.com/signup)
293 |
294 | Then click on your profile picture and go to "Access Tokens" like shown in the screenshot below 👇
295 |
296 | 
297 |
298 | Click on "Generate New Token" and select the token type to be "Automation" so that you don't require 2FA when running in a CI environment.
299 |
300 | 
301 |
302 | Before start publishing your package to npm, you'll need to replace the placeholders in `packages/generator/package.json` with actual information like: description, homepage, repository, author and keywords.
303 | Check the docs to know what all of those fields mean [npm package.json docs](https://docs.npmjs.com/cli/v8/configuring-npm/package-json).
304 |
305 | Now that you've your npm access token you can create a new github repository and add a new secret to your github actions secrets with this exact same name `NPM_TOKEN`.
306 |
307 | 
308 |
309 | Let's make a small change to this generator like changing the name of the generator as an example.
310 |
311 | ```diff
312 | - export const GENERATOR_NAME = 'my-gen'
313 | + export const GENERATOR_NAME = 'my-super-gen'
314 | ```
315 |
316 | Then commit & push to your repository on the `main` branch
317 |
318 | ```sh
319 | $ git add .
320 | $ git commit -m"fix: generator name"
321 | $ git push -u origin main
322 | ```
323 |
324 | After you push, go to your repository on github specifically on tha `Actions` tab and you'll immediately see the tests running and after they finish, the package will be published to npm with the version specified in the generator's package.json using your access token which you can then find using the following url `https://www.npmjs.com/package/$your-generator-name` 🥳.
325 |
326 | 
327 |
328 | ### Automatic Semantic Versioning 🤖
329 |
330 | Don't know what semantic versioning is?, Mahmoud Abdelwahab got you covered with a 1 minute video about it [check it out](https://www.youtube.com/watch?v=5NQUut8uf9w)
331 |
332 | Now we've a workflow for testing and automatic publishing the package to npm but It's not very nice having to go and manually bump the version in the `package.json` everytime you change something and wanna publish it.
333 |
334 | Using [semantic-release](https://github.com/semantic-release/semantic-release), we can just focus on our commit messages and It'll do the rest of the work for us like: bumping the version, github release, git tag, generating a CHANGELOG and a lot more.
335 |
336 | Remember the "(Github Actions) setup automatic semantic release" I've said yes to it at first.
337 |
338 | That had setup semantic-release for me with the Github Actions workflow and added husky with commitlint to force [Conventional Commit Messages](https://github.com/angular/angular/blob/master/CONTRIBUTING.md#-commit-message-format) which then semantic-release will recognize and decide the next version based on it and do all of the stuff for us.
339 |
340 | But there's a very small configuration we still need to make for this to work as intended.
341 |
342 | Remember when I said:
343 |
344 | > bumping the version, github release, git tag, generating a CHANGELOG and a lot more.
345 |
346 | Well, semantic-release needs read/write access over public/private repos to achieve all of that.
347 |
348 | Create a new github access token [from this link](https://github.com/settings/tokens/new?scopes=repo) providing a note for it so you can remember what it was for.
349 |
350 | Now that you've your github access token you can add a new secret to your github actions secrets with this exact same name GH_TOKEN which semantic-release will look for to do all of the magic for us.
351 |
352 | Let's make a smalll change to this generator like changing the name of the generator as an example and call it a minor release.
353 |
354 | ```diff
355 | generatorHandler({
356 | onManifest() {
357 | - logger.info(`${GENERATOR_NAME}:Registered`)
358 | + logger.info(`${GENERATOR_NAME}:Hooked`)
359 | ```
360 |
361 | Then commit & push to your repository on the `main` branch
362 |
363 | ```sh
364 | $ git add .
365 | $ git commit -m"new register message"
366 | $ git push -u origin main
367 | ```
368 |
369 | Oh crab what the hell is this?
370 | 
371 |
372 | Remember when I told you that this CLI has setup husky with commitlint to validate your commit messages if it was conventional or not before commiting so that semantic-release can decide what the next version is based on your commit messages.
373 |
374 | Now let's run a proper conventional commit message
375 |
376 | ```sh
377 | $ git add .
378 | $ git commit -m"feat: new register message"
379 | $ git push -u origin main
380 | ```
381 |
382 | After you push, go to your repository on github specifically on tha Actions tab and you'll see the same running tests and after they finish, you'll notice something different, semantic-release has bumped the version to `1.1.0` and modified the package.json version to sync it with npm, generated a CHANGELOG for you, created a new tag and published a github release for you 🤯
383 |
384 | 
385 |
386 | 
387 |
388 | WOW! I had a 0.01% chance that someone can read through all of that till the very end. I'm very proud of you, please mention or DM me on twitter and let me know you're one of the 0.01% of people.
389 |
--------------------------------------------------------------------------------
/dev.to/dev-to-git.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "id": 936395,
4 | "relativePathToArticle": "./blogs/create-prisma-generator/content.md"
5 | }
6 | ]
7 |
--------------------------------------------------------------------------------
/dev.to/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "dev.to",
3 | "private": true,
4 | "repository": {
5 | "type": "git",
6 | "url": "https://github.com/YassinEldeeb/create-prisma-generator.git"
7 | },
8 | "scripts": {
9 | "prettier": "prettier",
10 | "embedme": "embedme blogs/**/*.md",
11 | "prettier:base": "pnpm prettier \"**/*.{js,json,scss,md,ts,html,component.html}\"",
12 | "prettier:write": "pnpm prettier:base --write",
13 | "prettier:check": "pnpm prettier:base --list-different",
14 | "embedme:check": "pnpm embedme --verify",
15 | "embedme:write": "pnpm embedme",
16 | "dev-to-git": "dev-to-git"
17 | },
18 | "dependencies": {
19 | "axios": "^0.24.0",
20 | "dev-to-git": "^1.4.8",
21 | "embedme": "1.22.0",
22 | "prettier": "2.5.1"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/images/cool-banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YassinEldeeb/create-prisma-generator/f537239cdb37550ddda286336e6b4add4d268729/images/cool-banner.png
--------------------------------------------------------------------------------
/images/npx-create-prisma-generator.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YassinEldeeb/create-prisma-generator/f537239cdb37550ddda286336e6b4add4d268729/images/npx-create-prisma-generator.png
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "workspaces": [
4 | "packages/*"
5 | ],
6 | "scripts": {
7 | "new-template": "ts-node ./scripts/addNewTemplate.ts",
8 | "new-blog": "ts-node ./scripts/addNewBlog.ts",
9 | "guide-dependabot": "ts-node ./scripts/guideDependabot.ts",
10 | "simulate-dist": "node ./scripts/simulateDist.js",
11 | "gen-tags": "ts-node ./scripts/generateTags.ts",
12 | "prepare": "husky install"
13 | },
14 | "devDependencies": {
15 | "@commitlint/cli": "^15.0.0",
16 | "@commitlint/config-conventional": "^15.0.0",
17 | "@types/axios": "^0.14.0",
18 | "axios": "^0.24.0",
19 | "husky": "^7.0.0",
20 | "ts-node": "^10.4.0"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/packages/cli-usage/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "cli-usage",
3 | "private": true,
4 | "version": "1.0.0",
5 | "main": "index.js",
6 | "license": "MIT",
7 | "scripts": {
8 | "cli": "create-prisma-generator"
9 | },
10 | "devDependencies": {
11 | "create-prisma-generator": "workspace:*",
12 | "@cpg-cli/semantic-releases": "workspace:*",
13 | "@cpg-cli/github-actions": "workspace:*",
14 | "@cpg-cli/template": "workspace:*",
15 | "@cpg-cli/template-gen-usage": "workspace:*",
16 | "@cpg-cli/template-typescript": "workspace:*",
17 | "@cpg-cli/root-configs": "workspace:*"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/packages/cpg-github-actions/.npmignore:
--------------------------------------------------------------------------------
1 | src
2 | node_modules
3 | tsconfig.json
4 | README.md
5 | jest.config.js
6 | yarn.lock
7 | yarn-error.log
8 | pnpm-debug.log
9 | template/.turbo
10 | template/dist
11 | template/.pnpm-debug.log
12 | .pnpm-debug.log
13 | .turbo
14 | dist
--------------------------------------------------------------------------------
/packages/cpg-github-actions/bin.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | require('./index')()
3 |
--------------------------------------------------------------------------------
/packages/cpg-github-actions/index.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const fs = require('fs')
3 |
4 | const setup = () => {
5 | function copySync(from, to) {
6 | fs.mkdirSync(to, { recursive: true })
7 | fs.readdirSync(from).forEach((element) => {
8 | if (fs.lstatSync(path.join(from, element)).isFile()) {
9 | fs.copyFileSync(path.join(from, element), path.join(to, element))
10 | } else {
11 | copySync(path.join(from, element), path.join(to, element))
12 | }
13 | })
14 | }
15 |
16 | copySync(
17 | path.join(path.join(__dirname, `./template`)),
18 | path.join(process.cwd(), process.argv[2]),
19 | )
20 | }
21 |
22 | module.exports = setup
23 |
--------------------------------------------------------------------------------
/packages/cpg-github-actions/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@cpg-cli/github-actions",
3 | "version": "1.5.4",
4 | "main": "bin.js",
5 | "license": "MIT",
6 | "bin": {
7 | "cpg-github-actions": "bin.js"
8 | },
9 | "scripts": {},
10 | "files": [
11 | "template/**/*",
12 | "index.js",
13 | "bin.js"
14 | ],
15 | "homepage": "https://github.com/YassinEldeeb/create-prisma-generator",
16 | "repository": {
17 | "type": "git",
18 | "url": "https://github.com/YassinEldeeb/create-prisma-generator.git"
19 | },
20 | "author": "Yassin Eldeeb ",
21 | "keywords": [
22 | "create-prisma-generator",
23 | "prisma"
24 | ]
25 | }
26 |
--------------------------------------------------------------------------------
/packages/cpg-github-actions/template/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # To get started with Dependabot version updates, you'll need to specify which
2 | # package ecosystems to update and where the package manifests are located.
3 | # Please see the documentation for all configuration options:
4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
5 |
6 | version: 2
7 | updates:
8 | - package-ecosystem: 'npm' # See documentation for possible values
9 | directory: '$GENERATOR_DIR' # Location of package manifests
10 | schedule:
11 | interval: 'daily'
12 |
--------------------------------------------------------------------------------
/packages/cpg-github-actions/template/.github/workflows/CI.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | push:
5 | branches: [main]
6 | pull_request:
7 | branches: ['*']
8 |
9 | jobs:
10 | Test:
11 | runs-on: ${{ matrix.os }}
12 |
13 | strategy:
14 | matrix:
15 | node-version: [16.x]
16 | os: [ubuntu-latest, windows-latest]
17 |
18 | steps:
19 | - uses: actions/checkout@v2
20 | - name: Use Node.js ${{matrix.node-version}}
21 | uses: actions/setup-node@v2
22 | with:
23 | node-version: ${{ matrix.node-version }}
24 |
25 | $SETUP_PNPM
26 |
27 | - uses: actions/cache@v2
28 | with:
29 | path: '**/node_modules'
30 | key: ${{ runner.os }}-modules-${{ hashFiles('**/$PKG_MANAGER_LOCK') }}
31 |
32 | - name: Install dependencies
33 | run: $INSTALL_CMD_WITH_FROZEN_LOCK
34 |
35 | - name: Run Tests 🧪
36 | working-directory: $WORKING_DIR
37 | run: $PKG_MANAGER_RUN_CMD test
38 |
39 | Publish:
40 | runs-on: ubuntu-latest
41 | needs: [Test]
42 | if: ${{ github.ref == 'refs/heads/main' }}
43 |
44 | steps:
45 | - uses: actions/checkout@v2
46 | - name: Use Node.js
47 | uses: actions/setup-node@v2
48 | with:
49 | node-version: '16.x'
50 | registry-url: 'https://registry.npmjs.org'
51 |
52 | - uses: actions/cache@v2
53 | with:
54 | path: '**/node_modules'
55 | key: ${{ runner.os }}-modules-${{ hashFiles('**/$PKG_MANAGER_LOCK') }}
56 |
57 | - name: Install dependencies
58 | run: $INSTALL_CMD_WITH_FROZEN_LOCK
59 |
60 | - name: Publish 🚀
61 | working-directory: $WORKING_DIR
62 | run: $PKG_MANAGER_RUN_CMD publish
63 | env:
64 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
65 |
--------------------------------------------------------------------------------
/packages/cpg-root-configs/.npmignore:
--------------------------------------------------------------------------------
1 | src
2 | node_modules
3 | tsconfig.json
4 | README.md
5 | jest.config.js
6 | yarn.lock
7 | yarn-error.log
8 | pnpm-debug.log
9 | template/.turbo
10 | template/dist
11 | template/.pnpm-debug.log
12 | .pnpm-debug.log
13 | .turbo
14 | dist
--------------------------------------------------------------------------------
/packages/cpg-root-configs/bin.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | require('./index')()
3 |
--------------------------------------------------------------------------------
/packages/cpg-root-configs/index.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const fs = require('fs')
3 |
4 | const setup = () => {
5 | function copySync(from, to) {
6 | fs.mkdirSync(to, { recursive: true })
7 | fs.readdirSync(from).forEach((element) => {
8 | if (fs.lstatSync(path.join(from, element)).isFile()) {
9 | fs.copyFileSync(path.join(from, element), path.join(to, element))
10 | } else {
11 | copySync(path.join(from, element), path.join(to, element))
12 | }
13 | })
14 | }
15 | copySync(
16 | path.join(__dirname, `./template`),
17 | path.join(process.cwd(), process.argv[2]),
18 | )
19 |
20 | fs.renameSync(
21 | path.join(process.cwd(), process.argv[2], 'gitIgnoreConf.txt'),
22 | path.join(process.cwd(), process.argv[2], '.gitignore'),
23 | )
24 | }
25 |
26 | module.exports = setup
27 |
--------------------------------------------------------------------------------
/packages/cpg-root-configs/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@cpg-cli/root-configs",
3 | "version": "1.3.2",
4 | "main": "bin.js",
5 | "license": "MIT",
6 | "bin": {
7 | "cpg-root-configs": "bin.js"
8 | },
9 | "files": [
10 | "template/**/*",
11 | "index.js",
12 | "bin.js"
13 | ],
14 | "scripts": {
15 | "start": "node bin.js"
16 | },
17 | "homepage": "https://github.com/YassinEldeeb/create-prisma-generator",
18 | "repository": {
19 | "type": "git",
20 | "url": "https://github.com/YassinEldeeb/create-prisma-generator.git"
21 | },
22 | "author": "Yassin Eldeeb ",
23 | "keywords": [
24 | "create-prisma-generator"
25 | ]
26 | }
27 |
--------------------------------------------------------------------------------
/packages/cpg-root-configs/template/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true,
3 | "trailingComma": "all",
4 | "semi": false,
5 | "tabWidth": 2
6 | }
7 |
--------------------------------------------------------------------------------
/packages/cpg-root-configs/template/README.md:
--------------------------------------------------------------------------------
1 | # $PACKAGE_NAME
2 |
3 | > This generator was bootstraped using [create-prisma-generator](https://github.com/YassinEldeeb/create-prisma-generator)
4 |
--------------------------------------------------------------------------------
/packages/cpg-root-configs/template/gitIgnoreConf.txt:
--------------------------------------------------------------------------------
1 | dist
2 | node_modules
3 | yarn-error.log
4 | build
5 | pnpm-debug.log
6 | .env*
--------------------------------------------------------------------------------
/packages/cpg-semantic-releases/.npmignore:
--------------------------------------------------------------------------------
1 | src
2 | node_modules
3 | tsconfig.json
4 | README.md
5 | jest.config.js
6 | yarn.lock
7 | yarn-error.log
8 | pnpm-debug.log
9 | .turbo
10 | dist
--------------------------------------------------------------------------------
/packages/cpg-semantic-releases/bin.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | require('./index')()
3 |
--------------------------------------------------------------------------------
/packages/cpg-semantic-releases/index.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const fs = require('fs')
3 | const YAML = require('yaml')
4 |
5 | const setup = () => {
6 | // Modify CI.yml Publish workflow
7 | // To use semantic release
8 | const pkgName = process.argv[2]
9 | const usingWorkspaces = process.argv[3]
10 | const workingDir = path.join(process.cwd(), pkgName)
11 | const CIPath = path.join(workingDir, '.github/workflows/CI.yml')
12 |
13 | const CIYMLFile = fs.readFileSync(CIPath, 'utf8')
14 | const parsedCI = YAML.parse(CIYMLFile)
15 |
16 | const modifiedSteps = parsedCI.jobs.Publish.steps.map((e) => {
17 | if (e.name?.includes('Publish')) {
18 | return {
19 | ...e,
20 | run: 'npx semantic-release',
21 | env: { ...e.env, GITHUB_TOKEN: '${{ secrets.GH_TOKEN }}' },
22 | }
23 | } else {
24 | return e
25 | }
26 | })
27 |
28 | parsedCI.jobs.Publish.steps = modifiedSteps
29 |
30 | fs.writeFileSync(CIPath, YAML.stringify(parsedCI, { singleQuote: true }))
31 |
32 | // Add needed dependencies for
33 | // semantic release
34 | let PKGJSONPath
35 | if (usingWorkspaces) {
36 | PKGJSONPath = path.join(workingDir, './packages/generator/package.json')
37 | } else {
38 | PKGJSONPath = path.join(workingDir, './package.json')
39 | }
40 |
41 | const PkgJSONFile = JSON.parse(fs.readFileSync(PKGJSONPath, 'utf8'))
42 | const semanticReleaseDeps = {
43 | '@semantic-release/changelog': '^6.0.1',
44 | '@semantic-release/git': '^10.0.1',
45 | 'semantic-release': '^18.0.1',
46 | }
47 |
48 | for (const dependency in semanticReleaseDeps) {
49 | PkgJSONFile.devDependencies[dependency] = semanticReleaseDeps[dependency]
50 | }
51 |
52 | // Config semantic release
53 | const releaseConfig = {
54 | branches: ['main'],
55 | plugins: [
56 | '@semantic-release/commit-analyzer',
57 | '@semantic-release/release-notes-generator',
58 | [
59 | '@semantic-release/changelog',
60 | {
61 | changelogFile: 'CHANGELOG.md',
62 | },
63 | ],
64 | '@semantic-release/npm',
65 | '@semantic-release/github',
66 | [
67 | '@semantic-release/git',
68 | {
69 | assets: ['CHANGELOG.md', 'package.json'],
70 | message:
71 | 'chore(release): set `package.json` to ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}',
72 | },
73 | ],
74 | ],
75 | }
76 |
77 | PkgJSONFile.release = releaseConfig
78 | fs.writeFileSync(PKGJSONPath, JSON.stringify(PkgJSONFile, null, 2))
79 |
80 | // Copy template configs
81 | fs.copyFileSync(
82 | path.join(path.join(__dirname, `./template/commitlint.config.js`)),
83 | path.join(workingDir, 'commitlint.config.js'),
84 | )
85 | const rootPkgJSONPath = path.join(workingDir, 'package.json')
86 | const templatePkgJSON = fs.readFileSync(
87 | path.join(__dirname, `./template/package.json`),
88 | 'utf-8',
89 | )
90 |
91 | if (!fs.existsSync(rootPkgJSONPath)) {
92 | fs.writeFileSync(rootPkgJSONPath, templatePkgJSON)
93 | } else {
94 | const existingRootPkgJSON = JSON.parse(
95 | fs.readFileSync(rootPkgJSONPath, 'utf-8'),
96 | )
97 |
98 | // Merge the two package.json(s)
99 | fs.writeFileSync(
100 | rootPkgJSONPath,
101 | JSON.stringify(
102 | {
103 | ...existingRootPkgJSON,
104 | ...JSON.parse(templatePkgJSON),
105 | scripts: {
106 | ...existingRootPkgJSON.scripts,
107 | ...JSON.parse(templatePkgJSON).scripts,
108 | },
109 | },
110 | null,
111 | 2,
112 | ),
113 | )
114 | }
115 | }
116 |
117 | module.exports = setup
118 |
--------------------------------------------------------------------------------
/packages/cpg-semantic-releases/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@cpg-cli/semantic-releases",
3 | "version": "1.3.5",
4 | "main": "bin.js",
5 | "license": "MIT",
6 | "bin": {
7 | "cpg-semantic-releases": "bin.js"
8 | },
9 | "files": [
10 | "template/**/*",
11 | "index.js",
12 | "bin.js"
13 | ],
14 | "scripts": {
15 | "start": "node bin.js"
16 | },
17 | "dependencies": {
18 | "yaml": "^1.10.2"
19 | },
20 | "homepage": "https://github.com/YassinEldeeb/create-prisma-generator",
21 | "repository": {
22 | "type": "git",
23 | "url": "https://github.com/YassinEldeeb/create-prisma-generator.git"
24 | },
25 | "author": "Yassin Eldeeb ",
26 | "keywords": [
27 | "create-prisma-generator"
28 | ]
29 | }
30 |
--------------------------------------------------------------------------------
/packages/cpg-semantic-releases/template/commitlint.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: ['@commitlint/config-conventional'],
3 | }
4 |
--------------------------------------------------------------------------------
/packages/cpg-semantic-releases/template/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "devDependencies": {
3 | "@commitlint/cli": "16.2.3",
4 | "@commitlint/config-conventional": "16.0.0",
5 | "husky": "7.0.4"
6 | },
7 | "scripts": {
8 | "prepare": "husky install"
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/packages/cpg-template-gen-usage/.npmignore:
--------------------------------------------------------------------------------
1 | src
2 | node_modules
3 | tsconfig.json
4 | README.md
5 | jest.config.js
6 | yarn.lock
7 | yarn-error.log
8 | pnpm-debug.log
9 | template/.turbo
10 | dist/**/*
11 | template/.pnpm-debug.log
12 | .pnpm-debug.log
13 | .turbo
14 | dist
--------------------------------------------------------------------------------
/packages/cpg-template-gen-usage/bin.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | require('./index')()
3 |
--------------------------------------------------------------------------------
/packages/cpg-template-gen-usage/index.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const fs = require('fs')
3 |
4 | const setup = () => {
5 | function copySync(from, to) {
6 | fs.mkdirSync(to, { recursive: true })
7 | fs.readdirSync(from).forEach((element) => {
8 | if (fs.lstatSync(path.join(from, element)).isFile()) {
9 | fs.copyFileSync(path.join(from, element), path.join(to, element))
10 | } else {
11 | copySync(path.join(from, element), path.join(to, element))
12 | }
13 | })
14 | }
15 |
16 | copySync(
17 | path.join(path.join(__dirname, `./template`)),
18 | path.join(process.cwd(), process.argv[2]),
19 | )
20 | }
21 |
22 | module.exports = setup
23 |
--------------------------------------------------------------------------------
/packages/cpg-template-gen-usage/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@cpg-cli/template-gen-usage",
3 | "version": "1.4.4",
4 | "main": "bin.js",
5 | "license": "MIT",
6 | "bin": {
7 | "cpg-template-gen-usage": "bin.js"
8 | },
9 | "files": [
10 | "template/**/*",
11 | "index.js",
12 | "bin.js"
13 | ],
14 | "scripts": {},
15 | "homepage": "https://github.com/YassinEldeeb/create-prisma-generator",
16 | "repository": {
17 | "type": "git",
18 | "url": "https://github.com/YassinEldeeb/create-prisma-generator.git"
19 | },
20 | "author": "Yassin Eldeeb ",
21 | "keywords": [
22 | "create-prisma-generator"
23 | ]
24 | }
25 |
--------------------------------------------------------------------------------
/packages/cpg-template-gen-usage/template/usage/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "usage",
3 | "private": true,
4 | "version": "1.0.0",
5 | "main": "src/app.js",
6 | "license": "MIT",
7 | "scripts": {},
8 | "dependencies": {
9 | "@prisma/client": "3.7.0"
10 | },
11 | "devDependencies": {
12 | "@types/node": "16.11.7",
13 | "prisma": "3.7.0",
14 | "$PACKAGE_NAME": "$PACKAGE_VERSION",
15 | "typescript": "4.5.2"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/packages/cpg-template-gen-usage/template/usage/prisma/schema.prisma:
--------------------------------------------------------------------------------
1 | generator custom_generator {
2 | provider = "$CUSTOM_GENERATOR_PROVIDER"
3 | output = "../types"
4 | }
5 |
6 | datasource db {
7 | provider = "postgresql"
8 | url = env("DATABASE_URL")
9 | }
10 |
11 | model User {
12 | id Int @id @default(autoincrement())
13 | email String @unique
14 | name String?
15 | }
16 |
17 | enum NotificationType {
18 | newPosts
19 | newComments
20 | newFollowers
21 | reply
22 | heartOnPost
23 | heartOnComment
24 | heartOnReply
25 | }
26 |
27 | enum Language {
28 | Typescript
29 | Javascript
30 | Rust
31 | Go
32 | Python
33 | Cpp
34 | }
35 |
--------------------------------------------------------------------------------
/packages/cpg-template-typescript/.npmignore:
--------------------------------------------------------------------------------
1 | src
2 | node_modules
3 | tsconfig.json
4 | README.md
5 | jest.config.js
6 | yarn.lock
7 | yarn-error.log
8 | pnpm-debug.log
9 | template/.turbo
10 | dist/**/*
11 | template/.pnpm-debug.log
12 | .pnpm-debug.log
13 | .turbo
14 | dist
--------------------------------------------------------------------------------
/packages/cpg-template-typescript/bin.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | require('./index')()
3 |
--------------------------------------------------------------------------------
/packages/cpg-template-typescript/index.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const fs = require('fs')
3 |
4 | const setup = () => {
5 | function copySync(from, to) {
6 | fs.mkdirSync(to, { recursive: true })
7 | fs.readdirSync(from).forEach((element) => {
8 | if (fs.lstatSync(path.join(from, element)).isFile()) {
9 | fs.copyFileSync(path.join(from, element), path.join(to, element))
10 | } else {
11 | copySync(path.join(from, element), path.join(to, element))
12 | }
13 | })
14 | }
15 |
16 | copySync(
17 | path.join(path.join(__dirname, `./template`)),
18 | path.join(process.cwd(), process.argv[2]),
19 | )
20 | }
21 |
22 | module.exports = setup
23 |
--------------------------------------------------------------------------------
/packages/cpg-template-typescript/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@cpg-cli/template-typescript",
3 | "version": "1.4.8",
4 | "main": "bin.js",
5 | "license": "MIT",
6 | "bin": {
7 | "cpg-template-typescript": "bin.js"
8 | },
9 | "files": [
10 | "template/**/*",
11 | "index.js",
12 | "bin.js"
13 | ],
14 | "scripts": {
15 | "start": "node bin.js"
16 | },
17 | "homepage": "https://github.com/YassinEldeeb/create-prisma-generator",
18 | "repository": {
19 | "type": "git",
20 | "url": "https://github.com/YassinEldeeb/create-prisma-generator.git"
21 | },
22 | "author": "Yassin Eldeeb ",
23 | "keywords": [
24 | "create-prisma-generator"
25 | ]
26 | }
27 |
--------------------------------------------------------------------------------
/packages/cpg-template-typescript/template/.npmignore:
--------------------------------------------------------------------------------
1 | src
2 | node_modules
3 | tsconfig.json
4 | README.md
5 | jest.config.js
6 | yarn.lock
7 | yarn-error.log
8 | .pnpm-debug.log
9 | dist
--------------------------------------------------------------------------------
/packages/cpg-template-typescript/template/jest.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
2 | module.exports = {
3 | preset: 'ts-jest',
4 | testEnvironment: 'node',
5 | testPathIgnorePatterns: ['/node_modules/', '/dist/'],
6 | modulePathIgnorePatterns: ['__helpers__/', '__fixtures__/'],
7 | }
8 |
--------------------------------------------------------------------------------
/packages/cpg-template-typescript/template/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "$PACKAGE_NAME",
3 | "description": "Provide a description about your generator",
4 | "version": "1.0.0",
5 | "main": "dist/generator.js",
6 | "license": "MIT",
7 | "bin": {
8 | "$PACKAGE_NAME": "dist/bin.js"
9 | },
10 | "engines": {
11 | "node": ">=14.0"
12 | },
13 | "scripts": {
14 | "start": "node dist/bin.js",
15 | "dev": "npx tsc -w",
16 | "build": "npx tsc",
17 | "prepack": "yarn build",
18 | "test": "jest"
19 | },
20 | "dependencies": {
21 | "@prisma/client": "3.12.0",
22 | "@prisma/generator-helper": "3.12.0",
23 | "@prisma/sdk": "3.12.0",
24 | "prettier": "2.5.1"
25 | },
26 | "devDependencies": {
27 | "@types/jest": "27.0.3",
28 | "@types/node": "17.0.21",
29 | "@types/prettier": "2.4.2",
30 | "jest": "27.4.7",
31 | "prisma": "3.12.0",
32 | "ts-jest": "27.1.4",
33 | "typescript": "4.6.2"
34 | },
35 | "homepage": "Link to homepage or github readme here",
36 | "repository": {
37 | "type": "git",
38 | "url": "Repo link here"
39 | },
40 | "author": "Your Name ",
41 | "keywords": [
42 | "prisma",
43 | "prisma2",
44 | "generator"
45 | ]
46 | }
47 |
--------------------------------------------------------------------------------
/packages/cpg-template-typescript/template/src/__tests__/__fixtures__/getSampleDMMF.ts:
--------------------------------------------------------------------------------
1 | import { getDMMF, getSchemaSync } from '@prisma/sdk'
2 | import path from 'path'
3 |
4 | const samplePrismaSchema = getSchemaSync(path.join(__dirname, './sample.prisma'))
5 |
6 | export const getSampleDMMF = async () => {
7 | return getDMMF({
8 | datamodel: samplePrismaSchema,
9 | })
10 | }
11 |
--------------------------------------------------------------------------------
/packages/cpg-template-typescript/template/src/__tests__/__fixtures__/sample.prisma:
--------------------------------------------------------------------------------
1 | datasource db {
2 | provider = "postgresql"
3 | url = env("DATABASE_URL")
4 | }
5 |
6 | model User {
7 | id Int @id @default(autoincrement())
8 | email String @unique
9 | name String?
10 | }
11 |
12 | enum NotificationType {
13 | newPosts
14 | newComments
15 | newFollowers
16 | reply
17 | heartOnPost
18 | heartOnComment
19 | heartOnReply
20 | }
21 |
22 | enum Language {
23 | Typescript
24 | Javascript
25 | Rust
26 | Go
27 | Python
28 | Cpp
29 | }
30 |
31 | enum State {
32 | Active
33 | Pending
34 | Banned
35 | }
36 |
--------------------------------------------------------------------------------
/packages/cpg-template-typescript/template/src/__tests__/__snapshots__/genEnum.test.ts.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`enum generation: Language 1`] = `
4 | "enum Language {
5 | Typescript=\\"Typescript\\",
6 | Javascript=\\"Javascript\\",
7 | Rust=\\"Rust\\",
8 | Go=\\"Go\\",
9 | Python=\\"Python\\",
10 | Cpp=\\"Cpp\\"
11 | }"
12 | `;
13 |
14 | exports[`enum generation: NotificationType 1`] = `
15 | "enum NotificationType {
16 | newPosts=\\"newPosts\\",
17 | newComments=\\"newComments\\",
18 | newFollowers=\\"newFollowers\\",
19 | reply=\\"reply\\",
20 | heartOnPost=\\"heartOnPost\\",
21 | heartOnComment=\\"heartOnComment\\",
22 | heartOnReply=\\"heartOnReply\\"
23 | }"
24 | `;
25 |
26 | exports[`enum generation: State 1`] = `
27 | "enum State {
28 | Active=\\"Active\\",
29 | Pending=\\"Pending\\",
30 | Banned=\\"Banned\\"
31 | }"
32 | `;
33 |
--------------------------------------------------------------------------------
/packages/cpg-template-typescript/template/src/__tests__/genEnum.test.ts:
--------------------------------------------------------------------------------
1 | import { genEnum } from '../helpers/genEnum'
2 | import { getSampleDMMF } from './__fixtures__/getSampleDMMF'
3 |
4 | test('enum generation', async () => {
5 | const sampleDMMF = await getSampleDMMF()
6 |
7 | sampleDMMF.datamodel.enums.forEach((enumInfo) => {
8 | expect(genEnum(enumInfo)).toMatchSnapshot(enumInfo.name)
9 | })
10 | })
11 |
--------------------------------------------------------------------------------
/packages/cpg-template-typescript/template/src/bin.ts:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | import './generator'
3 |
--------------------------------------------------------------------------------
/packages/cpg-template-typescript/template/src/constants.ts:
--------------------------------------------------------------------------------
1 | export const GENERATOR_NAME = '$PACKAGE_NAME'
2 |
--------------------------------------------------------------------------------
/packages/cpg-template-typescript/template/src/generator.ts:
--------------------------------------------------------------------------------
1 | import { generatorHandler, GeneratorOptions } from '@prisma/generator-helper'
2 | import { logger } from '@prisma/sdk'
3 | import path from 'path'
4 | import { GENERATOR_NAME } from './constants'
5 | import { genEnum } from './helpers/genEnum'
6 | import { writeFileSafely } from './utils/writeFileSafely'
7 |
8 | const { version } = require('../package.json')
9 |
10 | generatorHandler({
11 | onManifest() {
12 | logger.info(`${GENERATOR_NAME}:Registered`)
13 | return {
14 | version,
15 | defaultOutput: '../generated',
16 | prettyName: GENERATOR_NAME,
17 | }
18 | },
19 | onGenerate: async (options: GeneratorOptions) => {
20 | options.dmmf.datamodel.enums.forEach(async (enumInfo) => {
21 | const tsEnum = genEnum(enumInfo)
22 |
23 | const writeLocation = path.join(
24 | options.generator.output?.value!,
25 | `${enumInfo.name}.ts`,
26 | )
27 |
28 | await writeFileSafely(writeLocation, tsEnum)
29 | })
30 | },
31 | })
32 |
--------------------------------------------------------------------------------
/packages/cpg-template-typescript/template/src/helpers/genEnum.ts:
--------------------------------------------------------------------------------
1 | import { DMMF } from '@prisma/generator-helper'
2 |
3 | export const genEnum = ({ name, values }: DMMF.DatamodelEnum) => {
4 | const enumValues = values.map(({ name }) => `${name}="${name}"`).join(',\n')
5 |
6 | return `enum ${name} { \n${enumValues}\n }`
7 | }
8 |
--------------------------------------------------------------------------------
/packages/cpg-template-typescript/template/src/utils/formatFile.ts:
--------------------------------------------------------------------------------
1 | import prettier from 'prettier'
2 |
3 | export const formatFile = (content: string): Promise => {
4 | return new Promise((res, rej) =>
5 | prettier.resolveConfig(process.cwd()).then((options) => {
6 | if (!options) {
7 | res(content) // no prettier config was found, no need to format
8 | }
9 |
10 | try {
11 | const formatted = prettier.format(content, {
12 | ...options,
13 | parser: 'typescript',
14 | })
15 |
16 | res(formatted)
17 | } catch (error) {
18 | rej(error)
19 | }
20 | })
21 | )
22 | }
23 |
--------------------------------------------------------------------------------
/packages/cpg-template-typescript/template/src/utils/writeFileSafely.ts:
--------------------------------------------------------------------------------
1 | import fs from 'fs'
2 | import path from 'path'
3 | import { formatFile } from './formatFile'
4 |
5 | export const writeFileSafely = async (writeLocation: string, content: any) => {
6 | fs.mkdirSync(path.dirname(writeLocation), {
7 | recursive: true,
8 | })
9 |
10 | fs.writeFileSync(writeLocation, await formatFile(content))
11 | }
12 |
--------------------------------------------------------------------------------
/packages/cpg-template-typescript/template/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2018",
4 | "module": "commonjs",
5 | "lib": ["esnext"],
6 | "strict": true,
7 | "strictPropertyInitialization": false,
8 | "esModuleInterop": true,
9 | "experimentalDecorators": true,
10 | "emitDecoratorMetadata": true,
11 | "skipLibCheck": true,
12 | "forceConsistentCasingInFileNames": true,
13 | "removeComments": true,
14 | "sourceMap": true,
15 | "baseUrl": ".",
16 | "moduleResolution": "Node",
17 | "outDir": "./dist",
18 | "rootDir": "./src",
19 | "newLine": "lf"
20 | },
21 | "include": ["src/**/*"],
22 | "exclude": ["**/node_modules", "**/*.test.ts","**/__tests__", "**/dest"]
23 | }
24 |
--------------------------------------------------------------------------------
/packages/cpg-template/.npmignore:
--------------------------------------------------------------------------------
1 | src
2 | node_modules
3 | tsconfig.json
4 | README.md
5 | jest.config.js
6 | yarn.lock
7 | yarn-error.log
8 | pnpm-debug.log
9 | template/.turbo
10 | template/dist
11 | template/.pnpm-debug.log
12 | .pnpm-debug.log
13 | .turbo
14 | dist
--------------------------------------------------------------------------------
/packages/cpg-template/bin.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | require('./index')()
3 |
--------------------------------------------------------------------------------
/packages/cpg-template/index.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const fs = require('fs')
3 |
4 | const setup = () => {
5 | function copySync(from, to) {
6 | fs.mkdirSync(to, { recursive: true })
7 | fs.readdirSync(from).forEach((element) => {
8 | if (fs.lstatSync(path.join(from, element)).isFile()) {
9 | fs.copyFileSync(path.join(from, element), path.join(to, element))
10 | } else {
11 | copySync(path.join(from, element), path.join(to, element))
12 | }
13 | })
14 | }
15 |
16 | copySync(
17 | path.join(path.join(__dirname, `./template`)),
18 | path.join(process.cwd(), process.argv[2]),
19 | )
20 | }
21 |
22 | module.exports = setup
23 |
--------------------------------------------------------------------------------
/packages/cpg-template/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@cpg-cli/template",
3 | "version": "1.4.8",
4 | "main": "bin.js",
5 | "license": "MIT",
6 | "bin": {
7 | "cpg-template": "bin.js"
8 | },
9 | "files": [
10 | "template/**/*",
11 | "index.js",
12 | "bin.js"
13 | ],
14 | "scripts": {
15 | "start": "node bin.js"
16 | },
17 | "homepage": "https://github.com/YassinEldeeb/create-prisma-generator",
18 | "repository": {
19 | "type": "git",
20 | "url": "https://github.com/YassinEldeeb/create-prisma-generator.git"
21 | },
22 | "author": "Yassin Eldeeb ",
23 | "keywords": [
24 | "create-prisma-generator"
25 | ]
26 | }
27 |
--------------------------------------------------------------------------------
/packages/cpg-template/template/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [
4 | "@babel/env",
5 | {
6 | "targets": {
7 | "node": "current"
8 | }
9 | }
10 | ]
11 | ],
12 | "plugins": [
13 | "@babel/plugin-proposal-class-properties",
14 | "@babel/plugin-proposal-object-rest-spread"
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/packages/cpg-template/template/.npmignore:
--------------------------------------------------------------------------------
1 | src
2 | node_modules
3 | tsconfig.json
4 | README.md
5 | jest.config.js
6 | yarn.lock
7 | yarn-error.log
8 | .pnpm-debug.log
9 | dist
--------------------------------------------------------------------------------
/packages/cpg-template/template/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | testEnvironment: 'node',
3 | testPathIgnorePatterns: ['/node_modules/'],
4 | modulePathIgnorePatterns: ['__helpers__/', '/dist/', '__fixtures__/'],
5 | transform: {
6 | '\\.[jt]sx?$': 'babel-jest',
7 | },
8 | }
9 |
--------------------------------------------------------------------------------
/packages/cpg-template/template/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "$PACKAGE_NAME",
3 | "description": "Provide a description about your generator",
4 | "version": "1.0.0",
5 | "main": "dist/generator.js",
6 | "license": "MIT",
7 | "bin": {
8 | "$PACKAGE_NAME": "dist/bin.js"
9 | },
10 | "engines": {
11 | "node": ">=14.0"
12 | },
13 | "scripts": {
14 | "start": "node dist/bin.js",
15 | "dev": "babel src -d dist --watch",
16 | "build": "babel src -d dist",
17 | "prepack": "yarn build",
18 | "test": "jest"
19 | },
20 | "dependencies": {
21 | "@prisma/client": "3.12.0",
22 | "@prisma/generator-helper": "3.12.0",
23 | "@prisma/sdk": "3.12.0",
24 | "prettier": "2.6.2"
25 | },
26 | "devDependencies": {
27 | "@babel/cli": "7.17.6",
28 | "@babel/core": "7.16.12",
29 | "@babel/node": "7.16.8",
30 | "@babel/preset-env": "7.16.5",
31 | "babel-jest": "27.5.1",
32 | "jest": "28.0.0",
33 | "prisma": "3.12.0"
34 | },
35 | "homepage": "Link to homepage or github readme here",
36 | "repository": {
37 | "type": "git",
38 | "url": "Repo link here"
39 | },
40 | "author": "Your Name ",
41 | "keywords": [
42 | "prisma",
43 | "prisma2",
44 | "generator"
45 | ]
46 | }
47 |
--------------------------------------------------------------------------------
/packages/cpg-template/template/src/__tests__/__fixtures__/getSampleDMMF.js:
--------------------------------------------------------------------------------
1 | import { getDMMF, getSchemaSync } from '@prisma/sdk'
2 | import path from 'path'
3 |
4 | const samplePrismaSchema = getSchemaSync(path.join(__dirname, './sample.prisma'))
5 |
6 | export const getSampleDMMF = async () => {
7 | return getDMMF({
8 | datamodel: samplePrismaSchema,
9 | })
10 | }
11 |
--------------------------------------------------------------------------------
/packages/cpg-template/template/src/__tests__/__fixtures__/sample.prisma:
--------------------------------------------------------------------------------
1 | datasource db {
2 | provider = "postgresql"
3 | url = env("DATABASE_URL")
4 | }
5 |
6 | model User {
7 | id Int @id @default(autoincrement())
8 | email String @unique
9 | name String?
10 | }
11 |
12 | enum NotificationType {
13 | newPosts
14 | newComments
15 | newFollowers
16 | reply
17 | heartOnPost
18 | heartOnComment
19 | heartOnReply
20 | }
21 |
22 | enum Language {
23 | Typescript
24 | Javascript
25 | Rust
26 | Go
27 | Python
28 | Cpp
29 | }
30 |
31 | enum State {
32 | Active
33 | Pending
34 | Banned
35 | }
36 |
--------------------------------------------------------------------------------
/packages/cpg-template/template/src/__tests__/__snapshots__/genEnum.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`enum generation: Language 1`] = `
4 | "enum Language {
5 | Typescript=\\"Typescript\\",
6 | Javascript=\\"Javascript\\",
7 | Rust=\\"Rust\\",
8 | Go=\\"Go\\",
9 | Python=\\"Python\\",
10 | Cpp=\\"Cpp\\"
11 | }"
12 | `;
13 |
14 | exports[`enum generation: NotificationType 1`] = `
15 | "enum NotificationType {
16 | newPosts=\\"newPosts\\",
17 | newComments=\\"newComments\\",
18 | newFollowers=\\"newFollowers\\",
19 | reply=\\"reply\\",
20 | heartOnPost=\\"heartOnPost\\",
21 | heartOnComment=\\"heartOnComment\\",
22 | heartOnReply=\\"heartOnReply\\"
23 | }"
24 | `;
25 |
26 | exports[`enum generation: State 1`] = `
27 | "enum State {
28 | Active=\\"Active\\",
29 | Pending=\\"Pending\\",
30 | Banned=\\"Banned\\"
31 | }"
32 | `;
33 |
--------------------------------------------------------------------------------
/packages/cpg-template/template/src/__tests__/genEnum.test.js:
--------------------------------------------------------------------------------
1 | import { genEnum } from '../helpers/genEnum'
2 | import { getSampleDMMF } from './__fixtures__/getSampleDMMF'
3 |
4 | test('enum generation', async () => {
5 | const sampleDMMF = await getSampleDMMF()
6 |
7 | sampleDMMF.datamodel.enums.forEach((enumInfo) => {
8 | expect(genEnum(enumInfo)).toMatchSnapshot(enumInfo.name)
9 | })
10 | })
11 |
--------------------------------------------------------------------------------
/packages/cpg-template/template/src/bin.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | import './generator'
3 |
--------------------------------------------------------------------------------
/packages/cpg-template/template/src/constants.js:
--------------------------------------------------------------------------------
1 | export const GENERATOR_NAME = '$PACKAGE_NAME'
2 |
--------------------------------------------------------------------------------
/packages/cpg-template/template/src/generator.js:
--------------------------------------------------------------------------------
1 | import { generatorHandler } from '@prisma/generator-helper'
2 | import { logger } from '@prisma/sdk'
3 | import path from 'path'
4 | import { GENERATOR_NAME } from './constants'
5 | import { genEnum } from './helpers/genEnum'
6 | import { writeFileSafely } from './utils/writeFileSafely'
7 |
8 | const { version } = require('../package.json')
9 |
10 | generatorHandler({
11 | onManifest() {
12 | logger.info(`${GENERATOR_NAME}:Registered`)
13 | return {
14 | version,
15 | defaultOutput: '../generated',
16 | prettyName: GENERATOR_NAME,
17 | }
18 | },
19 | onGenerate: async (options) => {
20 | options.dmmf.datamodel.enums.forEach(async (enumInfo) => {
21 | const tsEnum = genEnum(enumInfo)
22 |
23 | const writeLocation = path.join(
24 | options.generator.output?.value,
25 | `${enumInfo.name}.ts`,
26 | )
27 |
28 | await writeFileSafely(writeLocation, tsEnum)
29 | })
30 | },
31 | })
32 |
--------------------------------------------------------------------------------
/packages/cpg-template/template/src/helpers/genEnum.js:
--------------------------------------------------------------------------------
1 | export const genEnum = ({ name, values }) => {
2 | const enumValues = values.map(({ name }) => `${name}="${name}"`).join(',\n')
3 |
4 | return `enum ${name} { \n${enumValues}\n }`
5 | }
6 |
--------------------------------------------------------------------------------
/packages/cpg-template/template/src/utils/formatFile.js:
--------------------------------------------------------------------------------
1 | import prettier from 'prettier'
2 |
3 | export const formatFile = (content) => {
4 | return new Promise((res, rej) =>
5 | prettier.resolveConfig(process.cwd()).then((options) => {
6 | if (!options) {
7 | res(content) // no prettier config was found, no need to format
8 | }
9 |
10 | try {
11 | const formatted = prettier.format(content, {
12 | ...options,
13 | parser: 'typescript',
14 | })
15 |
16 | res(formatted)
17 | } catch (error) {
18 | rej(error)
19 | }
20 | }),
21 | )
22 | }
23 |
--------------------------------------------------------------------------------
/packages/cpg-template/template/src/utils/writeFileSafely.js:
--------------------------------------------------------------------------------
1 | import fs from 'fs'
2 | import path from 'path'
3 | import { formatFile } from './formatFile'
4 |
5 | export const writeFileSafely = async (writeLocation, content) => {
6 | fs.mkdirSync(path.dirname(writeLocation), {
7 | recursive: true,
8 | })
9 |
10 | fs.writeFileSync(writeLocation, await formatFile(content))
11 | }
12 |
--------------------------------------------------------------------------------
/packages/create-prisma-generator/.npmignore:
--------------------------------------------------------------------------------
1 | src
2 | node_modules
3 | tsconfig.json
4 | README.md
5 | jest.config.js
6 | yarn.lock
7 | yarn-error.log
8 | pnpm-debug.log
9 | .turbo
--------------------------------------------------------------------------------
/packages/create-prisma-generator/jest.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
2 | module.exports = {
3 | preset: 'ts-jest',
4 | testEnvironment: 'node',
5 | testPathIgnorePatterns: ['/node_modules/', '/dist/'],
6 | modulePathIgnorePatterns: [
7 | '__helpers__/',
8 | '__fixtures__/',
9 | '__mocks__/',
10 | 'types/',
11 | 'constants/',
12 | ],
13 | setupFilesAfterEnv: ['@alex_neo/jest-expect-message'],
14 | }
15 |
--------------------------------------------------------------------------------
/packages/create-prisma-generator/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "create-prisma-generator",
3 | "version": "1.9.8",
4 | "main": "dist/bin.js",
5 | "license": "MIT",
6 | "bin": {
7 | "create-prisma-generator": "dist/bin.js"
8 | },
9 | "files": [
10 | "dist/**/*"
11 | ],
12 | "scripts": {
13 | "start": "npx ts-node src/index.ts",
14 | "dev": "tsc -w",
15 | "build": "tsc",
16 | "prepack": "yarn build",
17 | "test": "jest --verbose",
18 | "test-unit": "pnpm test -- --testPathIgnorePatterns e2e",
19 | "test-e2e": "pnpm test -- --testPathPattern e2e",
20 | "test-w": "jest --watch"
21 | },
22 | "dependencies": {
23 | "@types/fs-extra": "^9.0.13",
24 | "chalk": "4.1.2",
25 | "fs-extra": "^10.0.0",
26 | "inquirer": "^8.2.0",
27 | "rimraf": "^3.0.2",
28 | "validate-npm-package-name": "^3.0.0"
29 | },
30 | "devDependencies": {
31 | "@alex_neo/jest-expect-message": "^1.0.5",
32 | "@nut-tree/nut-js": "^2.0.0-RC1",
33 | "@types/inquirer": "^8.1.3",
34 | "@types/jest": "^27.0.3",
35 | "@types/jest-expect-message": "^1.0.3",
36 | "@types/node": "^17.0.5",
37 | "@types/rimraf": "^3.0.2",
38 | "@types/validate-npm-package-name": "^3.0.3",
39 | "concurrently": "^6.5.1",
40 | "copyfiles": "^2.4.1",
41 | "jest": "^27.4.5",
42 | "memfs": "^3.4.1",
43 | "mock-stdin": "^1.0.0",
44 | "nodemon": "^2.0.15",
45 | "ts-jest": "^27.1.2",
46 | "tsc-watch": "^4.5.0",
47 | "typescript": "^4.5.4",
48 | "unionfs": "^4.4.0"
49 | },
50 | "homepage": "https://github.com/YassinEldeeb/create-prisma-generator",
51 | "repository": {
52 | "type": "git",
53 | "url": "https://github.com/YassinEldeeb/create-prisma-generator.git"
54 | },
55 | "author": "Yassin Eldeeb ",
56 | "keywords": [
57 | "prisma",
58 | "prisma2",
59 | "generator",
60 | "CPG",
61 | "CRA",
62 | "boilerplate",
63 | "setup",
64 | "development",
65 | "testing",
66 | "CLI"
67 | ]
68 | }
69 |
--------------------------------------------------------------------------------
/packages/create-prisma-generator/src/__tests__/__helpers__/answer.ts:
--------------------------------------------------------------------------------
1 | import { MockSTDIN } from 'mock-stdin'
2 | import { delay } from './delay'
3 | import { keys } from './keyCodes'
4 |
5 | export const answer = async (
6 | io: MockSTDIN,
7 | options?: { keys?: (keyof typeof keys)[]; text?: string },
8 | ) => {
9 | await delay(10)
10 | if (options?.keys) {
11 | options.keys.forEach((e) => io.send(keys[e]))
12 | } else if (options?.text) {
13 | io.send(options.text)
14 | }
15 | io.send(keys.enter)
16 | }
17 |
--------------------------------------------------------------------------------
/packages/create-prisma-generator/src/__tests__/__helpers__/clearInput.ts:
--------------------------------------------------------------------------------
1 | import { MockSTDIN } from 'mock-stdin'
2 | import { keys } from './keyCodes'
3 |
4 | export const clearInput = (io: MockSTDIN, inputLength: number) => {
5 | for (let i = 0; i < inputLength; i++) {
6 | io.send(keys.delete)
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/packages/create-prisma-generator/src/__tests__/__helpers__/delay.ts:
--------------------------------------------------------------------------------
1 | // helper function for timing
2 | export const delay = (ms: number) =>
3 | new Promise((resolve) => setTimeout(resolve, ms))
4 |
--------------------------------------------------------------------------------
/packages/create-prisma-generator/src/__tests__/__helpers__/keyCodes.ts:
--------------------------------------------------------------------------------
1 | // ASCII Escape key codes: https://www.rapidtables.com/code/text/ascii-table.html
2 | // Just look up the hex code and prefix it with "\x"
3 | export const keys = {
4 | up: '\x1B\x5B\x41',
5 | down: '\x1B\x5B\x42',
6 | enter: '\x0D',
7 | space: '\x20',
8 | delete: '\x7F',
9 | ctrl_C: '\x03',
10 | }
11 |
--------------------------------------------------------------------------------
/packages/create-prisma-generator/src/__tests__/__helpers__/skipQuestions.ts:
--------------------------------------------------------------------------------
1 | import { MockSTDIN } from 'mock-stdin'
2 | import { questions } from '../../utils/inquirer/promptQuestions'
3 | import { delay } from '../__helpers__/delay'
4 | import { keys } from '../__helpers__/keyCodes'
5 |
6 | export const skipQuestions = async (
7 | numOfSkips: number,
8 | io: MockSTDIN,
9 | skipWithNo?: boolean,
10 | ) => {
11 | for (
12 | let i = 0;
13 | i < (numOfSkips === -1 ? questions.length : numOfSkips);
14 | i++
15 | ) {
16 | await delay(10)
17 | if (skipWithNo) {
18 | io.send('No')
19 | }
20 | io.send(keys.enter)
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/packages/create-prisma-generator/src/__tests__/__helpers__/snapshotSerializers.ts:
--------------------------------------------------------------------------------
1 | import { CLIs } from '../../tinyClis'
2 |
3 | export const serializeCLI = (key: keyof typeof CLIs) => {
4 | return CLIs[key]('path', 'workspace')
5 | }
6 |
--------------------------------------------------------------------------------
/packages/create-prisma-generator/src/__tests__/__helpers__/spyConsole.ts:
--------------------------------------------------------------------------------
1 | export function spyConsole() {
2 | let spy: {
3 | console: jest.SpyInstance
4 | } = {} as any
5 |
6 | beforeEach(() => {
7 | spy.console = jest.spyOn(console, 'log')
8 | })
9 |
10 | afterEach(() => {
11 | spy.console.mockClear()
12 | })
13 |
14 | afterAll(() => {
15 | spy.console.mockRestore()
16 | })
17 |
18 | return spy
19 | }
20 |
--------------------------------------------------------------------------------
/packages/create-prisma-generator/src/__tests__/__in-memory-fs-snapshots__/output-from-sample1.json:
--------------------------------------------------------------------------------
1 | {
2 | "answers": {
3 | "generatorName": "prisma-generator-data-graph-some-uniqueness",
4 | "typescript": true,
5 | "packageManager": "pnpm",
6 | "githubActions": true,
7 | "semanticRelease": true,
8 | "usageTemplate": true
9 | },
10 | "pathSep": "\\",
11 | "fsSnapshot": {
12 | "prisma-generator-data-graph-some-uniqueness\\.prettierrc": "",
13 | "prisma-generator-data-graph-some-uniqueness\\README.md": "",
14 | "prisma-generator-data-graph-some-uniqueness\\.gitignore": "",
15 | "prisma-generator-data-graph-some-uniqueness\\packages\\usage\\package.json": "",
16 | "prisma-generator-data-graph-some-uniqueness\\packages\\usage\\prisma\\schema.prisma": "",
17 | "prisma-generator-data-graph-some-uniqueness\\packages\\generator\\.npmignore": "",
18 | "prisma-generator-data-graph-some-uniqueness\\packages\\generator\\jest.config.js": "",
19 | "prisma-generator-data-graph-some-uniqueness\\packages\\generator\\package.json": "",
20 | "prisma-generator-data-graph-some-uniqueness\\packages\\generator\\src\\bin.ts": "",
21 | "prisma-generator-data-graph-some-uniqueness\\packages\\generator\\src\\constants.ts": "",
22 | "prisma-generator-data-graph-some-uniqueness\\packages\\generator\\src\\generator.ts": "",
23 | "prisma-generator-data-graph-some-uniqueness\\packages\\generator\\src\\helpers\\genEnum.ts": "",
24 | "prisma-generator-data-graph-some-uniqueness\\packages\\generator\\src\\utils\\formatFile.ts": "",
25 | "prisma-generator-data-graph-some-uniqueness\\packages\\generator\\src\\utils\\writeFileSafely.ts": "",
26 | "prisma-generator-data-graph-some-uniqueness\\packages\\generator\\src\\__tests__\\genEnum.test.ts": "",
27 | "prisma-generator-data-graph-some-uniqueness\\packages\\generator\\src\\__tests__\\__fixtures__\\getSampleDMMF.ts": "",
28 | "prisma-generator-data-graph-some-uniqueness\\packages\\generator\\src\\__tests__\\__fixtures__\\sample.prisma": "",
29 | "prisma-generator-data-graph-some-uniqueness\\packages\\generator\\src\\__tests__\\__snapshots__\\genEnum.test.ts.snap": "",
30 | "prisma-generator-data-graph-some-uniqueness\\packages\\generator\\tsconfig.json": "",
31 | "prisma-generator-data-graph-some-uniqueness\\packages\\generator\\dist\\bin.js": "",
32 | "prisma-generator-data-graph-some-uniqueness\\.github\\dependabot.yml": "",
33 | "prisma-generator-data-graph-some-uniqueness\\.github\\workflows\\CI.yml": "",
34 | "prisma-generator-data-graph-some-uniqueness\\pnpm-workspace.yaml": "",
35 | "prisma-generator-data-graph-some-uniqueness\\commitlint.config.js": "",
36 | "prisma-generator-data-graph-some-uniqueness\\package.json": "",
37 | "prisma-generator-data-graph-some-uniqueness\\.husky\\commit-msg": ""
38 | }
39 | }
--------------------------------------------------------------------------------
/packages/create-prisma-generator/src/__tests__/__in-memory-fs-snapshots__/output-from-sample2.json:
--------------------------------------------------------------------------------
1 | {
2 | "answers": {
3 | "generatorName": "prisma-generator-data-graph-some-uniqueness",
4 | "typescript": false,
5 | "packageManager": "pnpm",
6 | "githubActions": false,
7 | "usageTemplate": false
8 | },
9 | "pathSep": "\\",
10 | "fsSnapshot": {
11 | "prisma-generator-data-graph-some-uniqueness\\.prettierrc": "",
12 | "prisma-generator-data-graph-some-uniqueness\\README.md": "",
13 | "prisma-generator-data-graph-some-uniqueness\\.gitignore": "",
14 | "prisma-generator-data-graph-some-uniqueness\\.babelrc": "",
15 | "prisma-generator-data-graph-some-uniqueness\\.npmignore": "",
16 | "prisma-generator-data-graph-some-uniqueness\\jest.config.js": "",
17 | "prisma-generator-data-graph-some-uniqueness\\package.json": "",
18 | "prisma-generator-data-graph-some-uniqueness\\src\\bin.js": "",
19 | "prisma-generator-data-graph-some-uniqueness\\src\\constants.js": "",
20 | "prisma-generator-data-graph-some-uniqueness\\src\\generator.js": "",
21 | "prisma-generator-data-graph-some-uniqueness\\src\\helpers\\genEnum.js": "",
22 | "prisma-generator-data-graph-some-uniqueness\\src\\utils\\formatFile.js": "",
23 | "prisma-generator-data-graph-some-uniqueness\\src\\utils\\writeFileSafely.js": "",
24 | "prisma-generator-data-graph-some-uniqueness\\src\\__tests__\\genEnum.test.js": "",
25 | "prisma-generator-data-graph-some-uniqueness\\src\\__tests__\\__fixtures__\\getSampleDMMF.js": "",
26 | "prisma-generator-data-graph-some-uniqueness\\src\\__tests__\\__fixtures__\\sample.prisma": "",
27 | "prisma-generator-data-graph-some-uniqueness\\src\\__tests__\\__snapshots__\\genEnum.test.js.snap": ""
28 | }
29 | }
--------------------------------------------------------------------------------
/packages/create-prisma-generator/src/__tests__/__in-memory-fs-snapshots__/output-from-sample3.json:
--------------------------------------------------------------------------------
1 | {
2 | "answers": {
3 | "generatorName": "prisma-generator-data-graph-some-uniqueness",
4 | "typescript": false,
5 | "packageManager": "yarn",
6 | "githubActions": true,
7 | "semanticRelease": false,
8 | "usageTemplate": false
9 | },
10 | "pathSep": "\\",
11 | "fsSnapshot": {
12 | "prisma-generator-data-graph-some-uniqueness\\.prettierrc": "",
13 | "prisma-generator-data-graph-some-uniqueness\\README.md": "",
14 | "prisma-generator-data-graph-some-uniqueness\\.gitignore": "",
15 | "prisma-generator-data-graph-some-uniqueness\\.babelrc": "",
16 | "prisma-generator-data-graph-some-uniqueness\\.npmignore": "",
17 | "prisma-generator-data-graph-some-uniqueness\\jest.config.js": "",
18 | "prisma-generator-data-graph-some-uniqueness\\package.json": "",
19 | "prisma-generator-data-graph-some-uniqueness\\src\\bin.js": "",
20 | "prisma-generator-data-graph-some-uniqueness\\src\\constants.js": "",
21 | "prisma-generator-data-graph-some-uniqueness\\src\\generator.js": "",
22 | "prisma-generator-data-graph-some-uniqueness\\src\\helpers\\genEnum.js": "",
23 | "prisma-generator-data-graph-some-uniqueness\\src\\utils\\formatFile.js": "",
24 | "prisma-generator-data-graph-some-uniqueness\\src\\utils\\writeFileSafely.js": "",
25 | "prisma-generator-data-graph-some-uniqueness\\src\\__tests__\\genEnum.test.js": "",
26 | "prisma-generator-data-graph-some-uniqueness\\src\\__tests__\\__fixtures__\\getSampleDMMF.js": "",
27 | "prisma-generator-data-graph-some-uniqueness\\src\\__tests__\\__fixtures__\\sample.prisma": "",
28 | "prisma-generator-data-graph-some-uniqueness\\src\\__tests__\\__snapshots__\\genEnum.test.js.snap": "",
29 | "prisma-generator-data-graph-some-uniqueness\\.github\\dependabot.yml": "",
30 | "prisma-generator-data-graph-some-uniqueness\\.github\\workflows\\CI.yml": ""
31 | }
32 | }
--------------------------------------------------------------------------------
/packages/create-prisma-generator/src/__tests__/__in-memory-fs-snapshots__/output-from-sample4.json:
--------------------------------------------------------------------------------
1 | {
2 | "answers": {
3 | "generatorName": "@org/prisma-generator-data-graph-some-uniqueness",
4 | "typescript": false,
5 | "packageManager": "npm",
6 | "githubActions": false,
7 | "usageTemplate": true
8 | },
9 | "pathSep": "\\",
10 | "fsSnapshot": {
11 | "org-prisma-generator-data-graph-some-uniqueness\\.prettierrc": "",
12 | "org-prisma-generator-data-graph-some-uniqueness\\README.md": "",
13 | "org-prisma-generator-data-graph-some-uniqueness\\.gitignore": "",
14 | "org-prisma-generator-data-graph-some-uniqueness\\packages\\usage\\package.json": "",
15 | "org-prisma-generator-data-graph-some-uniqueness\\packages\\usage\\prisma\\schema.prisma": "",
16 | "org-prisma-generator-data-graph-some-uniqueness\\packages\\generator\\.babelrc": "",
17 | "org-prisma-generator-data-graph-some-uniqueness\\packages\\generator\\.npmignore": "",
18 | "org-prisma-generator-data-graph-some-uniqueness\\packages\\generator\\jest.config.js": "",
19 | "org-prisma-generator-data-graph-some-uniqueness\\packages\\generator\\package.json": "",
20 | "org-prisma-generator-data-graph-some-uniqueness\\packages\\generator\\src\\bin.js": "",
21 | "org-prisma-generator-data-graph-some-uniqueness\\packages\\generator\\src\\constants.js": "",
22 | "org-prisma-generator-data-graph-some-uniqueness\\packages\\generator\\src\\generator.js": "",
23 | "org-prisma-generator-data-graph-some-uniqueness\\packages\\generator\\src\\helpers\\genEnum.js": "",
24 | "org-prisma-generator-data-graph-some-uniqueness\\packages\\generator\\src\\utils\\formatFile.js": "",
25 | "org-prisma-generator-data-graph-some-uniqueness\\packages\\generator\\src\\utils\\writeFileSafely.js": "",
26 | "org-prisma-generator-data-graph-some-uniqueness\\packages\\generator\\src\\__tests__\\genEnum.test.js": "",
27 | "org-prisma-generator-data-graph-some-uniqueness\\packages\\generator\\src\\__tests__\\__fixtures__\\getSampleDMMF.js": "",
28 | "org-prisma-generator-data-graph-some-uniqueness\\packages\\generator\\src\\__tests__\\__fixtures__\\sample.prisma": "",
29 | "org-prisma-generator-data-graph-some-uniqueness\\packages\\generator\\src\\__tests__\\__snapshots__\\genEnum.test.js.snap": "",
30 | "org-prisma-generator-data-graph-some-uniqueness\\packages\\generator\\dist\\bin.js": "",
31 | "org-prisma-generator-data-graph-some-uniqueness\\package.json": ""
32 | }
33 | }
--------------------------------------------------------------------------------
/packages/create-prisma-generator/src/__tests__/__in-memory-fs-snapshots__/output-from-sample5.json:
--------------------------------------------------------------------------------
1 | {
2 | "answers": {
3 | "generatorName": "prisma-generator-data-graph-some-uniqueness",
4 | "typescript": true,
5 | "packageManager": "yarn",
6 | "githubActions": true,
7 | "semanticRelease": false,
8 | "usageTemplate": false
9 | },
10 | "pathSep": "\\",
11 | "fsSnapshot": {
12 | "prisma-generator-data-graph-some-uniqueness\\.prettierrc": "",
13 | "prisma-generator-data-graph-some-uniqueness\\README.md": "",
14 | "prisma-generator-data-graph-some-uniqueness\\.gitignore": "",
15 | "prisma-generator-data-graph-some-uniqueness\\.npmignore": "",
16 | "prisma-generator-data-graph-some-uniqueness\\jest.config.js": "",
17 | "prisma-generator-data-graph-some-uniqueness\\package.json": "",
18 | "prisma-generator-data-graph-some-uniqueness\\src\\bin.ts": "",
19 | "prisma-generator-data-graph-some-uniqueness\\src\\constants.ts": "",
20 | "prisma-generator-data-graph-some-uniqueness\\src\\generator.ts": "",
21 | "prisma-generator-data-graph-some-uniqueness\\src\\helpers\\genEnum.ts": "",
22 | "prisma-generator-data-graph-some-uniqueness\\src\\utils\\formatFile.ts": "",
23 | "prisma-generator-data-graph-some-uniqueness\\src\\utils\\writeFileSafely.ts": "",
24 | "prisma-generator-data-graph-some-uniqueness\\src\\__tests__\\genEnum.test.ts": "",
25 | "prisma-generator-data-graph-some-uniqueness\\src\\__tests__\\__fixtures__\\getSampleDMMF.ts": "",
26 | "prisma-generator-data-graph-some-uniqueness\\src\\__tests__\\__fixtures__\\sample.prisma": "",
27 | "prisma-generator-data-graph-some-uniqueness\\src\\__tests__\\__snapshots__\\genEnum.test.ts.snap": "",
28 | "prisma-generator-data-graph-some-uniqueness\\tsconfig.json": "",
29 | "prisma-generator-data-graph-some-uniqueness\\.github\\dependabot.yml": "",
30 | "prisma-generator-data-graph-some-uniqueness\\.github\\workflows\\CI.yml": ""
31 | }
32 | }
--------------------------------------------------------------------------------
/packages/create-prisma-generator/src/__tests__/__snapshots__/check-tiny-clis-commands.test.ts.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`githubActionsTemplate command is configured properly 1`] = `"npx @cpg-cli/github-actions path"`;
4 |
5 | exports[`javascriptTemplate command is configured properly 1`] = `"npx @cpg-cli/template path"`;
6 |
7 | exports[`rootConfigs command is configured properly 1`] = `"npx @cpg-cli/root-configs path"`;
8 |
9 | exports[`setupSemanticRelease command is configured properly 1`] = `"npx @cpg-cli/semantic-releases path workspace"`;
10 |
11 | exports[`typescriptTemplate command is configured properly 1`] = `"npx @cpg-cli/template-typescript path"`;
12 |
13 | exports[`usageTemplate command is configured properly 1`] = `"npx @cpg-cli/template-gen-usage path"`;
14 |
--------------------------------------------------------------------------------
/packages/create-prisma-generator/src/__tests__/__snapshots__/get-proper-answers-object.test.ts.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`should get proper answers object when answering using sample1 1`] = `
4 | Object {
5 | "generatorName": "prisma-generator-data-graph",
6 | "githubActions": true,
7 | "packageManager": "pnpm",
8 | "semanticRelease": true,
9 | "typescript": true,
10 | "usageTemplate": true,
11 | }
12 | `;
13 |
14 | exports[`should get proper answers object when answering using sample2 1`] = `
15 | Object {
16 | "generatorName": "prisma-generator-data-graph",
17 | "githubActions": false,
18 | "packageManager": "pnpm",
19 | "typescript": false,
20 | "usageTemplate": false,
21 | }
22 | `;
23 |
24 | exports[`should get proper answers object when answering using sample3 1`] = `
25 | Object {
26 | "generatorName": "prisma-generator-data-graph",
27 | "githubActions": false,
28 | "packageManager": "npm",
29 | "typescript": false,
30 | "usageTemplate": true,
31 | }
32 | `;
33 |
34 | exports[`should get proper answers object when answering using sample4 1`] = `
35 | Object {
36 | "generatorName": "my-gen",
37 | "githubActions": false,
38 | "packageManager": "pnpm",
39 | "typescript": true,
40 | "usageTemplate": false,
41 | }
42 | `;
43 |
--------------------------------------------------------------------------------
/packages/create-prisma-generator/src/__tests__/check-ouput-structure.test.ts:
--------------------------------------------------------------------------------
1 | // --------------------------------------------------------------------------------
2 | // This is the BEAST test suit that's responsible for
3 | // checking the full generated boilerplate from 7+ different CLIs
4 | // from start to finish running on a virtual in-memory file-system
5 | // --------------------------------------------------------------------------------
6 | import { MockSTDIN, stdin } from 'mock-stdin'
7 | import { main } from '../index'
8 | import { answer } from './__helpers__/answer'
9 | import { skipQuestions } from './__helpers__/skipQuestions'
10 | import fs from 'fs'
11 | import path from 'path'
12 | import memfs from 'memfs'
13 | import { MockedFS } from './types/MockedFS'
14 | import child_process from 'child_process'
15 | import { getInstallCommand } from '../utils/getInstallCommands'
16 | import { validGenName } from './constants/valid-prisma-gen-name'
17 | import { transformScopedName } from '../utils/transformScopedName'
18 |
19 | const mockedFS: MockedFS = fs as any
20 |
21 | let io: MockSTDIN
22 | beforeEach(() => {
23 | io = stdin()
24 | })
25 |
26 | afterEach(async () => {
27 | mockedFS.goBackToInitialSetup()
28 | io.restore()
29 | })
30 |
31 | afterAll(() => mockedFS.reset())
32 |
33 | jest.mock('fs', () => {
34 | const { Volume } = require('memfs') as typeof memfs
35 | const fsModule = jest.requireActual(`fs`) as typeof fs
36 | const pathModule = jest.requireActual(`path`) as typeof path
37 |
38 | const packagesPath = pathModule.join(__dirname, '../../../')
39 | const packages = fsModule
40 | .readdirSync(packagesPath, { withFileTypes: true })
41 | .filter((dirent: any) => dirent.isDirectory())
42 |
43 | const InitialFSJSONSetup = {}
44 | // Setup in-memory `fs`
45 | packages.forEach((pkg: fs.Dirent) => {
46 | const blacklistedPackages = ['create-prisma-generator', 'cli-usage']
47 | if (blacklistedPackages.includes(pkg.name)) {
48 | return
49 | }
50 |
51 | function* walkSync(dir: string): any {
52 | const files = fsModule.readdirSync(dir, { withFileTypes: true })
53 | for (const file of files) {
54 | if (file.isDirectory()) {
55 | yield* walkSync(pathModule.join(dir, file.name))
56 | } else {
57 | yield pathModule.join(dir, file.name)
58 | }
59 | }
60 | }
61 |
62 | for (const filePath of walkSync(pathModule.join(packagesPath, pkg.name))) {
63 | const blackListedFolders = ['node_modules']
64 | const is_blacklisted =
65 | blackListedFolders.findIndex((bl) => filePath.indexOf(bl) > -1) > -1
66 | if (!is_blacklisted) {
67 | const mustHaveContent = ['.yml', 'package.json']
68 |
69 | const fileContent =
70 | filePath.includes(
71 | pathModule.join(packagesPath, pkg.name, 'template'),
72 | ) && mustHaveContent.findIndex((e) => filePath.includes(e)) === -1
73 | ? ''
74 | : fsModule.readFileSync(filePath, 'utf-8')
75 |
76 | // @ts-ignore
77 | InitialFSJSONSetup[filePath] = fileContent
78 | }
79 | }
80 | })
81 |
82 | // @ts-ignore
83 | InitialFSJSONSetup[
84 | pathModule.join(packagesPath, 'create-prisma-generator/file.txt')
85 | ] = ''
86 | const vol = Volume.fromJSON(InitialFSJSONSetup)
87 |
88 | ;(vol as any).actual = fsModule
89 | ;(vol as any).goBackToInitialSetup = () => {
90 | vol.reset()
91 | vol.fromJSON(InitialFSJSONSetup)
92 | }
93 |
94 | return vol
95 | })
96 |
97 | jest.mock('child_process', () => {
98 | return {
99 | execSync: async (cmd: string) => {
100 | if (cmd.includes('@cpg-cli/')) {
101 | const pkgName =
102 | 'cpg-' +
103 | cmd.split(' ')[1].replace('@cpg-cli/', '').replace('@latest', '')
104 |
105 | // Pass arguments to the Tiny CLIs
106 | process.argv[2] = cmd.split(' ')[2]
107 | process.argv[3] = cmd.split(' ')[3]
108 |
109 | const CLIIndexAbsolutePath = path.join(
110 | __dirname,
111 | `../../../${pkgName}`,
112 | 'index.js',
113 | )
114 | const relativePath = path.relative(__dirname, CLIIndexAbsolutePath)
115 |
116 | // Execute the tiny CLI by importing it
117 | // then calling the default exported function
118 | require(relativePath)()
119 | }
120 | },
121 | spawnSync: () => '',
122 | }
123 | })
124 |
125 | jest.spyOn(child_process, 'spawnSync')
126 |
127 | // If genName changed, fs-snapshot has to be deleted
128 | // to update the fs in-memory snapshot
129 | // It has to be named uniquely cause some operations
130 | // are depending on splitting a path using this name
131 | const genName = validGenName + '-some-uniqueness'
132 | const scopedGenName = '@org/' + validGenName + '-some-uniqueness'
133 |
134 | const sampleAnswers = {
135 | async sample1() {
136 | // LINK ..\utils\promptQuestions.ts#Q1-generatorName
137 | await answer(io, { text: genName })
138 |
139 | // Skip the rest of the questions
140 | await skipQuestions(-1, io)
141 | },
142 | async sample2() {
143 | // LINK ..\utils\promptQuestions.ts#Q1-generatorName
144 | await answer(io, { text: genName })
145 |
146 | // Skip the rest of the questions
147 | await skipQuestions(-1, io, true)
148 | },
149 | async sample3() {
150 | // LINK ..\utils\promptQuestions.ts#Q1-generatorName
151 | await answer(io, { text: genName })
152 |
153 | // LINK ..\utils\promptQuestions.ts#Q2-usingTypescript
154 | await answer(io, { text: 'No' })
155 |
156 | // LINK ..\utils\promptQuestions.ts#Q3-selectPkgManager
157 | await answer(io, { keys: ['up'] }) // > yarn
158 |
159 | // LINK ..\utils\promptQuestions.ts#Q4-usingGithubActions
160 | await answer(io)
161 |
162 | // Skip the rest of the questions
163 | await skipQuestions(-1, io, true)
164 | },
165 | async sample4() {
166 | // LINK ..\utils\promptQuestions.ts#Q1-generatorName
167 | await answer(io, { text: scopedGenName })
168 |
169 | // LINK ..\utils\promptQuestions.ts#Q2-usingTypescript
170 | await answer(io, { text: 'No' })
171 |
172 | // LINK ..\utils\promptQuestions.ts#Q3-selectPkgManager
173 | await answer(io, { keys: ['down'] }) // > npm
174 |
175 | // LINK ..\utils\promptQuestions.ts#Q4-usingGithubActions
176 | await answer(io, { text: 'No' })
177 |
178 | // Skip the rest of the questions
179 | await skipQuestions(-1, io)
180 | },
181 | async sample5() {
182 | // LINK ..\utils\promptQuestions.ts#Q1-generatorName
183 | await answer(io, { text: genName })
184 |
185 | // LINK ..\utils\promptQuestions.ts#Q2-usingTypescript
186 | await answer(io)
187 |
188 | // LINK ..\utils\promptQuestions.ts#Q3-selectPkgManager
189 | await answer(io, { keys: ['up'] }) // > yarn
190 |
191 | // LINK ..\utils\promptQuestions.ts#Q4-usingGithubActions
192 | await answer(io)
193 |
194 | // Skip the rest of the questions
195 | await skipQuestions(-1, io, true)
196 | },
197 | }
198 | const pkgManager = {
199 | sample1: 'pnpm',
200 | sample2: 'pnpm',
201 | sample3: 'yarn',
202 | sample4: 'npm',
203 | sample5: 'yarn',
204 | }
205 |
206 | Object.keys(sampleAnswers).map((sample) => {
207 | test(`check the ouput structure with ${sample}`, async () => {
208 | setTimeout(
209 | () => sampleAnswers[sample as keyof typeof sampleAnswers]().then(),
210 | 5,
211 | )
212 |
213 | const answers = (await main())!
214 |
215 | const dirName = transformScopedName(answers.generatorName)
216 |
217 | const fsSnapshot = mockedFS.toJSON()
218 | const newSnapshot = Object.keys(fsSnapshot)
219 | .filter((key) => {
220 | const condition = path.join(process.cwd(), dirName)
221 | return path.resolve(key).includes(condition)
222 | })
223 | .reduce((cur, key) => {
224 | return Object.assign(cur, {
225 | [path.join(dirName, key.split(dirName)[1])]: '',
226 | })
227 | }, {})
228 |
229 | const snapshotPath = path.join(
230 | __dirname,
231 | `__in-memory-fs-snapshots__/output-from-${sample}.json`,
232 | )
233 |
234 | const lastSnapshot = mockedFS.actual.existsSync(snapshotPath)
235 | ? JSON.parse(mockedFS.actual.readFileSync(snapshotPath, 'utf-8'))
236 | : null
237 |
238 | // Check if any files are missing
239 | if (lastSnapshot && lastSnapshot.fsSnapshot) {
240 | const missingFiles: string[] = []
241 | Object.keys(lastSnapshot.fsSnapshot).forEach((key) => {
242 | // Convert path seperator based on OS
243 | const pathOnCurrentOS = key.split(lastSnapshot.pathSep).join(path.sep)
244 |
245 | if (
246 | !Object.prototype.hasOwnProperty.call(newSnapshot, pathOnCurrentOS)
247 | ) {
248 | missingFiles.push(pathOnCurrentOS)
249 | }
250 | })
251 |
252 | expect(
253 | missingFiles,
254 | `Missing Files! If this was intentional delete 'output-from-${sample}.json' snapshot to update it.`,
255 | ).toEqual([])
256 | }
257 |
258 | // Delete snapshot to be Updated
259 | if (!lastSnapshot) {
260 | // Write in-memory fs JSON
261 | mockedFS.actual.writeFileSync(
262 | snapshotPath,
263 | JSON.stringify(
264 | { answers, pathSep: path.sep, fsSnapshot: newSnapshot },
265 | null,
266 | 2,
267 | ),
268 | )
269 | }
270 |
271 | // Treshold of 5 more files, anything more than that
272 | // is considered too much boilerplate
273 | const lastSnapshotLength = lastSnapshot
274 | ? Object.keys(lastSnapshot.fsSnapshot).length
275 | : 0
276 |
277 | if (lastSnapshotLength > 0) {
278 | const newSnapshotLength = Object.keys(newSnapshot).length
279 | const filesNumDiff = newSnapshotLength - lastSnapshotLength
280 | expect(
281 | filesNumDiff,
282 | 'This is too much boilerplate for just starting a project!',
283 | ).toBeLessThanOrEqual(5)
284 | }
285 |
286 | const installCommand = getInstallCommand(
287 | pkgManager[sample as keyof typeof pkgManager] as any,
288 | )
289 | // Check deps installtion
290 | expect(child_process.spawnSync).toHaveBeenCalledWith(installCommand, {
291 | cwd: path.join(process.cwd(), dirName),
292 | shell: true,
293 | stdio: 'inherit',
294 | })
295 | })
296 | })
297 |
--------------------------------------------------------------------------------
/packages/create-prisma-generator/src/__tests__/check-tiny-clis-commands.test.ts:
--------------------------------------------------------------------------------
1 | import { CLIs } from '../tinyClis'
2 | import { serializeCLI } from './__helpers__/snapshotSerializers'
3 |
4 | Object.keys(CLIs).forEach((cli) => {
5 | test(`${cli} command is configured properly`, () => {
6 | expect(serializeCLI(cli as keyof typeof CLIs)).toMatchSnapshot()
7 | })
8 | })
9 |
--------------------------------------------------------------------------------
/packages/create-prisma-generator/src/__tests__/constants/valid-prisma-gen-name.ts:
--------------------------------------------------------------------------------
1 | export const validGenName = 'prisma-generator-data-graph'
2 |
--------------------------------------------------------------------------------
/packages/create-prisma-generator/src/__tests__/e2e/setting-up-new-project.test.ts:
--------------------------------------------------------------------------------
1 | // TODO: run all tests in parallel in separate processes Using multiple threads
2 | import { MockSTDIN, stdin } from 'mock-stdin'
3 | import { validGenName } from '../constants/valid-prisma-gen-name'
4 | import { answer } from '../__helpers__/answer'
5 | import { skipQuestions } from '../__helpers__/skipQuestions'
6 | import fs from 'fs'
7 | import path from 'path'
8 | import { main } from '../..'
9 | import { execSync, spawnSync } from 'child_process'
10 |
11 | let io: MockSTDIN
12 | let tempDirPath: string
13 | let initialCWD: string
14 |
15 | beforeAll(() => {
16 | // Create temp folder in the same workspace.
17 | // cause:
18 | // 1. Github Actions: `EACCES: permission denied, mkdtemp '/tmpXXXXXX'`
19 | // Issue: https://github.com/actions/toolkit/issues/518
20 | // 2. I want the tiny CLIs to be in the same running process location
21 | // to inherit local packages instead of using the online versions
22 |
23 | const tempPath = path.join(__dirname, '../../../../temp')
24 | fs.mkdirSync(tempPath, { recursive: true })
25 |
26 | const tempPkgJSON = {
27 | name: 'temp-for-e2e-testing',
28 | private: true,
29 | devDependencies: {
30 | 'create-prisma-generator': 'workspace:*',
31 | '@cpg-cli/semantic-releases': 'workspace:*',
32 | '@cpg-cli/github-actions': 'workspace:*',
33 | '@cpg-cli/template': 'workspace:*',
34 | '@cpg-cli/template-gen-usage': 'workspace:*',
35 | '@cpg-cli/template-typescript': 'workspace:*',
36 | '@cpg-cli/root-configs': 'workspace:*',
37 | },
38 | }
39 |
40 | fs.writeFileSync(
41 | path.join(tempPath, 'package.json'),
42 | JSON.stringify(tempPkgJSON, null, 2),
43 | )
44 |
45 | // Link local deps(tiny CLIs)
46 | spawnSync('pnpm i', {
47 | shell: true,
48 | stdio: 'inherit',
49 | cwd: tempPath,
50 | })
51 |
52 | tempDirPath = tempPath
53 |
54 | initialCWD = process.cwd()
55 |
56 | process.chdir(tempDirPath)
57 | })
58 |
59 | beforeEach(() => {
60 | io = stdin()
61 | })
62 |
63 | afterEach(() => {
64 | fs.rmSync(path.join(tempDirPath, genName), { recursive: true })
65 | console.log('Cleaned up previous project folder 🧹')
66 | })
67 |
68 | afterAll(() => {
69 | // Switching to prevous CWD so that we
70 | // can delete the previous project folder
71 | process.chdir(initialCWD)
72 | console.log('Cleaned up temp folder 🧹')
73 | fs.rmSync(tempDirPath, { recursive: true })
74 | })
75 |
76 | const genName = validGenName
77 |
78 | const sampleAnswers = {
79 | async sample1() {
80 | // LINK ..\utils\promptQuestions.ts#Q1-generatorName
81 | await answer(io, { text: genName })
82 |
83 | // Skip the rest of the questions
84 | await skipQuestions(-1, io)
85 | },
86 | async sample2() {
87 | // LINK ..\utils\promptQuestions.ts#Q1-generatorName
88 | await answer(io, { text: genName })
89 |
90 | // Skip the rest of the questions
91 | await skipQuestions(-1, io, true)
92 | },
93 | async sample3() {
94 | // LINK ..\utils\promptQuestions.ts#Q1-generatorName
95 | await answer(io, { text: genName })
96 |
97 | // LINK ..\utils\promptQuestions.ts#Q2-usingTypescript
98 | await answer(io, { text: 'No' })
99 |
100 | // LINK ..\utils\promptQuestions.ts#Q3-selectPkgManager
101 | await answer(io, { keys: ['up'] }) // > yarn
102 |
103 | // LINK ..\utils\promptQuestions.ts#Q4-usingGithubActions
104 | await answer(io)
105 |
106 | // Skip the rest of the questions
107 | await skipQuestions(-1, io, true)
108 | },
109 | }
110 |
111 | const FOUR_MINUTES = 1000 * 60 * 4
112 |
113 | jest.mock('child_process', () => {
114 | const actualChildProcess = jest.requireActual('child_process')
115 |
116 | return {
117 | ...actualChildProcess,
118 | spawnSync: (cmd: string, options?: any) => {
119 | const installCommands = ['yarn', 'pnpm i', 'npm i']
120 |
121 | const projectPath = path.join(process.cwd(), genName)
122 | const workspacePath = path.join(projectPath, 'pnpm-workspace.yaml')
123 |
124 | //! Adding empty `pnpm-workspace.yaml` to seperate development workspace
125 | //! from temp testing workspace when using pnpm
126 | //? checking for project dir if it exists to avoid running this logic
127 | //? if spawnSync is being used in one of the teardown functions
128 | if (
129 | fs.existsSync(projectPath) &&
130 | !fs.existsSync(workspacePath) &&
131 | installCommands.includes(cmd)
132 | ) {
133 | fs.writeFileSync(workspacePath, '')
134 | }
135 |
136 | actualChildProcess.spawnSync(cmd, options)
137 | },
138 | }
139 | })
140 |
141 | Object.keys(sampleAnswers).map((sample) => {
142 | test(
143 | `setting up a new prisma generator like a normal user using ${sample}`,
144 | async () => {
145 | console.log(`------------------${sample}------------------`)
146 |
147 | setTimeout(
148 | () => sampleAnswers[sample as keyof typeof sampleAnswers]().then(),
149 | 5,
150 | )
151 |
152 | const projectWorkDir = path.join(process.cwd(), genName)
153 |
154 | const answers = (await main())!
155 | const { packageManager, usageTemplate } = answers
156 |
157 | // Running some scripts developers usually run after the boilerplate
158 | const runScript = (script: string, cwd: string = projectWorkDir) => {
159 | let command = ''
160 | if (packageManager === 'npm') {
161 | command = `${packageManager} run ${script}`
162 | } else {
163 | command = `${packageManager} ${script}`
164 | }
165 | execSync(command, { cwd })
166 | }
167 |
168 | // usageTemplate === Using Workspaces
169 | if (usageTemplate) {
170 | console.log(`Running generator tests`)
171 | const generatorPath = path.join(
172 | process.cwd(),
173 | genName,
174 | 'packages/generator',
175 | )
176 |
177 | runScript('test', generatorPath)
178 |
179 | console.log(
180 | `Running \`prisma generate\` to check if generator is linked properly`,
181 | )
182 | execSync('npx prisma generate', {
183 | cwd: path.join(generatorPath, '../usage'),
184 | stdio: 'inherit',
185 | })
186 | } else {
187 | console.log(`Running generator tests`)
188 | runScript('test')
189 | }
190 |
191 | console.log(`Checking if git repo is initalized`)
192 | execSync('git rev-parse --is-inside-work-tree', {
193 | cwd: projectWorkDir,
194 | stdio: 'inherit',
195 | })
196 | },
197 | FOUR_MINUTES,
198 | )
199 | })
200 |
--------------------------------------------------------------------------------
/packages/create-prisma-generator/src/__tests__/get-proper-answers-object.test.ts:
--------------------------------------------------------------------------------
1 | import { MockSTDIN, stdin } from 'mock-stdin'
2 | import { promptQuestions } from '../utils/inquirer/promptQuestions'
3 | import { skipQuestions } from './__helpers__/skipQuestions'
4 | import { answer } from './__helpers__/answer'
5 | import { validGenName } from './constants/valid-prisma-gen-name'
6 |
7 | // Mock stdin to send keystrokes to the CLI
8 | let io: MockSTDIN
9 | beforeEach(() => (io = stdin()))
10 | afterEach(() => io.restore())
11 |
12 | // See the Questions to know how to modify these samples
13 | // LINK ..\utils\promptQuestions.ts#questions
14 |
15 | let genName = validGenName
16 | const sampleAnswers = {
17 | async sample1() {
18 | // LINK ..\utils\promptQuestions.ts#Q1-generatorName
19 | await answer(io, { text: ` ${genName} ` })
20 |
21 | // Skip the rest of the questions
22 | await skipQuestions(-1, io)
23 | },
24 | async sample2() {
25 | // LINK ..\utils\promptQuestions.ts#Q1-generatorName
26 | await answer(io, { text: genName })
27 |
28 | // Skip the rest of the questions
29 | await skipQuestions(-1, io, true)
30 | },
31 | async sample3() {
32 | // LINK ..\utils\promptQuestions.ts#Q1-generatorName
33 | await answer(io, { text: genName })
34 |
35 | // LINK ..\utils\promptQuestions.ts#Q2-usingTypescript
36 | await answer(io, { text: 'No' })
37 |
38 | // LINK ..\utils\promptQuestions.ts#Q3-selectPkgManager
39 | await answer(io, { keys: ['down'] })
40 |
41 | // LINK ..\utils\promptQuestions.ts#Q4-usingGithubActions
42 | await answer(io, { text: 'No' })
43 |
44 | // Skip the rest of the questions
45 | await skipQuestions(-1, io)
46 | },
47 | async sample4() {
48 | // LINK ..\utils\promptQuestions.ts#Q1-generatorName
49 | await answer(io, { text: 'my-gen --skip-check' })
50 |
51 | // Skip the rest of the questions
52 | await skipQuestions(2, io)
53 | await skipQuestions(-1, io, true)
54 | },
55 | }
56 |
57 | Object.keys(sampleAnswers).map((answer) => {
58 | test(`should get proper answers object when answering using ${answer}`, async () => {
59 | setTimeout(
60 | () => sampleAnswers[answer as keyof typeof sampleAnswers]().then(),
61 | 5,
62 | )
63 | const answers = await promptQuestions()
64 |
65 | expect(answers).toMatchSnapshot()
66 | })
67 | })
68 |
--------------------------------------------------------------------------------
/packages/create-prisma-generator/src/__tests__/if-dir-exists.test.ts:
--------------------------------------------------------------------------------
1 | import { MockSTDIN, stdin } from 'mock-stdin'
2 | import { spyConsole } from './__helpers__/spyConsole'
3 | import { main } from '../index'
4 | import { answer } from './__helpers__/answer'
5 | import { skipQuestions } from './__helpers__/skipQuestions'
6 | import path from 'path'
7 | import { validGenName } from './constants/valid-prisma-gen-name'
8 |
9 | // Mock stdin to send keystrokes to the CLI
10 | let io: MockSTDIN
11 | beforeAll(() => {
12 | const actualFS = jest.requireActual('fs')
13 |
14 | // Check if validname directory exists before running the Tests
15 | if (!actualFS.existsSync(path.join(__dirname, validGenName))) {
16 | throw new Error(
17 | `'${validGenName}' directory should be placed under:\n ${__dirname}`,
18 | )
19 | }
20 | io = stdin()
21 | })
22 | afterAll(() => io.restore())
23 |
24 | let spy = spyConsole()
25 |
26 | // Mocking child_process to avoid setting up
27 | // the project if the test failed
28 | jest.mock('child_process')
29 |
30 | test("shouldn't start setting up the project if the directory already exists", async () => {
31 | const sendKeystrokes = async () => {
32 | await answer(io, { text: validGenName })
33 | // Skip the rest of the questions
34 | await skipQuestions(-1, io)
35 | }
36 |
37 | // Change working dir to "/__tests__"
38 | process.chdir(__dirname)
39 |
40 | setTimeout(() => sendKeystrokes().then(), 5)
41 |
42 | await main()
43 | expect(spy.console.mock.calls.toString()).toContain(
44 | 'directory already exists',
45 | )
46 | })
47 |
--------------------------------------------------------------------------------
/packages/create-prisma-generator/src/__tests__/invalid-generator-names.test.ts:
--------------------------------------------------------------------------------
1 | import { MockSTDIN, stdin } from 'mock-stdin'
2 | import { promptQuestions } from '../utils/inquirer/promptQuestions'
3 | import { validGenName } from './constants/valid-prisma-gen-name'
4 | import { answer } from './__helpers__/answer'
5 | import { clearInput } from './__helpers__/clearInput'
6 | import { delay } from './__helpers__/delay'
7 | import { skipQuestions } from './__helpers__/skipQuestions'
8 | import { spyConsole } from './__helpers__/spyConsole'
9 |
10 | // Mock stdin to send keystrokes to the CLI
11 | let io: MockSTDIN
12 | beforeEach(() => (io = stdin()))
13 | afterEach(() => io.restore())
14 |
15 | let spy = spyConsole()
16 |
17 | const invalidGeneratorNames = {
18 | '#invalid@pkgname': { errorMsg: "isn't a valid package name!" },
19 | 'PRISMA-GENERATOR-NEW': { errorMsg: "isn't a valid package name!" },
20 | prisma_generator_new: { errorMsg: 'prisma-generator-' },
21 | 'my-gen': { errorMsg: 'prisma-generator-' },
22 | 'prisma-generator': { errorMsg: 'prisma-generator-' },
23 | 'prisma-generator-': { errorMsg: 'prisma-generator-' },
24 | }
25 |
26 | Object.keys(invalidGeneratorNames).forEach((invalidPkgName) => {
27 | test(`shouldn't accept ${invalidPkgName} as a generator name`, async () => {
28 | const sendKeystrokes = async () => {
29 | await answer(io, { text: invalidPkgName })
30 |
31 | await delay(10)
32 |
33 | clearInput(io, invalidPkgName.length)
34 | await answer(io, { text: validGenName })
35 |
36 | // Skip the rest of the questions
37 | await skipQuestions(-1, io)
38 | }
39 | setTimeout(() => sendKeystrokes().then(), 5)
40 | await promptQuestions()
41 |
42 | expect(spy.console.mock.calls.toString()).toContain(
43 | invalidGeneratorNames[
44 | invalidPkgName as keyof typeof invalidGeneratorNames
45 | ].errorMsg,
46 | )
47 | })
48 | })
49 |
--------------------------------------------------------------------------------
/packages/create-prisma-generator/src/__tests__/prisma-generator-data-graph/why.txt:
--------------------------------------------------------------------------------
1 | This's for checking wether the CLI would overwrite this directory
2 | or throw an error when setting up a new project.
--------------------------------------------------------------------------------
/packages/create-prisma-generator/src/__tests__/skip-prisma-naming-convention.test.ts:
--------------------------------------------------------------------------------
1 | import { MockSTDIN, stdin } from 'mock-stdin'
2 | import { promptQuestions } from '../utils/inquirer/promptQuestions'
3 | import { validGenName } from './constants/valid-prisma-gen-name'
4 | import { answer } from './__helpers__/answer'
5 | import { clearInput } from './__helpers__/clearInput'
6 | import { delay } from './__helpers__/delay'
7 | import { skipQuestions } from './__helpers__/skipQuestions'
8 | import { spyConsole } from './__helpers__/spyConsole'
9 |
10 | // Mock stdin to send keystrokes to the CLI
11 | let io: MockSTDIN
12 | beforeEach(() => (io = stdin()))
13 | afterEach(() => io.restore())
14 |
15 | let spy = spyConsole()
16 |
17 | const skipFlag = '--skip-check'
18 |
19 | const validNonPrismaConventionGeneratorNames = [
20 | 'new-prisma_generator',
21 | 'my-generator',
22 | '@org/my-generator',
23 | ]
24 |
25 | const errorMessages = [
26 | 'prisma-generator-',
27 | "isn't a valid package name!",
28 | ]
29 |
30 | validNonPrismaConventionGeneratorNames.forEach((invalidPkgName) => {
31 | test(`should skip prisma's naming convention when ${skipFlag} flag is used`, async () => {
32 | const sendKeystrokes = async () => {
33 | await answer(io, { text: invalidPkgName + ` ${skipFlag}` })
34 |
35 | await delay(10)
36 |
37 | clearInput(io, invalidPkgName.length)
38 | await answer(io, { text: validGenName })
39 |
40 | // Skip the rest of the questions
41 | await skipQuestions(-1, io)
42 | }
43 | setTimeout(() => sendKeystrokes().then(), 5)
44 | await promptQuestions()
45 |
46 | const numberOfMatchingErrors = errorMessages.filter(
47 | (x) => spy.console.mock.calls.toString().indexOf(x) > -1,
48 | ).length
49 | expect(numberOfMatchingErrors).toEqual(0)
50 | })
51 | })
52 |
--------------------------------------------------------------------------------
/packages/create-prisma-generator/src/__tests__/types/MockedFS.ts:
--------------------------------------------------------------------------------
1 | import fs from 'fs'
2 | import { Volume } from 'memfs'
3 |
4 | type fs = typeof fs
5 | type Volume = typeof Volume
6 | export interface MockedFS extends Volume {
7 | actual: fs
8 | InitialFSJSONSetup: any
9 | reset: () => void
10 | toJSON: () => any
11 | goBackToInitialSetup: () => void
12 | }
13 |
--------------------------------------------------------------------------------
/packages/create-prisma-generator/src/__tests__/valid-generator-names.test.ts:
--------------------------------------------------------------------------------
1 | import { MockSTDIN, stdin } from 'mock-stdin'
2 | import { promptQuestions } from '../utils/inquirer/promptQuestions'
3 | import { validGenName } from './constants/valid-prisma-gen-name'
4 | import { answer } from './__helpers__/answer'
5 | import { clearInput } from './__helpers__/clearInput'
6 | import { delay } from './__helpers__/delay'
7 | import { skipQuestions } from './__helpers__/skipQuestions'
8 | import { spyConsole } from './__helpers__/spyConsole'
9 |
10 | // Mock stdin to send keystrokes to the CLI
11 | let io: MockSTDIN
12 | beforeEach(() => (io = stdin()))
13 | afterEach(() => io.restore())
14 |
15 | let spy = spyConsole()
16 |
17 | const validGeneratorNames = [validGenName, `@org/${validGenName}`]
18 | const errorMessages = [
19 | 'prisma-generator-',
20 | "isn't a valid package name!",
21 | ]
22 |
23 | validGeneratorNames.forEach((invalidPkgName) => {
24 | test(`shouldn't accept ${invalidPkgName} as a generator name`, async () => {
25 | const sendKeystrokes = async () => {
26 | await answer(io, { text: invalidPkgName })
27 |
28 | await delay(10)
29 |
30 | clearInput(io, invalidPkgName.length)
31 | await answer(io, { text: validGenName })
32 |
33 | // Skip the rest of the questions
34 | await skipQuestions(-1, io)
35 | }
36 | setTimeout(() => sendKeystrokes().then(), 5)
37 | await promptQuestions()
38 |
39 | const numberOfMatchingErrors = errorMessages.filter(
40 | (x) => spy.console.mock.calls.toString().indexOf(x) > -1,
41 | ).length
42 | expect(numberOfMatchingErrors).toEqual(0)
43 | })
44 | })
45 |
--------------------------------------------------------------------------------
/packages/create-prisma-generator/src/bin.ts:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | import './index'
3 |
--------------------------------------------------------------------------------
/packages/create-prisma-generator/src/config/husky-commit-msg-hook.ts:
--------------------------------------------------------------------------------
1 | // Husky adds hooks the same way for every machine
2 | // so It's not a problem including a snapshot
3 | // of the generated hook
4 | // source: https://github.com/typicode/husky/blob/68b410341c3e2de320a16541d18ca80f07faa0d5/src/index.ts#L56-L70
5 | export const huskyCommitMsgHook = `#!/bin/sh
6 | . "$(dirname "$0")/_/husky.sh"
7 |
8 | npx --no-install commitlint --edit "$1"
9 | `
10 |
--------------------------------------------------------------------------------
/packages/create-prisma-generator/src/config/pnpm-workspace.ts:
--------------------------------------------------------------------------------
1 | export const pnpmWorkspaceYML = `packages:
2 | # all packages in subdirs of packages/
3 | - 'packages/**'
4 | # exclude packages that are inside test directories
5 | - '!**/test/**'`
6 |
--------------------------------------------------------------------------------
/packages/create-prisma-generator/src/config/yarn-workspace.ts:
--------------------------------------------------------------------------------
1 | export const yarnWorkspaceJSON = JSON.stringify(
2 | {
3 | private: true,
4 | workspaces: ['packages/*'],
5 | },
6 | null,
7 | 2,
8 | )
9 |
--------------------------------------------------------------------------------
/packages/create-prisma-generator/src/index.ts:
--------------------------------------------------------------------------------
1 | import { execSync, spawnSync } from 'child_process'
2 | import chalk from 'chalk'
3 | import fs from 'fs'
4 | import path from 'path'
5 | import { runBlockingCommand } from './utils/getRunBlockingCommand'
6 | import { promptQuestions } from './utils/inquirer/promptQuestions'
7 | import { replacePlaceholders } from './utils/replacePlaceholders'
8 | import { yarnWorkspaceJSON } from './config/yarn-workspace'
9 | import { pnpmWorkspaceYML } from './config/pnpm-workspace'
10 | import { huskyCommitMsgHook } from './config/husky-commit-msg-hook'
11 | import { CLIs } from './tinyClis'
12 | import { getInstallCommand } from './utils/getInstallCommands'
13 | import { transformScopedName } from './utils/transformScopedName'
14 | import { getPkgManagerLockFile } from './utils/getPkgManagerLockFile'
15 | import { getRunWithFrozenLockCMD } from './utils/runWithFrozenLockCMD'
16 |
17 | export const main = async () => {
18 | const answers = await promptQuestions()
19 |
20 | const pkgName = answers.generatorName
21 | const dirName = transformScopedName(pkgName)
22 |
23 | // Reused variables
24 | const projectWorkdir = path.join(process.cwd(), dirName)
25 | const pkgManager = answers.packageManager
26 | const usingWorkspaces = answers.usageTemplate
27 | const workingDir = `cd ${dirName}`
28 | const generatorLocation = usingWorkspaces
29 | ? `${workingDir}/packages/generator`
30 | : workingDir
31 |
32 | // Validate if folder with the same name doesn't exist
33 | if (fs.existsSync(projectWorkdir)) {
34 | console.log(chalk.red(`${dirName} directory already exists!`))
35 | return
36 | }
37 |
38 | console.log(
39 | '\nCreating a new Prisma generator in',
40 | chalk.cyan(path.join(projectWorkdir)) + '.\n',
41 | )
42 |
43 | // Initialize git
44 | //! This needs to be at the top cause `husky` won't run
45 | //! if there was no repository
46 | fs.mkdirSync(projectWorkdir, { recursive: true })
47 | execSync(`${workingDir} && git init`)
48 | console.log(chalk.cyan('\nInitialized a git repository.\n'))
49 |
50 | // Adding default root configs
51 | const templateName = 'root default configs'
52 | runBlockingCommand(templateName, CLIs.rootConfigs(dirName))
53 |
54 | if (answers.usageTemplate) {
55 | const templateName = 'Usage Template'
56 | runBlockingCommand(templateName, CLIs.usageTemplate(`${dirName}/packages`))
57 | }
58 |
59 | if (answers.typescript) {
60 | const templateName = 'Typescript Template'
61 | const outputLocation = usingWorkspaces
62 | ? `${dirName}/packages/generator`
63 | : dirName
64 | runBlockingCommand(templateName, CLIs.typescriptTemplate(outputLocation))
65 | }
66 |
67 | // AKA: Javascript
68 | if (!answers.typescript) {
69 | const templateName = 'Javascript Template'
70 | const outputLocation = usingWorkspaces
71 | ? `${dirName}/packages/generator`
72 | : dirName
73 |
74 | runBlockingCommand(templateName, CLIs.javascriptTemplate(outputLocation))
75 | }
76 |
77 | if (answers.githubActions) {
78 | const templateName = 'Github actions Template'
79 | runBlockingCommand(templateName, CLIs.githubActionsTemplate(dirName))
80 |
81 | // Replace placeholders
82 | const workflowPath = path.join(projectWorkdir, '.github/workflows/CI.yml')
83 |
84 | let pkgManagerLockFile = getPkgManagerLockFile(pkgManager)
85 | let runWithFrozenLockCMD = getRunWithFrozenLockCMD(pkgManager)
86 | let runCMD = `${pkgManager} run`
87 |
88 | fs.writeFileSync(
89 | workflowPath,
90 | fs
91 | .readFileSync(workflowPath, 'utf-8')
92 | .replace(
93 | /\$WORKING_DIR/g,
94 | usingWorkspaces ? './packages/generator' : '.',
95 | )
96 | .replace(
97 | /\$SETUP_PNPM/,
98 | pkgManager == 'pnpm'
99 | ? `- name: Setup PNPM
100 | uses: pnpm/action-setup@v2.0.1
101 | with:
102 | version: 6.23.6`
103 | : '',
104 | )
105 | .replace(/\$PKG_MANAGER_LOCK/g, pkgManagerLockFile)
106 | .replace(/\$INSTALL_CMD_WITH_FROZEN_LOCK/g, runWithFrozenLockCMD)
107 | .replace(/\$PKG_MANAGER_RUN_CMD/g, runCMD),
108 | )
109 | const dependabotPath = path.join(projectWorkdir, '.github/dependabot.yml')
110 | fs.writeFileSync(
111 | dependabotPath,
112 | fs
113 | .readFileSync(dependabotPath, 'utf-8')
114 | .replace(
115 | /\$GENERATOR_DIR/g,
116 | usingWorkspaces ? '/packages/generator/' : '/',
117 | ),
118 | )
119 | }
120 |
121 | // Replace placeholders like $PACKAGE_NAME with actual pkgName
122 | // In places where It's needed
123 | replacePlaceholders(answers, dirName)
124 |
125 | // Setup Workspaces based on pkg manager
126 | if (usingWorkspaces) {
127 | if (pkgManager === 'yarn' || pkgManager === 'npm') {
128 | fs.writeFileSync(
129 | path.join(projectWorkdir, 'package.json'),
130 | yarnWorkspaceJSON,
131 | )
132 | } else if (pkgManager === 'pnpm') {
133 | fs.writeFileSync(
134 | path.join(projectWorkdir, 'pnpm-workspace.yaml'),
135 | pnpmWorkspaceYML,
136 | )
137 | }
138 |
139 | // Simulating a dist folder
140 | // to make pnpm happy :)
141 | fs.mkdirSync(path.join(projectWorkdir, 'packages/generator/dist'))
142 | fs.writeFileSync(
143 | path.join(projectWorkdir, 'packages/generator/dist/bin.js'),
144 | '',
145 | )
146 |
147 | console.log(chalk.cyan(`${pkgManager} Workspace`), 'configured correctly\n')
148 | }
149 |
150 | //! Should be after initializing the workspace
151 | //! to prevent overwritting the package.json
152 | //! at the root and merge it instead
153 | if (answers.semanticRelease) {
154 | const templateName = 'Automatic Semantic Release'
155 | const workspaceFlag = usingWorkspaces ? 'workspace' : ''
156 | runBlockingCommand(
157 | templateName,
158 | CLIs.setupSemanticRelease(dirName, workspaceFlag),
159 | 'Configuring',
160 | )
161 | }
162 |
163 | console.log(chalk.cyan(`Installing dependencies using ${pkgManager}\n`))
164 |
165 | // Install packages
166 | spawnSync(getInstallCommand(pkgManager), {
167 | shell: true,
168 | stdio: 'inherit',
169 | cwd: projectWorkdir,
170 | })
171 |
172 | //! Post-install
173 |
174 | // Build the generator package to start
175 | // testing the generator output
176 | const buildCommand = `${pkgManager === 'npm' ? 'npm run' : pkgManager} build`
177 | runBlockingCommand(
178 | 'Generator',
179 | `${generatorLocation} && ${buildCommand}`,
180 | 'Building',
181 | )
182 |
183 | // Switch to 'main' and Commit files
184 | execSync(
185 | `${workingDir} && git checkout -b main && git add . && git commit -m"init"`,
186 | )
187 | console.log(chalk.cyan('Created git commit.\n'))
188 |
189 | // Add commit-msg husky hook to lint commits
190 | // using commitlint before they are created
191 | //! must be added after initilizing a git repo
192 | //! and after commiting `init` to skip validation
193 | if (answers.semanticRelease) {
194 | fs.mkdirSync(path.join(projectWorkdir, './.husky'), {
195 | recursive: true,
196 | })
197 | fs.writeFileSync(
198 | path.join(projectWorkdir, './.husky/commit-msg'),
199 | huskyCommitMsgHook,
200 | )
201 | execSync(
202 | `${workingDir} && git add . && git commit -m"feat: added husky for safety commit-msg"`,
203 | )
204 | }
205 |
206 | // Success Messages
207 | console.log(chalk.green(`Success!`), `Created ${projectWorkdir}`)
208 | console.log(`We suggest that you begin by typing:\n`)
209 | console.log(chalk.cyan('cd'), dirName)
210 | console.log(chalk.cyan('code .'))
211 | console.log(`\nStart Generating ;)`)
212 |
213 | // return answers cause It's useful for tests
214 | // to verify the behavior based on the answers
215 | return answers
216 | }
217 | main()
218 |
--------------------------------------------------------------------------------
/packages/create-prisma-generator/src/tinyClis.ts:
--------------------------------------------------------------------------------
1 | // No need to attach @latest at the end of the package name cause
2 | // npx will always execute the latest available version in the NPM registry
3 | export const CLIs = {
4 | typescriptTemplate(path: string) {
5 | return `npx @cpg-cli/template-typescript ${path}`
6 | },
7 | rootConfigs(path: string) {
8 | return `npx @cpg-cli/root-configs ${path}`
9 | },
10 | usageTemplate(path: string) {
11 | return `npx @cpg-cli/template-gen-usage ${path}`
12 | },
13 | javascriptTemplate(path: string) {
14 | return `npx @cpg-cli/template ${path}`
15 | },
16 | githubActionsTemplate(path: string) {
17 | return `npx @cpg-cli/github-actions ${path}`
18 | },
19 | setupSemanticRelease(path: string, workspaceFlag: string) {
20 | return `npx @cpg-cli/semantic-releases ${path} ${workspaceFlag}`
21 | },
22 | }
23 |
--------------------------------------------------------------------------------
/packages/create-prisma-generator/src/types/answers.ts:
--------------------------------------------------------------------------------
1 | export interface Answers {
2 | generatorName: string
3 | typescript: boolean
4 | packageManager: 'yarn' | 'npm' | 'pnpm'
5 | githubActions: boolean
6 | semanticRelease: boolean
7 | usageTemplate: boolean
8 | }
9 |
--------------------------------------------------------------------------------
/packages/create-prisma-generator/src/utils/getInstallCommands.ts:
--------------------------------------------------------------------------------
1 | export const getInstallCommand = (pkgManager: 'npm' | 'yarn' | 'pnpm') => {
2 | let installCommand: string
3 | switch (pkgManager) {
4 | case 'npm':
5 | installCommand = 'npm i'
6 | break
7 | case 'yarn':
8 | installCommand = 'yarn'
9 | break
10 | case 'pnpm':
11 | installCommand = 'pnpm i'
12 | break
13 | }
14 |
15 | return installCommand
16 | }
17 |
--------------------------------------------------------------------------------
/packages/create-prisma-generator/src/utils/getPkgManagerLockFile.ts:
--------------------------------------------------------------------------------
1 | export const getPkgManagerLockFile = (
2 | pkgManager: 'npm' | 'yarn' | 'pnpm',
3 | ): string => {
4 | let pkgManagerLockFile
5 |
6 | switch (pkgManager) {
7 | case 'npm':
8 | pkgManagerLockFile = 'package-lock.json'
9 | break
10 | case 'yarn':
11 | pkgManagerLockFile = 'yarn.lock'
12 | break
13 | case 'pnpm':
14 | pkgManagerLockFile = 'pnpm-lock.yaml'
15 | break
16 | }
17 |
18 | return pkgManagerLockFile
19 | }
20 |
--------------------------------------------------------------------------------
/packages/create-prisma-generator/src/utils/getRunBlockingCommand.ts:
--------------------------------------------------------------------------------
1 | import { execSync } from 'child_process'
2 | import chalk from 'chalk'
3 |
4 | export const runBlockingCommand = (
5 | name: string,
6 | command: string,
7 | type: 'Loading' | 'Building' | 'Configuring' = 'Loading',
8 | ) => {
9 | console.log(chalk.cyan(`${type} ${name}...`))
10 | execSync(command)
11 |
12 | let successMsg
13 | switch (type) {
14 | case 'Loading':
15 | successMsg = 'Loaded'
16 | break
17 | case 'Building':
18 | successMsg = 'Built'
19 | break
20 | case 'Configuring':
21 | successMsg = 'Configured'
22 | break
23 | }
24 | console.log(chalk.green(`${name} ${successMsg} Successfully!\n`))
25 | }
26 |
--------------------------------------------------------------------------------
/packages/create-prisma-generator/src/utils/inquirer/filters/value.ts:
--------------------------------------------------------------------------------
1 | import { flags } from '../flags'
2 |
3 | export const filterValue = (value: any) => {
4 | if (typeof value !== 'string') {
5 | return value
6 | }
7 |
8 | let filteredPkgName = value
9 |
10 | Object.keys(flags).forEach((flag) => {
11 | filteredPkgName = filteredPkgName
12 | .replace(flags[flag as keyof typeof flags], '')
13 | .trim()
14 | })
15 |
16 | return filteredPkgName
17 | }
18 |
--------------------------------------------------------------------------------
/packages/create-prisma-generator/src/utils/inquirer/flags.ts:
--------------------------------------------------------------------------------
1 | export const flags = { skipPrismaNamingConventionFlag: '--skip-check' }
2 |
--------------------------------------------------------------------------------
/packages/create-prisma-generator/src/utils/inquirer/promptQuestions.ts:
--------------------------------------------------------------------------------
1 | import inquirer from 'inquirer'
2 | import { Answers } from '../../types/answers'
3 | import { filterValue } from './filters/value'
4 | import { validateGeneratorName } from './validations/generatorName'
5 |
6 | // ANCHOR[id=questions]
7 | export const questions: inquirer.QuestionCollection[] = [
8 | // ANCHOR[id=Q1-generatorName]
9 | {
10 | type: 'input',
11 | name: 'generatorName',
12 | message: "what's your generator name",
13 | // Validate Package Name
14 | validate(pkgName: string) {
15 | return validateGeneratorName(pkgName)
16 | },
17 | },
18 | // ANCHOR[id=Q2-usingTypescript]
19 | {
20 | type: 'confirm',
21 | name: 'typescript',
22 | message: 'do you want to use Typescript',
23 | },
24 | // ANCHOR[id=Q3-selectPkgManager]
25 | {
26 | type: 'list',
27 | name: 'packageManager',
28 | message: 'which package manager do you want to use',
29 | choices: ['yarn', 'pnpm', 'npm'],
30 | default: 'pnpm',
31 | },
32 | // ANCHOR[id=Q4-usingGithubActions]
33 | {
34 | type: 'confirm',
35 | name: 'githubActions',
36 | message: 'automate publishing the generator with Github Actions',
37 | },
38 | // ANCHOR[id=Q5-enableSemanticRelease]
39 | {
40 | type: 'confirm',
41 | name: 'semanticRelease',
42 | message: '(Github Actions) setup automatic semantic release',
43 | when: (answers: Answers) => {
44 | return answers.githubActions
45 | },
46 | },
47 | // ANCHOR[id=Q5-setupWorkspaceWithUsage]
48 | {
49 | type: 'confirm',
50 | name: 'usageTemplate',
51 | message: 'setup workspace for testing the generator',
52 | },
53 | ]
54 |
55 | export const promptQuestions = async (): Promise => {
56 | const answers = (await inquirer.prompt(questions)) as Answers
57 |
58 | Object.keys(answers).forEach((e) => {
59 | const value = answers[e as keyof typeof answers]
60 | ;(answers as any)[e] = filterValue(value)
61 | })
62 |
63 | return answers
64 | }
65 |
--------------------------------------------------------------------------------
/packages/create-prisma-generator/src/utils/inquirer/validations/generatorName.ts:
--------------------------------------------------------------------------------
1 | import validatePkgName from 'validate-npm-package-name'
2 | import chalk from 'chalk'
3 | import { flags } from '../flags'
4 |
5 | export const validateGeneratorName = (pkgName: string) => {
6 | const validPkgName = validatePkgName(
7 | pkgName.replace(flags.skipPrismaNamingConventionFlag, '').trim(),
8 | )
9 |
10 | if (!validPkgName.validForNewPackages) {
11 | console.log(chalk.red(`\n"${pkgName}" isn't a valid package name!`))
12 | validPkgName.errors?.forEach((e) => console.log(chalk.cyan(e)))
13 | validPkgName.warnings?.forEach((e) => console.log(chalk.yellow(e)))
14 | return false
15 | } else {
16 | const sanitizedPkgName = pkgName.trim()
17 | const namingConvention = 'prisma-generator-'
18 | const org = sanitizedPkgName.startsWith('@')
19 | ? sanitizedPkgName.split('/')[0]
20 | : null
21 | const skipCheck =
22 | sanitizedPkgName.trim().split(' ')[1] ===
23 | flags.skipPrismaNamingConventionFlag
24 |
25 | if (!skipCheck) {
26 | if (
27 | // This should be `.includes` and not `.startsWith`
28 | // to allow for scoped packages like @org/..
29 | !sanitizedPkgName.includes(namingConvention) ||
30 | // Add 1 to ensure he typed something after the naming convention
31 | sanitizedPkgName.length < namingConvention.length + 1
32 | ) {
33 | if (org) {
34 | console.log(
35 | chalk.cyan(
36 | `\nPrisma recommends you to use this naming convention:\n`,
37 | ),
38 | chalk.yellow(`${org}/${namingConvention}\n`),
39 | )
40 | } else {
41 | console.log(
42 | chalk.cyan(
43 | `\n\nPrisma recommends you to use this naming convention:\n`,
44 | ),
45 | chalk.yellow(`${namingConvention}`),
46 | )
47 | }
48 | console.log(
49 | chalk.grey(
50 | `use the \`${flags.skipPrismaNamingConventionFlag}\` flag to skip prisma's recommendation.\n`,
51 | ),
52 | )
53 | return false
54 | }
55 | }
56 |
57 | return true
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/packages/create-prisma-generator/src/utils/replacePlaceholders.ts:
--------------------------------------------------------------------------------
1 | import fs from 'fs'
2 | import path from 'path'
3 | import { Answers } from '../types/answers'
4 |
5 | export const replacePlaceholders = (answers: Answers, pkgName: string) => {
6 | let filesContainingPkgName = [
7 | // If generator template is in the workspace
8 | './README.md',
9 | './packages/generator/package.json',
10 | './packages/generator/README.md',
11 | './packages/generator/src/constants.js',
12 | './packages/generator/src/constants.ts',
13 | './packages/usage/package.json',
14 | './packages/usage/prisma/schema.prisma',
15 | // If generator template is in the root
16 | './package.json',
17 | './README.md',
18 | './src/constants.ts',
19 | './src/constants.js',
20 | ]
21 |
22 | filesContainingPkgName.forEach((filePath) => {
23 | const fullPath = path.join(process.cwd(), pkgName, filePath)
24 | if (!fs.existsSync(fullPath)) {
25 | filesContainingPkgName = filesContainingPkgName.filter(
26 | (e) => e != fullPath,
27 | )
28 | return
29 | }
30 |
31 | const file = fs.readFileSync(fullPath, { encoding: 'utf-8' })
32 | fs.writeFileSync(
33 | fullPath,
34 | file.replace(/\$PACKAGE_NAME/g, pkgName.toLowerCase()),
35 | )
36 | })
37 |
38 | // Replace the dependencie placeholder with generator version
39 | const packageJSONPath = path.join(
40 | process.cwd(),
41 | pkgName,
42 | './packages/usage/package.json',
43 | )
44 | const packageJSONFileExists = fs.existsSync(packageJSONPath)
45 | if (packageJSONFileExists) {
46 | const packageJSONFile = fs.readFileSync(packageJSONPath, {
47 | encoding: 'utf-8',
48 | })
49 | const packageVersion =
50 | answers.packageManager === 'yarn' || answers.packageManager === 'npm'
51 | ? '1.0.0'
52 | : 'workspace:*'
53 | fs.writeFileSync(
54 | packageJSONPath,
55 | packageJSONFile.replace(/\$PACKAGE_VERSION/g, packageVersion),
56 | )
57 | }
58 |
59 | // Replace generator provider placeholder with actual provider
60 | const prismaSchemaPath = path.join(
61 | process.cwd(),
62 | pkgName,
63 | './packages/usage/prisma/schema.prisma',
64 | )
65 | const prismaSchemaFileExists = fs.existsSync(prismaSchemaPath)
66 | if (prismaSchemaFileExists) {
67 | const prismaSchemaFile = fs.readFileSync(prismaSchemaPath, {
68 | encoding: 'utf-8',
69 | })
70 | const prismaProvider =
71 | answers.packageManager === 'yarn' || answers.packageManager === 'npm'
72 | ? `node ../../node_modules/${pkgName}`
73 | : `npx ${pkgName}`
74 |
75 | fs.writeFileSync(
76 | prismaSchemaPath,
77 | prismaSchemaFile.replace(/\$CUSTOM_GENERATOR_PROVIDER/g, prismaProvider),
78 | )
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/packages/create-prisma-generator/src/utils/runWithFrozenLockCMD.ts:
--------------------------------------------------------------------------------
1 | export const getRunWithFrozenLockCMD = (
2 | pkgManager: 'npm' | 'yarn' | 'pnpm',
3 | ): string => {
4 | let installWithFronzenLockCMD
5 |
6 | switch (pkgManager) {
7 | case 'npm':
8 | installWithFronzenLockCMD = 'npm ci'
9 | break
10 | case 'yarn':
11 | installWithFronzenLockCMD = 'yarn install --frozen-lockfile'
12 | break
13 | case 'pnpm':
14 | installWithFronzenLockCMD = 'pnpm i --frozen-lockfile'
15 | break
16 | }
17 |
18 | return installWithFronzenLockCMD
19 | }
20 |
--------------------------------------------------------------------------------
/packages/create-prisma-generator/src/utils/transformScopedName.ts:
--------------------------------------------------------------------------------
1 | export const transformScopedName = (pkgName: string) => {
2 | return pkgName.startsWith('@')
3 | ? pkgName.replace('@', '').replace('/', '-')
4 | : pkgName
5 | }
6 |
--------------------------------------------------------------------------------
/packages/create-prisma-generator/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "./dist",
5 | "rootDir": "./src"
6 | },
7 | "include": ["src/**/*"]
8 | }
9 |
--------------------------------------------------------------------------------
/pnpm-workspace.yaml:
--------------------------------------------------------------------------------
1 | packages:
2 | # all packages in subdirs of packages/ and dev.to/
3 | - 'packages/**'
4 | - dev.to
5 | # exclude packages that are inside template directories
6 | - '!**/template/**'
7 |
--------------------------------------------------------------------------------
/scripts/addNewBlog.ts:
--------------------------------------------------------------------------------
1 | import { kebabize } from './utils/kebabize'
2 | import { logger } from './utils/logger'
3 | import fs from 'fs'
4 | import path from 'path'
5 |
6 | const blogName = kebabize(process.argv[2] || '')
7 |
8 | if (!blogName) {
9 | logger.error('Please Provide a name for the blog')
10 | logger.info('Like this:')
11 | logger.info('pnpm new-blog building-prisma-generator-together')
12 | } else {
13 | const blogPath = path.join(process.cwd(), 'dev.to/blogs', blogName)
14 | const templateSource = path.join(__dirname, 'templates/addNewBlog')
15 |
16 | if (fs.existsSync(blogPath)) {
17 | logger.error('Blog already Exists!')
18 | } else {
19 | fs.mkdirSync(blogPath)
20 | fs.cpSync(templateSource, blogPath, {
21 | recursive: true,
22 | })
23 | const contentPath = path.join(blogPath, 'content.md')
24 | fs.writeFileSync(
25 | contentPath,
26 | fs.readFileSync(contentPath, 'utf-8').replace('$BLOG_NAME', blogName),
27 | )
28 |
29 | logger.success(`Your blog is ready at 'dev.to/blogs/${blogName}'`)
30 | console.log(`Start blogging ;)`)
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/scripts/addNewTemplate.ts:
--------------------------------------------------------------------------------
1 | import fs from 'fs'
2 | import path from 'path'
3 | import { logger } from './utils/logger'
4 | import { spawn } from 'child_process'
5 | import { kebabize } from './utils/kebabize'
6 |
7 | const templateName = kebabize(process.argv[2] || '')
8 |
9 | if (!templateName) {
10 | logger.error('Please Provide a name for the template')
11 | logger.info('Like this:')
12 | logger.info('pnpm new-template cpg-template-typescript')
13 | } else {
14 | if (!templateName.startsWith('cpg-')) {
15 | logger.error('Template name must start with "cpg-"')
16 | logger.info('Like this:')
17 | logger.info('pnpm new-template cpg-template-typescript')
18 | } else {
19 | const templatePath = path.join(process.cwd(), 'packages', templateName)
20 | if (fs.existsSync(templatePath)) {
21 | logger.error('Template already Exists!')
22 | } else {
23 | fs.mkdirSync(path.join(process.cwd(), 'packages', templateName))
24 | fs.mkdirSync(
25 | path.join(process.cwd(), 'packages', templateName, 'template'),
26 | )
27 | fs.writeFileSync(
28 | path.join(process.cwd(), 'packages', templateName, 'index.js'),
29 | fs.readFileSync(
30 | path.join(__dirname, 'templates/addNewTemplate/sample.template.js'),
31 | 'utf-8',
32 | ),
33 | )
34 | const binFile = `#!/usr/bin/env node
35 | require('./index')()
36 | `
37 | fs.writeFileSync(
38 | path.join(process.cwd(), 'packages', templateName, 'bin.js'),
39 | binFile,
40 | )
41 | fs.writeFileSync(
42 | path.join(process.cwd(), 'packages', templateName, '.npmignore'),
43 | fs.readFileSync(
44 | path.join(__dirname, 'templates/addNewTemplate/.npmignore'),
45 | 'utf-8',
46 | ),
47 | )
48 |
49 | fs.writeFileSync(
50 | path.join(process.cwd(), 'packages', templateName, 'package.json'),
51 | fs
52 | .readFileSync(
53 | path.join(__dirname, 'templates/addNewTemplate/package.json'),
54 | 'utf-8',
55 | )
56 | .replace('$PKG_NAME', templateName.replace('cpg-', ''))
57 | .replace('$PKG_BIN', templateName),
58 | )
59 |
60 | spawn(`cd packages/${templateName} && pnpm i`, {
61 | shell: true,
62 | stdio: 'inherit',
63 | }).on('exit', () => {
64 | const cliUsagePkgJSONPath = path.join(
65 | process.cwd(),
66 | 'packages/cli-usage',
67 | 'package.json',
68 | )
69 | const cliUsagePkgJSON = JSON.parse(
70 | fs.readFileSync(cliUsagePkgJSONPath, 'utf-8'),
71 | )
72 | cliUsagePkgJSON.devDependencies[
73 | `@cpg-cli/${templateName.replace('cpg-', '')}`
74 | ] = 'workspace:*'
75 | fs.writeFileSync(
76 | cliUsagePkgJSONPath,
77 | JSON.stringify(cliUsagePkgJSON, null, 2),
78 | )
79 |
80 | logger.success(`Your template is ready at 'packages/${templateName}'`)
81 | console.log(`Start hacking ;)`)
82 | })
83 | }
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/scripts/ci/publish.ts:
--------------------------------------------------------------------------------
1 | // Thanks for the nice guide:
2 | // https://dev.to/antongolub/you-don-t-need-semantic-release-sometimes-3k6k
3 | import { execSync } from 'child_process'
4 | import fs from 'fs'
5 | import path from 'path'
6 | import { AuthGithub } from './utils/authGithub'
7 | import { generateReleaseNotes } from './utils/genReleaseNotes'
8 | import { getNextVersion } from './utils/getNextVersion'
9 | import { githubRelease } from './utils/githubRelease'
10 | import { gitRelease } from './utils/gitRelease'
11 | import { hasPkgChanged } from './utils/hasPkgChanged'
12 | import { npmPublish } from './utils/npmPublish'
13 | import { updatePackageVersion } from './utils/updatePackageVersion'
14 |
15 | const { GIT_COMMITTER_NAME, GIT_COMMITTER_EMAIL, GITHUB_TOKEN } = process.env
16 |
17 | if (!GITHUB_TOKEN || !GIT_COMMITTER_NAME || !GIT_COMMITTER_EMAIL) {
18 | throw new Error(
19 | 'env.GITHUB_TOKEN, env.GIT_COMMITTER_NAME & env.GIT_COMMITTER_EMAIL must be set',
20 | )
21 | }
22 |
23 | // Git configuration
24 | const { repoPublicUrl, repoName } = AuthGithub()
25 | execSync("git fetch origin 'refs/tags/*:refs/tags/*'")
26 | const tags = execSync(`git tag -l --sort=-v:refname`)
27 | .toString()
28 | .split('\n')
29 | .map((tag) => tag.trim())
30 |
31 | const changesSinceLastCommit = execSync(
32 | `git diff ${execSync('git rev-parse HEAD^@')
33 | .toString()
34 | .trim()
35 | .split('\n')
36 | .at(-1)} HEAD --name-only`,
37 | )
38 | .toString()
39 | .trim()
40 | .split('\n')
41 |
42 | let isThereStagedFiles = false
43 |
44 | // Commits analysis
45 | const releaseSeverityOrder = ['major', 'minor', 'patch']
46 | const semanticRules = [
47 | { group: 'Features', releaseType: 'minor', prefixes: ['feat'] },
48 | {
49 | group: 'Fixes & improvements',
50 | releaseType: 'patch',
51 | prefixes: ['fix', 'perf', 'refactor', 'docs'],
52 | },
53 | {
54 | group: 'BREAKING CHANGES',
55 | releaseType: 'major',
56 | keywords: ['BREAKING CHANGE', 'BREAKING CHANGES'],
57 | },
58 | ]
59 |
60 | const packagesPath = path.join(process.cwd(), 'packages')
61 |
62 | fs.readdirSync(packagesPath, { withFileTypes: true })
63 | .filter((dirent) => dirent.isDirectory())
64 | .map((dirent) => {
65 | const pkgJSONPath = path.join(packagesPath, dirent.name, 'package.json')
66 | if (fs.existsSync(pkgJSONPath)) {
67 | const pkgJSON = JSON.parse(fs.readFileSync(pkgJSONPath, 'utf-8'))
68 | const pkgName = pkgJSON.name
69 | const releasePrefix = pkgName + '-v'
70 |
71 | if (!pkgJSON.private) {
72 | // Get prev release tag
73 | const lastTag = tags.find((tag) => tag.includes(releasePrefix))
74 |
75 | const commitsRange = lastTag
76 | ? `${execSync(`git rev-list -1 ${lastTag}`).toString().trim()}..HEAD`
77 | : 'HEAD'
78 |
79 | const newCommits = execSync(
80 | `git log --format=+++%s__%b__%h__%H ${commitsRange}`,
81 | )
82 | .toString()
83 | .split('+++')
84 | .filter(Boolean)
85 | .map((msg) => {
86 | const [subj, body, short, hash] = msg
87 | .split('__')
88 | .map((raw) => raw.trim())
89 | return { subj, body, short, hash }
90 | })
91 |
92 | const semanticChanges = newCommits.reduce(
93 | (acc: any[], { subj, body, short, hash }) => {
94 | semanticRules.forEach(
95 | ({ group, releaseType, prefixes, keywords }) => {
96 | // Thanks for this regex shebang:
97 | // https://gist.github.com/marcojahn/482410b728c31b221b70ea6d2c433f0c
98 | const prefixMatcher =
99 | prefixes &&
100 | new RegExp(
101 | `^(${prefixes.join(
102 | '|',
103 | )}){1}(\\([\\w-\\.]+\\))?(!)?: ([\\w ])+([\\s\\S]*)`,
104 | )
105 | const keywordsMatcher =
106 | keywords && new RegExp(`(${keywords.join('|')}):\\s(.+)`)
107 |
108 | const change =
109 | subj.match(prefixMatcher!)?.[0] ||
110 | body.match(keywordsMatcher!)?.[2]
111 |
112 | if (change) {
113 | acc.push({
114 | group,
115 | releaseType,
116 | change,
117 | subj,
118 | body,
119 | short,
120 | hash,
121 | })
122 | }
123 | },
124 | )
125 | return acc
126 | },
127 | [],
128 | )
129 |
130 | const nextReleaseType = releaseSeverityOrder.find((type) =>
131 | semanticChanges.find(({ releaseType }) => type === releaseType),
132 | )
133 | if (!nextReleaseType) {
134 | console.log(`${pkgName} - no semantic release.`)
135 | return
136 | }
137 |
138 | let nextVersion = ''
139 | console.log(`packages/${dirent.name}/`)
140 | if (
141 | lastTag
142 | ? hasPkgChanged(changesSinceLastCommit, `packages/${dirent.name}/`)
143 | : true
144 | ) {
145 | const lastVersion = execSync(`npm view ${pkgName} version`)
146 | .toString()
147 | .trim()
148 |
149 | nextVersion = getNextVersion(
150 | nextReleaseType,
151 | `${pkgName}-v${lastVersion}`,
152 | releasePrefix,
153 | )!
154 | } else {
155 | console.log(`${pkgName} didn't change --skipped`)
156 | return
157 | }
158 |
159 | const pkgCWD = pkgJSONPath.replace(`${path.sep}package.json`, '')
160 | updatePackageVersion(pkgCWD, nextVersion)
161 |
162 | const nextTag = `${pkgName}-v` + nextVersion
163 |
164 | // Generate release notes
165 | const releaseNotes = generateReleaseNotes(
166 | nextVersion,
167 | repoPublicUrl,
168 | nextTag,
169 | semanticChanges,
170 | pkgName,
171 | lastTag,
172 | )
173 |
174 | npmPublish(pkgCWD)
175 | // Add changes like package.json(s)
176 | const releaseMessage = `chore(release): ${nextTag} [skip ci]`
177 | execSync(`git add -A .`)
178 | execSync(`git tag -a ${nextTag} HEAD -m"${releaseMessage}"`)
179 | isThereStagedFiles = true
180 | githubRelease(
181 | nextTag,
182 | releaseNotes,
183 | repoName,
184 | GIT_COMMITTER_NAME,
185 | GITHUB_TOKEN,
186 | )
187 | }
188 | }
189 | })
190 |
191 | // Commit and Push all staged files
192 | if (isThereStagedFiles) {
193 | gitRelease()
194 | }
195 |
--------------------------------------------------------------------------------
/scripts/ci/utils/authGithub.ts:
--------------------------------------------------------------------------------
1 | import { execSync } from 'child_process'
2 | import { logger } from '../../utils/logger'
3 |
4 | export const AuthGithub = () => {
5 | const { GIT_COMMITTER_NAME, GIT_COMMITTER_EMAIL, GITHUB_TOKEN } = process.env
6 | const gitAuth = `${GIT_COMMITTER_NAME}:${GITHUB_TOKEN}`
7 | const originUrl = execSync(`git config --get remote.origin.url`)
8 | .toString()
9 | .trim()
10 |
11 | const [_, __, repoHost, repoName] = originUrl
12 | .replace(':', '/')
13 | .replace(/\.git/, '')
14 | .match(/.+(@|\/\/)([^/]+)\/(.+)$/) as RegExpMatchArray
15 |
16 | const repoPublicUrl = `https://${repoHost}/${repoName}`
17 | const repoAuthedUrl = `https://${gitAuth}@${repoHost}/${repoName}`
18 |
19 | execSync(`git config user.name ${GIT_COMMITTER_NAME}`)
20 | execSync(`git config user.email ${GIT_COMMITTER_EMAIL}`)
21 | execSync(`git remote set-url origin ${repoAuthedUrl}`)
22 |
23 | logger.success('Github Authenticated!')
24 |
25 | return { repoPublicUrl, repoName }
26 | }
27 |
--------------------------------------------------------------------------------
/scripts/ci/utils/genReleaseNotes.ts:
--------------------------------------------------------------------------------
1 | import { logger } from '../../utils/logger'
2 |
3 | export const generateReleaseNotes = (
4 | nextVersion: string,
5 | repoPublicUrl: string,
6 | nextTag: string,
7 | semanticChanges: any[],
8 | pkgName: string,
9 | lastTag?: string,
10 | ) => {
11 | const prettierPkgName = pkgName
12 | .replace('@cpg-cli/', '')
13 | .split('-')
14 | .map((e) => e[0].toUpperCase() + e.substring(1))
15 | .join(' ')
16 |
17 | const releaseHeader = lastTag
18 | ? `## ${prettierPkgName} v${nextVersion} 🥳`
19 | : `## ${prettierPkgName} First Release 🎉`
20 |
21 | const releaseDetails = Object.values(
22 | semanticChanges.reduce((acc, { group, change, short, hash }) => {
23 | const { commits } = acc[group] || (acc[group] = { commits: [], group })
24 | const commitRef = `* ${change} ([${short}](${repoPublicUrl}/commit/${hash}))`
25 |
26 | commits.push(commitRef)
27 |
28 | return acc
29 | }, {}),
30 | )
31 | .map(({ group, commits }: any) => `### ${group}\n${commits.join('\n')}`)
32 | .join('\n')
33 |
34 | const releaseNotes =
35 | releaseHeader +
36 | '\n' +
37 | releaseDetails +
38 | '\n\n' +
39 | `[Compare changes](${repoPublicUrl}/compare/${lastTag}...${nextTag})`
40 |
41 | logger.success('Release notes generated successfully!')
42 |
43 | return releaseNotes
44 | }
45 |
--------------------------------------------------------------------------------
/scripts/ci/utils/getNextVersion.ts:
--------------------------------------------------------------------------------
1 | export const getNextVersion = (
2 | nextReleaseType: string,
3 | lastTag: string,
4 | releasePrefix: string,
5 | ) => {
6 | if (!nextReleaseType) {
7 | return
8 | }
9 | if (!lastTag) {
10 | return '1.0.0'
11 | }
12 |
13 | const [c1, c2, c3] = lastTag.split(releasePrefix)[1].split('.')
14 | if (nextReleaseType === 'major') {
15 | return `${-~c1}.0.0`
16 | }
17 | if (nextReleaseType === 'minor') {
18 | return `${c1}.${-~c2}.0`
19 | }
20 | if (nextReleaseType === 'patch') {
21 | return `${c1}.${c2}.${-~c3}`
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/scripts/ci/utils/gitRelease.ts:
--------------------------------------------------------------------------------
1 | import { execSync } from 'child_process'
2 | import { logger } from '../../utils/logger'
3 |
4 | export const gitRelease = () => {
5 | const releaseMessage = `chore(release): sync packages versions with npm [skip ci]`
6 | execSync(`git commit --no-verify -m"${releaseMessage}"`)
7 | execSync(`git push --no-verify --follow-tags origin main`)
8 |
9 | logger.success('Pushed the updated package.json(s) and the new tags!')
10 | }
11 |
--------------------------------------------------------------------------------
/scripts/ci/utils/githubRelease.ts:
--------------------------------------------------------------------------------
1 | import { logger } from '../../utils/logger'
2 | import axios from 'axios'
3 |
4 | export const githubRelease = (
5 | tag: string,
6 | releaseNotes: string,
7 | repoName: string,
8 | GIT_COMMITTER_NAME: string,
9 | GITHUB_TOKEN: string,
10 | ) => {
11 | const releaseData = JSON.stringify({
12 | name: tag,
13 | tag_name: tag,
14 | body: releaseNotes,
15 | owner: GIT_COMMITTER_NAME,
16 | })
17 |
18 | // API Ref: https://docs.github.com/en/rest/reference/releases#create-a-release
19 | axios.post(`https://api.github.com/repos/${repoName}/releases`, releaseData, {
20 | headers: {
21 | Authorization: `Bearer ${GITHUB_TOKEN}`,
22 | 'Content-Type': 'application/json',
23 | Accept: 'application/vnd.github.v3+json',
24 | },
25 | })
26 |
27 | logger.success(`Published a new release with tag ${tag}!`)
28 | }
29 |
--------------------------------------------------------------------------------
/scripts/ci/utils/hasPkgChanged.ts:
--------------------------------------------------------------------------------
1 | import { execSync } from 'child_process'
2 |
3 | export const hasPkgChanged = (changes: string[], folder: string) => {
4 | const ignore = ['__tests__']
5 |
6 | const lastCommitMessage = execSync('git show -s --format=%s').toString()
7 |
8 | if (
9 | lastCommitMessage.includes('[force publish]') ||
10 | lastCommitMessage.includes('[force-publish]')
11 | ) {
12 | return true
13 | }
14 | return !!changes.find(
15 | (e) =>
16 | ignore.findIndex((item) => e.includes(item)) === -1 && e.includes(folder),
17 | )
18 | }
19 |
--------------------------------------------------------------------------------
/scripts/ci/utils/npmPublish.ts:
--------------------------------------------------------------------------------
1 | import { execSync } from 'child_process'
2 | import { logger } from '../../utils/logger'
3 |
4 | export const npmPublish = (cwd: string) => {
5 | execSync(`npm publish --no-git-tag-version`, { cwd })
6 |
7 | logger.success('Published the package successfully!')
8 | }
9 |
--------------------------------------------------------------------------------
/scripts/ci/utils/updatePackageVersion.ts:
--------------------------------------------------------------------------------
1 | import { execSync } from 'child_process'
2 |
3 | export const updatePackageVersion = (cwd: string, nextVersion: string) => {
4 | execSync(`npm --no-git-tag-version version ${nextVersion}`, {
5 | cwd,
6 | })
7 | }
8 |
--------------------------------------------------------------------------------
/scripts/generateTags.ts:
--------------------------------------------------------------------------------
1 | import fs from 'fs'
2 | import path from 'path'
3 | import { exec, execSync } from 'child_process'
4 |
5 | const packagesPath = path.join(process.cwd(), 'packages')
6 |
7 | const packages = fs
8 | .readdirSync(packagesPath, { withFileTypes: true })
9 | .filter((dirent) => dirent.isDirectory())
10 |
11 | packages.forEach((pkg) => {
12 | const packageJSON = JSON.parse(
13 | fs.readFileSync(path.join(packagesPath, pkg.name, 'package.json'), 'utf-8'),
14 | )
15 |
16 | if (!packageJSON.private) {
17 | exec(
18 | `npm view ${packageJSON.name} version`,
19 | function (error, stdout, stderr) {
20 | const pkgVersion = stdout.trim()
21 | execSync(`git tag ${packageJSON.name}-v${pkgVersion}`)
22 | },
23 | )
24 | }
25 | })
26 |
27 | execSync('git push --follow-tags origin main')
28 |
--------------------------------------------------------------------------------
/scripts/guideDependabot.ts:
--------------------------------------------------------------------------------
1 | import fs from 'fs'
2 | import path from 'path'
3 |
4 | const dependabotTemplate = (path: string) => ` - package-ecosystem: 'npm'
5 | directory: '${path}'
6 | schedule:
7 | interval: 'weekly'
8 | `
9 | const packagesPath = path.join(process.cwd(), 'packages')
10 | const packages = fs
11 | .readdirSync(packagesPath, { withFileTypes: true })
12 | .filter((dirent) => dirent.isDirectory())
13 |
14 | const dependabotConfig: string[] = []
15 | const dependabotYMLPath = path.join(process.cwd(), '.github/dependabot.yml')
16 | packages.forEach((pkg) => {
17 | const folders = fs
18 | .readdirSync(path.join(packagesPath, pkg.name), { withFileTypes: true })
19 | .filter((dirent) => dirent.isDirectory())
20 |
21 | if (
22 | folders.find((e) => e.name === 'template') &&
23 | // Check if the template folder contains package.json
24 | fs.existsSync(path.join(packagesPath, `${pkg.name}/template/package.json`))
25 | ) {
26 | dependabotConfig.push(
27 | dependabotTemplate('/packages/' + pkg.name + '/template'),
28 | )
29 | }
30 | })
31 |
32 | const placeholder = '# ADDED PROGRAMATICALLY'
33 | const dependabotFile = fs.readFileSync(dependabotYMLPath, 'utf-8')
34 | fs.writeFileSync(
35 | dependabotYMLPath,
36 | (dependabotFile.split(placeholder)[0] + placeholder).replace(
37 | placeholder,
38 | `${placeholder}\n` + dependabotConfig.join('\n'),
39 | ),
40 | )
41 |
--------------------------------------------------------------------------------
/scripts/removeTags.bash:
--------------------------------------------------------------------------------
1 | # Delete local tags.
2 | git tag -l | xargs git tag -d
3 | # Fetch remote tags.
4 | git fetch
5 | # Delete remote tags.
6 | git tag -l | xargs -n 1 git push --delete origin
7 | # Delete local tasg.
8 | git tag -l | xargs git tag -d
--------------------------------------------------------------------------------
/scripts/simulateDist.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs')
2 | const path = require('path')
3 |
4 | const cliPath = path.join(__dirname, '../packages/create-prisma-generator')
5 | fs.mkdirSync(path.join(cliPath, 'dist'))
6 | fs.writeFileSync(path.join(cliPath, 'dist/bin.js'), '')
7 |
--------------------------------------------------------------------------------
/scripts/templates/addNewBlog/assets/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YassinEldeeb/create-prisma-generator/f537239cdb37550ddda286336e6b4add4d268729/scripts/templates/addNewBlog/assets/.gitkeep
--------------------------------------------------------------------------------
/scripts/templates/addNewBlog/code/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YassinEldeeb/create-prisma-generator/f537239cdb37550ddda286336e6b4add4d268729/scripts/templates/addNewBlog/code/.gitkeep
--------------------------------------------------------------------------------
/scripts/templates/addNewBlog/content.md:
--------------------------------------------------------------------------------
1 | ---
2 | published: false
3 | title: ''
4 | cover_image: ''
5 | description: ''
6 | tags: typescript, javascript, prisma, generator
7 | ---
8 |
9 | This blog is hosted on [this github repo](https://github.com/YassinEldeeb/create-prisma-generator/tree/main/dev.to/blogs/$BLOG_NAME) in `content.md` file so feel free to correct me when I miss up by making a PR there.
10 |
--------------------------------------------------------------------------------
/scripts/templates/addNewTemplate/.npmignore:
--------------------------------------------------------------------------------
1 | src
2 | node_modules
3 | tsconfig.json
4 | README.md
5 | jest.config.js
6 | yarn.lock
7 | yarn-error.log
8 | pnpm-debug.log
9 | .turbo
10 | dist
--------------------------------------------------------------------------------
/scripts/templates/addNewTemplate/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@cpg-cli/$PKG_NAME",
3 | "version": "1.0.0",
4 | "main": "index.js",
5 | "license": "MIT",
6 | "bin": {
7 | "$PKG_BIN": "index.js"
8 | },
9 | "files": [
10 | "template/**/*",
11 | "index.js",
12 | "bin.js"
13 | ],
14 | "scripts": {
15 | "start": "node index.js"
16 | },
17 | "repository": {
18 | "type": "git",
19 | "url": "https://github.com/YassinEldeeb/create-prisma-generator.git"
20 | },
21 | "author": "Yassin Eldeeb ",
22 | "keywords": [
23 | "create-prisma-generator"
24 | ]
25 | }
26 |
--------------------------------------------------------------------------------
/scripts/templates/addNewTemplate/sample.template.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const fs = require('fs')
3 |
4 | const setup = () => {
5 | function copySync(from, to) {
6 | fs.mkdirSync(to, { recursive: true })
7 | fs.readdirSync(from).forEach((element) => {
8 | if (fs.lstatSync(path.join(from, element)).isFile()) {
9 | fs.copyFileSync(path.join(from, element), path.join(to, element))
10 | } else {
11 | copySync(path.join(from, element), path.join(to, element))
12 | }
13 | })
14 | }
15 |
16 | copySync(
17 | path.join(path.join(__dirname, `./template`)),
18 | path.join(process.cwd(), process.argv[2]),
19 | )
20 | }
21 |
22 | module.exports = setup
23 |
--------------------------------------------------------------------------------
/scripts/utils/kebabize.ts:
--------------------------------------------------------------------------------
1 | export const kebabize = (str: string) =>
2 | str
3 | .trim()
4 | .replace(
5 | /[A-Z]+(?![a-z])|[A-Z]/g,
6 | ($, ofs) => (ofs ? '-' : '') + $.toLowerCase(),
7 | )
8 |
--------------------------------------------------------------------------------
/scripts/utils/logger.ts:
--------------------------------------------------------------------------------
1 | const LCERROR = '\x1b[31m%s\x1b[0m' //red
2 | const LCWARN = '\x1b[33m%s\x1b[0m' //yellow
3 | const LCINFO = '\x1b[36m%s\x1b[0m' //cyan
4 | const LCSUCCESS = '\x1b[32m%s\x1b[0m' //green
5 |
6 | export const logger = class {
7 | static error(message: string, ...optionalParams: any[]) {
8 | console.error(LCERROR, message, ...optionalParams)
9 | }
10 | static warn(message: string, ...optionalParams: any[]) {
11 | console.warn(LCWARN, message, ...optionalParams)
12 | }
13 | static info(message: string, ...optionalParams: any[]) {
14 | console.info(LCINFO, message, ...optionalParams)
15 | }
16 | static success(message: string, ...optionalParams: any[]) {
17 | console.info(LCSUCCESS, message, ...optionalParams)
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2018",
4 | "module": "commonjs",
5 | "lib": ["es2018", "esnext.asynciterable"],
6 | "strict": true,
7 | "strictPropertyInitialization": false,
8 | "esModuleInterop": true,
9 | "experimentalDecorators": true,
10 | "emitDecoratorMetadata": true,
11 | "skipLibCheck": true,
12 | "forceConsistentCasingInFileNames": true,
13 | "removeComments": true,
14 | "sourceMap": true,
15 | "baseUrl": ".",
16 | "moduleResolution": "Node",
17 | "newLine": "lf"
18 | },
19 | "exclude": ["node_modules", "**/*.test.ts"]
20 | }
21 |
--------------------------------------------------------------------------------
/◭.⚙:
--------------------------------------------------------------------------------
1 | 🚀✨◭
--------------------------------------------------------------------------------