@seip/blue-bird 0.6.4 → 0.7.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/core/app.js CHANGED
@@ -10,8 +10,6 @@ import compression from "compression";
10
10
  import Config from "./config.js";
11
11
  import Logger from "./logger.js";
12
12
  import Debug from "./debug.js";
13
- import Template from "./template.js";
14
- import SEO from "./seo.js";
15
13
 
16
14
  const __dirname = Config.dirname();
17
15
  const props = Config.props();
@@ -36,7 +34,8 @@ class App {
36
34
  * @param {boolean} [options.cookieParser=true] - Whether to enable cookie parsing.
37
35
  * @param {boolean|Object} [options.rateLimit=false] - Enable global rate limiting.
38
36
  * @param {boolean|Object} [options.swagger=false] - Enable swagger.
39
- * @param {boolean} [options.compression=true] - Enable Gzip compression.
37
+ * @param {boolean} [options.compression=true] - Enable compression.
38
+ * @param {boolean} [options.astro=true] - Astro handler.
40
39
  * @example
41
40
  * const app = new App({
42
41
  * routes: [],
@@ -55,7 +54,14 @@ class App {
55
54
  * info: { title: "Blue Bird API", version: "1.0.0", description: "API Documentation" },
56
55
  * url: "http://localhost:8000"
57
56
  * },
58
- * compression: true
57
+ * compression:true,
58
+ * astro: {
59
+ * server: true,
60
+ * serverEntry: "./frontend/dist/server/entry.mjs",
61
+ * client: false,
62
+ * clientDir: "./frontend/dist/client",
63
+ * base: "/"
64
+ * }
59
65
  * });
60
66
  */
61
67
  constructor(options = {}) {
@@ -75,6 +81,7 @@ class App {
75
81
  this.rateLimit = options.rateLimit ?? false;
76
82
  this.swagger = options.swagger ?? false;
77
83
  this.compression = options.compression ?? true;
84
+ this.astro = options.astro || false;
78
85
  this.loggerInstance = new Logger();
79
86
  /** @type {Set<import('http').ServerResponse>} */
80
87
  this._hotReloadClients = new Set();
@@ -121,13 +128,16 @@ class App {
121
128
 
122
129
  if (this.static.path)
123
130
  this.app.use(
124
- express.static(
125
- path.join(__dirname, this.static.path),
126
- { ...this.static.options, setHeaders: (res) => {
127
- res.setHeader("X-Powered-By", "Blue Bird");
128
- res.setHeader("Cache-Control", "public, max-age=31536000, immutable");
129
- }},
130
- ),
131
+ express.static(path.join(__dirname, this.static.path), {
132
+ ...this.static.options,
133
+ setHeaders: (res) => {
134
+ res.setHeader("X-Powered-By", "Blue Bird");
135
+ res.setHeader(
136
+ "Cache-Control",
137
+ "public, max-age=31536000, immutable",
138
+ );
139
+ },
140
+ }),
131
141
  );
132
142
 
133
143
  this.app.use(cors(this.cors));
@@ -165,13 +175,12 @@ class App {
165
175
  if (this.logger || props.debug) this._middlewareLogger(this.logger);
166
176
 
167
177
  this.app.use((req, res, next) => {
168
- res.setHeader("X-Powered-By", "Blue Bird");
169
- next();
178
+ res.setHeader("X-Powered-By", "Blue Bird");
179
+ next();
170
180
  });
171
181
 
172
182
  if (props.debug) {
173
183
  Debug.middlewareMetrics(this.app);
174
- this._setupHotReload();
175
184
  }
176
185
 
177
186
  if (this.swagger) {
@@ -196,70 +205,65 @@ class App {
196
205
 
197
206
  this._dispatchRoutes();
198
207
 
199
- SEO.registerEndpoints(this.app);
200
-
201
- if (this.notFound) this._notFoundDefault();
202
-
203
- this._errorHandler();
204
- }
205
-
206
- /**
207
- * Sets up hot-reload using Server-Sent Events (SSE).
208
- * Watches the frontend/ directory for .html, .css, .js file changes and notifies connected browsers.
209
- * Also clears the Template cache on file changes so fresh content is served.
210
- * Only active when DEBUG=true in .env.
211
- * @private
212
- */
213
- _setupHotReload() {
214
- this.app.get("/__hot-reload", (req, res) => {
215
- res.setHeader("x-no-compression", "true");
216
- res.writeHead(200, {
217
- "Content-Type": "text/event-stream",
218
- "Cache-Control": "no-cache",
219
- "Connection": "keep-alive",
220
- "X-Accel-Buffering": "no",
221
- });
222
- res.write("data: connected\n\n");
223
- if (typeof res.flush === "function") {
224
- res.flush();
208
+ if (this.astro) {
209
+ const defaultAstro = {
210
+ server: true,
211
+ serverEntry: "./frontend/dist/server/entry.mjs",
212
+ client: false,
213
+ clientDir: "./frontend/dist/client",
214
+ base: "/",
215
+ };
216
+ const astroConfig =
217
+ typeof this.astro === "object"
218
+ ? { ...defaultAstro, ...this.astro }
219
+ : { ...defaultAstro };
220
+
221
+ if (astroConfig.client) {
222
+ const clientPath = path.resolve(astroConfig.clientDir);
223
+ if (fs.existsSync(clientPath)) {
224
+ this.app.use(astroConfig.base, express.static(clientPath));
225
+ console.log(
226
+ chalk.green(
227
+ `[OK] Astro Static Client Assets registered at ${astroConfig.base}`,
228
+ ),
229
+ );
230
+ } else {
231
+ console.warn(
232
+ chalk.yellow(
233
+ `[WARN] Astro client directory not found at: ${clientPath}`,
234
+ ),
235
+ );
236
+ }
225
237
  }
226
- this._hotReloadClients.add(res);
227
- req.on("close", () => {
228
- this._hotReloadClients.delete(res);
229
- });
230
- });
231
238
 
232
- const frontendPath = path.join(__dirname, "frontend");
233
- let debounceTimer = null;
234
-
235
- const notifyClients = () => {
236
- this._hotReloadClients.forEach((client) => {
237
- try {
238
- client.write("data: reload\n\n");
239
- if (typeof client.flush === "function") {
240
- client.flush();
239
+ if (astroConfig.server) {
240
+ const entryPath = path.resolve(astroConfig.serverEntry);
241
+ if (fs.existsSync(entryPath)) {
242
+ try {
243
+ const { handler: ssrHandler } = await import(entryPath);
244
+ this.app.use(ssrHandler);
245
+ console.log(
246
+ chalk.green("[OK] Astro SSR Handler registered successfully."),
247
+ );
248
+ } catch (error) {
249
+ console.error(
250
+ chalk.red("[ERROR] Failed to load Astro SSR Handler:"),
251
+ error.message,
252
+ );
241
253
  }
242
- } catch (_) {
243
- this._hotReloadClients.delete(client);
244
- }
245
- });
246
- };
247
-
248
- try {
249
- fs.watch(frontendPath, { recursive: true }, (eventType, filename) => {
250
- if (!filename) return;
251
- if (/\.(html|css|js)$/i.test(filename)) {
252
- if (debounceTimer) clearTimeout(debounceTimer);
253
- debounceTimer = setTimeout(() => {
254
- console.log(chalk.magenta(`[Hot Reload] ${filename} changed`));
255
- Template.clearCache();
256
- notifyClients();
257
- }, 200);
254
+ } else {
255
+ console.warn(
256
+ chalk.yellow(
257
+ `[WARN] Astro build entrypoint not found at: ${entryPath}`,
258
+ ),
259
+ );
258
260
  }
259
- });
260
- } catch (_) {
261
- console.log(chalk.yellow("[Hot Reload] Could not watch frontend/ directory"));
261
+ }
262
262
  }
263
+
264
+ if (this.notFound) this._notFoundDefault();
265
+
266
+ this._errorHandler();
263
267
  }
264
268
 
265
269
  /**
@@ -279,6 +283,7 @@ class App {
279
283
  Object.keys(req.params).length > 0
280
284
  ? ` ${JSON.stringify(req.params)}`
281
285
  : "";
286
+
282
287
  const ip = req.ip;
283
288
  const now = new Date().toISOString();
284
289
  const time = `${now.split("T")[0]} ${now.split("T")[1].split(".")[0]}`;
@@ -357,14 +362,14 @@ class App {
357
362
  this.app.listen(this.port, () => {
358
363
  console.log(
359
364
  chalk.bold.blue("Blue Bird Server Online\n") +
360
- chalk.bold.cyan("App URL: ") +
361
- chalk.green(`${this.appUrl}`) +
362
- "\n" +
363
- chalk.bold.cyan("Internal: ") +
364
- chalk.green(`${this.host}:${this.port}`) +
365
- "\n" +
366
- (props.debug ? chalk.bold.magenta("Hot Reload: enabled\n") : "") +
367
- chalk.gray("────────────────────────────────"),
365
+ chalk.bold.cyan("App URL: ") +
366
+ chalk.green(`${this.appUrl}`) +
367
+ "\n" +
368
+ chalk.bold.cyan("Internal: ") +
369
+ chalk.green(`${this.host}:${this.port}`) +
370
+ "\n" +
371
+ (props.debug ? chalk.bold.magenta("Hot Reload: enabled\n") : "") +
372
+ chalk.gray("────────────────────────────────"),
368
373
  );
369
374
  });
370
375
  })
package/core/cli/init.js CHANGED
@@ -3,6 +3,10 @@
3
3
  import fs from "node:fs";
4
4
  import path from "node:path";
5
5
  import chalk from "chalk";
6
+ import readline from "node:readline/promises";
7
+ import { stdin as input, stdout as output } from "node:process";
8
+ import crypto from "node:crypto";
9
+ import { execSync } from "node:child_process";
6
10
 
7
11
  /**
8
12
  * Initializes a new Blue Bird project by copying the base structure.
@@ -19,13 +23,63 @@ class ProjectInit {
19
23
  async run() {
20
24
  console.log(chalk.cyan("Starting Blue Bird project initialization..."));
21
25
 
26
+ const rl = readline.createInterface({ input, output });
27
+
28
+ let title = "Blue-Bird";
29
+ let port = 3000;
30
+ let appUrl = "http://localhost:3000";
31
+ let useMysql = false;
32
+ let dbName = "blue_bird";
33
+ let dbUser = "root";
34
+ let dbPassword = "root";
35
+ let dbPort = 3306;
36
+
37
+ try {
38
+ const ask = async (query, defaultValue) => {
39
+ const formattedQuery = defaultValue !== undefined ? `${query} [${defaultValue}]: ` : `${query}: `;
40
+ const answer = await rl.question(formattedQuery);
41
+ return answer.trim() || defaultValue;
42
+ };
43
+
44
+ title = await ask("Project Title", title);
45
+ const portInput = await ask("Server Port", port);
46
+ port = parseInt(portInput, 10);
47
+ if (Number.isNaN(port)) {
48
+ port = 3000;
49
+ }
50
+
51
+ const defaultAppUrl = `http://localhost:${port}`;
52
+ appUrl = await ask("Application URL", defaultAppUrl);
53
+
54
+ const mysqlAns = await ask("Do you want to configure MySQL? (y/n)", "n");
55
+ useMysql = mysqlAns.toLowerCase() === "y" || mysqlAns.toLowerCase() === "yes";
56
+
57
+ if (useMysql) {
58
+ dbName = await ask("Database Name", dbName);
59
+ dbUser = await ask("Database User", dbUser);
60
+ dbPassword = await ask("Database Password", dbPassword);
61
+ const dbPortInput = await ask("Database Port", dbPort);
62
+ dbPort = parseInt(dbPortInput, 10);
63
+ if (Number.isNaN(dbPort)) {
64
+ dbPort = 3306;
65
+ }
66
+ }
67
+ } catch (error) {
68
+ console.error(chalk.red("[ERROR] Error reading configuration input:"), error.message);
69
+ rl.close();
70
+ return;
71
+ } finally {
72
+ rl.close();
73
+ }
74
+
22
75
  const itemsToCopy = [
23
76
  "backend",
24
77
  "frontend",
25
78
  "docker",
26
79
  "docker-compose.yml",
27
80
  ".env_example",
28
- "AGENTS.md"
81
+ "AGENTS.md",
82
+ "index.js"
29
83
  ];
30
84
 
31
85
  try {
@@ -36,31 +90,78 @@ class ProjectInit {
36
90
  if (fs.existsSync(src)) {
37
91
  if (!fs.existsSync(dest)) {
38
92
  this.copyRecursive(src, dest);
39
- console.log(chalk.green(`✓ Copied ${item} to root.`));
93
+ console.log(chalk.green(`[OK] Copied ${item} to root.`));
40
94
  } else {
41
- console.log(chalk.yellow(`! ${item} already exists, skipping.`));
95
+ console.log(chalk.yellow(`[SKIP] ${item} already exists, skipping.`));
42
96
  }
43
97
  } else {
44
- console.warn(chalk.red(`✗ Source ${item} not found in ${this.sourceDir}`));
98
+ console.warn(chalk.red(`[ERROR] Source ${item} not found in ${this.sourceDir}`));
45
99
  }
46
100
  });
47
101
 
48
102
  const envPath = path.join(this.appDir, ".env");
49
103
  const envExamplePath = path.join(this.appDir, ".env_example");
50
- if (fs.existsSync(envExamplePath) && !fs.existsSync(envPath)) {
51
- fs.copyFileSync(envExamplePath, envPath);
52
- console.log(chalk.green("✓ Created .env from .env_example."));
104
+
105
+ if (fs.existsSync(envExamplePath)) {
106
+ let envContent = fs.readFileSync(envExamplePath, "utf-8");
107
+
108
+ const jwtSecret = crypto.randomBytes(32).toString("hex");
109
+
110
+ const updates = {
111
+ TITLE: title,
112
+ PORT: port,
113
+ APP_URL: appUrl,
114
+ JWT_SECRET: jwtSecret,
115
+ };
116
+
117
+ if (useMysql) {
118
+ updates.DB_NAME = dbName;
119
+ updates.DB_USER = dbUser;
120
+ updates.DB_PASSWORD = dbPassword;
121
+ updates.DB_PORT = dbPort;
122
+ updates.DATABASE_URL = `mysql://${dbUser}:${dbPassword}@localhost:${dbPort}/${dbName}`;
123
+ }
124
+
125
+ const lines = envContent.split(/\r?\n/);
126
+ const updatedLines = lines.map(line => {
127
+ const match = line.match(/^([A-Z_]+)=(.+)/);
128
+ if (match) {
129
+ const key = match[1];
130
+ if (updates[key] !== undefined) {
131
+ const value = updates[key];
132
+ if (typeof value === "string" && !value.startsWith('"')) {
133
+ return `${key}="${value}"`;
134
+ }
135
+ return `${key}=${value}`;
136
+ }
137
+ }
138
+ return line;
139
+ });
140
+ envContent = updatedLines.join("\n");
141
+
142
+ fs.writeFileSync(envPath, envContent, "utf-8");
143
+ console.log(chalk.green("[OK] Created and configured .env file."));
53
144
  }
54
145
 
55
146
  this.updatePackageJson();
56
147
 
148
+ if (useMysql) {
149
+ console.log(chalk.cyan("[INFO] Installing mysql2 and redis packages..."));
150
+ try {
151
+ execSync("npm install mysql2 redis", { stdio: "inherit", cwd: this.appDir });
152
+ console.log(chalk.green("[OK] Successfully installed mysql2 and redis."));
153
+ } catch (error) {
154
+ console.warn(chalk.yellow("[ERROR] Automatic package installation failed. Please run 'npm install mysql2 redis' manually."));
155
+ }
156
+ }
157
+
57
158
  console.log(chalk.blue("\nBlue Bird initialization completed!"));
58
159
  console.log(chalk.white("Next steps:"));
59
160
  console.log(chalk.bold(" npm install"));
60
161
  console.log(chalk.bold(" npm run dev"));
61
162
 
62
163
  } catch (error) {
63
- console.error(chalk.red("Error during initialization:"), error.message);
164
+ console.error(chalk.red("[ERROR] Error during initialization:"), error.message);
64
165
  }
65
166
  }
66
167
 
@@ -74,8 +175,11 @@ class ProjectInit {
74
175
  pkg.scripts = pkg.scripts || {};
75
176
 
76
177
  const scriptsToAdd = {
77
- "dev": "node --watch --env-file=.env backend/index.js",
78
- "start": "node --env-file=.env backend/index.js",
178
+ "dev": "node --watch --env-file=.env index.js",
179
+ "dev:astro": "astro dev --root frontend",
180
+ "dev:api": "node --watch --env-file=.env index.js",
181
+ "start": "node --env-file=.env index.js",
182
+ "build": "astro build --root frontend",
79
183
  "init": "blue-bird",
80
184
  "route": "blue-bird route",
81
185
  "swagger-install": "blue-bird swagger-install",
@@ -90,9 +194,14 @@ class ProjectInit {
90
194
  }
91
195
  }
92
196
 
197
+ if (pkg.type !== "module") {
198
+ pkg.type = "module";
199
+ updated = true;
200
+ }
201
+
93
202
  if (updated) {
94
203
  fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2));
95
- console.log(chalk.green(" Updated package.json scripts."));
204
+ console.log(chalk.green("[OK] Updated package.json configuration."));
96
205
  }
97
206
  }
98
207
  }
package/core/logger.js CHANGED
@@ -1,100 +1,99 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
- import Config from "./config.js"
3
+ import Config from "./config.js";
4
4
 
5
- const __dirname = Config.dirname()
5
+ const __dirname = Config.dirname();
6
6
 
7
7
  /**
8
8
  * Logger class for managing application logs by creating dated folders and log files.
9
9
  */
10
10
  class Logger {
11
-
12
- /**
13
- * Initializes the Logger instance and ensures the logs directory exists.
14
- */
15
- constructor() {
16
- this.folder = path.join(__dirname, "logs");
17
- this._currentDay = null;
18
- this._currentDayFolder = null;
19
- if (!fs.existsSync(this.folder)) {
20
- fs.mkdirSync(this.folder, { recursive: true });
21
- }
11
+ /**
12
+ * Initializes the Logger instance and ensures the logs directory exists.
13
+ */
14
+ constructor() {
15
+ this.folder = path.join(__dirname, "backend", "logs");
16
+ this._currentDay = null;
17
+ this._currentDayFolder = null;
18
+ if (!fs.existsSync(this.folder)) {
19
+ fs.mkdirSync(this.folder, { recursive: true });
22
20
  }
21
+ }
23
22
 
24
- /**
25
- * Ensures and returns the path to the log folder for the current day.
26
- * Caches the folder path for the current day to avoid repeated fs checks.
27
- * @returns {string} The absolute path to the current day's log folder.
28
- */
29
- nowFolder() {
30
- const today = this.now();
31
-
32
- if (this._currentDay === today && this._currentDayFolder) {
33
- return this._currentDayFolder;
34
- }
23
+ /**
24
+ * Ensures and returns the path to the log folder for the current day.
25
+ * Caches the folder path for the current day to avoid repeated fs checks.
26
+ * @returns {string} The absolute path to the current day's log folder.
27
+ */
28
+ nowFolder() {
29
+ const today = this.now();
35
30
 
36
- const folder = path.join(this.folder, today);
31
+ if (this._currentDay === today && this._currentDayFolder) {
32
+ return this._currentDayFolder;
33
+ }
37
34
 
38
- if (!fs.existsSync(folder)) {
39
- fs.mkdirSync(folder, { recursive: true });
40
- }
35
+ const folder = path.join(this.folder, today);
41
36
 
42
- this._currentDay = today;
43
- this._currentDayFolder = folder;
44
- return folder;
37
+ if (!fs.existsSync(folder)) {
38
+ fs.mkdirSync(folder, { recursive: true });
45
39
  }
46
40
 
47
- /**
48
- * Gets the current date formatted as YYYY-MM-DD.
49
- * @returns {string} The formatted date string.
50
- */
51
- now() {
52
- return new Date().toISOString().split("T")[0];
53
- }
41
+ this._currentDay = today;
42
+ this._currentDayFolder = folder;
43
+ return folder;
44
+ }
54
45
 
55
- /**
56
- * Appends an informational message to the info.log file (non-blocking).
57
- * @param {string} message - The message to log.
58
- */
59
- info(message) {
60
- const logFile = path.join(this.nowFolder(), 'info.log');
61
- fs.appendFile(logFile, `${message}\n`, (err) => {
62
- if (err) console.error('Logger write error:', err.message);
63
- });
64
- }
46
+ /**
47
+ * Gets the current date formatted as YYYY-MM-DD.
48
+ * @returns {string} The formatted date string.
49
+ */
50
+ now() {
51
+ return new Date().toISOString().split("T")[0];
52
+ }
65
53
 
66
- /**
67
- * Appends an error message to the error.log file (non-blocking).
68
- * @param {string} message - The error message to log.
69
- */
70
- error(message) {
71
- const logFile = path.join(this.nowFolder(), 'error.log');
72
- fs.appendFile(logFile, `${message}\n`, (err) => {
73
- if (err) console.error('Logger write error:', err.message);
74
- });
75
- }
54
+ /**
55
+ * Appends an informational message to the info.log file (non-blocking).
56
+ * @param {string} message - The message to log.
57
+ */
58
+ info(message) {
59
+ const logFile = path.join(this.nowFolder(), "info.log");
60
+ fs.appendFile(logFile, `${message}\n`, (err) => {
61
+ if (err) console.error("Logger write error:", err.message);
62
+ });
63
+ }
76
64
 
77
- /**
78
- * Appends a warning message to the warn.log file (non-blocking).
79
- * @param {string} message - The warning message to log.
80
- */
81
- warning(message) {
82
- const logFile = path.join(this.nowFolder(), 'warn.log');
83
- fs.appendFile(logFile, `${message}\n`, (err) => {
84
- if (err) console.error('Logger write error:', err.message);
85
- });
86
- }
65
+ /**
66
+ * Appends an error message to the error.log file (non-blocking).
67
+ * @param {string} message - The error message to log.
68
+ */
69
+ error(message) {
70
+ const logFile = path.join(this.nowFolder(), "error.log");
71
+ fs.appendFile(logFile, `${message}\n`, (err) => {
72
+ if (err) console.error("Logger write error:", err.message);
73
+ });
74
+ }
87
75
 
88
- /**
89
- * Appends a debug message to the debug.log file (non-blocking).
90
- * @param {string} message - The debug message to log.
91
- */
92
- debug(message) {
93
- const logFile = path.join(this.nowFolder(), 'debug.log');
94
- fs.appendFile(logFile, `${message}\n`, (err) => {
95
- if (err) console.error('Logger write error:', err.message);
96
- });
97
- }
76
+ /**
77
+ * Appends a warning message to the warn.log file (non-blocking).
78
+ * @param {string} message - The warning message to log.
79
+ */
80
+ warning(message) {
81
+ const logFile = path.join(this.nowFolder(), "warn.log");
82
+ fs.appendFile(logFile, `${message}\n`, (err) => {
83
+ if (err) console.error("Logger write error:", err.message);
84
+ });
85
+ }
86
+
87
+ /**
88
+ * Appends a debug message to the debug.log file (non-blocking).
89
+ * @param {string} message - The debug message to log.
90
+ */
91
+ debug(message) {
92
+ const logFile = path.join(this.nowFolder(), "debug.log");
93
+ fs.appendFile(logFile, `${message}\n`, (err) => {
94
+ if (err) console.error("Logger write error:", err.message);
95
+ });
96
+ }
98
97
  }
99
98
 
100
99
  export default Logger;
package/core/router.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import express from "express";
2
2
  import Config from "./config.js";
3
- import SEO from "./seo.js";
3
+
4
4
 
5
5
  const props = Config.props();
6
6
 
@@ -57,11 +57,7 @@ class Router {
57
57
  if (path === "/*" || path === "*") {
58
58
  path = /.*/;
59
59
  }
60
- if (this._seo && typeof path === "string") {
61
- const fullPath = this.path === "/" ? path : `${this.path}${path}`;
62
- const normalizedPath = fullPath === "//" ? "/" : fullPath.replace(/\/+/g, "/");
63
- SEO.addRoute(normalizedPath, this._languages);
64
- }
60
+
65
61
  this.router.get(path, callback);
66
62
  }
67
63
 
@@ -0,0 +1,35 @@
1
+ import { defineConfig } from 'astro/config';
2
+ import node from '@astrojs/node';
3
+ import { loadEnv } from 'vite';
4
+ import { fileURLToPath } from 'url';
5
+ import { dirname, resolve } from 'path';
6
+
7
+ const __dirname = dirname(fileURLToPath(import.meta.url));
8
+
9
+ const env = loadEnv(
10
+ process.env.NODE_ENV ?? 'development',
11
+ resolve(__dirname, '..'),
12
+ '',
13
+ );
14
+
15
+ const apiPort = env.PORT || '3000';
16
+ const apiHost = (env.HOST || 'localhost').replace(/^["']|["']$/g, '');
17
+ const apiTarget = `http://${apiHost}:${apiPort}`;
18
+
19
+ export default defineConfig({
20
+ output: 'server',
21
+ adapter: node({
22
+ mode: 'middleware',
23
+ }),
24
+ vite: {
25
+ envDir: resolve(__dirname, '..'),
26
+ server: {
27
+ proxy: {
28
+ '/api': {
29
+ target: apiTarget,
30
+ changeOrigin: true,
31
+ },
32
+ },
33
+ },
34
+ },
35
+ });