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 +92 -23
- package/package.json +1 -1
- package/templates/default/.eslintrc.js +1 -0
- package/templates/default/.github/workflows/deploy-dev.yml +2 -2
- package/templates/default/.github/workflows/deploy-prod.yml +2 -2
- package/templates/default/.github/workflows/deploy-rec.yml +2 -2
- package/templates/default/package.json +9 -20
- package/templates/default/packages/logger/package.json +1 -1
- package/templates/default/packages/ui/package.json +4 -4
- package/templates/default/packages/ui/src/components/Button.tsx +2 -9
- package/templates/default/packages/ui/src/components/Card.tsx +1 -6
- package/templates/default/packages/ui/src/components/Input.tsx +1 -7
- package/templates/default/scripts/deploy.mjs +40 -0
- package/templates/default/scripts/generate-app.mjs +195 -0
- package/templates/default/scripts/lib/package-manager.mjs +186 -0
- package/templates/default/scripts/setup.mjs +102 -0
- package/templates/default/scripts/deploy.sh +0 -25
- package/templates/default/scripts/generate-app.sh +0 -166
- package/templates/default/scripts/publish-cli.sh +0 -95
- package/templates/default/scripts/setup.sh +0 -70
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
|
-
|
|
368
|
-
|
|
369
|
-
|
|
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
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
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
|
-
|
|
485
|
+
console.log(chalk3.blue(`
|
|
486
|
+
\u{1F4E6} Installing dependencies with ${packageManager}...
|
|
487
|
+
`));
|
|
424
488
|
try {
|
|
425
|
-
|
|
426
|
-
|
|
489
|
+
execInherit(getInstallCommand(packageManager), projectDir);
|
|
490
|
+
console.log(chalk3.green("\n\u2713 Dependencies installed"));
|
|
427
491
|
} catch {
|
|
428
|
-
|
|
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(
|
|
506
|
+
console.log(chalk3.cyan(` ${getInstallCommand(packageManager)}`));
|
|
438
507
|
}
|
|
439
|
-
console.log(chalk3.cyan(
|
|
508
|
+
console.log(chalk3.cyan(` ${runCmd} dev`));
|
|
440
509
|
console.log("\nTo create an app:");
|
|
441
|
-
console.log(chalk3.cyan(
|
|
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
|
@@ -15,7 +15,7 @@ jobs:
|
|
|
15
15
|
runs-on: ubuntu-latest
|
|
16
16
|
steps:
|
|
17
17
|
- name: Checkout
|
|
18
|
-
uses: actions/checkout@
|
|
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@
|
|
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@
|
|
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@
|
|
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@
|
|
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@
|
|
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": "
|
|
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": "^
|
|
30
|
-
"@commitlint/config-conventional": "^
|
|
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": "^
|
|
34
|
+
"eslint-config-prettier": "^10.1.8",
|
|
37
35
|
"eslint-plugin-import": "^2.29.0",
|
|
38
|
-
"eslint-plugin-unused-imports": "^3.
|
|
36
|
+
"eslint-plugin-unused-imports": "^4.3.0",
|
|
39
37
|
"husky": "^9.0.0",
|
|
40
|
-
"lint-staged": "^
|
|
41
|
-
"prettier": "^3.
|
|
42
|
-
"prettier-plugin-tailwindcss": "^0.
|
|
43
|
-
"turbo": "^2.
|
|
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
|
}
|
|
@@ -25,14 +25,14 @@
|
|
|
25
25
|
"typecheck": "tsc --noEmit"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
|
-
"@types/react": "^
|
|
29
|
-
"@types/react-dom": "^
|
|
30
|
-
"react": "^
|
|
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": "^
|
|
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
|
|
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 ""
|