@navneet_25/tempjs 1.0.4 → 1.1.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/README.md CHANGED
@@ -35,9 +35,11 @@ tempjs list
35
35
  ```bash
36
36
  tempjs list # show available templates
37
37
  tempjs hotel # create project from hotel template using default theme
38
- tempjs hotel config # create project and run interactive theme/font setup
38
+ tempjs hotel config # create project and run full interactive configuration (theme, font, brand, database)
39
39
  tempjs theme # change/reset the theme of an initialized project
40
40
  tempjs font # change/reset the font pairing of an initialized project
41
+ tempjs brand # configure brand identity & contact info of an initialized project
42
+ tempjs init-db # set up .env file and run database schema sync on an initialized project
41
43
  tempjs real-estate --force # overwrite existing files
42
44
  tempjs --help # show help
43
45
  ```
@@ -46,7 +48,7 @@ tempjs --help # show help
46
48
 
47
49
  | Option | Description |
48
50
  |---------------|-------------|
49
- | `--config` | Prompt for theme and font pairings during template initialization |
51
+ | `--config` | Prompt for full configuration (theme, font, brand, db) during template initialization |
50
52
  | `--force` | Overwrite files in the current directory without prompting |
51
53
  | `--remote` | Fetch from GitHub even when a local template copy exists |
52
54
  | `--init-git` | Run `git init` after copying (optional) |
@@ -0,0 +1,108 @@
1
+ import { createInterface } from "node:readline/promises";
2
+ import { stdin as input, stdout as output } from "node:process";
3
+ import { readFile, writeFile } from "node:fs/promises";
4
+ import { findSiteTs } from "./theme-manager.js";
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");
15
+
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";
25
+
26
+ const rl = createInterface({ input, output });
27
+ try {
28
+ console.log("\n--- Configure Brand & Contact Details ---");
29
+
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: {
52
+ name: "${brandName}",
53
+ shortName: "${shortName}",
54
+ tagline: "Where Nature Meets Refined Comfort",
55
+ developerName: "${brandName}",
56
+ channelPartner: "${brandName} Partner",
57
+ copyright: "${brandName}. All Rights Reserved.",
58
+ managedBy: "Managed by ${brandName}.",
59
+ }`;
60
+
61
+ const newDomainBlock = `domain: {
62
+ baseUrl: "${baseUrl}",
63
+ wwwHost: "${wwwHost}",
64
+ }`;
65
+
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
+ }
74
+
75
+ const newContactBlock = `contact: {
76
+ phone: "${phone}",
77
+ phoneDisplay: "${phoneDisplay}",
78
+ countryCode: "${countryCode}",
79
+ email: "${email}",
80
+ address: {
81
+ locality: "${locality}",
82
+ region: "${region}",
83
+ country: "IN",
84
+ full: "${address}",
85
+ },
86
+ }`;
87
+
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
+ }
102
+
103
+ await writeFile(siteTsPath, siteContent, "utf8");
104
+ console.log("Successfully updated brand and contact configurations in constants/site.ts.");
105
+ } finally {
106
+ rl.close();
107
+ }
108
+ }
@@ -0,0 +1,120 @@
1
+ import { createInterface } from "node:readline/promises";
2
+ import { stdin as input, stdout as output } from "node:process";
3
+ import { readFile, writeFile } from "node:fs/promises";
4
+ import { existsSync } from "node:fs";
5
+ import { join, basename, resolve } from "node:path";
6
+ import crypto from "node:crypto";
7
+ import { execSync } from "node:child_process";
8
+
9
+ export async function promptAndSetupDb(targetDir) {
10
+ const envPath = join(targetDir, ".env");
11
+ const envExamplePath = join(targetDir, ".env.example");
12
+
13
+ // Load existing env values as defaults if .env exists
14
+ let existingEnv = "";
15
+ if (existsSync(envPath)) {
16
+ try {
17
+ existingEnv = await readFile(envPath, "utf8");
18
+ } catch {
19
+ // Ignore
20
+ }
21
+ }
22
+
23
+ // Helper to extract env var
24
+ const getEnvVal = (key, fallback) => {
25
+ const match = existingEnv.match(new RegExp(`^${key}\\s*=\\s*["']?([^"'\r\n]+)["']?`, "m"));
26
+ return match ? match[1] : fallback;
27
+ };
28
+
29
+ // Parse existing DB URL if any
30
+ const currentDbUrl = getEnvVal("DATABASE_URL", "");
31
+ let defaultHost = "localhost";
32
+ let defaultPort = "3306";
33
+ let defaultUser = "root";
34
+ let defaultPass = "";
35
+ let defaultDbName = basename(resolve(targetDir)).toLowerCase().replace(/[^a-z0-9_]/g, "_") + "_db";
36
+
37
+ if (currentDbUrl) {
38
+ const urlMatch = currentDbUrl.match(/mysql:\/\/([^:@]+)(?::([^@]+))?@([^:/]+)(?::(\d+))?\/([^?]+)/);
39
+ if (urlMatch) {
40
+ defaultUser = urlMatch[1];
41
+ defaultPass = urlMatch[2] || "";
42
+ defaultHost = urlMatch[3];
43
+ defaultPort = urlMatch[4] || "3306";
44
+ defaultDbName = urlMatch[5];
45
+ }
46
+ }
47
+
48
+ const defaultAdminUser = getEnvVal("ADMIN_USER", "admin");
49
+ const defaultAdminPass = getEnvVal("ADMIN_PASSWORD", crypto.randomBytes(4).toString("hex"));
50
+
51
+ const rl = createInterface({ input, output });
52
+ try {
53
+ console.log("\n--- Configure Database & Environment Variables ---");
54
+
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;
60
+
61
+ const adminUser = (await rl.question(`Admin Portal Username [${defaultAdminUser}]: `)).trim() || defaultAdminUser;
62
+ const adminPass = (await rl.question(`Admin Portal Password [${defaultAdminPass}]: `)).trim() || defaultAdminPass;
63
+
64
+ // Construct MySQL Connection URL
65
+ const dbUrl = `mysql://${user}${pass ? `:${encodeURIComponent(pass)}` : ""}@${host}:${port}/${dbName}`;
66
+
67
+ // Read template or write fresh environment file
68
+ let envContent = "";
69
+ if (existsSync(envExamplePath)) {
70
+ envContent = await readFile(envExamplePath, "utf8");
71
+ }
72
+
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}"
92
+ AUTH_SECRET="${randomSecret}"
93
+ ADMIN_USER="${adminUser}"
94
+ ADMIN_PASSWORD="${adminPass}"
95
+ AUTH_TRUST_HOST="true"
96
+ `;
97
+ }
98
+
99
+ await writeFile(envPath, envContent, "utf8");
100
+ console.log("Successfully created/updated .env configuration file.");
101
+
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]: ");
104
+ const choice = runMigrate.trim().toLowerCase();
105
+ if (choice === "" || choice === "y" || choice === "yes") {
106
+ console.log("\nRunning Prisma Database Push (npx prisma db push)...");
107
+ try {
108
+ execSync("npx prisma db push", { cwd: targetDir, stdio: "inherit" });
109
+ 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");
113
+ }
114
+ } else {
115
+ console.log("\nSkipped database table synchronization. Remember to run 'npx prisma db push' before running the app.");
116
+ }
117
+ } finally {
118
+ rl.close();
119
+ }
120
+ }
package/cli/index.js CHANGED
@@ -10,6 +10,8 @@ import {
10
10
  applyThemeAndFont,
11
11
  getSavedConfig,
12
12
  } from "./theme-manager.js";
13
+ import { promptAndApplyBrand } from "./brand-manager.js";
14
+ import { promptAndSetupDb } from "./db-setup.js";
13
15
  import {
14
16
  copyTemplate,
15
17
  findConflictingPaths,
@@ -26,13 +28,15 @@ tempjs — instantiate project templates from GitHub
26
28
  USAGE
27
29
  tempjs list
28
30
  tempjs <template-id> [options]
29
- tempjs <template-id> config Initialize with interactive theme & typography setup
31
+ tempjs <template-id> config Initialize with interactive theme, typography, brand & database setup
30
32
  tempjs theme Change the project's theme in an initialized directory
31
33
  tempjs font Change the project's font styling in an initialized directory
34
+ tempjs brand Configure the brand & contact info in an initialized directory
35
+ tempjs init-db Configure .env and sync database schema in an initialized directory
32
36
  tempjs --help
33
37
 
34
38
  OPTIONS
35
- --config Prompt for theme and font pairings during initialization
39
+ --config Prompt for full interactive setup (theme, font, brand, db) during initialization
36
40
  --force Overwrite existing files in the current directory
37
41
  --remote Fetch from GitHub even if a local template copy exists
38
42
  --init-git Run git init after copying the template
@@ -41,7 +45,8 @@ OPTIONS
41
45
  EXAMPLES
42
46
  mkdir hotel-client && cd hotel-client
43
47
  tempjs hotel config
44
- tempjs theme
48
+ tempjs brand
49
+ tempjs init-db
45
50
 
46
51
  ENVIRONMENT
47
52
  TEMPLATES_REPO_URL GitHub repo URL or owner/repo (overrides templates.json)
@@ -216,6 +221,12 @@ async function runTemplate(targetDir, templateId, flags, runWithConfig = false)
216
221
  const selectedTheme = await promptTheme("theme1");
217
222
  const selectedFont = await promptFont("default");
218
223
  await applyThemeAndFont(targetDir, selectedTheme, selectedFont);
224
+
225
+ // Interactive Brand & Contact Details configuration
226
+ await promptAndApplyBrand(targetDir);
227
+
228
+ // Environment & Database auto-setup
229
+ await promptAndSetupDb(targetDir);
219
230
  }
220
231
 
221
232
  console.log(`\nTemplate "${entry.name}" created successfully in ${targetDir}`);
@@ -254,16 +265,20 @@ async function main(argv) {
254
265
  return;
255
266
  }
256
267
 
257
- if (command === "theme" || command === "font") {
268
+ if (command === "theme" || command === "font" || command === "brand" || command === "init-db") {
258
269
  const targetDir = process.cwd();
259
270
  const currentConfig = getSavedConfig(targetDir);
260
271
 
261
272
  if (command === "theme") {
262
273
  const selectedTheme = await promptTheme(currentConfig.theme || "theme1");
263
274
  await applyThemeAndFont(targetDir, selectedTheme, currentConfig.font || "default");
264
- } else {
275
+ } else if (command === "font") {
265
276
  const selectedFont = await promptFont(currentConfig.font || "default");
266
277
  await applyThemeAndFont(targetDir, currentConfig.theme || "theme1", selectedFont);
278
+ } else if (command === "brand") {
279
+ await promptAndApplyBrand(targetDir);
280
+ } else if (command === "init-db") {
281
+ await promptAndSetupDb(targetDir);
267
282
  }
268
283
  return;
269
284
  }
@@ -316,8 +316,12 @@ export async function applyThemeAndFont(targetDir, themeId, fontId) {
316
316
  if (!globalsContent.includes("tempjs-theme.css")) {
317
317
  // Prepend import at the top of the file
318
318
  globalsContent = `@import "./tempjs-theme.css";\n` + globalsContent;
319
- await writeFile(globalsPath, globalsContent, "utf8");
320
319
  }
320
+
321
+ // Always append/update a timestamp touch comment to force bundler/Tailwind CSS recompilation
322
+ globalsContent = globalsContent.replace(/\/\* tempjs-touch: \d+ \*\/\s*$/, "");
323
+ globalsContent = globalsContent.trim() + `\n/* tempjs-touch: ${Date.now()} */\n`;
324
+ await writeFile(globalsPath, globalsContent, "utf8");
321
325
  } else {
322
326
  console.warn("Could not locate globals.css file in the project. CSS variables and font imports were not applied.");
323
327
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@navneet_25/tempjs",
3
- "version": "1.0.4",
3
+ "version": "1.1.0",
4
4
  "description": "CLI to instantiate website project templates from a single GitHub repository",
5
5
  "type": "module",
6
6
  "bin": {