create-rykira-app 1.0.0 → 1.0.2
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/bin/cli.js +213 -149
- package/package.json +1 -1
- package/template/.github/workflows/ci-cd.yml +97 -0
- package/template/README.md +72 -21
- package/template/infrastructure/docker/Dockerfile.admin +1 -1
- package/template/infrastructure/docker/Dockerfile.api +1 -1
- package/template/infrastructure/docker/Dockerfile.web +1 -1
- package/template/infrastructure/docker/compose.dev.yml +2 -0
- package/template/infrastructure/docker/compose.prod.yml +166 -119
- package/template/infrastructure/scripts/deploy.sh +49 -3
- package/template/infrastructure/scripts/setup-server.sh +44 -19
- package/template/infrastructure/traefik/traefik.yml +5 -0
- package/template/package.json +3 -2
- package/deployment.md +0 -168
- package/how-to-deploy-package.md +0 -132
- package/starter-usage.md +0 -132
- package/template/.github/workflows/ci.yml +0 -60
package/bin/cli.js
CHANGED
|
@@ -1,149 +1,213 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import { Command } from 'commander';
|
|
4
|
-
import inquirer from 'inquirer';
|
|
5
|
-
import fs from 'fs-extra';
|
|
6
|
-
import path from 'path';
|
|
7
|
-
import { fileURLToPath } from 'url';
|
|
8
|
-
import chalk from 'chalk';
|
|
9
|
-
import ora from 'ora';
|
|
10
|
-
import Handlebars from 'handlebars';
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
.
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
const
|
|
117
|
-
const
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { Command } from 'commander';
|
|
4
|
+
import inquirer from 'inquirer';
|
|
5
|
+
import fs from 'fs-extra';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
import { fileURLToPath } from 'url';
|
|
8
|
+
import chalk from 'chalk';
|
|
9
|
+
import ora from 'ora';
|
|
10
|
+
import Handlebars from 'handlebars';
|
|
11
|
+
import { createRequire } from 'module';
|
|
12
|
+
|
|
13
|
+
const require = createRequire(import.meta.url);
|
|
14
|
+
const pkg = require('../package.json');
|
|
15
|
+
|
|
16
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
17
|
+
const __dirname = path.dirname(__filename);
|
|
18
|
+
|
|
19
|
+
const program = new Command();
|
|
20
|
+
|
|
21
|
+
program
|
|
22
|
+
.name('create-rykira-app')
|
|
23
|
+
.description('Scaffold a new Rykira Starter Package project')
|
|
24
|
+
.version(pkg.version)
|
|
25
|
+
.argument('[project-directory]', 'Directory to create the project in')
|
|
26
|
+
.action(async (projectDirectory) => {
|
|
27
|
+
console.log(chalk.bold.blue('🚀 Welcome to Rykira Starter App Generator!'));
|
|
28
|
+
|
|
29
|
+
let targetDir = projectDirectory;
|
|
30
|
+
|
|
31
|
+
if (!targetDir) {
|
|
32
|
+
const answers = await inquirer.prompt([
|
|
33
|
+
{
|
|
34
|
+
type: 'input',
|
|
35
|
+
name: 'projectName',
|
|
36
|
+
message: 'What is the name of your project?',
|
|
37
|
+
default: 'my-rykira-app',
|
|
38
|
+
validate: (input) => {
|
|
39
|
+
if (/^([a-z0-9\-\_\.]+)$/.test(input)) return true;
|
|
40
|
+
return 'Project name may only include lower-case letters, numbers, dashes, underscores, and dots.';
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
]);
|
|
44
|
+
targetDir = answers.projectName;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const rootDir = path.resolve(process.cwd(), targetDir);
|
|
48
|
+
|
|
49
|
+
if (fs.existsSync(rootDir)) {
|
|
50
|
+
console.error(chalk.red(`Error: Directory ${targetDir} already exists.`));
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const promptAnswers = await inquirer.prompt([
|
|
55
|
+
{
|
|
56
|
+
type: 'input',
|
|
57
|
+
name: 'description',
|
|
58
|
+
message: 'Project description:',
|
|
59
|
+
default: 'A modern full-stack application',
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
type: 'input',
|
|
63
|
+
name: 'author',
|
|
64
|
+
message: 'Author name:',
|
|
65
|
+
default: 'Rykira User',
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
type: 'confirm',
|
|
69
|
+
name: 'includeAdmin',
|
|
70
|
+
message: 'Include Admin Panel?',
|
|
71
|
+
default: true,
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
type: 'confirm',
|
|
75
|
+
name: 'includeApi',
|
|
76
|
+
message: 'Include Express API?',
|
|
77
|
+
default: true,
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
type: 'list',
|
|
81
|
+
name: 'deploymentStrategy',
|
|
82
|
+
message: 'Choose your deployment strategy:',
|
|
83
|
+
choices: [
|
|
84
|
+
{ name: 'Automated CI/CD (GitHub Actions + GHCR)', value: 'cicd' },
|
|
85
|
+
{ name: 'Coolify (Self-Hosted PaaS)', value: 'coolify' },
|
|
86
|
+
{ name: 'Direct Source Build (Simple)', value: 'direct' }
|
|
87
|
+
],
|
|
88
|
+
default: 'cicd'
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
type: 'input',
|
|
92
|
+
name: 'githubUsername',
|
|
93
|
+
message: 'GitHub Username (required for GHCR image tagging):',
|
|
94
|
+
when: (answers) => answers.deploymentStrategy === 'cicd',
|
|
95
|
+
validate: (input) => input.length > 0 ? true : 'GitHub username is required for CI/CD setup'
|
|
96
|
+
}
|
|
97
|
+
]);
|
|
98
|
+
|
|
99
|
+
const spinner = ora(`Creating project in ${chalk.green(rootDir)}...`).start();
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
// Copy template files
|
|
103
|
+
const templateDir = path.resolve(__dirname, '../template');
|
|
104
|
+
await fs.copy(templateDir, rootDir, {
|
|
105
|
+
filter: (src) => {
|
|
106
|
+
// Avoid copying node_modules and build artifacts
|
|
107
|
+
if (src.includes('node_modules')) return false;
|
|
108
|
+
|
|
109
|
+
const basename = path.basename(src);
|
|
110
|
+
return !['.next', '.turbo', 'dist', 'coverage', 'build', '.DS_Store'].includes(basename);
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// Process Handlebars templates for package.json
|
|
115
|
+
const packageJsonPath = path.join(rootDir, 'package.json');
|
|
116
|
+
const packageJsonContent = await fs.readFile(packageJsonPath, 'utf-8');
|
|
117
|
+
const template = Handlebars.compile(packageJsonContent);
|
|
118
|
+
const result = template({
|
|
119
|
+
projectName: path.basename(rootDir),
|
|
120
|
+
projectDescription: promptAnswers.description,
|
|
121
|
+
authorName: promptAnswers.author,
|
|
122
|
+
});
|
|
123
|
+
await fs.writeFile(packageJsonPath, result);
|
|
124
|
+
|
|
125
|
+
// Handle Admin Panel removal
|
|
126
|
+
if (!promptAnswers.includeAdmin) {
|
|
127
|
+
spinner.text = 'Removing Admin Panel...';
|
|
128
|
+
await fs.remove(path.join(rootDir, 'apps/admin'));
|
|
129
|
+
await fs.remove(path.join(rootDir, 'infrastructure/docker/Dockerfile.admin'));
|
|
130
|
+
// Note: You might need to clean up pnpm-workspace.yaml or turbo.json if strict
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Handle API removal
|
|
134
|
+
if (!promptAnswers.includeApi) {
|
|
135
|
+
spinner.text = 'Removing Express API...';
|
|
136
|
+
await fs.remove(path.join(rootDir, 'apps/api'));
|
|
137
|
+
await fs.remove(path.join(rootDir, 'infrastructure/docker/Dockerfile.api'));
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Deployment Variables
|
|
141
|
+
const isCicd = promptAnswers.deploymentStrategy === 'cicd';
|
|
142
|
+
const isCoolify = promptAnswers.deploymentStrategy === 'coolify';
|
|
143
|
+
const isDirect = promptAnswers.deploymentStrategy === 'direct';
|
|
144
|
+
|
|
145
|
+
// Update Compose files with project name and deployment logic
|
|
146
|
+
const composeProdPath = path.join(rootDir, 'infrastructure/docker/compose.prod.yml');
|
|
147
|
+
const composeDevPath = path.join(rootDir, 'infrastructure/docker/compose.dev.yml');
|
|
148
|
+
|
|
149
|
+
const updateFileWithContext = async (filePath) => {
|
|
150
|
+
if (fs.existsSync(filePath)) {
|
|
151
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
152
|
+
const compiledTemplate = Handlebars.compile(content);
|
|
153
|
+
const compiledResult = compiledTemplate({
|
|
154
|
+
projectName: path.basename(rootDir),
|
|
155
|
+
githubUsername: promptAnswers.githubUsername,
|
|
156
|
+
isCicd,
|
|
157
|
+
isCoolify,
|
|
158
|
+
isDirect,
|
|
159
|
+
includeAdmin: promptAnswers.includeAdmin,
|
|
160
|
+
includeApi: promptAnswers.includeApi,
|
|
161
|
+
deploymentStrategy: promptAnswers.deploymentStrategy
|
|
162
|
+
});
|
|
163
|
+
await fs.writeFile(filePath, compiledResult);
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
await updateFileWithContext(composeProdPath);
|
|
168
|
+
await updateFileWithContext(composeDevPath);
|
|
169
|
+
|
|
170
|
+
// Update Deploy Script
|
|
171
|
+
const deployScriptPath = path.join(rootDir, 'infrastructure/scripts/deploy.sh');
|
|
172
|
+
await updateFileWithContext(deployScriptPath);
|
|
173
|
+
|
|
174
|
+
// Update README.md
|
|
175
|
+
const readmePath = path.join(rootDir, 'README.md');
|
|
176
|
+
await updateFileWithContext(readmePath);
|
|
177
|
+
|
|
178
|
+
// Cleanup
|
|
179
|
+
if (!isCicd) {
|
|
180
|
+
// Remove CI/CD workflows if not using CI/CD
|
|
181
|
+
// Note: If you want to keep CI (tests) for other modes, you might want to split the file or keep a basic one.
|
|
182
|
+
// For now, removing the specific ci-cd pipeline.
|
|
183
|
+
await fs.remove(path.join(rootDir, '.github/workflows/ci-cd.yml'));
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
spinner.succeed(chalk.green('Project created successfully!'));
|
|
187
|
+
|
|
188
|
+
console.log('\nNext steps:');
|
|
189
|
+
console.log(chalk.cyan(` cd ${targetDir}`));
|
|
190
|
+
console.log(chalk.cyan(' pnpm install'));
|
|
191
|
+
console.log(chalk.cyan(' cp .env.example .env'));
|
|
192
|
+
console.log(chalk.cyan(' pnpm dev'));
|
|
193
|
+
|
|
194
|
+
if (isCicd) {
|
|
195
|
+
console.log(chalk.yellow('\nCI/CD Setup:'));
|
|
196
|
+
console.log(' 1. Create a repository on GitHub');
|
|
197
|
+
console.log(' 2. Add secrets: HOST, USERNAME, SSH_KEY');
|
|
198
|
+
console.log(' 3. Push to main to trigger deployment');
|
|
199
|
+
} else if (isDirect) {
|
|
200
|
+
console.log(chalk.yellow('\nDeployment:'));
|
|
201
|
+
console.log(' 1. Setup your server using infrastructure/scripts/setup-server.sh');
|
|
202
|
+
console.log(' 2. Configure .env with DEPLOY_HOST details');
|
|
203
|
+
console.log(' 3. Run: npm run deploy');
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
} catch (error) {
|
|
207
|
+
spinner.fail(chalk.red('Failed to create project'));
|
|
208
|
+
console.error(error);
|
|
209
|
+
process.exit(1);
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
program.parse(process.argv);
|
package/package.json
CHANGED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
name: CI/CD Pipeline
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [ "main" ]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [ "main" ]
|
|
8
|
+
|
|
9
|
+
env:
|
|
10
|
+
REGISTRY: ghcr.io
|
|
11
|
+
IMAGE_NAME: ${{ github.repository }}
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
test:
|
|
15
|
+
name: Build, Lint, and Test
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v4
|
|
19
|
+
with:
|
|
20
|
+
fetch-depth: 2
|
|
21
|
+
|
|
22
|
+
- uses: pnpm/action-setup@v3
|
|
23
|
+
with:
|
|
24
|
+
version: 9
|
|
25
|
+
|
|
26
|
+
- uses: actions/setup-node@v4
|
|
27
|
+
with:
|
|
28
|
+
node-version: 20
|
|
29
|
+
cache: 'pnpm'
|
|
30
|
+
|
|
31
|
+
- name: Install dependencies
|
|
32
|
+
run: pnpm install
|
|
33
|
+
|
|
34
|
+
- name: Build
|
|
35
|
+
run: pnpm build
|
|
36
|
+
|
|
37
|
+
- name: Lint
|
|
38
|
+
run: pnpm lint
|
|
39
|
+
|
|
40
|
+
- name: Typecheck
|
|
41
|
+
run: pnpm typecheck
|
|
42
|
+
|
|
43
|
+
build-and-push:
|
|
44
|
+
needs: test
|
|
45
|
+
runs-on: ubuntu-latest
|
|
46
|
+
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
|
47
|
+
permissions:
|
|
48
|
+
contents: read
|
|
49
|
+
packages: write
|
|
50
|
+
strategy:
|
|
51
|
+
matrix:
|
|
52
|
+
service: [web, admin, api]
|
|
53
|
+
steps:
|
|
54
|
+
- uses: actions/checkout@v4
|
|
55
|
+
|
|
56
|
+
- name: Log in to the Container registry
|
|
57
|
+
uses: docker/login-action@v3
|
|
58
|
+
with:
|
|
59
|
+
registry: ${{ env.REGISTRY }}
|
|
60
|
+
username: ${{ github.actor }}
|
|
61
|
+
password: ${{ secrets.GITHUB_TOKEN }}
|
|
62
|
+
|
|
63
|
+
- name: Extract metadata (tags, labels) for Docker
|
|
64
|
+
id: meta
|
|
65
|
+
uses: docker/metadata-action@v5
|
|
66
|
+
with:
|
|
67
|
+
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-${{ matrix.service }}
|
|
68
|
+
tags: |
|
|
69
|
+
type=raw,value=latest
|
|
70
|
+
type=sha,prefix=
|
|
71
|
+
|
|
72
|
+
- name: Build and push Docker image
|
|
73
|
+
uses: docker/build-push-action@v5
|
|
74
|
+
with:
|
|
75
|
+
context: .
|
|
76
|
+
file: infrastructure/docker/Dockerfile.${{ matrix.service }}
|
|
77
|
+
push: true
|
|
78
|
+
tags: ${{ steps.meta.outputs.tags }}
|
|
79
|
+
labels: ${{ steps.meta.outputs.labels }}
|
|
80
|
+
|
|
81
|
+
deploy:
|
|
82
|
+
needs: build-and-push
|
|
83
|
+
runs-on: ubuntu-latest
|
|
84
|
+
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
|
85
|
+
steps:
|
|
86
|
+
- name: Deploy to VPS
|
|
87
|
+
uses: appleboy/ssh-action@v1.0.3
|
|
88
|
+
with:
|
|
89
|
+
host: ${{ secrets.HOST }}
|
|
90
|
+
username: ${{ secrets.USERNAME }}
|
|
91
|
+
key: ${{ secrets.SSH_KEY }}
|
|
92
|
+
script: |
|
|
93
|
+
cd ~/app || exit
|
|
94
|
+
# Pull latest images
|
|
95
|
+
docker compose -f infrastructure/docker/compose.prod.yml pull
|
|
96
|
+
# Restart containers
|
|
97
|
+
docker compose -f infrastructure/docker/compose.prod.yml up -d
|
package/template/README.md
CHANGED
|
@@ -1,21 +1,72 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
This is a Next.js monorepo template with shadcn/ui.
|
|
4
|
-
|
|
5
|
-
##
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
1
|
+
# {{projectName}}
|
|
2
|
+
|
|
3
|
+
This is a Next.js monorepo template with shadcn/ui.
|
|
4
|
+
|
|
5
|
+
## Getting Started
|
|
6
|
+
|
|
7
|
+
1. Install dependencies:
|
|
8
|
+
```bash
|
|
9
|
+
pnpm install
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
2. Start the development server:
|
|
13
|
+
```bash
|
|
14
|
+
pnpm dev
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Deployment
|
|
18
|
+
|
|
19
|
+
**Selected Strategy:** {{deploymentStrategy}}
|
|
20
|
+
|
|
21
|
+
To deploy your application:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pnpm run deploy
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
{{#if isCicd}}
|
|
28
|
+
### Automated CI/CD Setup
|
|
29
|
+
1. Push this repository to GitHub.
|
|
30
|
+
2. Go to Settings > Secrets and Variables > Actions.
|
|
31
|
+
3. Add the following secrets:
|
|
32
|
+
- `HOST`: Your server IP address.
|
|
33
|
+
- `USERNAME`: Your server SSH username (e.g., `root`).
|
|
34
|
+
- `SSH_KEY`: Your private SSH key.
|
|
35
|
+
4. Push to `main` to trigger deployment.
|
|
36
|
+
{{/if}}
|
|
37
|
+
|
|
38
|
+
{{#if isDirect}}
|
|
39
|
+
### Direct Source Build Setup
|
|
40
|
+
1. SSH into your server.
|
|
41
|
+
2. Run the setup script:
|
|
42
|
+
```bash
|
|
43
|
+
curl -sL https://raw.githubusercontent.com/user/repo/main/infrastructure/scripts/setup-server.sh | bash
|
|
44
|
+
```
|
|
45
|
+
3. Configure your `.env` file with `DEPLOY_HOST` and `DEPLOY_USER`.
|
|
46
|
+
4. Run `pnpm run deploy` from your local machine.
|
|
47
|
+
{{/if}}
|
|
48
|
+
|
|
49
|
+
{{#if isCoolify}}
|
|
50
|
+
### Coolify Setup
|
|
51
|
+
1. Create a project in your Coolify dashboard.
|
|
52
|
+
2. Connect this Git repository.
|
|
53
|
+
3. Coolify will automatically detect the configuration and build your application.
|
|
54
|
+
{{/if}}
|
|
55
|
+
|
|
56
|
+
## Adding components
|
|
57
|
+
|
|
58
|
+
To add components to your app, run the following command at the root of your `web` app:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
pnpm dlx shadcn@latest add button -c apps/web
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
This will place the ui components in the `packages/ui/src/components` directory.
|
|
65
|
+
|
|
66
|
+
## Using components
|
|
67
|
+
|
|
68
|
+
To use the components in your app, import them from the `ui` package.
|
|
69
|
+
|
|
70
|
+
```tsx
|
|
71
|
+
import { Button } from "@workspace/ui/components/button";
|
|
72
|
+
```
|
|
@@ -19,6 +19,7 @@ services:
|
|
|
19
19
|
build:
|
|
20
20
|
context: ../..
|
|
21
21
|
dockerfile: infrastructure/docker/Dockerfile.web
|
|
22
|
+
container_name: {{projectName}}-web-dev
|
|
22
23
|
env_file:
|
|
23
24
|
- ../../.env
|
|
24
25
|
ports:
|
|
@@ -36,6 +37,7 @@ services:
|
|
|
36
37
|
|
|
37
38
|
postgres:
|
|
38
39
|
image: postgres:16
|
|
40
|
+
container_name: {{projectName}}-postgres-dev
|
|
39
41
|
environment:
|
|
40
42
|
POSTGRES_DB: ${POSTGRES_DB}
|
|
41
43
|
POSTGRES_USER: ${POSTGRES_USER}
|