create-nexu 1.0.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/README.md +149 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +603 -0
- package/package.json +41 -0
- package/templates/default/.changeset/config.json +11 -0
- package/templates/default/.eslintignore +13 -0
- package/templates/default/.eslintrc.js +66 -0
- package/templates/default/.github/actions/quality/action.yml +53 -0
- package/templates/default/.github/dependabot.yml +51 -0
- package/templates/default/.github/workflows/deploy-dev.yml +83 -0
- package/templates/default/.github/workflows/deploy-prod.yml +83 -0
- package/templates/default/.github/workflows/deploy-rec.yml +83 -0
- package/templates/default/.husky/commit-msg +1 -0
- package/templates/default/.husky/pre-commit +1 -0
- package/templates/default/.prettierignore +7 -0
- package/templates/default/.prettierrc +19 -0
- package/templates/default/.vscode/extensions.json +14 -0
- package/templates/default/.vscode/settings.json +36 -0
- package/templates/default/apps/.gitkeep +0 -0
- package/templates/default/commitlint.config.js +26 -0
- package/templates/default/docker/docker-compose.dev.yml +49 -0
- package/templates/default/docker/docker-compose.prod.yml +64 -0
- package/templates/default/docker/docker-compose.yml +5 -0
- package/templates/default/package.json +56 -0
- package/templates/default/packages/cache/package.json +26 -0
- package/templates/default/packages/cache/src/index.ts +137 -0
- package/templates/default/packages/cache/tsconfig.json +9 -0
- package/templates/default/packages/cache/tsup.config.ts +9 -0
- package/templates/default/packages/config/eslint/index.js +20 -0
- package/templates/default/packages/config/package.json +9 -0
- package/templates/default/packages/config/typescript/base.json +26 -0
- package/templates/default/packages/constants/package.json +26 -0
- package/templates/default/packages/constants/src/index.ts +121 -0
- package/templates/default/packages/constants/tsconfig.json +9 -0
- package/templates/default/packages/constants/tsup.config.ts +9 -0
- package/templates/default/packages/logger/package.json +27 -0
- package/templates/default/packages/logger/src/index.ts +197 -0
- package/templates/default/packages/logger/tsconfig.json +11 -0
- package/templates/default/packages/logger/tsup.config.ts +9 -0
- package/templates/default/packages/result/package.json +26 -0
- package/templates/default/packages/result/src/index.ts +142 -0
- package/templates/default/packages/result/tsconfig.json +9 -0
- package/templates/default/packages/result/tsup.config.ts +9 -0
- package/templates/default/packages/types/package.json +26 -0
- package/templates/default/packages/types/src/index.ts +78 -0
- package/templates/default/packages/types/tsconfig.json +9 -0
- package/templates/default/packages/types/tsup.config.ts +10 -0
- package/templates/default/packages/ui/package.json +38 -0
- package/templates/default/packages/ui/src/components/Button.tsx +65 -0
- package/templates/default/packages/ui/src/components/Card.tsx +90 -0
- package/templates/default/packages/ui/src/components/Input.tsx +51 -0
- package/templates/default/packages/ui/src/index.ts +15 -0
- package/templates/default/packages/ui/tsconfig.json +11 -0
- package/templates/default/packages/ui/tsup.config.ts +11 -0
- package/templates/default/packages/utils/package.json +30 -0
- package/templates/default/packages/utils/src/index.test.ts +130 -0
- package/templates/default/packages/utils/src/index.ts +154 -0
- package/templates/default/packages/utils/tsconfig.json +10 -0
- package/templates/default/packages/utils/tsup.config.ts +10 -0
- package/templates/default/pnpm-workspace.yaml +3 -0
- package/templates/default/scripts/deploy.sh +25 -0
- package/templates/default/scripts/generate-app.sh +166 -0
- package/templates/default/scripts/publish-cli.sh +54 -0
- package/templates/default/scripts/setup.sh +70 -0
- package/templates/default/services/.env.example +16 -0
- package/templates/default/services/docker-compose.yml +207 -0
- package/templates/default/services/grafana/provisioning/dashboards/dashboards.yml +11 -0
- package/templates/default/services/grafana/provisioning/datasources/datasources.yml +9 -0
- package/templates/default/services/postgres/init/.gitkeep +2 -0
- package/templates/default/services/prometheus/prometheus.yml +13 -0
- package/templates/default/tsconfig.json +27 -0
- package/templates/default/turbo.json +40 -0
- package/templates/default/vitest.config.ts +15 -0
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
capitalize,
|
|
5
|
+
slugify,
|
|
6
|
+
truncate,
|
|
7
|
+
pick,
|
|
8
|
+
omit,
|
|
9
|
+
isEmpty,
|
|
10
|
+
chunk,
|
|
11
|
+
unique,
|
|
12
|
+
isEmail,
|
|
13
|
+
isUrl,
|
|
14
|
+
clamp,
|
|
15
|
+
daysBetween,
|
|
16
|
+
} from './index';
|
|
17
|
+
|
|
18
|
+
describe('String utilities', () => {
|
|
19
|
+
describe('capitalize', () => {
|
|
20
|
+
it('should capitalize first letter', () => {
|
|
21
|
+
expect(capitalize('hello')).toBe('Hello');
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('should return empty string for empty input', () => {
|
|
25
|
+
expect(capitalize('')).toBe('');
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
describe('slugify', () => {
|
|
30
|
+
it('should convert string to slug', () => {
|
|
31
|
+
expect(slugify('Hello World')).toBe('hello-world');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('should remove special characters', () => {
|
|
35
|
+
expect(slugify('Hello! World?')).toBe('hello-world');
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
describe('truncate', () => {
|
|
40
|
+
it('should truncate long strings', () => {
|
|
41
|
+
expect(truncate('Hello World', 8)).toBe('Hello...');
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('should not truncate short strings', () => {
|
|
45
|
+
expect(truncate('Hello', 10)).toBe('Hello');
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe('Object utilities', () => {
|
|
51
|
+
describe('pick', () => {
|
|
52
|
+
it('should pick specified keys', () => {
|
|
53
|
+
const obj = { a: 1, b: 2, c: 3 };
|
|
54
|
+
expect(pick(obj, ['a', 'c'])).toEqual({ a: 1, c: 3 });
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
describe('omit', () => {
|
|
59
|
+
it('should omit specified keys', () => {
|
|
60
|
+
const obj = { a: 1, b: 2, c: 3 };
|
|
61
|
+
expect(omit(obj, ['b'])).toEqual({ a: 1, c: 3 });
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
describe('isEmpty', () => {
|
|
66
|
+
it('should return true for empty values', () => {
|
|
67
|
+
expect(isEmpty(null)).toBe(true);
|
|
68
|
+
expect(isEmpty(undefined)).toBe(true);
|
|
69
|
+
expect(isEmpty('')).toBe(true);
|
|
70
|
+
expect(isEmpty([])).toBe(true);
|
|
71
|
+
expect(isEmpty({})).toBe(true);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('should return false for non-empty values', () => {
|
|
75
|
+
expect(isEmpty('hello')).toBe(false);
|
|
76
|
+
expect(isEmpty([1])).toBe(false);
|
|
77
|
+
expect(isEmpty({ a: 1 })).toBe(false);
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
describe('Array utilities', () => {
|
|
83
|
+
describe('chunk', () => {
|
|
84
|
+
it('should split array into chunks', () => {
|
|
85
|
+
expect(chunk([1, 2, 3, 4, 5], 2)).toEqual([[1, 2], [3, 4], [5]]);
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
describe('unique', () => {
|
|
90
|
+
it('should remove duplicates', () => {
|
|
91
|
+
expect(unique([1, 2, 2, 3, 3, 3])).toEqual([1, 2, 3]);
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
describe('Validation utilities', () => {
|
|
97
|
+
describe('isEmail', () => {
|
|
98
|
+
it('should validate emails', () => {
|
|
99
|
+
expect(isEmail('test@example.com')).toBe(true);
|
|
100
|
+
expect(isEmail('invalid')).toBe(false);
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
describe('isUrl', () => {
|
|
105
|
+
it('should validate URLs', () => {
|
|
106
|
+
expect(isUrl('https://example.com')).toBe(true);
|
|
107
|
+
expect(isUrl('not-a-url')).toBe(false);
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
describe('Number utilities', () => {
|
|
113
|
+
describe('clamp', () => {
|
|
114
|
+
it('should clamp values', () => {
|
|
115
|
+
expect(clamp(5, 0, 10)).toBe(5);
|
|
116
|
+
expect(clamp(-5, 0, 10)).toBe(0);
|
|
117
|
+
expect(clamp(15, 0, 10)).toBe(10);
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
describe('Date utilities', () => {
|
|
123
|
+
describe('daysBetween', () => {
|
|
124
|
+
it('should calculate days between dates', () => {
|
|
125
|
+
const date1 = new Date('2024-01-01');
|
|
126
|
+
const date2 = new Date('2024-01-10');
|
|
127
|
+
expect(daysBetween(date1, date2)).toBe(9);
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
});
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
// String utilities
|
|
2
|
+
export function capitalize(str: string): string {
|
|
3
|
+
if (!str) return '';
|
|
4
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function slugify(str: string): string {
|
|
8
|
+
return str
|
|
9
|
+
.toLowerCase()
|
|
10
|
+
.trim()
|
|
11
|
+
.replace(/[^\w\s-]/g, '')
|
|
12
|
+
.replace(/[\s_-]+/g, '-')
|
|
13
|
+
.replace(/^-+|-+$/g, '');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function truncate(str: string, length: number, suffix = '...'): string {
|
|
17
|
+
if (str.length <= length) return str;
|
|
18
|
+
return str.slice(0, length - suffix.length) + suffix;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Object utilities
|
|
22
|
+
export function pick<T extends object, K extends keyof T>(obj: T, keys: K[]): Pick<T, K> {
|
|
23
|
+
const result = {} as Pick<T, K>;
|
|
24
|
+
keys.forEach(key => {
|
|
25
|
+
if (key in obj) {
|
|
26
|
+
result[key] = obj[key];
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
return result;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function omit<T extends object, K extends keyof T>(obj: T, keys: K[]): Omit<T, K> {
|
|
33
|
+
const result = { ...obj };
|
|
34
|
+
keys.forEach(key => {
|
|
35
|
+
delete result[key];
|
|
36
|
+
});
|
|
37
|
+
return result as Omit<T, K>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function isEmpty(value: unknown): boolean {
|
|
41
|
+
if (value == null) return true;
|
|
42
|
+
if (typeof value === 'string' || Array.isArray(value)) return value.length === 0;
|
|
43
|
+
if (typeof value === 'object') return Object.keys(value).length === 0;
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Array utilities
|
|
48
|
+
export function chunk<T>(array: T[], size: number): T[][] {
|
|
49
|
+
const chunks: T[][] = [];
|
|
50
|
+
for (let i = 0; i < array.length; i += size) {
|
|
51
|
+
chunks.push(array.slice(i, i + size));
|
|
52
|
+
}
|
|
53
|
+
return chunks;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function unique<T>(array: T[]): T[] {
|
|
57
|
+
return [...new Set(array)];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function groupBy<T, K extends string | number | symbol>(
|
|
61
|
+
array: T[],
|
|
62
|
+
keyFn: (item: T) => K
|
|
63
|
+
): Record<K, T[]> {
|
|
64
|
+
return array.reduce(
|
|
65
|
+
(acc, item) => {
|
|
66
|
+
const key = keyFn(item);
|
|
67
|
+
if (!acc[key]) {
|
|
68
|
+
acc[key] = [];
|
|
69
|
+
}
|
|
70
|
+
acc[key].push(item);
|
|
71
|
+
return acc;
|
|
72
|
+
},
|
|
73
|
+
{} as Record<K, T[]>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Date utilities
|
|
78
|
+
export function formatDate(date: Date, locale = 'en-US'): string {
|
|
79
|
+
return new Intl.DateTimeFormat(locale, {
|
|
80
|
+
year: 'numeric',
|
|
81
|
+
month: 'long',
|
|
82
|
+
day: 'numeric',
|
|
83
|
+
}).format(date);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function isValidDate(date: unknown): date is Date {
|
|
87
|
+
return date instanceof Date && !isNaN(date.getTime());
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function daysBetween(date1: Date, date2: Date): number {
|
|
91
|
+
const oneDay = 24 * 60 * 60 * 1000;
|
|
92
|
+
return Math.round(Math.abs((date1.getTime() - date2.getTime()) / oneDay));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Async utilities
|
|
96
|
+
export function sleep(ms: number): Promise<void> {
|
|
97
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export async function retry<T>(
|
|
101
|
+
fn: () => Promise<T>,
|
|
102
|
+
options: { attempts?: number; delay?: number } = {}
|
|
103
|
+
): Promise<T> {
|
|
104
|
+
const { attempts = 3, delay = 1000 } = options;
|
|
105
|
+
let lastError: Error | undefined;
|
|
106
|
+
|
|
107
|
+
for (let i = 0; i < attempts; i++) {
|
|
108
|
+
try {
|
|
109
|
+
return await fn();
|
|
110
|
+
} catch (error) {
|
|
111
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
112
|
+
if (i < attempts - 1) {
|
|
113
|
+
await sleep(delay);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
throw lastError;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Validation utilities
|
|
122
|
+
export function isEmail(email: string): boolean {
|
|
123
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
124
|
+
return emailRegex.test(email);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function isUrl(url: string): boolean {
|
|
128
|
+
try {
|
|
129
|
+
new URL(url);
|
|
130
|
+
return true;
|
|
131
|
+
} catch {
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Number utilities
|
|
137
|
+
export function clamp(value: number, min: number, max: number): number {
|
|
138
|
+
return Math.min(Math.max(value, min), max);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function randomInt(min: number, max: number): number {
|
|
142
|
+
return Math.floor(Math.random() * (max - min + 1)) + min;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function formatNumber(num: number, locale = 'en-US'): string {
|
|
146
|
+
return new Intl.NumberFormat(locale).format(num);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function formatCurrency(amount: number, currency = 'USD', locale = 'en-US'): string {
|
|
150
|
+
return new Intl.NumberFormat(locale, {
|
|
151
|
+
style: 'currency',
|
|
152
|
+
currency,
|
|
153
|
+
}).format(amount);
|
|
154
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
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 ""
|
|
@@ -0,0 +1,166 @@
|
|
|
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"
|
|
@@ -0,0 +1,54 @@
|
|
|
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: Show package info
|
|
34
|
+
echo "4️⃣ Package info:"
|
|
35
|
+
cat package.json | grep -E '"name"|"version"'
|
|
36
|
+
echo ""
|
|
37
|
+
|
|
38
|
+
# Step 5: Confirm publish
|
|
39
|
+
read -p "Publish to npm? (y/N) " -n 1 -r
|
|
40
|
+
echo ""
|
|
41
|
+
|
|
42
|
+
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
|
43
|
+
echo "5️⃣ Publishing to npm..."
|
|
44
|
+
npm publish --access public
|
|
45
|
+
echo ""
|
|
46
|
+
echo "✅ Published successfully!"
|
|
47
|
+
echo ""
|
|
48
|
+
echo "Users can now run:"
|
|
49
|
+
echo " npm create nexu my-app"
|
|
50
|
+
echo " # or"
|
|
51
|
+
echo " npx create-nexu my-app"
|
|
52
|
+
else
|
|
53
|
+
echo "❌ Publish cancelled"
|
|
54
|
+
fi
|
|
@@ -0,0 +1,70 @@
|
|
|
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 ""
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Database
|
|
2
|
+
POSTGRES_USER=nexu
|
|
3
|
+
POSTGRES_PASSWORD=nexu
|
|
4
|
+
POSTGRES_DB=nexu
|
|
5
|
+
|
|
6
|
+
# Messaging
|
|
7
|
+
RABBITMQ_USER=nexu
|
|
8
|
+
RABBITMQ_PASSWORD=nexu
|
|
9
|
+
|
|
10
|
+
# Monitoring
|
|
11
|
+
GRAFANA_USER=admin
|
|
12
|
+
GRAFANA_PASSWORD=admin
|
|
13
|
+
|
|
14
|
+
# Storage
|
|
15
|
+
MINIO_USER=nexu
|
|
16
|
+
MINIO_PASSWORD=nexu1234
|