create-nexu 1.0.2 → 1.1.0

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/index.js CHANGED
@@ -80,6 +80,16 @@ function exec(command, cwd) {
80
80
  throw new Error(`Command failed: ${command}`);
81
81
  }
82
82
  }
83
+ function execInherit(command, cwd) {
84
+ try {
85
+ execSync(command, {
86
+ cwd,
87
+ stdio: "inherit"
88
+ });
89
+ } catch (error) {
90
+ throw new Error(`Command failed: ${command}`);
91
+ }
92
+ }
83
93
  function copyWithMerge(src, dest, overwrite = false) {
84
94
  if (fs.existsSync(dest) && !overwrite) {
85
95
  if (fs.statSync(src).isDirectory() && fs.statSync(dest).isDirectory()) {
@@ -107,6 +117,28 @@ function log(message, type = "info") {
107
117
  };
108
118
  console.log(`${colors[type](icons[type])} ${message}`);
109
119
  }
120
+ function getRunCommand(pm) {
121
+ switch (pm) {
122
+ case "pnpm":
123
+ return "pnpm";
124
+ case "yarn":
125
+ return "yarn";
126
+ case "npm":
127
+ default:
128
+ return "npm run";
129
+ }
130
+ }
131
+ function getInstallCommand(pm) {
132
+ switch (pm) {
133
+ case "pnpm":
134
+ return "pnpm install";
135
+ case "yarn":
136
+ return "yarn install";
137
+ case "npm":
138
+ default:
139
+ return "npm install";
140
+ }
141
+ }
110
142
 
111
143
  // src/commands/add.ts
112
144
  var __filename = fileURLToPath(import.meta.url);
@@ -327,6 +359,23 @@ async function init(projectName, options) {
327
359
  fs3.removeSync(projectDir);
328
360
  }
329
361
  }
362
+ let packageManager = options.packageManager;
363
+ if (!packageManager) {
364
+ const { pm } = await inquirer2.prompt([
365
+ {
366
+ type: "list",
367
+ name: "pm",
368
+ message: "Select package manager:",
369
+ choices: [
370
+ { name: "pnpm (recommended)", value: "pnpm" },
371
+ { name: "npm", value: "npm" },
372
+ { name: "yarn", value: "yarn" }
373
+ ],
374
+ default: "pnpm"
375
+ }
376
+ ]);
377
+ packageManager = pm;
378
+ }
330
379
  const { selectedPackages } = await inquirer2.prompt([
331
380
  {
332
381
  type: "checkbox",
@@ -364,9 +413,22 @@ async function init(projectName, options) {
364
413
  process.exit(1);
365
414
  }
366
415
  const packageJsonPath = path3.join(projectDir, "package.json");
367
- let packageJsonContent = fs3.readFileSync(packageJsonPath, "utf-8");
368
- packageJsonContent = packageJsonContent.replace("{{PROJECT_NAME}}", projectName);
369
- fs3.writeFileSync(packageJsonPath, packageJsonContent);
416
+ const packageJson = fs3.readJsonSync(packageJsonPath);
417
+ packageJson.name = projectName;
418
+ delete packageJson.packageManager;
419
+ if (!features.includes("changesets")) {
420
+ delete packageJson.scripts["changeset"];
421
+ delete packageJson.scripts["version-packages"];
422
+ delete packageJson.scripts["release"];
423
+ delete packageJson.devDependencies["@changesets/cli"];
424
+ }
425
+ if (!features.includes("husky")) {
426
+ delete packageJson.scripts["prepare"];
427
+ delete packageJson.devDependencies["husky"];
428
+ delete packageJson.devDependencies["lint-staged"];
429
+ delete packageJson["lint-staged"];
430
+ }
431
+ fs3.writeJsonSync(packageJsonPath, packageJson, { spaces: 2 });
370
432
  const packagesToRemove = SHARED_PACKAGES.filter((pkg) => !selectedPackages.includes(pkg));
371
433
  if (packagesToRemove.length > 0) {
372
434
  const removeSpinner = ora2("Removing unselected packages...").start();
@@ -394,20 +456,19 @@ async function init(projectName, options) {
394
456
  if (!features.includes("vscode")) {
395
457
  fs3.removeSync(path3.join(projectDir, ".vscode"));
396
458
  }
397
- const packageJson = fs3.readJsonSync(packageJsonPath);
398
- if (!features.includes("changesets")) {
399
- delete packageJson.scripts["changeset"];
400
- delete packageJson.scripts["version-packages"];
401
- delete packageJson.scripts["release"];
402
- delete packageJson.devDependencies["@changesets/cli"];
403
- }
404
- if (!features.includes("husky")) {
405
- delete packageJson.scripts["prepare"];
406
- delete packageJson.devDependencies["husky"];
407
- delete packageJson.devDependencies["lint-staged"];
408
- delete packageJson["lint-staged"];
459
+ if (packageManager === "yarn") {
460
+ fs3.removeSync(path3.join(projectDir, "pnpm-workspace.yaml"));
461
+ fs3.removeSync(path3.join(projectDir, ".npmrc"));
462
+ const updatedPkg = fs3.readJsonSync(packageJsonPath);
463
+ updatedPkg.workspaces = ["apps/*", "packages/*"];
464
+ fs3.writeJsonSync(packageJsonPath, updatedPkg, { spaces: 2 });
465
+ } else if (packageManager === "npm") {
466
+ fs3.removeSync(path3.join(projectDir, "pnpm-workspace.yaml"));
467
+ fs3.removeSync(path3.join(projectDir, ".npmrc"));
468
+ const updatedPkg = fs3.readJsonSync(packageJsonPath);
469
+ updatedPkg.workspaces = ["apps/*", "packages/*"];
470
+ fs3.writeJsonSync(packageJsonPath, updatedPkg, { spaces: 2 });
409
471
  }
410
- fs3.writeJsonSync(packageJsonPath, packageJson, { spaces: 2 });
411
472
  if (!options.skipGit) {
412
473
  const gitSpinner = ora2("Initializing git repository...").start();
413
474
  try {
@@ -419,13 +480,21 @@ async function init(projectName, options) {
419
480
  gitSpinner.warn("Failed to initialize git repository");
420
481
  }
421
482
  }
483
+ const runCmd = getRunCommand(packageManager);
422
484
  if (!options.skipInstall) {
423
- const installSpinner = ora2("Installing dependencies...").start();
485
+ console.log(chalk3.blue(`
486
+ \u{1F4E6} Installing dependencies with ${packageManager}...
487
+ `));
424
488
  try {
425
- exec("pnpm install", projectDir);
426
- installSpinner.succeed("Dependencies installed");
489
+ execInherit(getInstallCommand(packageManager), projectDir);
490
+ console.log(chalk3.green("\n\u2713 Dependencies installed"));
427
491
  } catch {
428
- installSpinner.warn('Failed to install dependencies. Run "pnpm install" manually.');
492
+ console.log(
493
+ chalk3.yellow(
494
+ `
495
+ ! Failed to install dependencies. Run "${getInstallCommand(packageManager)}" manually.`
496
+ )
497
+ );
429
498
  }
430
499
  }
431
500
  console.log("\n" + chalk3.green.bold("\u2728 Project created successfully!\n"));
@@ -434,11 +503,11 @@ async function init(projectName, options) {
434
503
  console.log(chalk3.cyan(` cd ${projectName}`));
435
504
  }
436
505
  if (options.skipInstall) {
437
- console.log(chalk3.cyan(" pnpm install"));
506
+ console.log(chalk3.cyan(` ${getInstallCommand(packageManager)}`));
438
507
  }
439
- console.log(chalk3.cyan(" pnpm dev"));
508
+ console.log(chalk3.cyan(` ${runCmd} dev`));
440
509
  console.log("\nTo create an app:");
441
- console.log(chalk3.cyan(" pnpm generate:app <name> <port>"));
510
+ console.log(chalk3.cyan(` ${runCmd} generate:app <name> <port>`));
442
511
  console.log("\nTo update with latest features:");
443
512
  console.log(chalk3.cyan(" npx create-nexu update"));
444
513
  console.log("");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-nexu",
3
- "version": "1.0.2",
3
+ "version": "1.1.0",
4
4
  "description": "CLI to create and update Nexu monorepo projects",
5
5
  "author": "Nexu Team",
6
6
  "license": "MIT",
@@ -61,6 +61,7 @@ module.exports = {
61
61
  '*.config.js',
62
62
  '*.config.mjs',
63
63
  '.eslintrc.js',
64
+ '.lintstagedrc.cjs',
64
65
  'commitlint.config.js',
65
66
  ],
66
67
  };
@@ -15,7 +15,7 @@ jobs:
15
15
  runs-on: ubuntu-latest
16
16
  steps:
17
17
  - name: Checkout
18
- uses: actions/checkout@v4
18
+ uses: actions/checkout@v6
19
19
 
20
20
  - name: Quality Check
21
21
  uses: ./.github/actions/quality
@@ -30,7 +30,7 @@ jobs:
30
30
 
31
31
  steps:
32
32
  - name: Checkout
33
- uses: actions/checkout@v4
33
+ uses: actions/checkout@v6
34
34
 
35
35
  - name: Build
36
36
  uses: ./.github/actions/build
@@ -15,7 +15,7 @@ jobs:
15
15
  runs-on: ubuntu-latest
16
16
  steps:
17
17
  - name: Checkout
18
- uses: actions/checkout@v4
18
+ uses: actions/checkout@v6
19
19
 
20
20
  - name: Quality Check
21
21
  uses: ./.github/actions/quality
@@ -30,7 +30,7 @@ jobs:
30
30
 
31
31
  steps:
32
32
  - name: Checkout
33
- uses: actions/checkout@v4
33
+ uses: actions/checkout@v6
34
34
 
35
35
  - name: Build
36
36
  uses: ./.github/actions/build
@@ -15,7 +15,7 @@ jobs:
15
15
  runs-on: ubuntu-latest
16
16
  steps:
17
17
  - name: Checkout
18
- uses: actions/checkout@v4
18
+ uses: actions/checkout@v6
19
19
 
20
20
  - name: Quality Check
21
21
  uses: ./.github/actions/quality
@@ -30,7 +30,7 @@ jobs:
30
30
 
31
31
  steps:
32
32
  - name: Checkout
33
- uses: actions/checkout@v4
33
+ uses: actions/checkout@v6
34
34
 
35
35
  - name: Build
36
36
  uses: ./.github/actions/build
@@ -17,40 +17,29 @@
17
17
  "docker:dev": "docker-compose -f docker/docker-compose.dev.yml up",
18
18
  "docker:build": "docker-compose -f docker/docker-compose.prod.yml build",
19
19
  "docker:prod": "docker-compose -f docker/docker-compose.prod.yml up -d",
20
- "generate:app": "./scripts/generate-app.sh",
21
- "generate:template": "./scripts/generate-template.sh",
22
- "publish:cli": "./scripts/publish-cli.sh",
20
+ "generate:app": "node scripts/generate-app.mjs",
23
21
  "changeset": "changeset",
24
22
  "version-packages": "changeset version",
25
23
  "release": "changeset publish"
26
24
  },
27
25
  "devDependencies": {
28
26
  "@changesets/cli": "^2.27.0",
29
- "@commitlint/cli": "^19.0.0",
30
- "@commitlint/config-conventional": "^19.0.0",
27
+ "@commitlint/cli": "^20.3.1",
28
+ "@commitlint/config-conventional": "^20.3.1",
31
29
  "@typescript-eslint/eslint-plugin": "^7.0.0",
32
30
  "@typescript-eslint/parser": "^7.0.0",
33
31
  "@vitest/coverage-v8": "^2.0.0",
34
32
  "@vitest/ui": "^2.0.0",
35
33
  "eslint": "^8.57.0",
36
- "eslint-config-prettier": "^9.1.0",
34
+ "eslint-config-prettier": "^10.1.8",
37
35
  "eslint-plugin-import": "^2.29.0",
38
- "eslint-plugin-unused-imports": "^3.1.0",
36
+ "eslint-plugin-unused-imports": "^4.3.0",
39
37
  "husky": "^9.0.0",
40
- "lint-staged": "^15.0.0",
41
- "prettier": "^3.2.0",
42
- "prettier-plugin-tailwindcss": "^0.5.0",
43
- "turbo": "^2.0.0",
38
+ "lint-staged": "^16.2.7",
39
+ "prettier": "^3.8.0",
40
+ "prettier-plugin-tailwindcss": "^0.7.2",
41
+ "turbo": "^2.7.5",
44
42
  "typescript": "^5.4.0",
45
43
  "vitest": "^2.0.0"
46
- },
47
- "lint-staged": {
48
- "*.{ts,tsx,js,jsx}": [
49
- "eslint --fix",
50
- "prettier --write"
51
- ],
52
- "*.{json,md,yml,yaml}": [
53
- "prettier --write"
54
- ]
55
44
  }
56
45
  }
@@ -20,7 +20,7 @@
20
20
  "typecheck": "tsc --noEmit"
21
21
  },
22
22
  "devDependencies": {
23
- "@types/node": "^20.0.0",
23
+ "@types/node": "^25.0.9",
24
24
  "tsup": "^8.0.0",
25
25
  "typescript": "^5.4.0"
26
26
  }
@@ -25,14 +25,14 @@
25
25
  "typecheck": "tsc --noEmit"
26
26
  },
27
27
  "devDependencies": {
28
- "@types/react": "^18.2.0",
29
- "@types/react-dom": "^18.2.0",
30
- "react": "^18.2.0",
28
+ "@types/react": "^19.2.8",
29
+ "@types/react-dom": "^19.2.3",
30
+ "react": "^19.2.3",
31
31
  "tsup": "^8.0.0",
32
32
  "typescript": "^5.4.0"
33
33
  },
34
34
  "peerDependencies": {
35
- "react": "^18.2.0",
35
+ "react": "^19.2.3",
36
36
  "react-dom": "^18.2.0"
37
37
  }
38
38
  }
@@ -31,19 +31,12 @@ export const Button: React.FC<ButtonProps> = ({
31
31
  }) => {
32
32
  return (
33
33
  <button
34
- className={`
35
- inline-flex items-center justify-center rounded-lg font-medium
36
- transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2
37
- disabled:cursor-not-allowed disabled:opacity-50
38
- ${variantStyles[variant]}
39
- ${sizeStyles[size]}
40
- ${className}
41
- `}
34
+ className={`inline-flex items-center justify-center rounded-lg font-medium transition-colors duration-200 focus:ring-2 focus:ring-offset-2 focus:outline-none disabled:cursor-not-allowed disabled:opacity-50 ${variantStyles[variant]} ${sizeStyles[size]} ${className} `}
42
35
  disabled={disabled || isLoading}
43
36
  {...props}
44
37
  >
45
38
  {isLoading && (
46
- <svg className="-ml-1 mr-2 h-4 w-4 animate-spin" fill="none" viewBox="0 0 24 24">
39
+ <svg className="mr-2 -ml-1 h-4 w-4 animate-spin" fill="none" viewBox="0 0 24 24">
47
40
  <circle
48
41
  className="opacity-25"
49
42
  cx="12"
@@ -28,12 +28,7 @@ export const Card: React.FC<CardProps> = ({
28
28
  }) => {
29
29
  return (
30
30
  <div
31
- className={`
32
- rounded-lg
33
- ${variantStyles[variant]}
34
- ${paddingStyles[padding]}
35
- ${className}
36
- `}
31
+ className={`rounded-lg ${variantStyles[variant]} ${paddingStyles[padding]} ${className} `}
37
32
  {...props}
38
33
  >
39
34
  {children}
@@ -20,13 +20,7 @@ export const Input = React.forwardRef<HTMLInputElement, InputProps>(
20
20
  <input
21
21
  ref={ref}
22
22
  id={inputId}
23
- className={`
24
- w-full rounded-lg border px-3 py-2 shadow-sm
25
- focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-500
26
- disabled:cursor-not-allowed disabled:bg-gray-100
27
- ${error ? 'border-red-500 focus:border-red-500 focus:ring-red-500' : 'border-gray-300'}
28
- ${className}
29
- `}
23
+ className={`w-full rounded-lg border px-3 py-2 shadow-sm focus:border-blue-500 focus:ring-2 focus:ring-blue-500 focus:outline-none disabled:cursor-not-allowed disabled:bg-gray-100 ${error ? 'border-red-500 focus:border-red-500 focus:ring-red-500' : 'border-gray-300'} ${className} `}
30
24
  aria-invalid={error ? 'true' : 'false'}
31
25
  aria-describedby={
32
26
  error ? `${inputId}-error` : helperText ? `${inputId}-helper` : undefined
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { execSync } from 'child_process';
4
+ import { fileURLToPath } from 'url';
5
+ import path from 'path';
6
+ import { detectPackageManager, getRunCommand } from './lib/package-manager.mjs';
7
+
8
+ const __filename = fileURLToPath(import.meta.url);
9
+ const __dirname = path.dirname(__filename);
10
+ const ROOT_DIR = path.resolve(__dirname, '..');
11
+
12
+ const environment = process.argv[2] || 'staging';
13
+ const pm = detectPackageManager(ROOT_DIR);
14
+ const runCmd = getRunCommand(pm);
15
+
16
+ console.log(`🚀 Deploying to ${environment}...`);
17
+ console.log(`📦 Using package manager: ${pm}`);
18
+
19
+ function run(cmd) {
20
+ console.log(`> ${cmd}`);
21
+ execSync(cmd, { stdio: 'inherit', cwd: ROOT_DIR });
22
+ }
23
+
24
+ // Build all packages
25
+ console.log('🔨 Building packages...');
26
+ run(`${runCmd} build`);
27
+
28
+ // Build Docker images
29
+ console.log('🐳 Building Docker images...');
30
+ run('docker-compose -f docker/docker-compose.prod.yml build');
31
+
32
+ // Push images (if deploying to production)
33
+ if (environment === 'production') {
34
+ console.log('📤 Pushing images to registry...');
35
+ run('docker-compose -f docker/docker-compose.prod.yml push');
36
+ }
37
+
38
+ console.log('');
39
+ console.log(`✅ Deployment to ${environment} complete!`);
40
+ console.log('');
@@ -0,0 +1,195 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from 'fs';
4
+ import path from 'path';
5
+ import { fileURLToPath } from 'url';
6
+
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = path.dirname(__filename);
9
+
10
+ // Colors for console output
11
+ const colors = {
12
+ red: '\x1b[31m',
13
+ green: '\x1b[32m',
14
+ blue: '\x1b[34m',
15
+ yellow: '\x1b[33m',
16
+ reset: '\x1b[0m',
17
+ };
18
+
19
+ const log = {
20
+ info: (msg) => console.log(`${colors.blue}${msg}${colors.reset}`),
21
+ success: (msg) => console.log(`${colors.green}${msg}${colors.reset}`),
22
+ warn: (msg) => console.log(`${colors.yellow}${msg}${colors.reset}`),
23
+ error: (msg) => console.log(`${colors.red}${msg}${colors.reset}`),
24
+ };
25
+
26
+ // Get directories
27
+ const ROOT_DIR = path.resolve(__dirname, '..');
28
+ const APPS_DIR = path.join(ROOT_DIR, 'apps');
29
+ const DOCKER_DIR = path.join(ROOT_DIR, 'docker');
30
+
31
+ // Get arguments
32
+ const args = process.argv.slice(2);
33
+ const appName = args[0];
34
+ const port = args[1] || '3000';
35
+
36
+ // Show usage
37
+ if (!appName) {
38
+ log.info('Usage: pnpm generate:app <app-name> [port]');
39
+ console.log('');
40
+ log.info('Examples:');
41
+ console.log(' pnpm generate:app web 3000');
42
+ console.log(' pnpm generate:app api 4000');
43
+ process.exit(1);
44
+ }
45
+
46
+ const APP_DIR = path.join(APPS_DIR, appName);
47
+
48
+ // Check if app already exists
49
+ if (fs.existsSync(APP_DIR)) {
50
+ log.error(`Error: L'application '${appName}' existe déjà dans apps/`);
51
+ process.exit(1);
52
+ }
53
+
54
+ log.info(`Creating app: ${appName} (port: ${port})`);
55
+
56
+ // Create app directory structure
57
+ fs.mkdirSync(path.join(APP_DIR, 'src'), { recursive: true });
58
+ fs.mkdirSync(path.join(APP_DIR, 'docker'), { recursive: true });
59
+
60
+ // Create Dockerfile
61
+ const dockerfile = `# ====== Base ======
62
+ FROM node:20-alpine AS base
63
+ RUN corepack enable && corepack prepare pnpm@9.0.0 --activate
64
+ WORKDIR /app
65
+
66
+ # ====== Dependencies ======
67
+ FROM base AS deps
68
+ COPY pnpm-lock.yaml pnpm-workspace.yaml package.json ./
69
+ COPY apps/${appName}/package.json ./apps/${appName}/
70
+ COPY packages/*/package.json ./packages/
71
+ RUN pnpm install --frozen-lockfile
72
+
73
+ # ====== Development ======
74
+ FROM base AS development
75
+ COPY --from=deps /app/node_modules ./node_modules
76
+ COPY . .
77
+ WORKDIR /app/apps/${appName}
78
+ EXPOSE ${port}
79
+ CMD ["pnpm", "dev"]
80
+
81
+ # ====== Builder ======
82
+ FROM base AS builder
83
+ COPY --from=deps /app/node_modules ./node_modules
84
+ COPY . .
85
+ RUN pnpm turbo build --filter=@repo/${appName}
86
+
87
+ # ====== Production ======
88
+ FROM node:20-alpine AS production
89
+ WORKDIR /app
90
+ ENV NODE_ENV=production
91
+
92
+ COPY --from=builder /app/apps/${appName}/dist ./dist
93
+ COPY --from=builder /app/apps/${appName}/package.json ./
94
+
95
+ RUN npm install --omit=dev
96
+
97
+ EXPOSE ${port}
98
+ CMD ["node", "dist/index.js"]
99
+ `;
100
+
101
+ fs.writeFileSync(path.join(APP_DIR, 'docker', 'Dockerfile'), dockerfile);
102
+
103
+ // Create docker-compose.yml for the app
104
+ const dockerCompose = `services:
105
+ ${appName}:
106
+ build:
107
+ context: ../..
108
+ dockerfile: apps/${appName}/docker/Dockerfile
109
+ target: development
110
+ ports:
111
+ - "${port}:${port}"
112
+ environment:
113
+ - NODE_ENV=development
114
+ - PORT=${port}
115
+ volumes:
116
+ - ../../apps/${appName}:/app/apps/${appName}
117
+ - ../../packages:/app/packages
118
+ - /app/node_modules
119
+ - /app/apps/${appName}/node_modules
120
+ command: pnpm dev
121
+ `;
122
+
123
+ fs.writeFileSync(path.join(APP_DIR, 'docker-compose.yml'), dockerCompose);
124
+
125
+ // Create docker-compose.prod.yml for the app
126
+ const dockerComposeProd = `services:
127
+ ${appName}:
128
+ build:
129
+ context: ../..
130
+ dockerfile: apps/${appName}/docker/Dockerfile
131
+ target: production
132
+ ports:
133
+ - "${port}:${port}"
134
+ environment:
135
+ - NODE_ENV=production
136
+ - PORT=${port}
137
+ restart: unless-stopped
138
+ `;
139
+
140
+ fs.writeFileSync(path.join(APP_DIR, 'docker-compose.prod.yml'), dockerComposeProd);
141
+
142
+ // Update main docker-compose.yml
143
+ function updateMainCompose() {
144
+ const mainComposePath = path.join(DOCKER_DIR, 'docker-compose.yml');
145
+ const includePath = `../apps/${appName}/docker-compose.yml`;
146
+
147
+ // Create docker directory if it doesn't exist
148
+ if (!fs.existsSync(DOCKER_DIR)) {
149
+ fs.mkdirSync(DOCKER_DIR, { recursive: true });
150
+ }
151
+
152
+ // Create main compose if it doesn't exist
153
+ if (!fs.existsSync(mainComposePath)) {
154
+ const content = `# Main docker-compose - includes all apps
155
+ # Each app has its own docker-compose.yml in apps/<app-name>/
156
+
157
+ include:
158
+ - path: ${includePath}
159
+ `;
160
+ fs.writeFileSync(mainComposePath, content);
161
+ return;
162
+ }
163
+
164
+ // Read existing file
165
+ let content = fs.readFileSync(mainComposePath, 'utf-8');
166
+
167
+ // Check if app is already included
168
+ if (content.includes(`apps/${appName}/docker-compose.yml`)) {
169
+ return;
170
+ }
171
+
172
+ // If include is empty array [], replace it
173
+ if (content.includes('include: []')) {
174
+ content = content.replace('include: []', `include:\n - path: ${includePath}`);
175
+ fs.writeFileSync(mainComposePath, content);
176
+ } else {
177
+ // Append to existing include list
178
+ content = content.trimEnd() + `\n - path: ${includePath}\n`;
179
+ fs.writeFileSync(mainComposePath, content);
180
+ }
181
+ }
182
+
183
+ updateMainCompose();
184
+
185
+ log.success(`\n✓ Created app: apps/${appName}`);
186
+ console.log('');
187
+ log.warn('Files created:');
188
+ console.log(` - apps/${appName}/docker/Dockerfile`);
189
+ console.log(` - apps/${appName}/docker-compose.yml`);
190
+ console.log(` - apps/${appName}/docker-compose.prod.yml`);
191
+ console.log('');
192
+ log.warn('Commands:');
193
+ console.log(` Dev (app only): cd apps/${appName} && docker compose up`);
194
+ console.log(' Dev (all apps): pnpm docker:dev');
195
+ console.log(` Prod (app only): cd apps/${appName} && docker compose -f docker-compose.prod.yml up -d`);
@@ -0,0 +1,186 @@
1
+ /**
2
+ * Package manager detection and utilities
3
+ * Supports: npm, yarn, pnpm
4
+ */
5
+
6
+ import fs from 'fs';
7
+ import path from 'path';
8
+ import { execSync, spawnSync } from 'child_process';
9
+
10
+ /**
11
+ * Detect which package manager is being used in a project
12
+ * @param {string} projectDir - The project directory to check
13
+ * @returns {'pnpm' | 'yarn' | 'npm'} The detected package manager
14
+ */
15
+ export function detectPackageManager(projectDir = process.cwd()) {
16
+ // Check for lock files
17
+ if (fs.existsSync(path.join(projectDir, 'pnpm-lock.yaml'))) {
18
+ return 'pnpm';
19
+ }
20
+ if (fs.existsSync(path.join(projectDir, 'yarn.lock'))) {
21
+ return 'yarn';
22
+ }
23
+ if (fs.existsSync(path.join(projectDir, 'package-lock.json'))) {
24
+ return 'npm';
25
+ }
26
+
27
+ // Check packageManager field in package.json
28
+ const packageJsonPath = path.join(projectDir, 'package.json');
29
+ if (fs.existsSync(packageJsonPath)) {
30
+ try {
31
+ const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
32
+ if (pkg.packageManager) {
33
+ if (pkg.packageManager.startsWith('pnpm')) return 'pnpm';
34
+ if (pkg.packageManager.startsWith('yarn')) return 'yarn';
35
+ if (pkg.packageManager.startsWith('npm')) return 'npm';
36
+ }
37
+ } catch {
38
+ // Ignore parse errors
39
+ }
40
+ }
41
+
42
+ // Check npm_config_user_agent environment variable (set by package managers)
43
+ const userAgent = process.env.npm_config_user_agent || '';
44
+ if (userAgent.includes('pnpm')) return 'pnpm';
45
+ if (userAgent.includes('yarn')) return 'yarn';
46
+
47
+ // Default to npm
48
+ return 'npm';
49
+ }
50
+
51
+ /**
52
+ * Check if a package manager is installed
53
+ * @param {string} pm - Package manager name
54
+ * @returns {boolean}
55
+ */
56
+ export function isPackageManagerInstalled(pm) {
57
+ try {
58
+ const result = spawnSync(process.platform === 'win32' ? 'where' : 'which', [pm], {
59
+ stdio: 'pipe',
60
+ });
61
+ return result.status === 0;
62
+ } catch {
63
+ return false;
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Get the run command for a package manager
69
+ * @param {'pnpm' | 'yarn' | 'npm'} pm
70
+ * @returns {string}
71
+ */
72
+ export function getRunCommand(pm) {
73
+ switch (pm) {
74
+ case 'pnpm':
75
+ return 'pnpm';
76
+ case 'yarn':
77
+ return 'yarn';
78
+ case 'npm':
79
+ default:
80
+ return 'npm run';
81
+ }
82
+ }
83
+
84
+ /**
85
+ * Get the execute command (for running binaries)
86
+ * @param {'pnpm' | 'yarn' | 'npm'} pm
87
+ * @returns {string}
88
+ */
89
+ export function getExecCommand(pm) {
90
+ switch (pm) {
91
+ case 'pnpm':
92
+ return 'pnpm exec';
93
+ case 'yarn':
94
+ return 'yarn';
95
+ case 'npm':
96
+ default:
97
+ return 'npx';
98
+ }
99
+ }
100
+
101
+ /**
102
+ * Get the install command
103
+ * @param {'pnpm' | 'yarn' | 'npm'} pm
104
+ * @param {object} options
105
+ * @param {boolean} options.frozen - Use frozen lockfile
106
+ * @returns {string}
107
+ */
108
+ export function getInstallCommand(pm, options = {}) {
109
+ const { frozen = false } = options;
110
+
111
+ switch (pm) {
112
+ case 'pnpm':
113
+ return frozen ? 'pnpm install --frozen-lockfile' : 'pnpm install';
114
+ case 'yarn':
115
+ return frozen ? 'yarn install --frozen-lockfile' : 'yarn install';
116
+ case 'npm':
117
+ default:
118
+ return frozen ? 'npm ci' : 'npm install';
119
+ }
120
+ }
121
+
122
+ /**
123
+ * Get the add dependency command
124
+ * @param {'pnpm' | 'yarn' | 'npm'} pm
125
+ * @param {string} pkg - Package name
126
+ * @param {object} options
127
+ * @param {boolean} options.dev - Install as dev dependency
128
+ * @returns {string}
129
+ */
130
+ export function getAddCommand(pm, pkg, options = {}) {
131
+ const { dev = false } = options;
132
+
133
+ switch (pm) {
134
+ case 'pnpm':
135
+ return dev ? `pnpm add -D ${pkg}` : `pnpm add ${pkg}`;
136
+ case 'yarn':
137
+ return dev ? `yarn add -D ${pkg}` : `yarn add ${pkg}`;
138
+ case 'npm':
139
+ default:
140
+ return dev ? `npm install -D ${pkg}` : `npm install ${pkg}`;
141
+ }
142
+ }
143
+
144
+ /**
145
+ * Get workspace filter command
146
+ * @param {'pnpm' | 'yarn' | 'npm'} pm
147
+ * @param {string} workspace - Workspace name
148
+ * @returns {string}
149
+ */
150
+ export function getWorkspaceFilter(pm, workspace) {
151
+ switch (pm) {
152
+ case 'pnpm':
153
+ return `--filter=${workspace}`;
154
+ case 'yarn':
155
+ return `workspace ${workspace}`;
156
+ case 'npm':
157
+ default:
158
+ return `-w ${workspace}`;
159
+ }
160
+ }
161
+
162
+ /**
163
+ * Run a command with the detected package manager
164
+ * @param {string} script - Script to run
165
+ * @param {object} options
166
+ * @param {string} options.cwd - Working directory
167
+ * @param {'pnpm' | 'yarn' | 'npm'} options.pm - Package manager (auto-detected if not provided)
168
+ */
169
+ export function runScript(script, options = {}) {
170
+ const { cwd = process.cwd(), pm = detectPackageManager(cwd) } = options;
171
+ const runCmd = getRunCommand(pm);
172
+ const cmd = `${runCmd} ${script}`;
173
+ console.log(`> ${cmd}`);
174
+ execSync(cmd, { stdio: 'inherit', cwd });
175
+ }
176
+
177
+ export default {
178
+ detectPackageManager,
179
+ isPackageManagerInstalled,
180
+ getRunCommand,
181
+ getExecCommand,
182
+ getInstallCommand,
183
+ getAddCommand,
184
+ getWorkspaceFilter,
185
+ runScript,
186
+ };
@@ -0,0 +1,102 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from 'fs';
4
+ import path from 'path';
5
+ import { execSync } from 'child_process';
6
+ import { fileURLToPath } from 'url';
7
+ import {
8
+ detectPackageManager,
9
+ isPackageManagerInstalled,
10
+ getInstallCommand,
11
+ getRunCommand,
12
+ } from './lib/package-manager.mjs';
13
+
14
+ const __filename = fileURLToPath(import.meta.url);
15
+ const __dirname = path.dirname(__filename);
16
+ const ROOT_DIR = path.resolve(__dirname, '..');
17
+
18
+ // Detect package manager
19
+ const pm = detectPackageManager(ROOT_DIR);
20
+ const runCmd = getRunCommand(pm);
21
+
22
+ console.log('🚀 Setting up monorepo...');
23
+ console.log(`📦 Using package manager: ${pm}`);
24
+
25
+ function run(cmd) {
26
+ console.log(`> ${cmd}`);
27
+ execSync(cmd, { stdio: 'inherit', cwd: ROOT_DIR });
28
+ }
29
+
30
+ // Check if package manager is installed
31
+ if (!isPackageManagerInstalled(pm)) {
32
+ console.log(`📦 Installing ${pm}...`);
33
+ if (pm === 'pnpm') {
34
+ run('npm install -g pnpm');
35
+ } else if (pm === 'yarn') {
36
+ run('npm install -g yarn');
37
+ }
38
+ }
39
+
40
+ // Install dependencies
41
+ console.log('📦 Installing dependencies...');
42
+ run(getInstallCommand(pm));
43
+
44
+ // Setup husky
45
+ console.log('🐶 Setting up Husky...');
46
+ run(`${runCmd} prepare`);
47
+
48
+ // Create environment files
49
+ console.log('🔐 Creating environment files...');
50
+
51
+ const envPath = path.join(ROOT_DIR, '.env');
52
+ if (!fs.existsSync(envPath)) {
53
+ const envContent = `# Database
54
+ DATABASE_URL=postgresql://postgres:postgres@localhost:5432/nexu
55
+ DB_USER=postgres
56
+ DB_PASSWORD=postgres
57
+ DB_NAME=nexu
58
+
59
+ # API
60
+ API_URL=http://localhost:4000
61
+
62
+ # App
63
+ NODE_ENV=development
64
+ `;
65
+ fs.writeFileSync(envPath, envContent);
66
+ console.log('✅ Created .env file');
67
+ }
68
+
69
+ const webEnvPath = path.join(ROOT_DIR, 'apps', 'web', '.env.local');
70
+ const webAppsDir = path.join(ROOT_DIR, 'apps', 'web');
71
+ if (fs.existsSync(webAppsDir) && !fs.existsSync(webEnvPath)) {
72
+ const webEnvContent = `NEXT_PUBLIC_API_URL=http://localhost:4000
73
+ `;
74
+ fs.writeFileSync(webEnvPath, webEnvContent);
75
+ console.log('✅ Created apps/web/.env.local');
76
+ }
77
+
78
+ const apiEnvPath = path.join(ROOT_DIR, 'apps', 'api', '.env');
79
+ const apiAppsDir = path.join(ROOT_DIR, 'apps', 'api');
80
+ if (fs.existsSync(apiAppsDir) && !fs.existsSync(apiEnvPath)) {
81
+ const apiEnvContent = `DATABASE_URL=postgresql://postgres:postgres@localhost:5432/nexu
82
+ PORT=4000
83
+ NODE_ENV=development
84
+ `;
85
+ fs.writeFileSync(apiEnvPath, apiEnvContent);
86
+ console.log('✅ Created apps/api/.env');
87
+ }
88
+
89
+ // Build packages
90
+ console.log('🔨 Building packages...');
91
+ run(`${runCmd} build`);
92
+
93
+ console.log('');
94
+ console.log('✅ Setup complete!');
95
+ console.log('');
96
+ console.log('Available commands:');
97
+ console.log(` ${runCmd} dev - Start development servers`);
98
+ console.log(` ${runCmd} build - Build all packages`);
99
+ console.log(` ${runCmd} lint - Run linting`);
100
+ console.log(` ${runCmd} test - Run tests`);
101
+ console.log(` ${runCmd} docker:dev - Start with Docker (dev)`);
102
+ console.log('');
@@ -1,25 +0,0 @@
1
- #!/bin/bash
2
-
3
- set -e
4
-
5
- ENVIRONMENT=${1:-staging}
6
-
7
- echo "🚀 Deploying to $ENVIRONMENT..."
8
-
9
- # Build all packages
10
- echo "🔨 Building packages..."
11
- pnpm build
12
-
13
- # Build Docker images
14
- echo "🐳 Building Docker images..."
15
- docker-compose -f docker/docker-compose.prod.yml build
16
-
17
- # Push images (if deploying to production)
18
- if [ "$ENVIRONMENT" = "production" ]; then
19
- echo "📤 Pushing images to registry..."
20
- docker-compose -f docker/docker-compose.prod.yml push
21
- fi
22
-
23
- echo ""
24
- echo "✅ Deployment to $ENVIRONMENT complete!"
25
- echo ""
@@ -1,166 +0,0 @@
1
- #!/bin/bash
2
-
3
- set -e
4
-
5
- # Colors
6
- RED='\033[0;31m'
7
- GREEN='\033[0;32m'
8
- BLUE='\033[0;34m'
9
- YELLOW='\033[1;33m'
10
- NC='\033[0m'
11
-
12
- # Get script directory
13
- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
14
- ROOT_DIR="$(dirname "$SCRIPT_DIR")"
15
- APPS_DIR="$ROOT_DIR/apps"
16
- DOCKER_DIR="$ROOT_DIR/docker"
17
-
18
- # Show usage
19
- if [ -z "$1" ]; then
20
- echo -e "${BLUE}Usage:${NC} pnpm generate:app <app-name> [port]"
21
- echo ""
22
- echo -e "${BLUE}Exemples:${NC}"
23
- echo " pnpm generate:app web 3000"
24
- echo " pnpm generate:app api 4000"
25
- exit 1
26
- fi
27
-
28
- APP_NAME=$1
29
- PORT=${2:-3000}
30
- APP_DIR="$APPS_DIR/$APP_NAME"
31
-
32
- # Check if app already exists
33
- if [ -d "$APP_DIR" ]; then
34
- echo -e "${RED}Error:${NC} L'application '$APP_NAME' existe déjà dans apps/"
35
- exit 1
36
- fi
37
-
38
- echo -e "${BLUE}Creating${NC} app: $APP_NAME (port: $PORT)"
39
-
40
- # Create app directory structure
41
- mkdir -p "$APP_DIR/src"
42
- mkdir -p "$APP_DIR/docker"
43
-
44
- # Create Dockerfile
45
- cat > "$APP_DIR/docker/Dockerfile" << EOF
46
- # ====== Base ======
47
- FROM node:20-alpine AS base
48
- RUN corepack enable && corepack prepare pnpm@9.0.0 --activate
49
- WORKDIR /app
50
-
51
- # ====== Dependencies ======
52
- FROM base AS deps
53
- COPY pnpm-lock.yaml pnpm-workspace.yaml package.json ./
54
- COPY apps/$APP_NAME/package.json ./apps/$APP_NAME/
55
- COPY packages/*/package.json ./packages/
56
- RUN pnpm install --frozen-lockfile
57
-
58
- # ====== Development ======
59
- FROM base AS development
60
- COPY --from=deps /app/node_modules ./node_modules
61
- COPY . .
62
- WORKDIR /app/apps/$APP_NAME
63
- EXPOSE $PORT
64
- CMD ["pnpm", "dev"]
65
-
66
- # ====== Builder ======
67
- FROM base AS builder
68
- COPY --from=deps /app/node_modules ./node_modules
69
- COPY . .
70
- RUN pnpm turbo build --filter=@repo/$APP_NAME
71
-
72
- # ====== Production ======
73
- FROM node:20-alpine AS production
74
- WORKDIR /app
75
- ENV NODE_ENV=production
76
-
77
- COPY --from=builder /app/apps/$APP_NAME/dist ./dist
78
- COPY --from=builder /app/apps/$APP_NAME/package.json ./
79
-
80
- RUN npm install --omit=dev
81
-
82
- EXPOSE $PORT
83
- CMD ["node", "dist/index.js"]
84
- EOF
85
-
86
- # Create docker-compose.yml for the app
87
- cat > "$APP_DIR/docker-compose.yml" << EOF
88
- services:
89
- $APP_NAME:
90
- build:
91
- context: ../..
92
- dockerfile: apps/$APP_NAME/docker/Dockerfile
93
- target: development
94
- ports:
95
- - "$PORT:$PORT"
96
- environment:
97
- - NODE_ENV=development
98
- - PORT=$PORT
99
- volumes:
100
- - ../../apps/$APP_NAME:/app/apps/$APP_NAME
101
- - ../../packages:/app/packages
102
- - /app/node_modules
103
- - /app/apps/$APP_NAME/node_modules
104
- command: pnpm dev
105
- EOF
106
-
107
- # Create docker-compose.prod.yml for the app
108
- cat > "$APP_DIR/docker-compose.prod.yml" << EOF
109
- services:
110
- $APP_NAME:
111
- build:
112
- context: ../..
113
- dockerfile: apps/$APP_NAME/docker/Dockerfile
114
- target: production
115
- ports:
116
- - "$PORT:$PORT"
117
- environment:
118
- - NODE_ENV=production
119
- - PORT=$PORT
120
- restart: unless-stopped
121
- EOF
122
-
123
- # Update main docker-compose.yml
124
- update_main_compose() {
125
- MAIN_COMPOSE="$DOCKER_DIR/docker-compose.yml"
126
- INCLUDE_PATH="../apps/$APP_NAME/docker-compose.yml"
127
-
128
- # Create main compose if it doesn't exist
129
- if [ ! -f "$MAIN_COMPOSE" ]; then
130
- cat > "$MAIN_COMPOSE" << MAINEOF
131
- # Main docker-compose - includes all apps
132
- # Each app has its own docker-compose.yml in apps/<app-name>/
133
-
134
- include:
135
- - path: $INCLUDE_PATH
136
- MAINEOF
137
- return
138
- fi
139
-
140
- # Check if app is already included
141
- if grep -q "apps/$APP_NAME/docker-compose.yml" "$MAIN_COMPOSE" 2>/dev/null; then
142
- return
143
- fi
144
-
145
- # If include is empty array [], replace it
146
- if grep -q "include: \[\]" "$MAIN_COMPOSE"; then
147
- sed -i '' "s|include: \[\]|include:\n - path: $INCLUDE_PATH|" "$MAIN_COMPOSE"
148
- else
149
- # Append to existing include list
150
- echo " - path: $INCLUDE_PATH" >> "$MAIN_COMPOSE"
151
- fi
152
- }
153
-
154
- update_main_compose
155
-
156
- echo -e "${GREEN}✓${NC} Created app: apps/$APP_NAME"
157
- echo ""
158
- echo -e "${YELLOW}Files created:${NC}"
159
- echo " - apps/$APP_NAME/docker/Dockerfile"
160
- echo " - apps/$APP_NAME/docker-compose.yml"
161
- echo " - apps/$APP_NAME/docker-compose.prod.yml"
162
- echo ""
163
- echo -e "${YELLOW}Commands:${NC}"
164
- echo " Dev (app only): cd apps/$APP_NAME && docker compose up"
165
- echo " Dev (all apps): pnpm docker:dev"
166
- echo " Prod (app only): cd apps/$APP_NAME && docker compose -f docker-compose.prod.yml up -d"
@@ -1,95 +0,0 @@
1
- #!/bin/bash
2
-
3
- # Publish create-nexu CLI to npm
4
- # This script generates the template, builds, and publishes
5
-
6
- set -e
7
-
8
- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
9
- ROOT_DIR="$(dirname "$SCRIPT_DIR")"
10
- CLI_DIR="$ROOT_DIR/create-nexu"
11
-
12
- echo "📦 Publishing create-nexu..."
13
- echo ""
14
-
15
- # Step 1: Generate template
16
- echo "1️⃣ Generating template..."
17
- "$SCRIPT_DIR/generate-template.sh"
18
- echo ""
19
-
20
- # Step 2: Build CLI
21
- echo "2️⃣ Building CLI..."
22
- cd "$CLI_DIR"
23
- pnpm build
24
- echo ""
25
-
26
- # Step 3: Run checks
27
- echo "3️⃣ Running checks..."
28
- pnpm typecheck
29
- pnpm lint
30
- echo "✅ All checks passed"
31
- echo ""
32
-
33
- # Step 4: Version bump
34
- CURRENT_VERSION=$(node -p "require('./package.json').version")
35
- echo "4️⃣ Current version: $CURRENT_VERSION"
36
- echo ""
37
- echo "Select version bump:"
38
- echo " 1) patch (bug fixes)"
39
- echo " 2) minor (new features)"
40
- echo " 3) major (breaking changes)"
41
- echo " 4) skip (keep current version)"
42
- echo ""
43
- read -p "Choice [1-4]: " -n 1 -r VERSION_CHOICE
44
- echo ""
45
-
46
- case $VERSION_CHOICE in
47
- 1)
48
- npm version patch --no-git-tag-version
49
- ;;
50
- 2)
51
- npm version minor --no-git-tag-version
52
- ;;
53
- 3)
54
- npm version major --no-git-tag-version
55
- ;;
56
- 4)
57
- echo "Keeping version $CURRENT_VERSION"
58
- ;;
59
- *)
60
- echo "Invalid choice, keeping current version"
61
- ;;
62
- esac
63
-
64
- NEW_VERSION=$(node -p "require('./package.json').version")
65
- echo ""
66
- echo "Version: $NEW_VERSION"
67
- echo ""
68
-
69
- # Step 5: Show package info
70
- echo "5️⃣ Package info:"
71
- cat package.json | grep -E '"name"|"version"'
72
- echo ""
73
-
74
- # Step 6: Confirm publish
75
- read -p "Publish v$NEW_VERSION to npm? (y/N) " -n 1 -r
76
- echo ""
77
-
78
- if [[ $REPLY =~ ^[Yy]$ ]]; then
79
- echo "6️⃣ Publishing to npm..."
80
- npm publish --access public
81
- echo ""
82
- echo "✅ Published create-nexu@$NEW_VERSION successfully!"
83
- echo ""
84
- echo "Users can now run:"
85
- echo " npm create nexu my-app"
86
- echo " # or"
87
- echo " npx create-nexu my-app"
88
- else
89
- echo "❌ Publish cancelled"
90
- # Revert version if changed
91
- if [ "$CURRENT_VERSION" != "$NEW_VERSION" ]; then
92
- npm version "$CURRENT_VERSION" --no-git-tag-version --allow-same-version
93
- echo "Version reverted to $CURRENT_VERSION"
94
- fi
95
- fi
@@ -1,70 +0,0 @@
1
- #!/bin/bash
2
-
3
- set -e
4
-
5
- echo "🚀 Setting up monorepo..."
6
-
7
- # Check if pnpm is installed
8
- if ! command -v pnpm &> /dev/null; then
9
- echo "📦 Installing pnpm..."
10
- npm install -g pnpm
11
- fi
12
-
13
- # Install dependencies
14
- echo "📦 Installing dependencies..."
15
- pnpm install
16
-
17
- # Setup husky
18
- echo "🐶 Setting up Husky..."
19
- pnpm prepare
20
-
21
- # Create environment files
22
- echo "🔐 Creating environment files..."
23
-
24
- if [ ! -f ".env" ]; then
25
- cat > .env << EOF
26
- # Database
27
- DATABASE_URL=postgresql://postgres:postgres@localhost:5432/nexu
28
- DB_USER=postgres
29
- DB_PASSWORD=postgres
30
- DB_NAME=nexu
31
-
32
- # API
33
- API_URL=http://localhost:4000
34
-
35
- # App
36
- NODE_ENV=development
37
- EOF
38
- echo "✅ Created .env file"
39
- fi
40
-
41
- if [ ! -f "apps/web/.env.local" ]; then
42
- cat > apps/web/.env.local << EOF
43
- NEXT_PUBLIC_API_URL=http://localhost:4000
44
- EOF
45
- echo "✅ Created apps/web/.env.local"
46
- fi
47
-
48
- if [ ! -f "apps/api/.env" ]; then
49
- cat > apps/api/.env << EOF
50
- DATABASE_URL=postgresql://postgres:postgres@localhost:5432/nexu
51
- PORT=4000
52
- NODE_ENV=development
53
- EOF
54
- echo "✅ Created apps/api/.env"
55
- fi
56
-
57
- # Build packages
58
- echo "🔨 Building packages..."
59
- pnpm build
60
-
61
- echo ""
62
- echo "✅ Setup complete!"
63
- echo ""
64
- echo "Available commands:"
65
- echo " pnpm dev - Start development servers"
66
- echo " pnpm build - Build all packages"
67
- echo " pnpm lint - Run linting"
68
- echo " pnpm test - Run tests"
69
- echo " pnpm docker:dev - Start with Docker (dev)"
70
- echo ""