@navneet_25/tempjs 1.1.0 → 2.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.
@@ -3,52 +3,78 @@ import { stdin as input, stdout as output } from "node:process";
3
3
  import { readFile, writeFile } from "node:fs/promises";
4
4
  import { findSiteTs } from "./theme-manager.js";
5
5
 
6
- export async function promptAndApplyBrand(targetDir) {
7
- const siteTsPath = await findSiteTs(targetDir);
8
- if (!siteTsPath) {
9
- console.error("Error: Could not locate constants/site.ts. Make sure you are inside an initialized tempjs template directory.");
10
- return;
11
- }
12
-
13
- // Load current values if possible to provide defaults
14
- let siteContent = await readFile(siteTsPath, "utf8");
6
+ /**
7
+ * @typedef {{
8
+ * yes?: boolean,
9
+ * name?: string,
10
+ * shortName?: string,
11
+ * baseUrl?: string,
12
+ * phone?: string,
13
+ * phoneDisplay?: string,
14
+ * countryCode?: string,
15
+ * email?: string,
16
+ * address?: string
17
+ * }} BrandOptions
18
+ */
15
19
 
16
- // Parse existing values using simple regex
17
- const currentBrandName = (siteContent.match(/name:\s*"([^"]+)"/) || [])[1] || "Chanakya Resort";
18
- const currentShortName = (siteContent.match(/shortName:\s*"([^"]+)"/) || [])[1] || "Chanakya";
19
- const currentBaseUrl = (siteContent.match(/baseUrl:\s*"([^"]+)"/) || [])[1] || "https://chanakyaresort.com";
20
- const currentPhone = (siteContent.match(/phone:\s*"([^"]+)"/) || [])[1] || "9876543210";
21
- const currentPhoneDisplay = (siteContent.match(/phoneDisplay:\s*"([^"]+)"/) || [])[1] || "+91 98765 43210";
22
- const currentCountryCode = (siteContent.match(/countryCode:\s*"([^"]+)"/) || [])[1] || "91";
23
- const currentEmail = (siteContent.match(/email:\s*"([^"]+)"/) || [])[1] || "reservations@chanakyaresort.com";
24
- const currentAddress = (siteContent.match(/full:\s*"([^"]+)"/) || [])[1] || "Lonavala, Maharashtra, India";
20
+ /**
21
+ * @param {string} siteContent
22
+ */
23
+ function parseCurrentBrandValues(siteContent) {
24
+ return {
25
+ brandName: (siteContent.match(/name:\s*"([^"]+)"/) || [])[1] || "Chanakya Resort",
26
+ shortName: (siteContent.match(/shortName:\s*"([^"]+)"/) || [])[1] || "Chanakya",
27
+ baseUrl: (siteContent.match(/baseUrl:\s*"([^"]+)"/) || [])[1] || "https://chanakyaresort.com",
28
+ phone: (siteContent.match(/phone:\s*"([^"]+)"/) || [])[1] || "9876543210",
29
+ phoneDisplay: (siteContent.match(/phoneDisplay:\s*"([^"]+)"/) || [])[1] || "+91 98765 43210",
30
+ countryCode: (siteContent.match(/countryCode:\s*"([^"]+)"/) || [])[1] || "91",
31
+ email: (siteContent.match(/email:\s*"([^"]+)"/) || [])[1] || "reservations@chanakyaresort.com",
32
+ address: (siteContent.match(/full:\s*"([^"]+)"/) || [])[1] || "Lonavala, Maharashtra, India",
33
+ };
34
+ }
25
35
 
26
- const rl = createInterface({ input, output });
36
+ /**
37
+ * @param {string} baseUrl
38
+ */
39
+ function deriveWwwHost(baseUrl) {
27
40
  try {
28
- console.log("\n--- Configure Brand & Contact Details ---");
41
+ const url = new URL(baseUrl);
42
+ return url.hostname.startsWith("www.") ? url.hostname : `www.${url.hostname}`;
43
+ } catch {
44
+ let wwwHost = baseUrl.replace(/^https?:\/\//, "");
45
+ if (!wwwHost.startsWith("www.")) wwwHost = `www.${wwwHost}`;
46
+ return wwwHost;
47
+ }
48
+ }
49
+
50
+ /**
51
+ * @param {string} siteContent
52
+ * @param {{
53
+ * brandName: string,
54
+ * shortName: string,
55
+ * baseUrl: string,
56
+ * phone: string,
57
+ * phoneDisplay: string,
58
+ * countryCode: string,
59
+ * email: string,
60
+ * address: string
61
+ * }} values
62
+ */
63
+ function applyBrandToSiteContent(siteContent, values) {
64
+ const {
65
+ brandName,
66
+ shortName,
67
+ baseUrl,
68
+ phone,
69
+ phoneDisplay,
70
+ countryCode,
71
+ email,
72
+ address,
73
+ } = values;
29
74
 
30
- const brandName = (await rl.question(`Brand Name [${currentBrandName}]: `)).trim() || currentBrandName;
31
- const shortName = (await rl.question(`Short/Display Name [${currentShortName}]: `)).trim() || currentShortName;
32
- const baseUrl = (await rl.question(`Base URL [${currentBaseUrl}]: `)).trim() || currentBaseUrl;
33
-
34
- // Derive wwwHost from baseUrl
35
- let wwwHost = "www.example.com";
36
- try {
37
- const url = new URL(baseUrl);
38
- wwwHost = url.hostname.startsWith("www.") ? url.hostname : `www.${url.hostname}`;
39
- } catch {
40
- wwwHost = baseUrl.replace(/^https?:\/\//, "");
41
- if (!wwwHost.startsWith("www.")) wwwHost = `www.${wwwHost}`;
42
- }
43
-
44
- const phone = (await rl.question(`Contact Phone Number [${currentPhone}]: `)).trim() || currentPhone;
45
- const phoneDisplay = (await rl.question(`Display Phone Number [${currentPhoneDisplay}]: `)).trim() || currentPhoneDisplay;
46
- const countryCode = (await rl.question(`Country Code [${currentCountryCode}]: `)).trim() || currentCountryCode;
47
- const email = (await rl.question(`Contact Email [${currentEmail}]: `)).trim() || currentEmail;
48
- const address = (await rl.question(`Full Address [${currentAddress}]: `)).trim() || currentAddress;
49
-
50
- // Build replacement blocks
51
- const newBrandBlock = `brand: {
75
+ const wwwHost = deriveWwwHost(baseUrl);
76
+
77
+ const newBrandBlock = `brand: {
52
78
  name: "${brandName}",
53
79
  shortName: "${shortName}",
54
80
  tagline: "Where Nature Meets Refined Comfort",
@@ -58,21 +84,20 @@ export async function promptAndApplyBrand(targetDir) {
58
84
  managedBy: "Managed by ${brandName}.",
59
85
  }`;
60
86
 
61
- const newDomainBlock = `domain: {
87
+ const newDomainBlock = `domain: {
62
88
  baseUrl: "${baseUrl}",
63
89
  wwwHost: "${wwwHost}",
64
90
  }`;
65
91
 
66
- // Extract address locality/region/country or keep defaults
67
- let locality = "Lonavala";
68
- let region = "MH";
69
- const addressParts = address.split(",");
70
- if (addressParts.length >= 2) {
71
- locality = addressParts[0].trim();
72
- region = addressParts[1].trim();
73
- }
92
+ let locality = "Lonavala";
93
+ let region = "MH";
94
+ const addressParts = address.split(",");
95
+ if (addressParts.length >= 2) {
96
+ locality = addressParts[0].trim();
97
+ region = addressParts[1].trim();
98
+ }
74
99
 
75
- const newContactBlock = `contact: {
100
+ const newContactBlock = `contact: {
76
101
  phone: "${phone}",
77
102
  phoneDisplay: "${phoneDisplay}",
78
103
  countryCode: "${countryCode}",
@@ -85,20 +110,107 @@ export async function promptAndApplyBrand(targetDir) {
85
110
  },
86
111
  }`;
87
112
 
88
- // Apply replacements using exact patterns matching our templates
89
- const brandRegex = /brand:\s*\{[\s\S]*?\n\s*\},/;
90
- const domainRegex = /domain:\s*\{[\s\S]*?\n\s*\},/;
91
- const contactRegex = /contact:\s*\{[\s\S]*?address:\s*\{[\s\S]*?\}[\s\S]*?\n\s*\},/;
92
-
93
- if (brandRegex.test(siteContent)) {
94
- siteContent = siteContent.replace(brandRegex, newBrandBlock + ",");
95
- }
96
- if (domainRegex.test(siteContent)) {
97
- siteContent = siteContent.replace(domainRegex, newDomainBlock + ",");
98
- }
99
- if (contactRegex.test(siteContent)) {
100
- siteContent = siteContent.replace(contactRegex, newContactBlock + ",");
101
- }
113
+ const brandRegex = /brand:\s*\{[\s\S]*?\n\s*\},/;
114
+ const domainRegex = /domain:\s*\{[\s\S]*?\n\s*\},/;
115
+ const contactRegex = /contact:\s*\{[\s\S]*?address:\s*\{[\s\S]*?\}[\s\S]*?\n\s*\},/;
116
+
117
+ if (brandRegex.test(siteContent)) {
118
+ siteContent = siteContent.replace(brandRegex, newBrandBlock + ",");
119
+ }
120
+ if (domainRegex.test(siteContent)) {
121
+ siteContent = siteContent.replace(domainRegex, newDomainBlock + ",");
122
+ }
123
+ if (contactRegex.test(siteContent)) {
124
+ siteContent = siteContent.replace(contactRegex, newContactBlock + ",");
125
+ }
126
+
127
+ return siteContent;
128
+ }
129
+
130
+ /**
131
+ * @param {string} targetDir
132
+ * @param {BrandOptions} options
133
+ */
134
+ export async function applyBrand(targetDir, options = {}) {
135
+ const siteTsPath = await findSiteTs(targetDir);
136
+ if (!siteTsPath) {
137
+ throw new Error(
138
+ "Could not locate constants/site.ts. Make sure you are inside an initialized tempjs template directory."
139
+ );
140
+ }
141
+
142
+ let siteContent = await readFile(siteTsPath, "utf8");
143
+ const current = parseCurrentBrandValues(siteContent);
144
+
145
+ const values = {
146
+ brandName: options.name?.trim() || current.brandName,
147
+ shortName: options.shortName?.trim() || current.shortName,
148
+ baseUrl: options.baseUrl?.trim() || current.baseUrl,
149
+ phone: options.phone?.trim() || current.phone,
150
+ phoneDisplay: options.phoneDisplay?.trim() || current.phoneDisplay,
151
+ countryCode: options.countryCode?.trim() || current.countryCode,
152
+ email: options.email?.trim() || current.email,
153
+ address: options.address?.trim() || current.address,
154
+ };
155
+
156
+ siteContent = applyBrandToSiteContent(siteContent, values);
157
+ await writeFile(siteTsPath, siteContent, "utf8");
158
+ console.log("Successfully updated brand and contact configurations in constants/site.ts.");
159
+ }
160
+
161
+ /**
162
+ * @param {string} targetDir
163
+ * @param {BrandOptions} options
164
+ */
165
+ export async function promptAndApplyBrand(targetDir, options = {}) {
166
+ if (options.yes) {
167
+ await applyBrand(targetDir, options);
168
+ return;
169
+ }
170
+
171
+ const siteTsPath = await findSiteTs(targetDir);
172
+ if (!siteTsPath) {
173
+ console.error(
174
+ "Error: Could not locate constants/site.ts. Make sure you are inside an initialized tempjs template directory."
175
+ );
176
+ return;
177
+ }
178
+
179
+ let siteContent = await readFile(siteTsPath, "utf8");
180
+ const current = parseCurrentBrandValues(siteContent);
181
+
182
+ const rl = createInterface({ input, output });
183
+ try {
184
+ console.log("\n--- Configure Brand & Contact Details ---");
185
+
186
+ const brandName =
187
+ (await rl.question(`Brand Name [${current.brandName}]: `)).trim() || current.brandName;
188
+ const shortName =
189
+ (await rl.question(`Short/Display Name [${current.shortName}]: `)).trim() || current.shortName;
190
+ const baseUrl =
191
+ (await rl.question(`Base URL [${current.baseUrl}]: `)).trim() || current.baseUrl;
192
+ const phone =
193
+ (await rl.question(`Contact Phone Number [${current.phone}]: `)).trim() || current.phone;
194
+ const phoneDisplay =
195
+ (await rl.question(`Display Phone Number [${current.phoneDisplay}]: `)).trim() ||
196
+ current.phoneDisplay;
197
+ const countryCode =
198
+ (await rl.question(`Country Code [${current.countryCode}]: `)).trim() || current.countryCode;
199
+ const email =
200
+ (await rl.question(`Contact Email [${current.email}]: `)).trim() || current.email;
201
+ const address =
202
+ (await rl.question(`Full Address [${current.address}]: `)).trim() || current.address;
203
+
204
+ siteContent = applyBrandToSiteContent(siteContent, {
205
+ brandName,
206
+ shortName,
207
+ baseUrl,
208
+ phone,
209
+ phoneDisplay,
210
+ countryCode,
211
+ email,
212
+ address,
213
+ });
102
214
 
103
215
  await writeFile(siteTsPath, siteContent, "utf8");
104
216
  console.log("Successfully updated brand and contact configurations in constants/site.ts.");
package/cli/config.js CHANGED
@@ -5,7 +5,20 @@ import { fileURLToPath } from "node:url";
5
5
  const packageRoot = join(dirname(fileURLToPath(import.meta.url)), "..");
6
6
 
7
7
  /** @typedef {{ owner: string, repo: string, branch: string, templatesPath: string }} RepositoryConfig */
8
- /** @typedef {{ directory: string, name: string, description: string }} TemplateEntry */
8
+ /** @typedef {{
9
+ * directory: string,
10
+ * name: string,
11
+ * description: string,
12
+ * version?: string,
13
+ * stack?: string[],
14
+ * packageManager?: string,
15
+ * node?: string,
16
+ * setupTime?: string,
17
+ * docker?: boolean,
18
+ * tags?: string[],
19
+ * features?: string[],
20
+ * docs?: string
21
+ * }} TemplateEntry */
9
22
  /** @typedef {{ repository: RepositoryConfig, templates: Record<string, TemplateEntry> }} Manifest */
10
23
 
11
24
  /**
package/cli/copy.js CHANGED
@@ -9,23 +9,10 @@ import {
9
9
  statSync,
10
10
  } from "node:fs";
11
11
  import { join } from "node:path";
12
+ import { NEVER_COPY_NAMES, shouldSkipFileName } from "./fs-ignore.js";
12
13
 
13
- const NEVER_COPY = new Set([".git", ".gitignore.bak"]);
14
14
  const NEVER_OVERWRITE = new Set([".git"]);
15
15
 
16
- /**
17
- * Files that must never be transferred into a generated project.
18
- * @param {string} name
19
- * @returns {boolean}
20
- */
21
- function shouldSkipFile(name) {
22
- if (NEVER_COPY.has(name)) return true;
23
- if (name === ".env" || name.startsWith(".env.")) {
24
- return name !== ".env.example";
25
- }
26
- return false;
27
- }
28
-
29
16
  /**
30
17
  * @param {string} dir
31
18
  * @returns {string[]}
@@ -75,7 +62,7 @@ function collectConflicts(sourceDir, targetDir, relative, conflicts) {
75
62
 
76
63
  const sourceNames = readdirSync(currentSource);
77
64
  for (const name of sourceNames) {
78
- if (shouldSkipFile(name)) continue;
65
+ if (shouldSkipFileName(name)) continue;
79
66
 
80
67
  const relPath = relative ? join(relative, name) : name;
81
68
  const sourcePath = join(sourceDir, relPath);
@@ -126,7 +113,7 @@ function copyRecursive(sourceRoot, targetRoot, relative) {
126
113
  const names = readdirSync(sourcePath);
127
114
 
128
115
  for (const name of names) {
129
- if (shouldSkipFile(name)) continue;
116
+ if (shouldSkipFileName(name) || NEVER_COPY_NAMES.has(name)) continue;
130
117
 
131
118
  const relPath = relative ? join(relative, name) : name;
132
119
  const from = join(sourceRoot, relPath);
@@ -138,7 +125,6 @@ function copyRecursive(sourceRoot, targetRoot, relative) {
138
125
 
139
126
  const stat = lstatSync(from);
140
127
  if (stat.isSymbolicLink()) {
141
- const linkTarget = readlinkSync(from);
142
128
  mkdirSync(join(to, ".."), { recursive: true });
143
129
  cpSync(from, to, { recursive: true, force: true });
144
130
  continue;
@@ -158,7 +144,7 @@ function copyRecursive(sourceRoot, targetRoot, relative) {
158
144
  * @param {string} dir
159
145
  */
160
146
  export function removeDirectory(dir) {
161
- if (existsSync(dir)) {
147
+ if (dir && existsSync(dir)) {
162
148
  rmSync(dir, { recursive: true, force: true });
163
149
  }
164
150
  }
package/cli/db-setup.js CHANGED
@@ -6,11 +6,29 @@ import { join, basename, resolve } from "node:path";
6
6
  import crypto from "node:crypto";
7
7
  import { execSync } from "node:child_process";
8
8
 
9
- export async function promptAndSetupDb(targetDir) {
9
+ /**
10
+ * @typedef {{
11
+ * yes?: boolean,
12
+ * dbHost?: string,
13
+ * dbPort?: string,
14
+ * dbUser?: string,
15
+ * dbPassword?: string,
16
+ * dbName?: string,
17
+ * adminUser?: string,
18
+ * adminPassword?: string,
19
+ * skipDbPush?: boolean,
20
+ * dbPush?: boolean
21
+ * }} DbSetupOptions
22
+ */
23
+
24
+ /**
25
+ * @param {string} targetDir
26
+ * @param {DbSetupOptions} options
27
+ */
28
+ export async function setupDb(targetDir, options = {}) {
10
29
  const envPath = join(targetDir, ".env");
11
30
  const envExamplePath = join(targetDir, ".env.example");
12
31
 
13
- // Load existing env values as defaults if .env exists
14
32
  let existingEnv = "";
15
33
  if (existsSync(envPath)) {
16
34
  try {
@@ -20,22 +38,23 @@ export async function promptAndSetupDb(targetDir) {
20
38
  }
21
39
  }
22
40
 
23
- // Helper to extract env var
24
41
  const getEnvVal = (key, fallback) => {
25
42
  const match = existingEnv.match(new RegExp(`^${key}\\s*=\\s*["']?([^"'\r\n]+)["']?`, "m"));
26
43
  return match ? match[1] : fallback;
27
44
  };
28
45
 
29
- // Parse existing DB URL if any
30
46
  const currentDbUrl = getEnvVal("DATABASE_URL", "");
31
47
  let defaultHost = "localhost";
32
48
  let defaultPort = "3306";
33
49
  let defaultUser = "root";
34
50
  let defaultPass = "";
35
- let defaultDbName = basename(resolve(targetDir)).toLowerCase().replace(/[^a-z0-9_]/g, "_") + "_db";
51
+ let defaultDbName =
52
+ basename(resolve(targetDir)).toLowerCase().replace(/[^a-z0-9_]/g, "_") + "_db";
36
53
 
37
54
  if (currentDbUrl) {
38
- const urlMatch = currentDbUrl.match(/mysql:\/\/([^:@]+)(?::([^@]+))?@([^:/]+)(?::(\d+))?\/([^?]+)/);
55
+ const urlMatch = currentDbUrl.match(
56
+ /mysql:\/\/([^:@]+)(?::([^@]+))?@([^:/]+)(?::(\d+))?\/([^?]+)/
57
+ );
39
58
  if (urlMatch) {
40
59
  defaultUser = urlMatch[1];
41
60
  defaultPass = urlMatch[2] || "";
@@ -48,71 +67,173 @@ export async function promptAndSetupDb(targetDir) {
48
67
  const defaultAdminUser = getEnvVal("ADMIN_USER", "admin");
49
68
  const defaultAdminPass = getEnvVal("ADMIN_PASSWORD", crypto.randomBytes(4).toString("hex"));
50
69
 
51
- const rl = createInterface({ input, output });
52
- try {
53
- console.log("\n--- Configure Database & Environment Variables ---");
70
+ const host = options.dbHost?.trim() || defaultHost;
71
+ const port = options.dbPort?.trim() || defaultPort;
72
+ const user = options.dbUser?.trim() || defaultUser;
73
+ const pass = options.dbPassword !== undefined ? options.dbPassword : defaultPass;
74
+ const dbName = options.dbName?.trim() || defaultDbName;
75
+ const adminUser = options.adminUser?.trim() || defaultAdminUser;
76
+ const adminPass = options.adminPassword?.trim() || defaultAdminPass;
54
77
 
55
- const host = (await rl.question(`MySQL Database Host [${defaultHost}]: `)).trim() || defaultHost;
56
- const port = (await rl.question(`MySQL Database Port [${defaultPort}]: `)).trim() || defaultPort;
57
- const user = (await rl.question(`MySQL Database Username [${defaultUser}]: `)).trim() || defaultUser;
58
- const pass = (await rl.question(`MySQL Database Password [${defaultPass ? "*****" : "(empty)"}]: `)).trim() || defaultPass;
59
- const dbName = (await rl.question(`MySQL Database Name [${defaultDbName}]: `)).trim() || defaultDbName;
78
+ const dbUrl = `mysql://${user}${pass ? `:${encodeURIComponent(pass)}` : ""}@${host}:${port}/${dbName}`;
60
79
 
61
- const adminUser = (await rl.question(`Admin Portal Username [${defaultAdminUser}]: `)).trim() || defaultAdminUser;
62
- const adminPass = (await rl.question(`Admin Portal Password [${defaultAdminPass}]: `)).trim() || defaultAdminPass;
80
+ let envContent = "";
81
+ if (existsSync(envExamplePath)) {
82
+ envContent = await readFile(envExamplePath, "utf8");
83
+ }
63
84
 
64
- // Construct MySQL Connection URL
65
- const dbUrl = `mysql://${user}${pass ? `:${encodeURIComponent(pass)}` : ""}@${host}:${port}/${dbName}`;
85
+ const randomSecret = crypto.randomBytes(32).toString("base64");
66
86
 
67
- // Read template or write fresh environment file
68
- let envContent = "";
69
- if (existsSync(envExamplePath)) {
70
- envContent = await readFile(envExamplePath, "utf8");
87
+ const updateEnvKey = (content, key, value) => {
88
+ const regex = new RegExp(`^#?\\s*${key}\\s*=.*$`, "m");
89
+ if (regex.test(content)) {
90
+ return content.replace(regex, `${key}="${value}"`);
71
91
  }
92
+ return `${content.trim()}\n${key}="${value}"\n`;
93
+ };
72
94
 
73
- // Generate fresh secret
74
- const randomSecret = crypto.randomBytes(32).toString("base64");
75
-
76
- // Replace or construct env keys
77
- const updateEnvKey = (content, key, value) => {
78
- const regex = new RegExp(`^#?\\s*${key}\\s*=.*$`, "m");
79
- if (regex.test(content)) {
80
- return content.replace(regex, `${key}="${value}"`);
81
- }
82
- return `${content.trim()}\n${key}="${value}"\n`;
83
- };
84
-
85
- if (envContent) {
86
- envContent = updateEnvKey(envContent, "DATABASE_URL", dbUrl);
87
- envContent = updateEnvKey(envContent, "AUTH_SECRET", randomSecret);
88
- envContent = updateEnvKey(envContent, "ADMIN_USER", adminUser);
89
- envContent = updateEnvKey(envContent, "ADMIN_PASSWORD", adminPass);
90
- } else {
91
- envContent = `DATABASE_URL="${dbUrl}"
95
+ if (envContent) {
96
+ envContent = updateEnvKey(envContent, "DATABASE_URL", dbUrl);
97
+ envContent = updateEnvKey(envContent, "AUTH_SECRET", randomSecret);
98
+ envContent = updateEnvKey(envContent, "ADMIN_USER", adminUser);
99
+ envContent = updateEnvKey(envContent, "ADMIN_PASSWORD", adminPass);
100
+ } else {
101
+ envContent = `DATABASE_URL="${dbUrl}"
92
102
  AUTH_SECRET="${randomSecret}"
93
103
  ADMIN_USER="${adminUser}"
94
104
  ADMIN_PASSWORD="${adminPass}"
95
105
  AUTH_TRUST_HOST="true"
96
106
  `;
107
+ }
108
+
109
+ await writeFile(envPath, envContent, "utf8");
110
+ console.log("Successfully created/updated .env configuration file.");
111
+
112
+ const shouldPush =
113
+ options.dbPush ||
114
+ (options.yes && !options.skipDbPush);
115
+
116
+ if (shouldPush) {
117
+ console.log("\nRunning Prisma Database Push (npx prisma db push)...");
118
+ try {
119
+ execSync("npx prisma db push", { cwd: targetDir, stdio: "inherit" });
120
+ console.log("\nSuccessfully synchronized database schemas.");
121
+ } catch {
122
+ console.error(
123
+ "\nWarning: Database synchronization failed. Make sure your MySQL server is running and the database details are correct."
124
+ );
125
+ console.error("You can run this manually later inside the project folder via: npx prisma db push");
97
126
  }
127
+ } else if (options.yes && options.skipDbPush) {
128
+ console.log("\nSkipped database table synchronization (--skip-db-push).");
129
+ }
130
+ }
131
+
132
+ /**
133
+ * @param {string} targetDir
134
+ * @param {DbSetupOptions} options
135
+ */
136
+ export async function promptAndSetupDb(targetDir, options = {}) {
137
+ if (options.yes) {
138
+ await setupDb(targetDir, options);
139
+ return;
140
+ }
98
141
 
99
- await writeFile(envPath, envContent, "utf8");
100
- console.log("Successfully created/updated .env configuration file.");
142
+ const envPath = join(targetDir, ".env");
143
+ const envExamplePath = join(targetDir, ".env.example");
144
+
145
+ let existingEnv = "";
146
+ if (existsSync(envPath)) {
147
+ try {
148
+ existingEnv = await readFile(envPath, "utf8");
149
+ } catch {
150
+ // Ignore
151
+ }
152
+ }
153
+
154
+ const getEnvVal = (key, fallback) => {
155
+ const match = existingEnv.match(new RegExp(`^${key}\\s*=\\s*["']?([^"'\r\n]+)["']?`, "m"));
156
+ return match ? match[1] : fallback;
157
+ };
158
+
159
+ const currentDbUrl = getEnvVal("DATABASE_URL", "");
160
+ let defaultHost = "localhost";
161
+ let defaultPort = "3306";
162
+ let defaultUser = "root";
163
+ let defaultPass = "";
164
+ let defaultDbName =
165
+ basename(resolve(targetDir)).toLowerCase().replace(/[^a-z0-9_]/g, "_") + "_db";
166
+
167
+ if (currentDbUrl) {
168
+ const urlMatch = currentDbUrl.match(
169
+ /mysql:\/\/([^:@]+)(?::([^@]+))?@([^:/]+)(?::(\d+))?\/([^?]+)/
170
+ );
171
+ if (urlMatch) {
172
+ defaultUser = urlMatch[1];
173
+ defaultPass = urlMatch[2] || "";
174
+ defaultHost = urlMatch[3];
175
+ defaultPort = urlMatch[4] || "3306";
176
+ defaultDbName = urlMatch[5];
177
+ }
178
+ }
179
+
180
+ const defaultAdminUser = getEnvVal("ADMIN_USER", "admin");
181
+ const defaultAdminPass = getEnvVal("ADMIN_PASSWORD", crypto.randomBytes(4).toString("hex"));
182
+
183
+ const rl = createInterface({ input, output });
184
+ try {
185
+ console.log("\n--- Configure Database & Environment Variables ---");
101
186
 
102
- // Prompt to run prisma database push
103
- const runMigrate = await rl.question("\nDo you want to initialize the database tables now using Prisma? (y/n) [y]: ");
187
+ const host =
188
+ (await rl.question(`MySQL Database Host [${defaultHost}]: `)).trim() || defaultHost;
189
+ const port =
190
+ (await rl.question(`MySQL Database Port [${defaultPort}]: `)).trim() || defaultPort;
191
+ const user =
192
+ (await rl.question(`MySQL Database Username [${defaultUser}]: `)).trim() || defaultUser;
193
+ const pass =
194
+ (await rl.question(`MySQL Database Password [${defaultPass ? "*****" : "(empty)"}]: `)).trim() ||
195
+ defaultPass;
196
+ const dbName =
197
+ (await rl.question(`MySQL Database Name [${defaultDbName}]: `)).trim() || defaultDbName;
198
+ const adminUser =
199
+ (await rl.question(`Admin Portal Username [${defaultAdminUser}]: `)).trim() ||
200
+ defaultAdminUser;
201
+ const adminPass =
202
+ (await rl.question(`Admin Portal Password [${defaultAdminPass}]: `)).trim() ||
203
+ defaultAdminPass;
204
+
205
+ await setupDb(targetDir, {
206
+ dbHost: host,
207
+ dbPort: port,
208
+ dbUser: user,
209
+ dbPassword: pass,
210
+ dbName,
211
+ adminUser,
212
+ adminPassword: adminPass,
213
+ skipDbPush: true,
214
+ });
215
+
216
+ const runMigrate = await rl.question(
217
+ "\nDo you want to initialize the database tables now using Prisma? (y/n) [y]: "
218
+ );
104
219
  const choice = runMigrate.trim().toLowerCase();
105
220
  if (choice === "" || choice === "y" || choice === "yes") {
106
221
  console.log("\nRunning Prisma Database Push (npx prisma db push)...");
107
222
  try {
108
223
  execSync("npx prisma db push", { cwd: targetDir, stdio: "inherit" });
109
224
  console.log("\nSuccessfully synchronized database schemas.");
110
- } catch (err) {
111
- console.error("\nWarning: Database synchronization failed. Make sure your MySQL server is running and the database details are correct.");
112
- console.error("You can run this manually later inside the project folder via: npx prisma db push");
225
+ } catch {
226
+ console.error(
227
+ "\nWarning: Database synchronization failed. Make sure your MySQL server is running and the database details are correct."
228
+ );
229
+ console.error(
230
+ "You can run this manually later inside the project folder via: npx prisma db push"
231
+ );
113
232
  }
114
233
  } else {
115
- console.log("\nSkipped database table synchronization. Remember to run 'npx prisma db push' before running the app.");
234
+ console.log(
235
+ "\nSkipped database table synchronization. Remember to run 'npx prisma db push' before running the app."
236
+ );
116
237
  }
117
238
  } finally {
118
239
  rl.close();