@dartix-software-solutions/create-fullstack-app 2.0.12 → 2.0.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/backends/fastapi/templates/app/core/config.py.hbs +13 -13
- package/dist/backends/fastapi/templates/app/main.py.hbs +31 -31
- package/dist/backends/fastapi/templates/app/routers/health.py.hbs +8 -8
- package/dist/backends/fastapi/templates/app/routers/users.py.hbs +30 -30
- package/dist/backends/fastapi/templates/app/schemas/user.py.hbs +12 -12
- package/dist/backends/fastapi/templates/requirements.txt.hbs +6 -6
- package/dist/bin/create-fullstack-app.js +1 -1
- package/dist/{chunk-36CSXSDC.js → chunk-JQDTKR4K.js} +110 -43
- package/dist/databases/mongodb/templates/connection.ts.hbs +15 -15
- package/dist/databases/mongodb/templates/health-check.ts.hbs +11 -11
- package/dist/databases/mysql/templates/connection.ts.hbs +14 -14
- package/dist/databases/mysql/templates/health-check.ts.hbs +12 -12
- package/dist/databases/postgres/templates/connection.ts.hbs +14 -14
- package/dist/databases/postgres/templates/health-check.ts.hbs +12 -12
- package/dist/databases/redis/templates/connection.ts.hbs +14 -14
- package/dist/databases/redis/templates/health-check.ts.hbs +10 -10
- package/dist/databases/sqlite/templates/connection.ts.hbs +12 -12
- package/dist/databases/sqlite/templates/health-check.ts.hbs +10 -10
- package/dist/frontend-extras/react-table/templates/components/SampleTable.tsx.hbs +25 -25
- package/dist/frontend-extras/react-table/templates/data/sample-table-data.ts.hbs +4 -4
- package/dist/frontends/mobile/expo/templates/App.react-navigation.tsx.hbs +10 -10
- package/dist/frontends/mobile/expo/templates/app/(auth)/_layout.tsx.hbs +5 -5
- package/dist/frontends/mobile/expo/templates/app/(auth)/login.tsx.hbs +3 -3
- package/dist/frontends/mobile/expo/templates/app/(auth)/register.tsx.hbs +3 -3
- package/dist/frontends/mobile/expo/templates/app/(tabs)/_layout.tsx.hbs +11 -11
- package/dist/frontends/mobile/expo/templates/app/(tabs)/index.tsx.hbs +3 -3
- package/dist/frontends/mobile/expo/templates/app/(tabs)/profile.tsx.hbs +3 -3
- package/dist/frontends/mobile/expo/templates/app/(tabs)/settings.tsx.hbs +3 -3
- package/dist/frontends/mobile/expo/templates/app/+not-found.tsx.hbs +14 -14
- package/dist/frontends/mobile/expo/templates/app/_layout.tsx.hbs +11 -11
- package/dist/frontends/mobile/expo/templates/app/index.tsx.hbs +29 -29
- package/dist/frontends/mobile/expo/templates/types/router.ts.hbs +10 -10
- package/dist/frontends/mobile/react-native-cli/templates/app.json.hbs +4 -4
- package/dist/frontends/mobile/react-native-cli/templates/babel.config.js.hbs +14 -14
- package/dist/frontends/mobile/react-native-cli/templates/components/Avatar.tsx.hbs +29 -29
- package/dist/frontends/mobile/react-native-cli/templates/types/env.d.ts.hbs +3 -3
- package/dist/frontends/web/angular/templates/index.html.hbs +12 -12
- package/dist/frontends/web/angular/templates/main.ts.hbs +5 -5
- package/dist/frontends/web/angular/templates/pages/dashboard/dashboard.component.html.hbs +3 -3
- package/dist/frontends/web/angular/templates/pages/dashboard/dashboard.component.ts.hbs +10 -10
- package/dist/frontends/web/angular/templates/styles.css.hbs +11 -11
- package/dist/frontends/web/vue/templates/App.vue.hbs +9 -9
- package/dist/frontends/web/vue/templates/components/Layout.vue.hbs +17 -17
- package/dist/frontends/web/vue/templates/components/LoadingSpinner.vue.hbs +7 -7
- package/dist/frontends/web/vue/templates/components/Navbar.vue.hbs +24 -24
- package/dist/frontends/web/vue/templates/composables/useApi.ts.hbs +8 -8
- package/dist/frontends/web/vue/templates/composables/useAuth.ts.hbs +20 -20
- package/dist/frontends/web/vue/templates/env.d.ts.hbs +1 -1
- package/dist/frontends/web/vue/templates/index.html.hbs +12 -12
- package/dist/frontends/web/vue/templates/lib/config.ts.hbs +2 -2
- package/dist/frontends/web/vue/templates/main.ts.hbs +9 -9
- package/dist/frontends/web/vue/templates/pages/About.vue.hbs +6 -6
- package/dist/frontends/web/vue/templates/pages/Dashboard.vue.hbs +21 -21
- package/dist/frontends/web/vue/templates/pages/Home.vue.hbs +19 -19
- package/dist/frontends/web/vue/templates/pages/Login.vue.hbs +34 -34
- package/dist/frontends/web/vue/templates/pages/NotFound.vue.hbs +7 -7
- package/dist/frontends/web/vue/templates/router/index.ts.hbs +34 -34
- package/dist/frontends/web/vue/templates/stores/app.ts.hbs +8 -8
- package/dist/frontends/web/vue/templates/tsconfig.json.hbs +15 -15
- package/dist/frontends/web/vue/templates/vite.config.ts.hbs +6 -6
- package/dist/index.js +1 -1
- package/dist/orms/prisma/file-map.js +1 -0
- package/dist/orms/prisma/meta.js +5 -2
- package/dist/orms/prisma/templates/db-client.ts.hbs +10 -1
- package/dist/orms/prisma/templates/injection-module-import.hbs +1 -1
- package/dist/orms/prisma/templates/injection-module-register.hbs +1 -1
- package/dist/orms/prisma/templates/models/user.ts.hbs +3 -3
- package/dist/orms/prisma/templates/prisma.config.ts.hbs +12 -0
- package/dist/orms/prisma/templates/prisma.module.ts.hbs +9 -9
- package/dist/orms/prisma/templates/prisma.service.ts.hbs +24 -13
- package/dist/orms/prisma/templates/schema.prisma.hbs +0 -1
- package/dist/orms/prisma/templates/services/user.service.nestjs.ts.hbs +16 -16
- package/dist/orms/prisma/templates/services/user.service.ts.hbs +10 -10
- package/package.json +1 -1
- package/dist/plugins/devops/docker/templates/.dockerignore.hbs +0 -5
- package/dist/plugins/devops/gitlab-ci/templates/.gitlab-ci.yml.hbs +0 -14
- package/dist/plugins/devtools/eslint/templates/.eslintignore.hbs +0 -3
- package/dist/plugins/devtools/lint-staged/templates/.lintstagedrc.hbs +0 -5
- package/dist/plugins/devtools/prettier/templates/.prettierignore.hbs +0 -4
- package/dist/plugins/devtools/prettier/templates/.prettierrc.hbs +0 -6
- package/dist/plugins/mobile-navigation/expo-router/templates/.gitkeep +0 -0
- package/dist/plugins/testing/detox/templates/.detoxrc.js.hbs +0 -7
- package/dist/plugins/testing/maestro/templates/.maestro/home.yaml.hbs +0 -4
- package/dist/plugins/testing/maestro/templates/.maestro/login.yaml.hbs +0 -5
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
class Settings(BaseSettings):
|
|
5
|
-
model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf-8", extra="ignore")
|
|
6
|
-
|
|
7
|
-
app_name: str = "{{projectName}} API"
|
|
8
|
-
port: int = 8000
|
|
9
|
-
cors_origin: str = "http://localhost:5173"
|
|
10
|
-
debug: bool = True
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
settings = Settings()
|
|
1
|
+
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Settings(BaseSettings):
|
|
5
|
+
model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf-8", extra="ignore")
|
|
6
|
+
|
|
7
|
+
app_name: str = "{{projectName}} API"
|
|
8
|
+
port: int = 8000
|
|
9
|
+
cors_origin: str = "http://localhost:5173"
|
|
10
|
+
debug: bool = True
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
settings = Settings()
|
|
@@ -1,31 +1,31 @@
|
|
|
1
|
-
from contextlib import asynccontextmanager
|
|
2
|
-
|
|
3
|
-
from fastapi import FastAPI
|
|
4
|
-
from fastapi.middleware.cors import CORSMiddleware
|
|
5
|
-
|
|
6
|
-
from app.core.config import settings
|
|
7
|
-
from app.routers import health, users
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
@asynccontextmanager
|
|
11
|
-
async def lifespan(_app: FastAPI):
|
|
12
|
-
yield
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
app = FastAPI(
|
|
16
|
-
title=settings.app_name,
|
|
17
|
-
lifespan=lifespan,
|
|
18
|
-
docs_url="/docs" if settings.debug else None,
|
|
19
|
-
redoc_url="/redoc" if settings.debug else None,
|
|
20
|
-
)
|
|
21
|
-
|
|
22
|
-
app.add_middleware(
|
|
23
|
-
CORSMiddleware,
|
|
24
|
-
allow_origins=[settings.cors_origin],
|
|
25
|
-
allow_credentials=True,
|
|
26
|
-
allow_methods=["*"],
|
|
27
|
-
allow_headers=["*"],
|
|
28
|
-
)
|
|
29
|
-
|
|
30
|
-
app.include_router(health.router, tags=["health"])
|
|
31
|
-
app.include_router(users.router, prefix="/users", tags=["users"])
|
|
1
|
+
from contextlib import asynccontextmanager
|
|
2
|
+
|
|
3
|
+
from fastapi import FastAPI
|
|
4
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
5
|
+
|
|
6
|
+
from app.core.config import settings
|
|
7
|
+
from app.routers import health, users
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@asynccontextmanager
|
|
11
|
+
async def lifespan(_app: FastAPI):
|
|
12
|
+
yield
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
app = FastAPI(
|
|
16
|
+
title=settings.app_name,
|
|
17
|
+
lifespan=lifespan,
|
|
18
|
+
docs_url="/docs" if settings.debug else None,
|
|
19
|
+
redoc_url="/redoc" if settings.debug else None,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
app.add_middleware(
|
|
23
|
+
CORSMiddleware,
|
|
24
|
+
allow_origins=[settings.cors_origin],
|
|
25
|
+
allow_credentials=True,
|
|
26
|
+
allow_methods=["*"],
|
|
27
|
+
allow_headers=["*"],
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
app.include_router(health.router, tags=["health"])
|
|
31
|
+
app.include_router(users.router, prefix="/users", tags=["users"])
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
from fastapi import APIRouter
|
|
2
|
-
|
|
3
|
-
router = APIRouter()
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
@router.get("/health")
|
|
7
|
-
async def health():
|
|
8
|
-
return {"status": "ok"}
|
|
1
|
+
from fastapi import APIRouter
|
|
2
|
+
|
|
3
|
+
router = APIRouter()
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@router.get("/health")
|
|
7
|
+
async def health():
|
|
8
|
+
return {"status": "ok"}
|
|
@@ -1,30 +1,30 @@
|
|
|
1
|
-
from fastapi import APIRouter, HTTPException, status
|
|
2
|
-
|
|
3
|
-
from app.schemas.user import UserCreate, UserRead
|
|
4
|
-
|
|
5
|
-
router = APIRouter()
|
|
6
|
-
|
|
7
|
-
_db: list[UserRead] = [
|
|
8
|
-
UserRead(id=1, email="demo@example.com", name="Demo User"),
|
|
9
|
-
]
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
@router.get("/", response_model=list[UserRead])
|
|
13
|
-
async def list_users() -> list[UserRead]:
|
|
14
|
-
return list(_db)
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
@router.get("/{user_id}", response_model=UserRead)
|
|
18
|
-
async def get_user(user_id: int) -> UserRead:
|
|
19
|
-
for user in _db:
|
|
20
|
-
if user.id == user_id:
|
|
21
|
-
return user
|
|
22
|
-
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found")
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
@router.post("/", response_model=UserRead, status_code=status.HTTP_201_CREATED)
|
|
26
|
-
async def create_user(body: UserCreate) -> UserRead:
|
|
27
|
-
next_id = max((u.id for u in _db), default=0) + 1
|
|
28
|
-
user = UserRead(id=next_id, email=body.email, name=body.name)
|
|
29
|
-
_db.append(user)
|
|
30
|
-
return user
|
|
1
|
+
from fastapi import APIRouter, HTTPException, status
|
|
2
|
+
|
|
3
|
+
from app.schemas.user import UserCreate, UserRead
|
|
4
|
+
|
|
5
|
+
router = APIRouter()
|
|
6
|
+
|
|
7
|
+
_db: list[UserRead] = [
|
|
8
|
+
UserRead(id=1, email="demo@example.com", name="Demo User"),
|
|
9
|
+
]
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@router.get("/", response_model=list[UserRead])
|
|
13
|
+
async def list_users() -> list[UserRead]:
|
|
14
|
+
return list(_db)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@router.get("/{user_id}", response_model=UserRead)
|
|
18
|
+
async def get_user(user_id: int) -> UserRead:
|
|
19
|
+
for user in _db:
|
|
20
|
+
if user.id == user_id:
|
|
21
|
+
return user
|
|
22
|
+
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@router.post("/", response_model=UserRead, status_code=status.HTTP_201_CREATED)
|
|
26
|
+
async def create_user(body: UserCreate) -> UserRead:
|
|
27
|
+
next_id = max((u.id for u in _db), default=0) + 1
|
|
28
|
+
user = UserRead(id=next_id, email=body.email, name=body.name)
|
|
29
|
+
_db.append(user)
|
|
30
|
+
return user
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
from pydantic import BaseModel, EmailStr, Field
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
class UserRead(BaseModel):
|
|
5
|
-
id: int = Field(..., ge=1)
|
|
6
|
-
email: EmailStr
|
|
7
|
-
name: str
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class UserCreate(BaseModel):
|
|
11
|
-
email: EmailStr
|
|
12
|
-
name: str
|
|
1
|
+
from pydantic import BaseModel, EmailStr, Field
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class UserRead(BaseModel):
|
|
5
|
+
id: int = Field(..., ge=1)
|
|
6
|
+
email: EmailStr
|
|
7
|
+
name: str
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class UserCreate(BaseModel):
|
|
11
|
+
email: EmailStr
|
|
12
|
+
name: str
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
fastapi>=0.115.0
|
|
2
|
-
uvicorn[standard]>=0.32.0
|
|
3
|
-
pydantic-settings>=2.6.0
|
|
4
|
-
python-dotenv>=1.0.0
|
|
5
|
-
gunicorn>=23.0.0
|
|
6
|
-
email-validator>=2.2.0
|
|
1
|
+
fastapi>=0.115.0
|
|
2
|
+
uvicorn[standard]>=0.32.0
|
|
3
|
+
pydantic-settings>=2.6.0
|
|
4
|
+
python-dotenv>=1.0.0
|
|
5
|
+
gunicorn>=23.0.0
|
|
6
|
+
email-validator>=2.2.0
|
|
@@ -1970,11 +1970,12 @@ var SingleAppLayout = class {
|
|
|
1970
1970
|
if (isFullstack) {
|
|
1971
1971
|
const feDir = context.hasMobile ? "mobile" : "client";
|
|
1972
1972
|
const scripts = {
|
|
1973
|
-
dev: `
|
|
1974
|
-
|
|
1975
|
-
"
|
|
1976
|
-
build:
|
|
1977
|
-
lint: `
|
|
1973
|
+
"dev:client": `npm run dev --prefix ${feDir}`,
|
|
1974
|
+
"dev:server": "npm run dev --prefix server",
|
|
1975
|
+
"build:client": `npm run build --prefix ${feDir}`,
|
|
1976
|
+
"build:server": "npm run build --prefix server",
|
|
1977
|
+
"lint:client": `npm run lint --prefix ${feDir}`,
|
|
1978
|
+
"lint:server": "npm run lint --prefix server"
|
|
1978
1979
|
};
|
|
1979
1980
|
if (context.hasHusky) {
|
|
1980
1981
|
scripts.prepare = "husky";
|
|
@@ -1986,10 +1987,7 @@ var SingleAppLayout = class {
|
|
|
1986
1987
|
name: context.projectName,
|
|
1987
1988
|
version: "0.1.0",
|
|
1988
1989
|
private: true,
|
|
1989
|
-
scripts
|
|
1990
|
-
devDependencies: {
|
|
1991
|
-
concurrently: "^9.1.0"
|
|
1992
|
-
}
|
|
1990
|
+
scripts
|
|
1993
1991
|
},
|
|
1994
1992
|
null,
|
|
1995
1993
|
2
|
|
@@ -2005,7 +2003,6 @@ var SingleAppLayout = class {
|
|
|
2005
2003
|
const isFullstack = hasFE && hasBE;
|
|
2006
2004
|
if (isFullstack) {
|
|
2007
2005
|
const feDir = context.hasMobile ? "mobile" : "client";
|
|
2008
|
-
targets.push({ path: "package.json", target: TARGETS.ROOT, name: context.projectName });
|
|
2009
2006
|
targets.push({
|
|
2010
2007
|
path: `${feDir}/package.json`,
|
|
2011
2008
|
target: TARGETS.FRONTEND,
|
|
@@ -2931,34 +2928,36 @@ function resolveDependencies(plugins, packageJsonTargets, context) {
|
|
|
2931
2928
|
});
|
|
2932
2929
|
}
|
|
2933
2930
|
for (const plugin of plugins) {
|
|
2934
|
-
const
|
|
2935
|
-
if (
|
|
2931
|
+
const targetKeys = resolveTargetsForPlugin(plugin, packageJsonTargets, context);
|
|
2932
|
+
if (targetKeys.length === 0) {
|
|
2936
2933
|
logger.warn(
|
|
2937
2934
|
`Could not resolve target package.json for plugin "${plugin.meta.id}" (category: ${plugin.meta.category})`
|
|
2938
2935
|
);
|
|
2939
2936
|
continue;
|
|
2940
2937
|
}
|
|
2941
|
-
const
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
if (merged.dependencies[dep.name]
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2938
|
+
for (const targetKey of targetKeys) {
|
|
2939
|
+
const merged = result.get(targetKey);
|
|
2940
|
+
if (!merged) continue;
|
|
2941
|
+
for (const dep of plugin.meta.deps) {
|
|
2942
|
+
if (merged.dependencies[dep.name]) {
|
|
2943
|
+
if (merged.dependencies[dep.name] !== dep.version) {
|
|
2944
|
+
logger.warn(
|
|
2945
|
+
`Dependency version conflict for "${dep.name}": "${merged.dependencies[dep.name]}" vs "${dep.version}" (from ${plugin.meta.id})`
|
|
2946
|
+
);
|
|
2947
|
+
}
|
|
2949
2948
|
}
|
|
2949
|
+
merged.dependencies[dep.name] = dep.version;
|
|
2950
2950
|
}
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
);
|
|
2951
|
+
for (const dep of plugin.meta.devDeps) {
|
|
2952
|
+
if (merged.devDependencies[dep.name]) {
|
|
2953
|
+
if (merged.devDependencies[dep.name] !== dep.version) {
|
|
2954
|
+
logger.warn(
|
|
2955
|
+
`Dev dependency version conflict for "${dep.name}": "${merged.devDependencies[dep.name]}" vs "${dep.version}" (from ${plugin.meta.id})`
|
|
2956
|
+
);
|
|
2957
|
+
}
|
|
2959
2958
|
}
|
|
2959
|
+
merged.devDependencies[dep.name] = dep.version;
|
|
2960
2960
|
}
|
|
2961
|
-
merged.devDependencies[dep.name] = dep.version;
|
|
2962
2961
|
}
|
|
2963
2962
|
}
|
|
2964
2963
|
for (const [key, merged] of result) {
|
|
@@ -2969,6 +2968,15 @@ function resolveDependencies(plugins, packageJsonTargets, context) {
|
|
|
2969
2968
|
}
|
|
2970
2969
|
return result;
|
|
2971
2970
|
}
|
|
2971
|
+
function resolveTargetsForPlugin(plugin, packageJsonTargets, context) {
|
|
2972
|
+
if (plugin.meta.category === "devtools" && context.isSingleApp && context.isFullstack) {
|
|
2973
|
+
const frontendTarget = packageJsonTargets.find((t) => t.target === TARGETS.FRONTEND)?.path;
|
|
2974
|
+
const backendTarget = packageJsonTargets.find((t) => t.target === TARGETS.BACKEND)?.path;
|
|
2975
|
+
return [frontendTarget, backendTarget].filter((p) => Boolean(p));
|
|
2976
|
+
}
|
|
2977
|
+
const single = resolveTargetForPlugin(plugin, packageJsonTargets, context);
|
|
2978
|
+
return single ? [single] : [];
|
|
2979
|
+
}
|
|
2972
2980
|
function resolveTargetForPlugin(plugin, packageJsonTargets, context) {
|
|
2973
2981
|
const inferredTarget = inferTargetFromPluginOutputs(plugin, context);
|
|
2974
2982
|
if (inferredTarget) {
|
|
@@ -3612,7 +3620,7 @@ function detectFileCollisions(activePlugins, context, pathResolver) {
|
|
|
3612
3620
|
|
|
3613
3621
|
// src/generator/post-generate.ts
|
|
3614
3622
|
import { execa } from "execa";
|
|
3615
|
-
import { readFile } from "fs/promises";
|
|
3623
|
+
import { readFile, readdir } from "fs/promises";
|
|
3616
3624
|
import { join } from "path";
|
|
3617
3625
|
|
|
3618
3626
|
// src/cli/ui/spinner.ts
|
|
@@ -3711,8 +3719,9 @@ async function runPostGenerate(outputDir, context, options) {
|
|
|
3711
3719
|
if (!options.skipInstall) {
|
|
3712
3720
|
const installCmd = getInstallCommand(context.packageManager);
|
|
3713
3721
|
const [cmd, ...args] = installCmd.split(" ");
|
|
3714
|
-
const installTargets = getInstallTargets(outputDir, context);
|
|
3722
|
+
const installTargets = await getInstallTargets(outputDir, context);
|
|
3715
3723
|
try {
|
|
3724
|
+
let installCount = 0;
|
|
3716
3725
|
for (const target of installTargets) {
|
|
3717
3726
|
await withSpinner(
|
|
3718
3727
|
`Installing ${target.label} dependencies with ${context.packageManager}...`,
|
|
@@ -3724,8 +3733,9 @@ async function runPostGenerate(outputDir, context, options) {
|
|
|
3724
3733
|
},
|
|
3725
3734
|
`${target.label} dependencies installed`
|
|
3726
3735
|
);
|
|
3736
|
+
installCount += 1;
|
|
3727
3737
|
}
|
|
3728
|
-
installed =
|
|
3738
|
+
installed = installCount > 0;
|
|
3729
3739
|
} catch (error) {
|
|
3730
3740
|
logger.warn(`Failed to install dependencies: ${error.message}`);
|
|
3731
3741
|
logger.info(`You can run "${installCmd}" manually`);
|
|
@@ -3758,26 +3768,33 @@ function buildNextSteps(context, installed) {
|
|
|
3758
3768
|
steps.push(`Set up database: ${pm} run db:migrate && ${pm} run db:seed`);
|
|
3759
3769
|
}
|
|
3760
3770
|
}
|
|
3761
|
-
|
|
3771
|
+
if (context.isFullstack) {
|
|
3772
|
+
const feDir = context.hasMobile ? "mobile" : "client";
|
|
3773
|
+
steps.push(`Start frontend: ${pm} run dev --prefix ${feDir}`);
|
|
3774
|
+
steps.push(`Start backend: ${pm} run dev --prefix server`);
|
|
3775
|
+
} else {
|
|
3776
|
+
steps.push(`Start development: ${pm} run dev`);
|
|
3777
|
+
}
|
|
3762
3778
|
if (context.hasMobile) {
|
|
3763
3779
|
steps.push("Run on device: Press 'i' for iOS, 'a' for Android, or scan QR code");
|
|
3764
3780
|
}
|
|
3765
3781
|
return steps;
|
|
3766
3782
|
}
|
|
3767
|
-
function getInstallTargets(outputDir, context) {
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
3783
|
+
async function getInstallTargets(outputDir, context) {
|
|
3784
|
+
const packageDirs = await discoverPackageDirs(outputDir);
|
|
3785
|
+
const targets = [];
|
|
3786
|
+
for (const dir of packageDirs) {
|
|
3787
|
+
if (!await hasInstallableDependencies(dir)) continue;
|
|
3788
|
+
targets.push({
|
|
3789
|
+
label: labelForInstallTarget(dir, outputDir, context),
|
|
3790
|
+
cwd: dir
|
|
3791
|
+
});
|
|
3775
3792
|
}
|
|
3776
|
-
return
|
|
3793
|
+
return targets;
|
|
3777
3794
|
}
|
|
3778
3795
|
async function runBootstrapScripts(outputDir, context) {
|
|
3779
3796
|
const pm = context.packageManager;
|
|
3780
|
-
const installTargets = getInstallTargets(outputDir, context);
|
|
3797
|
+
const installTargets = await getInstallTargets(outputDir, context);
|
|
3781
3798
|
const safeBootstrapScripts = ["db:generate", "generate", "codegen", "graphql:codegen", "types:generate"];
|
|
3782
3799
|
for (const target of installTargets) {
|
|
3783
3800
|
try {
|
|
@@ -3804,6 +3821,56 @@ async function runBootstrapScripts(outputDir, context) {
|
|
|
3804
3821
|
}
|
|
3805
3822
|
}
|
|
3806
3823
|
}
|
|
3824
|
+
async function discoverPackageDirs(outputDir) {
|
|
3825
|
+
const dirs = [];
|
|
3826
|
+
await walkForPackageJson(outputDir, outputDir, dirs);
|
|
3827
|
+
const unique = [...new Set(dirs)];
|
|
3828
|
+
unique.sort((a, b) => {
|
|
3829
|
+
if (a === outputDir) return -1;
|
|
3830
|
+
if (b === outputDir) return 1;
|
|
3831
|
+
return a.localeCompare(b);
|
|
3832
|
+
});
|
|
3833
|
+
return unique;
|
|
3834
|
+
}
|
|
3835
|
+
async function walkForPackageJson(root, current, dirs) {
|
|
3836
|
+
const entries = await readdir(current, { withFileTypes: true });
|
|
3837
|
+
let hasPackageJson = false;
|
|
3838
|
+
for (const entry of entries) {
|
|
3839
|
+
if (entry.isFile() && entry.name === "package.json") {
|
|
3840
|
+
hasPackageJson = true;
|
|
3841
|
+
break;
|
|
3842
|
+
}
|
|
3843
|
+
}
|
|
3844
|
+
if (hasPackageJson) {
|
|
3845
|
+
dirs.push(current);
|
|
3846
|
+
}
|
|
3847
|
+
for (const entry of entries) {
|
|
3848
|
+
if (!entry.isDirectory()) continue;
|
|
3849
|
+
if (entry.name === "node_modules" || entry.name === ".git") continue;
|
|
3850
|
+
await walkForPackageJson(root, join(current, entry.name), dirs);
|
|
3851
|
+
}
|
|
3852
|
+
}
|
|
3853
|
+
function labelForInstallTarget(targetDir, outputDir, context) {
|
|
3854
|
+
if (targetDir === outputDir) return "root";
|
|
3855
|
+
if (targetDir === join(outputDir, "server")) return "server";
|
|
3856
|
+
if (targetDir === join(outputDir, "client")) return "client";
|
|
3857
|
+
if (targetDir === join(outputDir, "mobile")) return "mobile";
|
|
3858
|
+
const relative = targetDir.slice(outputDir.length + 1).replace(/\\/g, "/");
|
|
3859
|
+
if (relative) return relative;
|
|
3860
|
+
return context.projectName;
|
|
3861
|
+
}
|
|
3862
|
+
async function hasInstallableDependencies(cwd) {
|
|
3863
|
+
try {
|
|
3864
|
+
const packageJsonPath = join(cwd, "package.json");
|
|
3865
|
+
const raw = await readFile(packageJsonPath, "utf8");
|
|
3866
|
+
const pkg = JSON.parse(raw);
|
|
3867
|
+
const depsCount = Object.keys(pkg.dependencies ?? {}).length;
|
|
3868
|
+
const devDepsCount = Object.keys(pkg.devDependencies ?? {}).length;
|
|
3869
|
+
return depsCount > 0 || devDepsCount > 0;
|
|
3870
|
+
} catch {
|
|
3871
|
+
return false;
|
|
3872
|
+
}
|
|
3873
|
+
}
|
|
3807
3874
|
|
|
3808
3875
|
// src/generator/pipeline.ts
|
|
3809
3876
|
var TOTAL_STEPS = 16;
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import { MongoClient } from 'mongodb';
|
|
2
|
-
|
|
3
|
-
let client: MongoClient | null = null;
|
|
4
|
-
|
|
5
|
-
export async function getMongoClient(): Promise<MongoClient> {
|
|
6
|
-
if (!client) {
|
|
7
|
-
const uri = process.env.MONGODB_URI;
|
|
8
|
-
if (!uri) {
|
|
9
|
-
throw new Error('MONGODB_URI is not set');
|
|
10
|
-
}
|
|
11
|
-
client = new MongoClient(uri);
|
|
12
|
-
await client.connect();
|
|
13
|
-
}
|
|
14
|
-
return client;
|
|
15
|
-
}
|
|
1
|
+
import { MongoClient } from 'mongodb';
|
|
2
|
+
|
|
3
|
+
let client: MongoClient | null = null;
|
|
4
|
+
|
|
5
|
+
export async function getMongoClient(): Promise<MongoClient> {
|
|
6
|
+
if (!client) {
|
|
7
|
+
const uri = process.env.MONGODB_URI;
|
|
8
|
+
if (!uri) {
|
|
9
|
+
throw new Error('MONGODB_URI is not set');
|
|
10
|
+
}
|
|
11
|
+
client = new MongoClient(uri);
|
|
12
|
+
await client.connect();
|
|
13
|
+
}
|
|
14
|
+
return client;
|
|
15
|
+
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { getMongoClient } from './db-connection.js';
|
|
2
|
-
|
|
3
|
-
export async function checkMongodbHealth(): Promise<boolean> {
|
|
4
|
-
try {
|
|
5
|
-
const c = await getMongoClient();
|
|
6
|
-
await c.db().command({ ping: 1 });
|
|
7
|
-
return true;
|
|
8
|
-
} catch {
|
|
9
|
-
return false;
|
|
10
|
-
}
|
|
11
|
-
}
|
|
1
|
+
import { getMongoClient } from './db-connection.js';
|
|
2
|
+
|
|
3
|
+
export async function checkMongodbHealth(): Promise<boolean> {
|
|
4
|
+
try {
|
|
5
|
+
const c = await getMongoClient();
|
|
6
|
+
await c.db().command({ ping: 1 });
|
|
7
|
+
return true;
|
|
8
|
+
} catch {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import mysql from 'mysql2/promise';
|
|
2
|
-
|
|
3
|
-
let pool: mysql.Pool | null = null;
|
|
4
|
-
|
|
5
|
-
export function getMysqlPool(): mysql.Pool {
|
|
6
|
-
if (!pool) {
|
|
7
|
-
const url = process.env.DATABASE_URL;
|
|
8
|
-
if (!url) {
|
|
9
|
-
throw new Error('DATABASE_URL is not set');
|
|
10
|
-
}
|
|
11
|
-
pool = mysql.createPool(url);
|
|
12
|
-
}
|
|
13
|
-
return pool;
|
|
14
|
-
}
|
|
1
|
+
import mysql from 'mysql2/promise';
|
|
2
|
+
|
|
3
|
+
let pool: mysql.Pool | null = null;
|
|
4
|
+
|
|
5
|
+
export function getMysqlPool(): mysql.Pool {
|
|
6
|
+
if (!pool) {
|
|
7
|
+
const url = process.env.DATABASE_URL;
|
|
8
|
+
if (!url) {
|
|
9
|
+
throw new Error('DATABASE_URL is not set');
|
|
10
|
+
}
|
|
11
|
+
pool = mysql.createPool(url);
|
|
12
|
+
}
|
|
13
|
+
return pool;
|
|
14
|
+
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { getMysqlPool } from './db-connection.js';
|
|
2
|
-
|
|
3
|
-
export async function checkMysqlHealth(): Promise<boolean> {
|
|
4
|
-
try {
|
|
5
|
-
const connection = await getMysqlPool().getConnection();
|
|
6
|
-
await connection.ping();
|
|
7
|
-
connection.release();
|
|
8
|
-
return true;
|
|
9
|
-
} catch {
|
|
10
|
-
return false;
|
|
11
|
-
}
|
|
12
|
-
}
|
|
1
|
+
import { getMysqlPool } from './db-connection.js';
|
|
2
|
+
|
|
3
|
+
export async function checkMysqlHealth(): Promise<boolean> {
|
|
4
|
+
try {
|
|
5
|
+
const connection = await getMysqlPool().getConnection();
|
|
6
|
+
await connection.ping();
|
|
7
|
+
connection.release();
|
|
8
|
+
return true;
|
|
9
|
+
} catch {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import pg from 'pg';
|
|
2
|
-
|
|
3
|
-
let pool: pg.Pool | null = null;
|
|
4
|
-
|
|
5
|
-
export function getPostgresPool(): pg.Pool {
|
|
6
|
-
if (!pool) {
|
|
7
|
-
const url = process.env.DATABASE_URL;
|
|
8
|
-
if (!url) {
|
|
9
|
-
throw new Error('DATABASE_URL is not set');
|
|
10
|
-
}
|
|
11
|
-
pool = new pg.Pool({ connectionString: url });
|
|
12
|
-
}
|
|
13
|
-
return pool;
|
|
14
|
-
}
|
|
1
|
+
import pg from 'pg';
|
|
2
|
+
|
|
3
|
+
let pool: pg.Pool | null = null;
|
|
4
|
+
|
|
5
|
+
export function getPostgresPool(): pg.Pool {
|
|
6
|
+
if (!pool) {
|
|
7
|
+
const url = process.env.DATABASE_URL;
|
|
8
|
+
if (!url) {
|
|
9
|
+
throw new Error('DATABASE_URL is not set');
|
|
10
|
+
}
|
|
11
|
+
pool = new pg.Pool({ connectionString: url });
|
|
12
|
+
}
|
|
13
|
+
return pool;
|
|
14
|
+
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { getPostgresPool } from './db-connection.js';
|
|
2
|
-
|
|
3
|
-
export async function checkPostgresHealth(): Promise<boolean> {
|
|
4
|
-
try {
|
|
5
|
-
const client = await getPostgresPool().connect();
|
|
6
|
-
await client.query('SELECT 1');
|
|
7
|
-
client.release();
|
|
8
|
-
return true;
|
|
9
|
-
} catch {
|
|
10
|
-
return false;
|
|
11
|
-
}
|
|
12
|
-
}
|
|
1
|
+
import { getPostgresPool } from './db-connection.js';
|
|
2
|
+
|
|
3
|
+
export async function checkPostgresHealth(): Promise<boolean> {
|
|
4
|
+
try {
|
|
5
|
+
const client = await getPostgresPool().connect();
|
|
6
|
+
await client.query('SELECT 1');
|
|
7
|
+
client.release();
|
|
8
|
+
return true;
|
|
9
|
+
} catch {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import Redis from 'ioredis';
|
|
2
|
-
|
|
3
|
-
let client: Redis | null = null;
|
|
4
|
-
|
|
5
|
-
export function getRedis(): Redis {
|
|
6
|
-
if (!client) {
|
|
7
|
-
const url = process.env.REDIS_URL;
|
|
8
|
-
if (!url) {
|
|
9
|
-
throw new Error('REDIS_URL is not set');
|
|
10
|
-
}
|
|
11
|
-
client = new Redis(url);
|
|
12
|
-
}
|
|
13
|
-
return client;
|
|
14
|
-
}
|
|
1
|
+
import Redis from 'ioredis';
|
|
2
|
+
|
|
3
|
+
let client: Redis | null = null;
|
|
4
|
+
|
|
5
|
+
export function getRedis(): Redis {
|
|
6
|
+
if (!client) {
|
|
7
|
+
const url = process.env.REDIS_URL;
|
|
8
|
+
if (!url) {
|
|
9
|
+
throw new Error('REDIS_URL is not set');
|
|
10
|
+
}
|
|
11
|
+
client = new Redis(url);
|
|
12
|
+
}
|
|
13
|
+
return client;
|
|
14
|
+
}
|