@dartix-software-solutions/create-fullstack-app 2.0.10 → 2.0.13

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.
Files changed (79) hide show
  1. package/dist/backends/fastapi/templates/app/core/config.py.hbs +13 -13
  2. package/dist/backends/fastapi/templates/app/main.py.hbs +31 -31
  3. package/dist/backends/fastapi/templates/app/routers/health.py.hbs +8 -8
  4. package/dist/backends/fastapi/templates/app/routers/users.py.hbs +30 -30
  5. package/dist/backends/fastapi/templates/app/schemas/user.py.hbs +12 -12
  6. package/dist/backends/fastapi/templates/requirements.txt.hbs +6 -6
  7. package/dist/bin/create-fullstack-app.js +1 -1
  8. package/dist/{chunk-YB5MXG5R.js → chunk-Z7MIDMEN.js} +175 -40
  9. package/dist/databases/mongodb/templates/connection.ts.hbs +15 -15
  10. package/dist/databases/mongodb/templates/health-check.ts.hbs +11 -11
  11. package/dist/databases/mysql/templates/connection.ts.hbs +14 -14
  12. package/dist/databases/mysql/templates/health-check.ts.hbs +12 -12
  13. package/dist/databases/postgres/templates/connection.ts.hbs +14 -14
  14. package/dist/databases/postgres/templates/health-check.ts.hbs +12 -12
  15. package/dist/databases/redis/templates/connection.ts.hbs +14 -14
  16. package/dist/databases/redis/templates/health-check.ts.hbs +10 -10
  17. package/dist/databases/sqlite/templates/connection.ts.hbs +12 -12
  18. package/dist/databases/sqlite/templates/health-check.ts.hbs +10 -10
  19. package/dist/frontend-extras/react-table/templates/components/SampleTable.tsx.hbs +25 -25
  20. package/dist/frontend-extras/react-table/templates/data/sample-table-data.ts.hbs +4 -4
  21. package/dist/frontends/mobile/expo/templates/App.react-navigation.tsx.hbs +10 -10
  22. package/dist/frontends/mobile/expo/templates/app/(auth)/_layout.tsx.hbs +5 -5
  23. package/dist/frontends/mobile/expo/templates/app/(auth)/login.tsx.hbs +3 -3
  24. package/dist/frontends/mobile/expo/templates/app/(auth)/register.tsx.hbs +3 -3
  25. package/dist/frontends/mobile/expo/templates/app/(tabs)/_layout.tsx.hbs +11 -11
  26. package/dist/frontends/mobile/expo/templates/app/(tabs)/index.tsx.hbs +3 -3
  27. package/dist/frontends/mobile/expo/templates/app/(tabs)/profile.tsx.hbs +3 -3
  28. package/dist/frontends/mobile/expo/templates/app/(tabs)/settings.tsx.hbs +3 -3
  29. package/dist/frontends/mobile/expo/templates/app/+not-found.tsx.hbs +14 -14
  30. package/dist/frontends/mobile/expo/templates/app/_layout.tsx.hbs +11 -11
  31. package/dist/frontends/mobile/expo/templates/app/index.tsx.hbs +29 -29
  32. package/dist/frontends/mobile/expo/templates/types/router.ts.hbs +10 -10
  33. package/dist/frontends/mobile/react-native-cli/templates/app.json.hbs +4 -4
  34. package/dist/frontends/mobile/react-native-cli/templates/babel.config.js.hbs +14 -14
  35. package/dist/frontends/mobile/react-native-cli/templates/components/Avatar.tsx.hbs +29 -29
  36. package/dist/frontends/mobile/react-native-cli/templates/types/env.d.ts.hbs +3 -3
  37. package/dist/frontends/web/angular/templates/index.html.hbs +12 -12
  38. package/dist/frontends/web/angular/templates/main.ts.hbs +5 -5
  39. package/dist/frontends/web/angular/templates/pages/dashboard/dashboard.component.html.hbs +3 -3
  40. package/dist/frontends/web/angular/templates/pages/dashboard/dashboard.component.ts.hbs +10 -10
  41. package/dist/frontends/web/angular/templates/styles.css.hbs +11 -11
  42. package/dist/frontends/web/vue/templates/App.vue.hbs +9 -9
  43. package/dist/frontends/web/vue/templates/components/Layout.vue.hbs +17 -17
  44. package/dist/frontends/web/vue/templates/components/LoadingSpinner.vue.hbs +7 -7
  45. package/dist/frontends/web/vue/templates/components/Navbar.vue.hbs +24 -24
  46. package/dist/frontends/web/vue/templates/composables/useApi.ts.hbs +8 -8
  47. package/dist/frontends/web/vue/templates/composables/useAuth.ts.hbs +20 -20
  48. package/dist/frontends/web/vue/templates/env.d.ts.hbs +1 -1
  49. package/dist/frontends/web/vue/templates/index.html.hbs +12 -12
  50. package/dist/frontends/web/vue/templates/lib/config.ts.hbs +2 -2
  51. package/dist/frontends/web/vue/templates/main.ts.hbs +9 -9
  52. package/dist/frontends/web/vue/templates/pages/About.vue.hbs +6 -6
  53. package/dist/frontends/web/vue/templates/pages/Dashboard.vue.hbs +21 -21
  54. package/dist/frontends/web/vue/templates/pages/Home.vue.hbs +19 -19
  55. package/dist/frontends/web/vue/templates/pages/Login.vue.hbs +34 -34
  56. package/dist/frontends/web/vue/templates/pages/NotFound.vue.hbs +7 -7
  57. package/dist/frontends/web/vue/templates/router/index.ts.hbs +34 -34
  58. package/dist/frontends/web/vue/templates/stores/app.ts.hbs +8 -8
  59. package/dist/frontends/web/vue/templates/tsconfig.json.hbs +15 -15
  60. package/dist/frontends/web/vue/templates/vite.config.ts.hbs +6 -6
  61. package/dist/index.js +1 -1
  62. package/dist/orms/prisma/templates/injection-module-import.hbs +1 -1
  63. package/dist/orms/prisma/templates/injection-module-register.hbs +1 -1
  64. package/dist/orms/prisma/templates/models/user.ts.hbs +3 -3
  65. package/dist/orms/prisma/templates/prisma.module.ts.hbs +9 -9
  66. package/dist/orms/prisma/templates/prisma.service.ts.hbs +13 -13
  67. package/dist/orms/prisma/templates/services/user.service.nestjs.ts.hbs +16 -16
  68. package/dist/orms/prisma/templates/services/user.service.ts.hbs +10 -10
  69. package/package.json +1 -1
  70. package/dist/plugins/devops/docker/templates/.dockerignore.hbs +0 -5
  71. package/dist/plugins/devops/gitlab-ci/templates/.gitlab-ci.yml.hbs +0 -14
  72. package/dist/plugins/devtools/eslint/templates/.eslintignore.hbs +0 -3
  73. package/dist/plugins/devtools/lint-staged/templates/.lintstagedrc.hbs +0 -5
  74. package/dist/plugins/devtools/prettier/templates/.prettierignore.hbs +0 -4
  75. package/dist/plugins/devtools/prettier/templates/.prettierrc.hbs +0 -6
  76. package/dist/plugins/mobile-navigation/expo-router/templates/.gitkeep +0 -0
  77. package/dist/plugins/testing/detox/templates/.detoxrc.js.hbs +0 -7
  78. package/dist/plugins/testing/maestro/templates/.maestro/home.yaml.hbs +0 -4
  79. 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
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  main
4
- } from "../chunk-YB5MXG5R.js";
4
+ } from "../chunk-Z7MIDMEN.js";
5
5
 
6
6
  // src/bin/create-fullstack-app.ts
7
7
  main().catch((error) => {
@@ -1970,11 +1970,9 @@ var SingleAppLayout = class {
1970
1970
  if (isFullstack) {
1971
1971
  const feDir = context.hasMobile ? "mobile" : "client";
1972
1972
  const scripts = {
1973
- dev: `concurrently "npm run dev:${context.hasMobile ? "mobile" : "client"}" "npm run dev:server"`,
1974
- [`dev:${context.hasMobile ? "mobile" : "client"}`]: `cd ${feDir} && npm run dev`,
1975
- "dev:server": "cd server && npm run dev",
1976
- build: `cd ${feDir} && npm run build && cd ../server && npm run build`,
1977
- lint: `cd ${feDir} && npm run lint && cd ../server && npm run lint`
1973
+ dev: "npm run dev --workspaces --if-present --parallel",
1974
+ build: "npm run build --workspaces --if-present",
1975
+ lint: "npm run lint --workspaces --if-present"
1978
1976
  };
1979
1977
  if (context.hasHusky) {
1980
1978
  scripts.prepare = "husky";
@@ -1986,10 +1984,8 @@ var SingleAppLayout = class {
1986
1984
  name: context.projectName,
1987
1985
  version: "0.1.0",
1988
1986
  private: true,
1989
- scripts,
1990
- devDependencies: {
1991
- concurrently: "^9.1.0"
1992
- }
1987
+ workspaces: [feDir, "server"],
1988
+ scripts
1993
1989
  },
1994
1990
  null,
1995
1991
  2
@@ -2931,34 +2927,36 @@ function resolveDependencies(plugins, packageJsonTargets, context) {
2931
2927
  });
2932
2928
  }
2933
2929
  for (const plugin of plugins) {
2934
- const targetKey = resolveTargetForPlugin(plugin, packageJsonTargets, context);
2935
- if (!targetKey) {
2930
+ const targetKeys = resolveTargetsForPlugin(plugin, packageJsonTargets, context);
2931
+ if (targetKeys.length === 0) {
2936
2932
  logger.warn(
2937
2933
  `Could not resolve target package.json for plugin "${plugin.meta.id}" (category: ${plugin.meta.category})`
2938
2934
  );
2939
2935
  continue;
2940
2936
  }
2941
- const merged = result.get(targetKey);
2942
- if (!merged) continue;
2943
- for (const dep of plugin.meta.deps) {
2944
- if (merged.dependencies[dep.name]) {
2945
- if (merged.dependencies[dep.name] !== dep.version) {
2946
- logger.warn(
2947
- `Dependency version conflict for "${dep.name}": "${merged.dependencies[dep.name]}" vs "${dep.version}" (from ${plugin.meta.id})`
2948
- );
2937
+ for (const targetKey of targetKeys) {
2938
+ const merged = result.get(targetKey);
2939
+ if (!merged) continue;
2940
+ for (const dep of plugin.meta.deps) {
2941
+ if (merged.dependencies[dep.name]) {
2942
+ if (merged.dependencies[dep.name] !== dep.version) {
2943
+ logger.warn(
2944
+ `Dependency version conflict for "${dep.name}": "${merged.dependencies[dep.name]}" vs "${dep.version}" (from ${plugin.meta.id})`
2945
+ );
2946
+ }
2949
2947
  }
2948
+ merged.dependencies[dep.name] = dep.version;
2950
2949
  }
2951
- merged.dependencies[dep.name] = dep.version;
2952
- }
2953
- for (const dep of plugin.meta.devDeps) {
2954
- if (merged.devDependencies[dep.name]) {
2955
- if (merged.devDependencies[dep.name] !== dep.version) {
2956
- logger.warn(
2957
- `Dev dependency version conflict for "${dep.name}": "${merged.devDependencies[dep.name]}" vs "${dep.version}" (from ${plugin.meta.id})`
2958
- );
2950
+ for (const dep of plugin.meta.devDeps) {
2951
+ if (merged.devDependencies[dep.name]) {
2952
+ if (merged.devDependencies[dep.name] !== dep.version) {
2953
+ logger.warn(
2954
+ `Dev dependency version conflict for "${dep.name}": "${merged.devDependencies[dep.name]}" vs "${dep.version}" (from ${plugin.meta.id})`
2955
+ );
2956
+ }
2959
2957
  }
2958
+ merged.devDependencies[dep.name] = dep.version;
2960
2959
  }
2961
- merged.devDependencies[dep.name] = dep.version;
2962
2960
  }
2963
2961
  }
2964
2962
  for (const [key, merged] of result) {
@@ -2969,7 +2967,21 @@ function resolveDependencies(plugins, packageJsonTargets, context) {
2969
2967
  }
2970
2968
  return result;
2971
2969
  }
2972
- function resolveTargetForPlugin(plugin, packageJsonTargets, _context) {
2970
+ function resolveTargetsForPlugin(plugin, packageJsonTargets, context) {
2971
+ if (plugin.meta.category === "devtools" && context.isSingleApp && context.isFullstack) {
2972
+ const frontendTarget = packageJsonTargets.find((t) => t.target === TARGETS.FRONTEND)?.path;
2973
+ const backendTarget = packageJsonTargets.find((t) => t.target === TARGETS.BACKEND)?.path;
2974
+ return [frontendTarget, backendTarget].filter((p) => Boolean(p));
2975
+ }
2976
+ const single = resolveTargetForPlugin(plugin, packageJsonTargets, context);
2977
+ return single ? [single] : [];
2978
+ }
2979
+ function resolveTargetForPlugin(plugin, packageJsonTargets, context) {
2980
+ const inferredTarget = inferTargetFromPluginOutputs(plugin, context);
2981
+ if (inferredTarget) {
2982
+ const inferredPackage = packageJsonTargets.find((t) => t.target === inferredTarget);
2983
+ if (inferredPackage) return inferredPackage.path;
2984
+ }
2973
2985
  const categoryTarget = CATEGORY_TARGET_MAP[plugin.meta.category];
2974
2986
  if (!categoryTarget) {
2975
2987
  const rootTarget2 = packageJsonTargets.find((t) => t.target === TARGETS.ROOT);
@@ -2990,6 +3002,23 @@ function resolveTargetForPlugin(plugin, packageJsonTargets, _context) {
2990
3002
  const rootTarget = packageJsonTargets.find((t) => t.target === TARGETS.ROOT);
2991
3003
  return rootTarget?.path || packageJsonTargets[0]?.path || null;
2992
3004
  }
3005
+ function inferTargetFromPluginOutputs(plugin, context) {
3006
+ const targets = /* @__PURE__ */ new Set();
3007
+ for (const entry of plugin.fileMap.files) {
3008
+ if (!entry.when || entry.when(context)) {
3009
+ targets.add(entry.target);
3010
+ }
3011
+ }
3012
+ for (const injection of plugin.fileMap.injections) {
3013
+ if (!injection.when || injection.when(context)) {
3014
+ targets.add(injection.target);
3015
+ }
3016
+ }
3017
+ if (targets.size === 1) {
3018
+ return [...targets][0];
3019
+ }
3020
+ return null;
3021
+ }
2993
3022
 
2994
3023
  // src/generator/script-builder.ts
2995
3024
  function buildScripts2(plugins, packageJsonTargets, _context) {
@@ -3590,6 +3619,8 @@ function detectFileCollisions(activePlugins, context, pathResolver) {
3590
3619
 
3591
3620
  // src/generator/post-generate.ts
3592
3621
  import { execa } from "execa";
3622
+ import { readFile, readdir } from "fs/promises";
3623
+ import { join } from "path";
3593
3624
 
3594
3625
  // src/cli/ui/spinner.ts
3595
3626
  import ora from "ora";
@@ -3687,23 +3718,29 @@ async function runPostGenerate(outputDir, context, options) {
3687
3718
  if (!options.skipInstall) {
3688
3719
  const installCmd = getInstallCommand(context.packageManager);
3689
3720
  const [cmd, ...args] = installCmd.split(" ");
3721
+ const installTargets = await getInstallTargets(outputDir, context);
3690
3722
  try {
3691
- await withSpinner(
3692
- `Installing dependencies with ${context.packageManager}...`,
3693
- async () => {
3694
- await execa(cmd, args, {
3695
- cwd: outputDir,
3696
- stdio: "pipe"
3697
- });
3698
- },
3699
- "Dependencies installed"
3700
- );
3723
+ for (const target of installTargets) {
3724
+ await withSpinner(
3725
+ `Installing ${target.label} dependencies with ${context.packageManager}...`,
3726
+ async () => {
3727
+ await execa(cmd, args, {
3728
+ cwd: target.cwd,
3729
+ stdio: "pipe"
3730
+ });
3731
+ },
3732
+ `${target.label} dependencies installed`
3733
+ );
3734
+ }
3701
3735
  installed = true;
3702
3736
  } catch (error) {
3703
3737
  logger.warn(`Failed to install dependencies: ${error.message}`);
3704
3738
  logger.info(`You can run "${installCmd}" manually`);
3705
3739
  }
3706
3740
  }
3741
+ if (installed) {
3742
+ await runBootstrapScripts(outputDir, context);
3743
+ }
3707
3744
  const steps = buildNextSteps(context, installed);
3708
3745
  printPostGeneration(context.projectName, outputDir, context.packageManager, steps);
3709
3746
  return { installed, gitInitialized };
@@ -3722,7 +3759,11 @@ function buildNextSteps(context, installed) {
3722
3759
  }
3723
3760
  steps.push("Fill in your environment variable values");
3724
3761
  if (context.hasDatabase && context.hasPrisma) {
3725
- steps.push(`Set up database: ${pm} run db:migrate && ${pm} run db:seed`);
3762
+ if (context.isFullstack) {
3763
+ steps.push(`Set up database: cd server && ${pm} run db:migrate && ${pm} run db:seed`);
3764
+ } else {
3765
+ steps.push(`Set up database: ${pm} run db:migrate && ${pm} run db:seed`);
3766
+ }
3726
3767
  }
3727
3768
  steps.push(`Start development: ${pm} run dev`);
3728
3769
  if (context.hasMobile) {
@@ -3730,6 +3771,100 @@ function buildNextSteps(context, installed) {
3730
3771
  }
3731
3772
  return steps;
3732
3773
  }
3774
+ async function getInstallTargets(outputDir, context) {
3775
+ const packageDirs = await discoverPackageDirs(outputDir);
3776
+ const targets = [];
3777
+ for (const dir of packageDirs) {
3778
+ if (!await hasInstallableDependencies(dir)) continue;
3779
+ targets.push({
3780
+ label: labelForInstallTarget(dir, outputDir, context),
3781
+ cwd: dir
3782
+ });
3783
+ }
3784
+ if (targets.length === 0) {
3785
+ targets.push({ label: "project", cwd: outputDir });
3786
+ }
3787
+ return targets;
3788
+ }
3789
+ async function runBootstrapScripts(outputDir, context) {
3790
+ const pm = context.packageManager;
3791
+ const installTargets = await getInstallTargets(outputDir, context);
3792
+ const safeBootstrapScripts = ["db:generate", "generate", "codegen", "graphql:codegen", "types:generate"];
3793
+ for (const target of installTargets) {
3794
+ try {
3795
+ const packageJsonPath = join(target.cwd, "package.json");
3796
+ const raw = await readFile(packageJsonPath, "utf8");
3797
+ const pkg = JSON.parse(raw);
3798
+ const scripts = pkg.scripts ?? {};
3799
+ for (const scriptName of safeBootstrapScripts) {
3800
+ if (!scripts[scriptName]) continue;
3801
+ const [cmd, ...args] = `${pm} run ${scriptName}`.split(" ");
3802
+ await withSpinner(
3803
+ `Running ${target.label}:${scriptName}...`,
3804
+ async () => {
3805
+ await execa(cmd, args, {
3806
+ cwd: target.cwd,
3807
+ stdio: "pipe"
3808
+ });
3809
+ },
3810
+ `${target.label}:${scriptName} completed`
3811
+ );
3812
+ }
3813
+ } catch (error) {
3814
+ logger.debug(`Bootstrap scripts skipped for ${target.label}: ${error.message}`);
3815
+ }
3816
+ }
3817
+ }
3818
+ async function discoverPackageDirs(outputDir) {
3819
+ const dirs = [];
3820
+ await walkForPackageJson(outputDir, outputDir, dirs);
3821
+ const unique = [...new Set(dirs)];
3822
+ unique.sort((a, b) => {
3823
+ if (a === outputDir) return -1;
3824
+ if (b === outputDir) return 1;
3825
+ return a.localeCompare(b);
3826
+ });
3827
+ return unique;
3828
+ }
3829
+ async function walkForPackageJson(root, current, dirs) {
3830
+ const entries = await readdir(current, { withFileTypes: true });
3831
+ let hasPackageJson = false;
3832
+ for (const entry of entries) {
3833
+ if (entry.isFile() && entry.name === "package.json") {
3834
+ hasPackageJson = true;
3835
+ break;
3836
+ }
3837
+ }
3838
+ if (hasPackageJson) {
3839
+ dirs.push(current);
3840
+ }
3841
+ for (const entry of entries) {
3842
+ if (!entry.isDirectory()) continue;
3843
+ if (entry.name === "node_modules" || entry.name === ".git") continue;
3844
+ await walkForPackageJson(root, join(current, entry.name), dirs);
3845
+ }
3846
+ }
3847
+ function labelForInstallTarget(targetDir, outputDir, context) {
3848
+ if (targetDir === outputDir) return "root";
3849
+ if (targetDir === join(outputDir, "server")) return "server";
3850
+ if (targetDir === join(outputDir, "client")) return "client";
3851
+ if (targetDir === join(outputDir, "mobile")) return "mobile";
3852
+ const relative = targetDir.slice(outputDir.length + 1).replace(/\\/g, "/");
3853
+ if (relative) return relative;
3854
+ return context.projectName;
3855
+ }
3856
+ async function hasInstallableDependencies(cwd) {
3857
+ try {
3858
+ const packageJsonPath = join(cwd, "package.json");
3859
+ const raw = await readFile(packageJsonPath, "utf8");
3860
+ const pkg = JSON.parse(raw);
3861
+ const depsCount = Object.keys(pkg.dependencies ?? {}).length;
3862
+ const devDepsCount = Object.keys(pkg.devDependencies ?? {}).length;
3863
+ return depsCount > 0 || devDepsCount > 0;
3864
+ } catch {
3865
+ return false;
3866
+ }
3867
+ }
3733
3868
 
3734
3869
  // src/generator/pipeline.ts
3735
3870
  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
+ }