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 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
- const __filename = fileURLToPath(import.meta.url);
13
- const __dirname = path.dirname(__filename);
14
-
15
- const program = new Command();
16
-
17
- program
18
- .name('create-rykira-app')
19
- .description('Scaffold a new Rykira Starter Package project')
20
- .version('1.0.0')
21
- .argument('[project-directory]', 'Directory to create the project in')
22
- .action(async (projectDirectory) => {
23
- console.log(chalk.bold.blue('🚀 Welcome to Rykira Starter App Generator!'));
24
-
25
- let targetDir = projectDirectory;
26
-
27
- if (!targetDir) {
28
- const answers = await inquirer.prompt([
29
- {
30
- type: 'input',
31
- name: 'projectName',
32
- message: 'What is the name of your project?',
33
- default: 'my-rykira-app',
34
- validate: (input) => {
35
- if (/^([a-z0-9\-\_\.]+)$/.test(input)) return true;
36
- return 'Project name may only include lower-case letters, numbers, dashes, underscores, and dots.';
37
- },
38
- },
39
- ]);
40
- targetDir = answers.projectName;
41
- }
42
-
43
- const rootDir = path.resolve(process.cwd(), targetDir);
44
-
45
- if (fs.existsSync(rootDir)) {
46
- console.error(chalk.red(`Error: Directory ${targetDir} already exists.`));
47
- process.exit(1);
48
- }
49
-
50
- const promptAnswers = await inquirer.prompt([
51
- {
52
- type: 'input',
53
- name: 'description',
54
- message: 'Project description:',
55
- default: 'A modern full-stack application',
56
- },
57
- {
58
- type: 'input',
59
- name: 'author',
60
- message: 'Author name:',
61
- default: 'Rykira User',
62
- },
63
- {
64
- type: 'confirm',
65
- name: 'includeAdmin',
66
- message: 'Include Admin Panel?',
67
- default: true,
68
- },
69
- {
70
- type: 'confirm',
71
- name: 'includeApi',
72
- message: 'Include Express API?',
73
- default: true,
74
- },
75
- ]);
76
-
77
- const spinner = ora(`Creating project in ${chalk.green(rootDir)}...`).start();
78
-
79
- try {
80
- // Copy template files
81
- const templateDir = path.resolve(__dirname, '../template');
82
- await fs.copy(templateDir, rootDir, {
83
- filter: (src) => {
84
- // Avoid copying node_modules
85
- return !src.includes('node_modules');
86
- }
87
- });
88
-
89
- // Process Handlebars templates
90
- const packageJsonPath = path.join(rootDir, 'package.json');
91
- const packageJsonContent = await fs.readFile(packageJsonPath, 'utf-8');
92
- const template = Handlebars.compile(packageJsonContent);
93
- const result = template({
94
- projectName: path.basename(rootDir),
95
- projectDescription: promptAnswers.description,
96
- authorName: promptAnswers.author,
97
- });
98
- await fs.writeFile(packageJsonPath, result);
99
-
100
- // Handle Admin Panel removal
101
- if (!promptAnswers.includeAdmin) {
102
- spinner.text = 'Removing Admin Panel...';
103
- await fs.remove(path.join(rootDir, 'apps/admin'));
104
- await fs.remove(path.join(rootDir, 'infrastructure/docker/Dockerfile.admin'));
105
- // Note: You might need to clean up pnpm-workspace.yaml or turbo.json if strict
106
- }
107
-
108
- // Handle API removal
109
- if (!promptAnswers.includeApi) {
110
- spinner.text = 'Removing Express API...';
111
- await fs.remove(path.join(rootDir, 'apps/api'));
112
- await fs.remove(path.join(rootDir, 'infrastructure/docker/Dockerfile.api'));
113
- }
114
-
115
- // Update Compose files with project name
116
- const composeProdPath = path.join(rootDir, 'infrastructure/docker/compose.prod.yml');
117
- const composeDevPath = path.join(rootDir, 'infrastructure/docker/compose.dev.yml');
118
-
119
- const updateComposeFile = async (filePath) => {
120
- if (fs.existsSync(filePath)) {
121
- const content = await fs.readFile(filePath, 'utf-8');
122
- const composeTemplate = Handlebars.compile(content);
123
- const composeResult = composeTemplate({
124
- projectName: path.basename(rootDir)
125
- });
126
- await fs.writeFile(filePath, composeResult);
127
- }
128
- };
129
-
130
- await updateComposeFile(composeProdPath);
131
- await updateComposeFile(composeDevPath);
132
-
133
-
134
- spinner.succeed(chalk.green('Project created successfully!'));
135
-
136
- console.log('\nNext steps:');
137
- console.log(chalk.cyan(` cd ${targetDir}`));
138
- console.log(chalk.cyan(' pnpm install'));
139
- console.log(chalk.cyan(' cp .env.example .env'));
140
- console.log(chalk.cyan(' pnpm dev'));
141
-
142
- } catch (error) {
143
- spinner.fail(chalk.red('Failed to create project'));
144
- console.error(error);
145
- process.exit(1);
146
- }
147
- });
148
-
149
- program.parse(process.argv);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-rykira-app",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "CLI to scaffold Rykira Starter Package",
5
5
  "bin": {
6
6
  "create-rykira-app": "./bin/cli.js"
@@ -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
@@ -1,21 +1,72 @@
1
- # shadcn/ui monorepo template
2
-
3
- This is a Next.js monorepo template with shadcn/ui.
4
-
5
- ## Adding components
6
-
7
- To add components to your app, run the following command at the root of your `web` app:
8
-
9
- ```bash
10
- pnpm dlx shadcn@latest add button -c apps/web
11
- ```
12
-
13
- This will place the ui components in the `packages/ui/src/components` directory.
14
-
15
- ## Using components
16
-
17
- To use the components in your app, import them from the `ui` package.
18
-
19
- ```tsx
20
- import { Button } from "@workspace/ui/components/button";
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
+ ```
@@ -23,7 +23,7 @@ COPY --from=pruner /app/out/full/ .
23
23
  RUN pnpm turbo build --filter=admin
24
24
 
25
25
 
26
- FROM node:20-alpine AS runner
26
+ FROM base AS runner
27
27
 
28
28
  WORKDIR /app
29
29
 
@@ -23,7 +23,7 @@ COPY --from=pruner /app/out/full/ .
23
23
  RUN pnpm turbo build --filter=api
24
24
 
25
25
 
26
- FROM node:20-alpine AS runner
26
+ FROM base AS runner
27
27
 
28
28
  WORKDIR /app
29
29
 
@@ -35,7 +35,7 @@ RUN pnpm turbo build --filter=web
35
35
  # 3️⃣ RUN APPLICATION
36
36
  # -----------------------------
37
37
 
38
- FROM node:20-alpine AS runner
38
+ FROM base AS runner
39
39
 
40
40
  WORKDIR /app
41
41
 
@@ -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}