@brezel/installer 1.0.1 ā 1.0.3
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 +81 -0
- package/package.json +9 -2
- package/.releaserc.json +0 -20
- package/install.sh +0 -41
- package/src/index.ts +0 -538
- package/tsconfig.json +0 -13
package/README.md
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Brezel Installer
|
|
2
|
+
|
|
3
|
+
Official installer for Brezel ERP. This tool automates the setup of Brezel instances across different environments, including native Linux/macOS, Laravel Valet, and Docker.
|
|
4
|
+
|
|
5
|
+
## Fast-Track Installation
|
|
6
|
+
|
|
7
|
+
Run this command in your terminal to start the installation:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
curl -sL https://brezel.io/install.sh | bash
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Features
|
|
14
|
+
|
|
15
|
+
- **Environment Validation:** Checks for Git, Node, NPM, and PHP 8.3+ before execution.
|
|
16
|
+
- **macOS Valet Support:** Automatically handles domain linking (`valet link`) and SSL configuration (`valet secure`).
|
|
17
|
+
- **Docker Integration:** Complete containerized setup including service orchestration and networking.
|
|
18
|
+
- **Native Setup:** Automates dependency installation and system configuration for bare-metal deployments.
|
|
19
|
+
- **System Components:** Optional automated setup for MariaDB, Nginx, SSL (Certbot), and Cron tasks.
|
|
20
|
+
- **Output Preview:** Provides real-time feedback for background tasks like Composer and NPM builds.
|
|
21
|
+
- **Non-Interactive Mode:** Full CLI support for automated deployments and CI/CD pipelines.
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
### Interactive Mode
|
|
26
|
+
Run the installer and follow the terminal prompts:
|
|
27
|
+
```bash
|
|
28
|
+
./install.sh
|
|
29
|
+
# OR
|
|
30
|
+
node dist/index.js
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Non-Interactive Mode
|
|
34
|
+
Specify configuration via CLI flags:
|
|
35
|
+
```bash
|
|
36
|
+
node dist/index.js \
|
|
37
|
+
--dir ./my-brezel \
|
|
38
|
+
--system production \
|
|
39
|
+
--mode native \
|
|
40
|
+
--url https://api.brezel.io \
|
|
41
|
+
--no-interactive
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### CLI Options
|
|
45
|
+
|
|
46
|
+
| Option | Description | Default |
|
|
47
|
+
| :--- | :--- | :--- |
|
|
48
|
+
| `-d, --dir` | Installation directory | `./brezel` |
|
|
49
|
+
| `-m, --mode` | Installation mode (`native`, `valet`, `docker`) | `native` |
|
|
50
|
+
| `-s, --system` | System/tenant name | `example` |
|
|
51
|
+
| `-u, --url` | API URL | `http://{system}.test` |
|
|
52
|
+
| `--spa-url` | SPA (frontend) URL | `http://localhost:5173` |
|
|
53
|
+
| `--php-path` | Path to PHP 8.3+ executable | `php` |
|
|
54
|
+
| `--gitlab-token` | GitLab Personal Access Token | (Prompted) |
|
|
55
|
+
| `--source-mode` | `clone` or `fork` | `clone` |
|
|
56
|
+
| `--components` | Optional components (`mariadb,nginx,ssl,cron`) | `""` |
|
|
57
|
+
| `--no-interactive`| Run without user prompts | `false` |
|
|
58
|
+
|
|
59
|
+
## Development
|
|
60
|
+
|
|
61
|
+
1. **Install Dependencies:**
|
|
62
|
+
```bash
|
|
63
|
+
npm install
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
2. **Build:**
|
|
67
|
+
```bash
|
|
68
|
+
npm run build
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
3. **Run Dev:**
|
|
72
|
+
```bash
|
|
73
|
+
npm run dev
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Prerequisites
|
|
77
|
+
|
|
78
|
+
- **Node.js:** v18+
|
|
79
|
+
- **Git:** Latest
|
|
80
|
+
- **Native/Valet:** PHP 8.3+ and Composer
|
|
81
|
+
- **Docker:** Docker Compose
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@brezel/installer",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "Installer for Brezel",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -11,7 +11,11 @@
|
|
|
11
11
|
"start": "node dist/index.js",
|
|
12
12
|
"dev": "ts-node src/index.ts"
|
|
13
13
|
},
|
|
14
|
-
"keywords": [
|
|
14
|
+
"keywords": [
|
|
15
|
+
"brezel",
|
|
16
|
+
"installer",
|
|
17
|
+
"cli"
|
|
18
|
+
],
|
|
15
19
|
"author": "flynamic",
|
|
16
20
|
"license": "MIT",
|
|
17
21
|
"repository": {
|
|
@@ -19,6 +23,9 @@
|
|
|
19
23
|
"url": "git+https://github.com/brezelio/installer.git"
|
|
20
24
|
},
|
|
21
25
|
"type": "commonjs",
|
|
26
|
+
"files": [
|
|
27
|
+
"dist"
|
|
28
|
+
],
|
|
22
29
|
"devDependencies": {
|
|
23
30
|
"@semantic-release/commit-analyzer": "^13.0.1",
|
|
24
31
|
"@semantic-release/git": "^10.0.1",
|
package/.releaserc.json
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"branches": [
|
|
3
|
-
"main"
|
|
4
|
-
],
|
|
5
|
-
"plugins": [
|
|
6
|
-
"@semantic-release/commit-analyzer",
|
|
7
|
-
"@semantic-release/release-notes-generator",
|
|
8
|
-
"@semantic-release/npm",
|
|
9
|
-
"@semantic-release/github",
|
|
10
|
-
[
|
|
11
|
-
"@semantic-release/git",
|
|
12
|
-
{
|
|
13
|
-
"assets": [
|
|
14
|
-
"package.json"
|
|
15
|
-
],
|
|
16
|
-
"message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
|
|
17
|
-
}
|
|
18
|
-
]
|
|
19
|
-
]
|
|
20
|
-
}
|
package/install.sh
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
#!/bin/sh
|
|
2
|
-
set -e
|
|
3
|
-
|
|
4
|
-
# Colors for output
|
|
5
|
-
RED='\033[0;31m'
|
|
6
|
-
GREEN='\033[0;32m'
|
|
7
|
-
BLUE='\033[0;34m'
|
|
8
|
-
NC='\033[0m' # No Color
|
|
9
|
-
|
|
10
|
-
echo "${BLUE}š„Ø Welcome to the Brezel Installer!${NC}"
|
|
11
|
-
|
|
12
|
-
# Check for Node.js
|
|
13
|
-
if ! command -v node >/dev/null 2>&1; then
|
|
14
|
-
echo "${RED}ā Node.js is required but not found.${NC}"
|
|
15
|
-
echo "Please install Node.js 18+ and try again."
|
|
16
|
-
exit 1
|
|
17
|
-
fi
|
|
18
|
-
|
|
19
|
-
# Check for npm
|
|
20
|
-
if ! command -v npm >/dev/null 2>&1; then
|
|
21
|
-
echo "${RED}ā npm is required but not found.${NC}"
|
|
22
|
-
exit 1
|
|
23
|
-
fi
|
|
24
|
-
|
|
25
|
-
# In production, we would use npx -y @kibro/brezel-installer@latest
|
|
26
|
-
# For development/demo purposes in this repo:
|
|
27
|
-
if [ -f "package.json" ] && [ -d "src" ]; then
|
|
28
|
-
echo "š¦ Preparing installer..."
|
|
29
|
-
npm install --silent
|
|
30
|
-
npm run build --silent
|
|
31
|
-
fi
|
|
32
|
-
|
|
33
|
-
if [ -f "./dist/index.js" ]; then
|
|
34
|
-
echo "š Launching installer..."
|
|
35
|
-
# Pass all arguments to the TS installer
|
|
36
|
-
node ./dist/index.js "$@"
|
|
37
|
-
else
|
|
38
|
-
# Fallback to npx if no local build is found
|
|
39
|
-
echo "š Fetching and launching latest installer..."
|
|
40
|
-
npx -y @brezel/installer@latest "$@"
|
|
41
|
-
fi
|
package/src/index.ts
DELETED
|
@@ -1,538 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { Command } from 'commander';
|
|
3
|
-
import chalk from 'chalk';
|
|
4
|
-
import prompts from 'prompts';
|
|
5
|
-
import { execa } from 'execa';
|
|
6
|
-
import fs from 'fs';
|
|
7
|
-
import path from 'path';
|
|
8
|
-
import ora from 'ora';
|
|
9
|
-
|
|
10
|
-
const program = new Command();
|
|
11
|
-
|
|
12
|
-
async function runTask(title: string, cmd: string, args: string[], options: any) {
|
|
13
|
-
const spinner = ora(title).start();
|
|
14
|
-
const subprocess = execa(cmd, args, { ...options, all: true });
|
|
15
|
-
|
|
16
|
-
subprocess.stdout?.on('data', (data) => {
|
|
17
|
-
const line = data.toString().trim().split('\n').pop();
|
|
18
|
-
if (line) {
|
|
19
|
-
spinner.text = `${title} ${chalk.dim(`(${line.substring(0, 60).trim()}...)`)}`;
|
|
20
|
-
}
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
subprocess.stderr?.on('data', (data) => {
|
|
24
|
-
const line = data.toString().trim().split('\n').pop();
|
|
25
|
-
if (line) {
|
|
26
|
-
spinner.text = `${title} ${chalk.dim(`(${line.substring(0, 60).trim()}...)`)}`;
|
|
27
|
-
}
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
try {
|
|
31
|
-
await subprocess;
|
|
32
|
-
spinner.succeed(title);
|
|
33
|
-
} catch (e: any) {
|
|
34
|
-
spinner.fail(`${title} failed.`);
|
|
35
|
-
if (e.all) console.error(chalk.red(e.all));
|
|
36
|
-
process.exit(1);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
program
|
|
41
|
-
.name('brezel-installer')
|
|
42
|
-
.description('Installer for Brezel (SPA + API)')
|
|
43
|
-
.version('1.0.0')
|
|
44
|
-
.option('-d, --dir <directory>', 'Installation directory', './brezel')
|
|
45
|
-
.option('-m, --mode <mode>', 'Installation mode (native, docker)', 'native')
|
|
46
|
-
.option('-s, --system <name>', 'System name', 'example')
|
|
47
|
-
.option('-u, --url <url>', 'API URL', 'http://brezel-api.test')
|
|
48
|
-
.option('--spa-url <url>', 'SPA URL', 'http://localhost:5173')
|
|
49
|
-
.option('--db-host <host>', 'Database host', '127.0.0.1')
|
|
50
|
-
.option('--db-port <port>', 'Database port', '3306')
|
|
51
|
-
.option('--db-name <name>', 'Database name', 'brezel_meta')
|
|
52
|
-
.option('--db-user <user>', 'Database user', 'root')
|
|
53
|
-
.option('--db-password <password>', 'Database password', '')
|
|
54
|
-
.option('--php-path <path>', 'Path to PHP executable', 'php')
|
|
55
|
-
.option('--gitlab-token <token>', 'GitLab Personal Access Token')
|
|
56
|
-
.option('--no-interactive', 'Run in non-interactive mode')
|
|
57
|
-
.option('--source-mode <mode>', 'Source control mode (clone, fork)', 'clone')
|
|
58
|
-
.option('--components <list>', 'Optional components to install (mariadb, nginx, ssl, cron)', '');
|
|
59
|
-
|
|
60
|
-
const REPO_SKELETON = 'https://github.com/brezelio/brezel.git';
|
|
61
|
-
|
|
62
|
-
program.action(async (options) => {
|
|
63
|
-
console.log(chalk.bold.blue('\nš„Ø Welcome to the Brezel Installer!\n'));
|
|
64
|
-
|
|
65
|
-
const checkPhp = async (phpPath: string) => {
|
|
66
|
-
try {
|
|
67
|
-
const { stdout } = await execa(phpPath, ['-r', 'echo PHP_VERSION;']);
|
|
68
|
-
// Use regex to find the version string (e.g. 8.3.1 or 8.4.14) in case of warnings
|
|
69
|
-
const match = stdout.match(/(\d+)\.(\d+)\.(\d+)/);
|
|
70
|
-
if (!match) return null;
|
|
71
|
-
|
|
72
|
-
const major = parseInt(match[1], 10);
|
|
73
|
-
const minor = parseInt(match[2], 10);
|
|
74
|
-
const version = match[0];
|
|
75
|
-
|
|
76
|
-
return {
|
|
77
|
-
version,
|
|
78
|
-
valid: (major > 8 || (major === 8 && minor >= 3))
|
|
79
|
-
};
|
|
80
|
-
} catch (e) {
|
|
81
|
-
return null;
|
|
82
|
-
}
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
// 1. Prerequisites Check
|
|
86
|
-
const spinner = ora('Checking prerequisites...').start();
|
|
87
|
-
const checks: any = {};
|
|
88
|
-
|
|
89
|
-
try {
|
|
90
|
-
// Critical Deps
|
|
91
|
-
for (const dep of ['git', 'node', 'npm']) {
|
|
92
|
-
await execa(dep, ['--version']);
|
|
93
|
-
checks[dep] = true;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// PHP version check
|
|
97
|
-
const phpResult = await checkPhp(options.phpPath || 'php');
|
|
98
|
-
if (phpResult) {
|
|
99
|
-
checks.php = phpResult.version;
|
|
100
|
-
checks.phpValid = phpResult.valid;
|
|
101
|
-
} else {
|
|
102
|
-
checks.php = false;
|
|
103
|
-
checks.phpValid = false;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// Composer check
|
|
107
|
-
try {
|
|
108
|
-
await execa('composer', ['--version']);
|
|
109
|
-
checks.composer = true;
|
|
110
|
-
} catch (e) {
|
|
111
|
-
checks.composer = false;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// Docker check
|
|
115
|
-
try {
|
|
116
|
-
await execa('docker', ['--version']);
|
|
117
|
-
checks.docker = true;
|
|
118
|
-
} catch (e) {
|
|
119
|
-
checks.docker = false;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// Valet check (macOS only)
|
|
123
|
-
if (process.platform === 'darwin') {
|
|
124
|
-
try {
|
|
125
|
-
await execa('valet', ['--version']);
|
|
126
|
-
checks.valet = true;
|
|
127
|
-
} catch (e) {
|
|
128
|
-
checks.valet = false;
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
spinner.succeed('System check complete.');
|
|
133
|
-
} catch (error) {
|
|
134
|
-
spinner.fail('Missing critical prerequisites (git, node, or npm).');
|
|
135
|
-
process.exit(1);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
let responses = options;
|
|
139
|
-
|
|
140
|
-
if (options.interactive !== false) {
|
|
141
|
-
const interactiveResponses = await prompts([
|
|
142
|
-
{
|
|
143
|
-
type: 'select',
|
|
144
|
-
name: 'mode',
|
|
145
|
-
message: 'How do you want to install Brezel?',
|
|
146
|
-
choices: [
|
|
147
|
-
{
|
|
148
|
-
title: `Native (Bare metal) ${!checks.phpValid && checks.php ? chalk.yellow('(PHP 8.3+ required, current: ' + checks.php + ')') : (!checks.php ? chalk.red('(PHP not found)') : '')}`,
|
|
149
|
-
value: 'native',
|
|
150
|
-
disabled: false
|
|
151
|
-
},
|
|
152
|
-
...(checks.valet ? [{
|
|
153
|
-
title: `Valet (macOS Magic) ${chalk.dim('- handles domain mapping automatically')}`,
|
|
154
|
-
value: 'valet'
|
|
155
|
-
}] : []),
|
|
156
|
-
{
|
|
157
|
-
title: `Docker (Containerized) ${!checks.docker ? chalk.red('(Docker required)') : ''}`,
|
|
158
|
-
value: 'docker',
|
|
159
|
-
disabled: !checks.docker
|
|
160
|
-
}
|
|
161
|
-
],
|
|
162
|
-
initial: (options.mode === 'valet' && checks.valet) ? 1 : ((options.mode === 'docker' && checks.docker) ? (checks.valet ? 2 : 1) : 0)
|
|
163
|
-
},
|
|
164
|
-
{
|
|
165
|
-
type: (prev, values) => (values.mode === 'native' || values.mode === 'valet') ? 'text' : null,
|
|
166
|
-
name: 'phpPath',
|
|
167
|
-
message: 'Path to PHP 8.3+ executable:',
|
|
168
|
-
initial: options.phpPath || 'php',
|
|
169
|
-
validate: async (val) => {
|
|
170
|
-
const res = await checkPhp(val);
|
|
171
|
-
if (!res) return 'PHP not found at this path.';
|
|
172
|
-
if (!res.valid) return `PHP version ${res.version} is too old. 8.3+ required.`;
|
|
173
|
-
return true;
|
|
174
|
-
}
|
|
175
|
-
},
|
|
176
|
-
{
|
|
177
|
-
type: 'select',
|
|
178
|
-
name: 'sourceMode',
|
|
179
|
-
message: 'Source control mode:',
|
|
180
|
-
choices: [
|
|
181
|
-
{ title: 'Clone without history', value: 'clone' },
|
|
182
|
-
{ title: 'Fork and clone', value: 'fork' }
|
|
183
|
-
],
|
|
184
|
-
initial: options.sourceMode === 'fork' ? 1 : 0
|
|
185
|
-
},
|
|
186
|
-
{
|
|
187
|
-
type: (prev) => prev === 'fork' ? 'text' : null,
|
|
188
|
-
name: 'forkUrl',
|
|
189
|
-
message: 'Enter your fork URL (git@...):',
|
|
190
|
-
validate: (v) => v.length > 0 ? true : 'Fork URL is required'
|
|
191
|
-
},
|
|
192
|
-
{
|
|
193
|
-
type: 'text',
|
|
194
|
-
name: 'gitlabToken',
|
|
195
|
-
message: 'GitLab Personal Access Token (for @kibro packages, scope: read_api, read_registry)',
|
|
196
|
-
initial: options.gitlabToken,
|
|
197
|
-
validate: (v) => (v && v.length > 0) ? true : 'Token is required'
|
|
198
|
-
},
|
|
199
|
-
{
|
|
200
|
-
type: 'text',
|
|
201
|
-
name: 'dir',
|
|
202
|
-
message: 'Installation directory:',
|
|
203
|
-
initial: options.dir
|
|
204
|
-
},
|
|
205
|
-
{
|
|
206
|
-
type: 'text',
|
|
207
|
-
name: 'system',
|
|
208
|
-
message: 'System name:',
|
|
209
|
-
initial: options.system
|
|
210
|
-
},
|
|
211
|
-
{
|
|
212
|
-
type: 'text',
|
|
213
|
-
name: 'url',
|
|
214
|
-
message: 'API URL:',
|
|
215
|
-
initial: (prev: any, values: any) => options.url !== 'http://brezel-api.test' ? options.url : `http://${values.system}.test`
|
|
216
|
-
},
|
|
217
|
-
{
|
|
218
|
-
type: 'text',
|
|
219
|
-
name: 'spaUrl',
|
|
220
|
-
message: 'SPA URL:',
|
|
221
|
-
initial: (prev: any, values: any) => {
|
|
222
|
-
if (options.spaUrl !== 'http://localhost:5173') return options.spaUrl;
|
|
223
|
-
if (values.mode === 'valet') return `http://${values.system}.test:5173`;
|
|
224
|
-
return `http://localhost:5173`;
|
|
225
|
-
}
|
|
226
|
-
},
|
|
227
|
-
{
|
|
228
|
-
type: 'multiselect',
|
|
229
|
-
name: 'components',
|
|
230
|
-
message: 'Select optional components to install:',
|
|
231
|
-
choices: [
|
|
232
|
-
{ title: 'MariaDB', value: 'mariadb' },
|
|
233
|
-
{ title: 'Nginx', value: 'nginx' },
|
|
234
|
-
{ title: 'SSL (Certbot)', value: 'ssl' },
|
|
235
|
-
{ title: 'Cron jobs', value: 'cron' }
|
|
236
|
-
],
|
|
237
|
-
initial: (options.components && typeof options.components === 'string')
|
|
238
|
-
? options.components.split(',').map(c => ['mariadb', 'nginx', 'ssl', 'cron'].indexOf(c))
|
|
239
|
-
: undefined
|
|
240
|
-
},
|
|
241
|
-
{
|
|
242
|
-
type: 'text',
|
|
243
|
-
name: 'dbHost',
|
|
244
|
-
message: 'Database Host',
|
|
245
|
-
initial: options.dbHost
|
|
246
|
-
},
|
|
247
|
-
{
|
|
248
|
-
type: 'text',
|
|
249
|
-
name: 'dbPort',
|
|
250
|
-
message: 'Database Port',
|
|
251
|
-
initial: options.dbPort
|
|
252
|
-
},
|
|
253
|
-
{
|
|
254
|
-
type: 'text',
|
|
255
|
-
name: 'dbName',
|
|
256
|
-
message: 'Database Name',
|
|
257
|
-
initial: options.dbName
|
|
258
|
-
},
|
|
259
|
-
{
|
|
260
|
-
type: 'text',
|
|
261
|
-
name: 'dbUser',
|
|
262
|
-
message: 'Database User',
|
|
263
|
-
initial: options.dbUser
|
|
264
|
-
},
|
|
265
|
-
{
|
|
266
|
-
type: 'password',
|
|
267
|
-
name: 'dbPassword',
|
|
268
|
-
message: 'Database Password',
|
|
269
|
-
initial: options.dbPassword
|
|
270
|
-
}
|
|
271
|
-
]);
|
|
272
|
-
|
|
273
|
-
responses = { ...options, ...interactiveResponses };
|
|
274
|
-
} else {
|
|
275
|
-
// In non-interactive mode, parse components string into array
|
|
276
|
-
if (typeof responses.components === 'string') {
|
|
277
|
-
responses.components = responses.components.split(',').filter(Boolean);
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
if (!responses.dir) process.exit(1);
|
|
282
|
-
|
|
283
|
-
// Validation after response
|
|
284
|
-
const isNative = responses.mode === 'native' || responses.mode === 'valet';
|
|
285
|
-
|
|
286
|
-
if (isNative) {
|
|
287
|
-
const phpRes = await checkPhp(responses.phpPath);
|
|
288
|
-
if (!phpRes || !phpRes.valid) {
|
|
289
|
-
ora(`Native/Valet mode requires PHP 8.3+, but version ${phpRes?.version || 'none'} was found at ${responses.phpPath}.`).fail();
|
|
290
|
-
process.exit(1);
|
|
291
|
-
}
|
|
292
|
-
if (!checks.composer) {
|
|
293
|
-
ora('Native/Valet mode requires Composer, but it was not found.').fail();
|
|
294
|
-
process.exit(1);
|
|
295
|
-
}
|
|
296
|
-
} else if (responses.mode === 'docker' && !checks.docker) {
|
|
297
|
-
ora('Docker mode requires Docker, but it was not found.').fail();
|
|
298
|
-
process.exit(1);
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
const rootDir = path.resolve(responses.dir);
|
|
302
|
-
|
|
303
|
-
if (!fs.existsSync(rootDir)) {
|
|
304
|
-
const s = ora(`Cloning Brezel skeleton (${responses.sourceMode === 'clone' ? 'no history' : 'fork'})...`).start();
|
|
305
|
-
try {
|
|
306
|
-
const cloneUrl = responses.sourceMode === 'fork' ? responses.forkUrl : REPO_SKELETON;
|
|
307
|
-
const cloneArgs = ['clone'];
|
|
308
|
-
if (responses.sourceMode === 'clone') {
|
|
309
|
-
cloneArgs.push('--depth', '1');
|
|
310
|
-
}
|
|
311
|
-
cloneArgs.push(cloneUrl, rootDir);
|
|
312
|
-
|
|
313
|
-
await execa('git', cloneArgs);
|
|
314
|
-
|
|
315
|
-
if (responses.sourceMode === 'clone') {
|
|
316
|
-
fs.rmSync(path.join(rootDir, '.git'), { recursive: true, force: true });
|
|
317
|
-
await execa('git', ['init'], { cwd: rootDir });
|
|
318
|
-
await execa('git', ['add', '.'], { cwd: rootDir });
|
|
319
|
-
await execa('git', ['commit', '-m', 'Initial commit from Brezel Installer'], { cwd: rootDir });
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
s.succeed('Brezel skeleton cloned.');
|
|
323
|
-
} catch (e) {
|
|
324
|
-
s.fail('Failed to clone repository.');
|
|
325
|
-
console.error(e);
|
|
326
|
-
process.exit(1);
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
// 2. Optional Components (Bare metal only)
|
|
331
|
-
if (responses.mode === 'native' && responses.components && responses.components.length > 0) {
|
|
332
|
-
console.log(chalk.bold.cyan('\nš Installing optional components...'));
|
|
333
|
-
for (const component of responses.components) {
|
|
334
|
-
const compSpinner = ora(`Installing ${component}...`).start();
|
|
335
|
-
try {
|
|
336
|
-
if (process.platform === 'linux') {
|
|
337
|
-
if (component === 'mariadb') {
|
|
338
|
-
await execa('sudo', ['apt-get', 'update']);
|
|
339
|
-
await execa('sudo', ['apt-get', 'install', '-y', 'mariadb-server']);
|
|
340
|
-
compSpinner.succeed('MariaDB installed.');
|
|
341
|
-
} else if (component === 'nginx') {
|
|
342
|
-
await execa('sudo', ['apt-get', 'install', '-y', 'nginx']);
|
|
343
|
-
compSpinner.succeed('Nginx installed.');
|
|
344
|
-
} else if (component === 'ssl') {
|
|
345
|
-
await execa('sudo', ['apt-get', 'install', '-y', 'certbot', 'python3-certbot-nginx']);
|
|
346
|
-
compSpinner.succeed('Certbot installed.');
|
|
347
|
-
} else if (component === 'cron') {
|
|
348
|
-
const cronJob = `* * * * * cd ${rootDir} && ${responses.phpPath} bakery schedule:run >> /dev/null 2>&1`;
|
|
349
|
-
compSpinner.info('Cron job suggested: ' + cronJob);
|
|
350
|
-
}
|
|
351
|
-
} else if (process.platform === 'darwin') {
|
|
352
|
-
if (component === 'mariadb') {
|
|
353
|
-
await execa('brew', ['install', 'mariadb']);
|
|
354
|
-
compSpinner.succeed('MariaDB installed via Homebrew.');
|
|
355
|
-
} else if (component === 'nginx') {
|
|
356
|
-
await execa('brew', ['install', 'nginx']);
|
|
357
|
-
compSpinner.succeed('Nginx installed via Homebrew.');
|
|
358
|
-
} else {
|
|
359
|
-
compSpinner.warn(`${component} installation not fully automated for macOS.`);
|
|
360
|
-
}
|
|
361
|
-
} else {
|
|
362
|
-
compSpinner.warn(`${component} installation not supported on this OS.`);
|
|
363
|
-
}
|
|
364
|
-
} catch (e) {
|
|
365
|
-
compSpinner.fail(`Failed to install ${component}.`);
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
if (responses.mode === 'docker') {
|
|
371
|
-
console.log(chalk.bold.cyan('\nš³ Setting up Docker environment...'));
|
|
372
|
-
|
|
373
|
-
const envExamplePath = path.join(rootDir, '.env.example');
|
|
374
|
-
const envPath = path.join(rootDir, '.env');
|
|
375
|
-
|
|
376
|
-
if (fs.existsSync(envExamplePath)) {
|
|
377
|
-
let envContent = fs.readFileSync(envExamplePath, 'utf-8');
|
|
378
|
-
|
|
379
|
-
envContent = envContent.replace(/APP_URL=.*/, `APP_URL=${responses.url}`);
|
|
380
|
-
envContent = envContent.replace(/VITE_APP_API_URL=.*/, `VITE_APP_API_URL=${responses.url}`);
|
|
381
|
-
envContent = envContent.replace(/VITE_APP_SYSTEM=.*/, `VITE_APP_SYSTEM=${responses.system}`);
|
|
382
|
-
envContent = envContent.replace(/TENANCY_HOST=.*/, `TENANCY_HOST=db`);
|
|
383
|
-
envContent = envContent.replace(/TENANCY_PASSWORD=.*/, `TENANCY_PASSWORD=password`);
|
|
384
|
-
|
|
385
|
-
if (!envContent.includes('APP_SYSTEM=')) {
|
|
386
|
-
envContent += `\nAPP_SYSTEM=${responses.system}\n`;
|
|
387
|
-
} else {
|
|
388
|
-
envContent = envContent.replace(/APP_SYSTEM=.*/, `APP_SYSTEM=${responses.system}`);
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
fs.writeFileSync(envPath, envContent);
|
|
392
|
-
ora('Configured .env for Docker').succeed();
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
await runTask('Building and starting Docker containers', 'docker', ['compose', 'up', '-d', '--build'], {
|
|
396
|
-
cwd: rootDir,
|
|
397
|
-
env: {
|
|
398
|
-
...process.env,
|
|
399
|
-
COMPOSER_TOKEN: responses.gitlabToken,
|
|
400
|
-
NPM_TOKEN: responses.gitlabToken,
|
|
401
|
-
APP_SYSTEM: responses.system,
|
|
402
|
-
APP_URL: responses.url
|
|
403
|
-
}
|
|
404
|
-
});
|
|
405
|
-
|
|
406
|
-
console.log(chalk.bold.cyan('\nš„ Initializing Brezel in Docker...'));
|
|
407
|
-
const initSpinner = ora('Waiting for database...').start();
|
|
408
|
-
|
|
409
|
-
await new Promise(r => setTimeout(r, 10000));
|
|
410
|
-
|
|
411
|
-
const runDockerCmd = async (cmd: string[]) => {
|
|
412
|
-
await execa('docker', ['compose', 'exec', 'api', ...cmd], { cwd: rootDir, stdio: 'inherit' });
|
|
413
|
-
};
|
|
414
|
-
|
|
415
|
-
try {
|
|
416
|
-
initSpinner.text = 'Running bakery init...';
|
|
417
|
-
await runDockerCmd(['php', 'bakery', 'init']);
|
|
418
|
-
|
|
419
|
-
console.log(chalk.dim(`Creating system "${responses.system}"...`));
|
|
420
|
-
await runDockerCmd(['php', 'bakery', 'system', 'create', responses.system]);
|
|
421
|
-
|
|
422
|
-
console.log(chalk.dim('Applying system config...'));
|
|
423
|
-
await runDockerCmd(['php', 'bakery', 'apply']);
|
|
424
|
-
|
|
425
|
-
initSpinner.succeed('Brezel initialized in Docker.');
|
|
426
|
-
|
|
427
|
-
} catch (e) {
|
|
428
|
-
initSpinner.fail('Initialization in Docker failed.');
|
|
429
|
-
console.error(e);
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
console.log(chalk.bold.green('\nā
Docker Installation complete!'));
|
|
433
|
-
console.log(chalk.white(`
|
|
434
|
-
Services are running:
|
|
435
|
-
API: ${responses.url} (mapped to localhost:8081)
|
|
436
|
-
SPA: ${responses.spaUrl} (mapped to localhost:3000)
|
|
437
|
-
|
|
438
|
-
To stop:
|
|
439
|
-
cd ${rootDir}
|
|
440
|
-
docker compose down
|
|
441
|
-
`));
|
|
442
|
-
|
|
443
|
-
return;
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
// 3. Install Dependencies (Native/Valet Mode)
|
|
447
|
-
if (isNative) {
|
|
448
|
-
console.log(chalk.bold.cyan('\nš¦ Installing Dependencies...'));
|
|
449
|
-
|
|
450
|
-
await execa('composer', ['config', 'gitlab-token.gitlab.kiwis-and-brownies.de', responses.gitlabToken], { cwd: rootDir });
|
|
451
|
-
|
|
452
|
-
await runTask('Installing PHP dependencies (Composer)', 'composer', ['install', '--no-interaction'], { cwd: rootDir });
|
|
453
|
-
|
|
454
|
-
const npmrcPath = path.join(rootDir, '.npmrc');
|
|
455
|
-
const npmrcContent = `
|
|
456
|
-
@kibro:registry=https://gitlab.kiwis-and-brownies.de/api/v4/packages/npm/
|
|
457
|
-
//gitlab.kiwis-and-brownies.de/api/v4/packages/npm/:_authToken=${responses.gitlabToken}
|
|
458
|
-
`;
|
|
459
|
-
fs.writeFileSync(npmrcPath, npmrcContent);
|
|
460
|
-
|
|
461
|
-
await runTask('Installing Node dependencies (npm)', 'npm', ['install'], { cwd: rootDir });
|
|
462
|
-
|
|
463
|
-
const envExamplePath = path.join(rootDir, '.env.example');
|
|
464
|
-
const envPath = path.join(rootDir, '.env');
|
|
465
|
-
if (fs.existsSync(envExamplePath)) {
|
|
466
|
-
let envContent = fs.readFileSync(envExamplePath, 'utf-8');
|
|
467
|
-
|
|
468
|
-
envContent = envContent.replace(/APP_URL=.*/, `APP_URL=${responses.url}`);
|
|
469
|
-
envContent = envContent.replace(/VITE_APP_API_URL=.*/, `VITE_APP_API_URL=${responses.url}`);
|
|
470
|
-
envContent = envContent.replace(/VITE_APP_SYSTEM=.*/, `VITE_APP_SYSTEM=${responses.system}`);
|
|
471
|
-
|
|
472
|
-
envContent = envContent.replace(/TENANCY_HOST=.*/, `TENANCY_HOST=${responses.dbHost}`);
|
|
473
|
-
envContent = envContent.replace(/TENANCY_PORT=.*/, `TENANCY_PORT=${responses.dbPort}`);
|
|
474
|
-
envContent = envContent.replace(/TENANCY_DATABASE=.*/, `TENANCY_DATABASE=${responses.dbName}`);
|
|
475
|
-
envContent = envContent.replace(/TENANCY_USERNAME=.*/, `TENANCY_USERNAME=${responses.dbUser}`);
|
|
476
|
-
envContent = envContent.replace(/TENANCY_PASSWORD=.*/, `TENANCY_PASSWORD=${responses.dbPassword}`);
|
|
477
|
-
|
|
478
|
-
fs.writeFileSync(envPath, envContent);
|
|
479
|
-
ora('Configured .env').succeed();
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
console.log(chalk.bold.cyan('\nš„ Initializing Brezel...'));
|
|
483
|
-
|
|
484
|
-
const runBakery = async (args: string[]) => {
|
|
485
|
-
await execa(responses.phpPath, ['bakery', ...args], { cwd: rootDir, stdio: 'inherit' });
|
|
486
|
-
};
|
|
487
|
-
|
|
488
|
-
try {
|
|
489
|
-
console.log(chalk.dim('Running initialization...'));
|
|
490
|
-
await runBakery(['init']);
|
|
491
|
-
console.log(chalk.dim(`Creating system "${responses.system}"...`));
|
|
492
|
-
await runBakery(['system', 'create', responses.system]);
|
|
493
|
-
console.log(chalk.dim('Applying system config...'));
|
|
494
|
-
await runBakery(['apply']);
|
|
495
|
-
} catch (e) {
|
|
496
|
-
console.error(chalk.red('Initialization failed.'));
|
|
497
|
-
console.error(e);
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
await runTask('Building SPA', 'npm', ['run', 'build'], { cwd: rootDir });
|
|
501
|
-
|
|
502
|
-
// Valet specific setup
|
|
503
|
-
if (responses.mode === 'valet') {
|
|
504
|
-
console.log(chalk.bold.cyan('\nš© Valet Setup...'));
|
|
505
|
-
try {
|
|
506
|
-
await execa('valet', ['link', responses.system], { cwd: rootDir });
|
|
507
|
-
ora(`Linked ${responses.system}.test to Valet.`).succeed();
|
|
508
|
-
|
|
509
|
-
// Try to secure it
|
|
510
|
-
if (responses.url.startsWith('https://')) {
|
|
511
|
-
await execa('valet', ['secure', responses.system], { cwd: rootDir });
|
|
512
|
-
ora(`Secured ${responses.system}.test with SSL.`).succeed();
|
|
513
|
-
}
|
|
514
|
-
} catch (e) {
|
|
515
|
-
console.warn(chalk.yellow('Valet link/secure failed. You might need to run it manually.'));
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
console.log(chalk.bold.cyan('\nš¦ Installing Export Services...'));
|
|
521
|
-
try {
|
|
522
|
-
await execa('npx', ['@kibro/export-installer@latest'], { stdio: 'inherit' });
|
|
523
|
-
} catch (e) {
|
|
524
|
-
console.warn(chalk.yellow('Export services installer finished with warning or error.'));
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
console.log(chalk.bold.green('\nā
Installation complete!'));
|
|
528
|
-
console.log(chalk.white(`
|
|
529
|
-
To start the server (API + SPA dev):
|
|
530
|
-
cd ${responses.dir}
|
|
531
|
-
npm run dev
|
|
532
|
-
|
|
533
|
-
For Windows users:
|
|
534
|
-
bin\\serve_on_windows.ps1
|
|
535
|
-
`));
|
|
536
|
-
});
|
|
537
|
-
|
|
538
|
-
program.parse();
|
package/tsconfig.json
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2020",
|
|
4
|
-
"module": "CommonJS",
|
|
5
|
-
"moduleResolution": "node",
|
|
6
|
-
"esModuleInterop": true,
|
|
7
|
-
"forceConsistentCasingInFileNames": true,
|
|
8
|
-
"strict": true,
|
|
9
|
-
"skipLibCheck": true,
|
|
10
|
-
"outDir": "./dist"
|
|
11
|
-
},
|
|
12
|
-
"include": ["src/**/*"]
|
|
13
|
-
}
|