Skip to content
This repository was archived by the owner on Sep 8, 2024. It is now read-only.

Commit dc8a48b

Browse files
committed
Add exclude parameter to project creation
Major additions: - '.env.local' updated dynamically based on 'exclude' value - 'uploadthing' files split into separate folder in 'setup_assets'. Only added to project when required - Add logic to remove 'uploadthing' remote pattern from 'next.config.mjs' - Add logic to remove 'exclude' packages from 'package.json' Minor additions: - Split 'not sure where to start?' output into sections and add items to it - Add style replacement for 'shadcn/ui': 'new-york' -> 'default'
1 parent 46b7753 commit dc8a48b

File tree

16 files changed

+362
-60
lines changed

16 files changed

+362
-60
lines changed

README.md

+17-10
Original file line numberDiff line numberDiff line change
@@ -42,14 +42,18 @@ All projects are created using the same stack, consisting of the following:
4242

4343
2. Frontend
4444

45-
- [NextJS](https://nextjs.org/)
46-
- [TailwindCSS](https://tailwindcss.com/)
47-
- [TypeScript](https://www.typescriptlang.org/)
48-
- [Uploadthing](https://uploadthing.com/)
49-
- [Clerk](https://clerk.com/docs/quickstarts/nextjs)
50-
- [Stripe](https://stripe.com/docs)
51-
- [Lucide React](https://lucide.dev/)
52-
- [Shadcn UI](https://ui.shadcn.com/)
45+
a. Core:
46+
47+
- [NextJS](https://nextjs.org/)
48+
- [TailwindCSS](https://tailwindcss.com/)
49+
- [TypeScript](https://www.typescriptlang.org/)
50+
- [Lucide React](https://lucide.dev/)
51+
- [Shadcn UI](https://ui.shadcn.com/)
52+
53+
b. Optional:
54+
- [Clerk](https://clerk.com/docs/quickstarts/nextjs)
55+
- [Uploadthing](https://uploadthing.com/)
56+
- [Stripe](https://stripe.com/docs)
5357

5458
_Note: all libraries and packages are automatically installed to their latest versions when running the tool._
5559

@@ -65,10 +69,13 @@ We've also added some extra files too! You can find out more about them in our [
6569
pip install create_api_app
6670
```
6771

68-
3. Create a project:
72+
3. Create a project with a `name` and an `optional` string of `exclusion` characters for the `optional` packages.
73+
74+
Exclusion options: `c`, `u`, `s`, `cs`, `cu`, `us`, `cus`.
75+
_`c` = `Clerk`, `u` = `Uploadthing`, `s` = `Stripe`_
6976

7077
```python
71-
create-api-app <project_name>
78+
create-api-app <project_name> <exclusions>
7279
```
7380

7481
And that's it! You'll find two folders in your project, one called `frontend` (for NextJS) and another called `backend` (for FastAPI).

_project_demo/frontend/components.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"$schema": "https://ui.shadcn.com/schema.json",
3-
"style": "new-york",
3+
"style": "default",
44
"rsc": true,
55
"tsx": true,
66
"tailwind": {
@@ -14,4 +14,4 @@
1414
"components": "@/components",
1515
"utils": "@/lib/utils"
1616
}
17-
}
17+
}

create_api_app/conf/constants/__init__.py

+37
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
from enum import StrEnum
2+
3+
from create_api_app.conf.storage import Package, Packages
4+
5+
16
# Define core backend packages
27
BACKEND_CORE_PACKAGES = [
38
"fastapi",
@@ -19,3 +24,35 @@
1924
PASS = "[green]\u2713[/green]"
2025
FAIL = "[red]\u274c[/red]"
2126
PARTY = ":party_popper:"
27+
28+
29+
class ExcludeOptions(StrEnum):
30+
clerk = "c"
31+
uploadthing = "u"
32+
stripe = "s"
33+
clerk_n_stripe = "cs"
34+
clerk_n_uploadthing = "cu"
35+
uploadthing_n_stripe = "us"
36+
all = "cus"
37+
38+
39+
clerk = Package(
40+
name="clerk",
41+
letter="c",
42+
dependencies=["@clerk/nextjs"],
43+
colour="cyan",
44+
)
45+
uploadthing = Package(
46+
name="uploadthing",
47+
letter="u",
48+
dependencies=["uploadthing", "@uploadthing/react"],
49+
colour="red",
50+
)
51+
stripe = Package(
52+
name="stripe",
53+
letter="s",
54+
dependencies=["@stripe/react-stripe-js", "@stripe/stripe-js"],
55+
colour="magenta",
56+
)
57+
58+
PACKAGES = Packages([clerk, uploadthing, stripe])

create_api_app/conf/constants/content.py

+77
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
from create_api_app.conf.storage import Packages
2+
3+
14
class PoetryContent:
25
"""A helper class for retrieving content for the Poetry installation."""
36

@@ -28,3 +31,77 @@ def tailwind_font(self) -> str:
2831
" },",
2932
]
3033
)
34+
35+
@classmethod
36+
def ut_remote_pattern(cls) -> list[str]:
37+
"""Provides the `uploadthing` `remotePattern` found in the `next.config.mjs` file."""
38+
return [
39+
" {\n",
40+
' protocol: "https",\n',
41+
' hostname: "utfs.io",\n',
42+
" pathname: `/a/${process.env.NEXT_PUBLIC_UPLOADTHING_APP_ID}/*`,\n",
43+
" },\n",
44+
]
45+
46+
47+
class EnvFileContent:
48+
"""A helper class for creating the `.env.local` file."""
49+
50+
def __init__(self, packages: Packages) -> None:
51+
self.packages = packages
52+
53+
def uploadthing(self) -> list[str]:
54+
"""Returns the `Uploadthing` API key content."""
55+
return [
56+
"# Uploadthing: storing files and handling file uploading",
57+
"# https://uploadthing.com/",
58+
"UPLOADTHING_SECRET=",
59+
"NEXT_PUBLIC_UPLOADTHING_APP_ID=",
60+
"",
61+
]
62+
63+
def clerk(self) -> list[str]:
64+
"""Returns the `Clerk` API key content."""
65+
return [
66+
"# Clerk: User Authentication",
67+
"# https://clerk.com/docs/quickstarts/nextjs",
68+
"NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=",
69+
"CLERK_SECRET_KEY=",
70+
"",
71+
"NEXT_PUBLIC_CLERK_SIGN_IN_URL=/auth/sign-in",
72+
"NEXT_PUBLIC_CLERK_SIGN_UP_URL=/auth/sign-up",
73+
"NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL=/",
74+
"NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL=/",
75+
"",
76+
]
77+
78+
def stripe(self) -> list[str]:
79+
"""Returns the `Sstripe` API key content."""
80+
return [
81+
"# Stripe: user payments",
82+
"# https://stripe.com/docs",
83+
"NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=",
84+
"STRIPE_SECRET_KEY=",
85+
"STRIPE_WEBHOOK_SECRET=",
86+
"NEXT_PUBLIC_STRIPE_CLIENT_ID=",
87+
"NEXT_PUBLIC_PLATFORM_SUBSCRIPTION_PERCENT=1",
88+
"NEXT_PUBLIC_PLATFORM_ONETIME_FEE=2",
89+
"NEXT_PUBLIC_PLATFORM_PERCENT=1",
90+
"NEXT_PRODUCT_ID=",
91+
"",
92+
]
93+
94+
def make(self) -> str:
95+
"""Creates the `env` file content."""
96+
map = {
97+
"clerk": self.clerk(),
98+
"uploadthing": self.uploadthing(),
99+
"stripe": self.stripe(),
100+
}
101+
102+
content = []
103+
for package in self.packages.items:
104+
if not package.exclude:
105+
content.extend(map[package.name])
106+
107+
return "\n".join(content)

create_api_app/conf/constants/filepaths.py

+19
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ class SetupAssetsDirNames:
1111
BACKEND = "backend"
1212
APP = "app"
1313

14+
UPLOADTHING = "uploadthing"
15+
1416

1517
class AssetFilenames:
1618
"""A storage container for asset filenames."""
@@ -33,6 +35,7 @@ class SetupDirPaths:
3335
BACKEND_ASSETS = DIRPATHS_DICT[SetupAssetsDirNames.BACKEND]
3436
FRONTEND_ASSETS = DIRPATHS_DICT[SetupAssetsDirNames.FRONTEND]
3537
ROOT_ASSETS = DIRPATHS_DICT["root"]
38+
UPLOADTHING_ASSETS = DIRPATHS_DICT[SetupAssetsDirNames.UPLOADTHING]
3639

3740

3841
def __dotenv_setter(name: str, value: str) -> None:
@@ -43,6 +46,10 @@ def set_project_name(name: str) -> None:
4346
__dotenv_setter("PROJECT_NAME", name)
4447

4548

49+
def set_exclude_value(value: str) -> None:
50+
__dotenv_setter("EXCLUDE", value)
51+
52+
4653
def set_poetry_version(version: str) -> None:
4754
__dotenv_setter("POETRY_VERSION", version)
4855

@@ -51,6 +58,10 @@ def get_project_name() -> str:
5158
return os.environ.get("PROJECT_NAME")
5259

5360

61+
def get_exclude_value() -> str:
62+
return os.environ.get("EXCLUDE")
63+
64+
5465
def get_poetry_version() -> str:
5566
return os.environ.get("POETRY_VERSION")
5667

@@ -72,3 +83,11 @@ def __init__(self, project_name: str = None) -> None:
7283
self.ENV_LOCAL = os.path.join(self.ROOT, ".env.local")
7384
self.SETTINGS = os.path.join(self.BACKEND_APP, "config", "settings.py")
7485
self.MODELS = os.path.join(self.BACKEND_APP, "models", "__init__.py")
86+
87+
self.PACKAGE_JSON = os.path.join(self.FRONTEND, "package.json")
88+
self.NEXT_CONF = os.path.join(self.FRONTEND, "next.config.mjs")
89+
self.SHAD_CONF = os.path.join(self.FRONTEND, "components.json")
90+
self.LAYOUT = os.path.join(self.FRONTEND, "src", "app", "layout.tsx")
91+
self.HOMEPAGE = os.path.join(
92+
self.FRONTEND, "src", "pages", "Homepage", "Homepage.tsx"
93+
)

create_api_app/conf/file_handler.py

+11-15
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
1-
2-
31
def write_to_file(item: str, path: str) -> None:
42
"""Stores an item in a file."""
53
with open(path, "w") as file:
64
file.write(item)
75

86

7+
def append_to_file(item: str, path: str) -> None:
8+
"""Appends content to a file."""
9+
with open(path, "a") as file:
10+
file.write(item)
11+
12+
913
def add_content_to_file(content: str, path: str) -> None:
1014
"""Writes multiple lines (`content`) to a file at `path`."""
1115
with open(path, "w") as file:
@@ -14,23 +18,19 @@ def add_content_to_file(content: str, path: str) -> None:
1418

1519
def read_all_file_content(path: str) -> str:
1620
"""Retrieves all content from a basic file."""
17-
with open(path, 'r') as file:
21+
with open(path, "r") as file:
1822
content = file.read()
19-
23+
2024
return content
2125

2226

2327
def insert_into_file(position: str, new_content: str, path: str) -> None:
2428
"""Adds `new_content` to a file (`path`) at a specific `position`.
25-
29+
2630
Note: `new = (position + new_content).strip()`."""
2731
content = read_all_file_content(path)
2832

29-
content = content.replace(
30-
position,
31-
(position + new_content).strip(),
32-
1
33-
)
33+
content = content.replace(position, (position + new_content).strip(), 1)
3434

3535
add_content_to_file(content, path)
3636

@@ -39,10 +39,6 @@ def replace_content(old: str, new: str, path: str, count: int = 1) -> None:
3939
"""Replaces `old` content with `new` ones in a file at `path`."""
4040
content = read_all_file_content(path)
4141

42-
content = content.replace(
43-
old,
44-
new.strip(),
45-
count
46-
)
42+
content = content.replace(old, new.strip(), count)
4743

4844
add_content_to_file(content, path)

create_api_app/conf/storage.py

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
from dataclasses import dataclass
2+
3+
4+
@dataclass
5+
class Package:
6+
"""Represents a single package."""
7+
8+
name: str
9+
letter: str
10+
dependencies: list[str]
11+
colour: str
12+
exclude: bool = False
13+
14+
def update_exclude(self, exclude: str) -> None:
15+
"""Updates `self.exclude` to `True` if its `self.letter` exists in the `exclude` parameter."""
16+
self.exclude = True if self.letter in exclude else False
17+
18+
def name_str(self) -> str:
19+
"""Returns the name wrapped in its colour as a string."""
20+
return f"[{self.colour}]{self.name}[/{self.colour}]"
21+
22+
23+
@dataclass
24+
class Packages:
25+
"""Stores multiple packages."""
26+
27+
items: list[Package]
28+
29+
def update(self, exclude: str) -> None:
30+
"""Updates the packages based on the given parameters."""
31+
if exclude:
32+
for package in self.items:
33+
package.update_exclude(exclude)
34+
35+
def exclusions(self) -> list[Package]:
36+
"""Retrieves the packages that are excluded from the project."""
37+
return [package for package in self.items if package.exclude]
38+
39+
40+
@dataclass
41+
class RemotePattern:
42+
"""A storage container representing a `remotePattern` found in the `next.config.mjs`."""
43+
44+
protocol: str = None
45+
hostname: str = None
46+
pathname: str = None

0 commit comments

Comments
 (0)